Skip to content

Commit

Permalink
Merge pull request #794 from microsoftgraph/Issue2286/Fix-DateTimeTim…
Browse files Browse the repository at this point in the history
…eZone-ToDateTime-returning-incorrect-parsed-date

Fixes DateTimeTimeZone.ToDateTime returning incorrect parsed date
  • Loading branch information
andrueastman authored Feb 1, 2024
2 parents e50fe1f + 3af2729 commit 1cf11c6
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 11 deletions.
10 changes: 7 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project does NOT adhere to [Semantic Versioning](https://semver.org/spe

## [Unreleased]

## [5.63.1-preview] - 2024-01-24

- Fixes DateTimeTimeZone.ToDateTime returning incorrect parsed date(https://github.com/microsoftgraph/msgraph-sdk-dotnet/issues/2286)

## [5.63.0-preview] - 2024-01-24

- Latest metadata updates from 24th January 2024.
Expand Down Expand Up @@ -202,7 +206,7 @@ and this project does NOT adhere to [Semantic Versioning](https://semver.org/spe

## [5.25.0-preview] - 2023-04-06

### Changed
### Changed

- Fixes missing dateTime query parameters for bookingBusinesses (https://github.com/microsoftgraph/msgraph-sdk-dotnet/issues/1791)
- Fixes missing expand clauses for calendars and contactFolder (https://github.com/microsoftgraph/msgraph-sdk-dotnet/issues/1788)
Expand Down Expand Up @@ -297,14 +301,14 @@ and this project does NOT adhere to [Semantic Versioning](https://semver.org/spe
- Adds OdataDeltaLink property to collection responses for delta
- Changes the ResponeHandler parameter in request builders to be a RequestOption in dotnet [#1858](https://github.com/microsoft/kiota/issues/1858)
- Latest metadata updates from 18th October 2022 snapshot

## [5.12.0-preview] - 2022-09-28

### Changed

- Fixes incorrect types for collection types referencing enums - [Kiota #1846](https://github.com/microsoft/kiota/pull/1846)
- Fixes missing return object types for PATCH/POST/PUT calls - https://github.com/microsoftgraph/msgraph-beta-sdk-dotnet/issues/478
- Fixes missing QueryParameters for odata functions e.g delta
- Fixes missing QueryParameters for odata functions e.g delta
- Latest metadata updates from 27th September 2022 snapshot

## [5.11.0-preview] - 2022-09-13
Expand Down
23 changes: 17 additions & 6 deletions src/Microsoft.Graph/Extensions/DateTimeTimeZoneExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,26 +22,37 @@ public static class DateTimeTimeZoneExtensions
/// <returns></returns>
public static DateTime ToDateTime(this DateTimeTimeZone dateTimeTimeZone)
{
DateTime dateTime = DateTime.ParseExact(dateTimeTimeZone.DateTime, DateTimeFormat, CultureInfo.InvariantCulture);
DateTime parsedDateTime = DateTime.ParseExact(dateTimeTimeZone.DateTime, DateTimeFormat, CultureInfo.InvariantCulture);

// Now we need to determine which DateTimeKind to set based on the time zone specified in the input object.
TimeZoneInfo timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(dateTimeTimeZone.TimeZone);

DateTimeKind kind;
if (timeZoneInfo.Id == TimeZoneInfo.Utc.Id)
if(timeZoneInfo.StandardName == TimeZoneInfo.Utc.StandardName)
{
kind = DateTimeKind.Utc;
// however, if the parsedDateTime.Kind is Local, we need to align to be utc too
if (parsedDateTime.Kind == DateTimeKind.Local)
{
parsedDateTime = parsedDateTime.ToUniversalTime();
}
}
else if (timeZoneInfo.Id == TimeZoneInfo.Local.Id)
else if (timeZoneInfo.StandardName == TimeZoneInfo.Local.StandardName)
{
kind = DateTimeKind.Local;
// however, if the parsedDateTime.Kind is UTC, we need to align it to be local too
if (parsedDateTime.Kind == DateTimeKind.Utc)
{
parsedDateTime = parsedDateTime.ToLocalTime();
}
}
else
{
kind = DateTimeKind.Unspecified;
//if timeZoneInfo passed is not UTC or Local, then it is Unspecified
//Infer from parsedDateTime.Kind rather than blindly set it to Unspecified
kind = parsedDateTime.Kind;
}

return DateTime.SpecifyKind(dateTime, kind);
return DateTime.SpecifyKind(parsedDateTime, kind);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,106 @@ public class DateTimeZoneExtensionsTests
internal const string DateTimeFormat = "yyyy-MM-ddTHH:mm:ss.fffffffK";

[Fact]
public void ToDateTime_Should_Convert_DateTimeTimeZone_To_DateTime()
public void ToDateTime_Should_Correctly_Convert_DateTimeString_To_UTCDateTimeObject()
{
var dateTimeString = "2019-01-25T06:37:39.8058788Z";
var expectedDateTime = DateTime.ParseExact(dateTimeString , DateTimeFormat, CultureInfo.InvariantCulture).ToUniversalTime();

DateTimeTimeZone dateTimeTimeZone = new DateTimeTimeZone
{
TimeZone = "UTC",
DateTime = dateTimeString
};

var actualDateTime = dateTimeTimeZone.ToDateTime();

Assert.Equal(expectedDateTime, actualDateTime);
Assert.Equal(expectedDateTime.Kind, actualDateTime.Kind);
Assert.Equal(DateTimeKind.Utc, actualDateTime.Kind);

//scenario where the dateTime is not in UTC
dateTimeTimeZone = new DateTimeTimeZone
{
TimeZone = "UTC",
DateTime = "2019-01-25T06:37:39.8058788"
};
actualDateTime = dateTimeTimeZone.ToDateTime();
Assert.Equal(expectedDateTime, actualDateTime);
Assert.Equal(expectedDateTime.Kind, actualDateTime.Kind);
Assert.Equal(DateTimeKind.Utc, actualDateTime.Kind);
}

[Fact]
public void ToDateTime_Should_Correctly_Convert_DateTimeString_To_Local_DateTimeObject_Correctly()
{
var localDateTime = DateTime.Now;
var localDateTimeString = localDateTime.ToString(DateTimeFormat, CultureInfo.InvariantCulture);
DateTimeTimeZone dateTimeTimeZone = new DateTimeTimeZone
{
TimeZone = TimeZoneInfo.Local.Id,
DateTime = localDateTimeString
};

var actualDateTime = dateTimeTimeZone.ToDateTime().ToLocalTime();
var expectedDateTime = localDateTime;

Assert.Equal(expectedDateTime, actualDateTime);
Assert.Equal(expectedDateTime.Kind, actualDateTime.Kind);
}

[Fact]
public void ToDateTime_Should_Correctly_Convert_DateTimeString_To_Local_No_timezone_offset_provided()
{
var localDateTime = DateTime.Now;
var localDateTimeString = localDateTime.ToString("yyyy-MM-ddTHH:mm:ss.fffffff", CultureInfo.InvariantCulture);
DateTimeTimeZone dateTimeTimeZone = new DateTimeTimeZone
{
TimeZone = TimeZoneInfo.Local.Id,
DateTime = localDateTimeString
};

var actualDateTime = dateTimeTimeZone.ToDateTime().ToLocalTime();
var expectedDateTime = localDateTime;
Assert.Equal(expectedDateTime, actualDateTime);
Assert.Equal(expectedDateTime.Kind, actualDateTime.Kind);
}

[Fact]
public void ToDateTime_Should_Correctly_Convert_DateTimeString_To_UnspecifiedDateTimeObject()
{
DateTimeTimeZone dateTimeTimeZone = new DateTimeTimeZone
{
TimeZone = "Asia/Jerusalem",
DateTime = "2019-01-25T06:37:39.8058788Z"
};

var actualDateTime = dateTimeTimeZone.ToDateTime();
var expectedDateTime = DateTime.ParseExact(dateTimeTimeZone.DateTime, DateTimeFormat, CultureInfo.InvariantCulture);
var expectedDateTime = DateTime.ParseExact(dateTimeTimeZone.DateTime, DateTimeFormat, CultureInfo.InvariantCulture).ToUniversalTime();

Assert.Equal(expectedDateTime, actualDateTime.ToUniversalTime());

//scenario where the dateTime is Local but the timezone is unspecified
expectedDateTime = DateTime.Now;
var dateTimeString = expectedDateTime.ToString(DateTimeFormat, CultureInfo.InvariantCulture);
dateTimeTimeZone = new DateTimeTimeZone
{
TimeZone = "Asia/Jerusalem",
DateTime = dateTimeString
};
actualDateTime = dateTimeTimeZone.ToDateTime();
Assert.Equal(expectedDateTime, actualDateTime.ToLocalTime());

//scenario where the dateTime has no timezone offset and timezone is unspecified
dateTimeTimeZone = new DateTimeTimeZone
{
TimeZone = "Asia/Jerusalem",
DateTime = "2024-01-16T08:30:00.0000000"
};

actualDateTime = dateTimeTimeZone.ToDateTime();
expectedDateTime = new DateTime(2024, 1, 16, 08, 30, 0, DateTimeKind.Unspecified);
Assert.Equal(expectedDateTime, actualDateTime);
Assert.Equal(DateTimeKind.Unspecified, actualDateTime.Kind);
}

[Fact]
Expand Down

0 comments on commit 1cf11c6

Please sign in to comment.