Skip to content

Commit e26f594

Browse files
authored
Merge pull request #2312 from microsoftgraph/Issue2286/DateTimeTimeZone-ToDateTime-fix
DateTimeTimeZone ToDateTime fix for when parseExact returns local tim…
2 parents 26cdfc9 + 430af0f commit e26f594

File tree

3 files changed

+117
-14
lines changed

3 files changed

+117
-14
lines changed

CHANGELOG.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project does adheres to [Semantic Versioning](https://semver.org/spec/v
77

88
## [Unreleased]
99

10+
## [5.40.1] - 2024-02-01
11+
12+
- Fixes DateTimeTimeZone.ToDateTime returning incorrect parsed date(https://github.com/microsoftgraph/msgraph-sdk-dotnet/issues/2286)
13+
1014
## [5.40.0] - 2024-01-24
1115

1216
- Latest metadata updates from 24th January 2024.
@@ -32,7 +36,7 @@ and this project does adheres to [Semantic Versioning](https://semver.org/spec/v
3236

3337
## [5.35.0] - 2023-11-15
3438

35-
- Fixes `Accept` header values generated by the SDK.
39+
- Fixes `Accept` header values generated by the SDK.
3640
- Latest metadata updates from 14th November 2023.
3741

3842
## [5.33.0] - 2023-11-02
@@ -41,7 +45,7 @@ and this project does adheres to [Semantic Versioning](https://semver.org/spec/v
4145

4246
## [5.32.0] - 2023-10-24
4347

44-
- SDK is compatible with trimming(https://github.com/microsoftgraph/msgraph-sdk-dotnet/pull/2174)
48+
- SDK is compatible with trimming(https://github.com/microsoftgraph/msgraph-sdk-dotnet/pull/2174)
4549
- Latest metadata updates from 24th October 2023.
4650

4751
## [5.31.0] - 2023-10-19
@@ -77,7 +81,7 @@ and this project does adheres to [Semantic Versioning](https://semver.org/spec/v
7781

7882
## [5.25.0] - 2023-08-30
7983

80-
- Add `WithUrl` request builders to allow for easier making of requests with arbitrary Urls(https://github.com/microsoft/kiota/pull/3212)
84+
- Add `WithUrl` request builders to allow for easier making of requests with arbitrary Urls(https://github.com/microsoft/kiota/pull/3212)
8185
- Latest metadata updates from 29th August 2023.
8286

8387
## [5.24.0] - 2023-08-23
@@ -185,7 +189,7 @@ and this project does adheres to [Semantic Versioning](https://semver.org/spec/v
185189

186190
## [5.5.0] - 2023-04-06
187191

188-
### Changed
192+
### Changed
189193

190194
- Fixes missing dateTime query parameters for bookingBusinesses (https://github.com/microsoftgraph/msgraph-sdk-dotnet/issues/1791)
191195
- Fixes missing exapand clauses for calendars and contactFolder (https://github.com/microsoftgraph/msgraph-sdk-dotnet/issues/1788)
@@ -294,7 +298,7 @@ and this project does adheres to [Semantic Versioning](https://semver.org/spec/v
294298

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

300304
## [5.0.0-preview.11] - 2022-07-20

src/Microsoft.Graph/Extensions/DateTimeTimeZoneExtensions.cs

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77

88
namespace Microsoft.Graph.Models
99
{
10+
1011
/// <summary>
1112
/// Implements DateTimeTimeZone Extensions
1213
/// </summary>
13-
1414
public static class DateTimeTimeZoneExtensions
1515
{
1616
internal const string DateTimeFormat = "yyyy-MM-ddTHH:mm:ss.fffffffK";
@@ -22,26 +22,37 @@ public static class DateTimeTimeZoneExtensions
2222
/// <returns></returns>
2323
public static DateTime ToDateTime(this DateTimeTimeZone dateTimeTimeZone)
2424
{
25-
DateTime dateTime = DateTime.ParseExact(dateTimeTimeZone.DateTime, DateTimeFormat, CultureInfo.InvariantCulture);
25+
DateTime parsedDateTime = DateTime.ParseExact(dateTimeTimeZone.DateTime, DateTimeFormat, CultureInfo.InvariantCulture);
2626

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

3030
DateTimeKind kind;
31-
if (timeZoneInfo.Id == TimeZoneInfo.Utc.Id)
31+
if(timeZoneInfo.StandardName == TimeZoneInfo.Utc.StandardName)
3232
{
3333
kind = DateTimeKind.Utc;
34+
// however, if the parsedDateTime.Kind is Local, we need to align to be utc too
35+
if (parsedDateTime.Kind == DateTimeKind.Local)
36+
{
37+
parsedDateTime = parsedDateTime.ToUniversalTime();
38+
}
3439
}
35-
else if (timeZoneInfo.Id == TimeZoneInfo.Local.Id)
40+
else if (timeZoneInfo.StandardName == TimeZoneInfo.Local.StandardName)
3641
{
3742
kind = DateTimeKind.Local;
43+
// however, if the parsedDateTime.Kind is UTC, we need to align it to be local too
44+
if (parsedDateTime.Kind == DateTimeKind.Utc)
45+
{
46+
parsedDateTime = parsedDateTime.ToLocalTime();
47+
}
3848
}
3949
else
4050
{
41-
kind = DateTimeKind.Unspecified;
51+
//if timeZoneInfo passed is not UTC or Local, then it is Unspecified
52+
//Infer from parsedDateTime.Kind rather than blindly set it to Unspecified
53+
kind = parsedDateTime.Kind;
4254
}
43-
44-
return DateTime.SpecifyKind(dateTime, kind);
55+
return DateTime.SpecifyKind(parsedDateTime, kind);
4556
}
4657

4758
/// <summary>

tests/Microsoft.Graph.DotnetCore.Test/Models/Extensions/DateTimeZoneExtensionsTests.cs

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,106 @@ public class DateTimeZoneExtensionsTests
1111
internal const string DateTimeFormat = "yyyy-MM-ddTHH:mm:ss.fffffffK";
1212

1313
[Fact]
14-
public void ToDateTime_Should_Convert_DateTimeTimeZone_To_DateTime()
14+
public void ToDateTime_Should_Correctly_Convert_DateTimeString_To_UTCDateTimeObject()
1515
{
16+
var dateTimeString = "2019-01-25T06:37:39.8058788Z";
17+
var expectedDateTime = DateTime.ParseExact(dateTimeString , DateTimeFormat, CultureInfo.InvariantCulture).ToUniversalTime();
18+
1619
DateTimeTimeZone dateTimeTimeZone = new DateTimeTimeZone
1720
{
1821
TimeZone = "UTC",
22+
DateTime = dateTimeString
23+
};
24+
25+
var actualDateTime = dateTimeTimeZone.ToDateTime();
26+
27+
Assert.Equal(expectedDateTime, actualDateTime);
28+
Assert.Equal(expectedDateTime.Kind, actualDateTime.Kind);
29+
Assert.Equal(DateTimeKind.Utc, actualDateTime.Kind);
30+
31+
//scenario where the dateTime is not in UTC
32+
dateTimeTimeZone = new DateTimeTimeZone
33+
{
34+
TimeZone = "UTC",
35+
DateTime = "2019-01-25T06:37:39.8058788"
36+
};
37+
actualDateTime = dateTimeTimeZone.ToDateTime();
38+
Assert.Equal(expectedDateTime, actualDateTime);
39+
Assert.Equal(expectedDateTime.Kind, actualDateTime.Kind);
40+
Assert.Equal(DateTimeKind.Utc, actualDateTime.Kind);
41+
}
42+
43+
[Fact]
44+
public void ToDateTime_Should_Correctly_Convert_DateTimeString_To_Local_DateTimeObject_Correctly()
45+
{
46+
var localDateTime = DateTime.Now;
47+
var localDateTimeString = localDateTime.ToString(DateTimeFormat, CultureInfo.InvariantCulture);
48+
DateTimeTimeZone dateTimeTimeZone = new DateTimeTimeZone
49+
{
50+
TimeZone = TimeZoneInfo.Local.Id,
51+
DateTime = localDateTimeString
52+
};
53+
54+
var actualDateTime = dateTimeTimeZone.ToDateTime().ToLocalTime();
55+
var expectedDateTime = localDateTime;
56+
57+
Assert.Equal(expectedDateTime, actualDateTime);
58+
Assert.Equal(expectedDateTime.Kind, actualDateTime.Kind);
59+
}
60+
61+
[Fact]
62+
public void ToDateTime_Should_Correctly_Convert_DateTimeString_To_Local_No_timezone_offset_provided()
63+
{
64+
var localDateTime = DateTime.Now;
65+
var localDateTimeString = localDateTime.ToString("yyyy-MM-ddTHH:mm:ss.fffffff", CultureInfo.InvariantCulture);
66+
DateTimeTimeZone dateTimeTimeZone = new DateTimeTimeZone
67+
{
68+
TimeZone = TimeZoneInfo.Local.Id,
69+
DateTime = localDateTimeString
70+
};
71+
72+
var actualDateTime = dateTimeTimeZone.ToDateTime().ToLocalTime();
73+
var expectedDateTime = localDateTime;
74+
Assert.Equal(expectedDateTime, actualDateTime);
75+
Assert.Equal(expectedDateTime.Kind, actualDateTime.Kind);
76+
}
77+
78+
[Fact]
79+
public void ToDateTime_Should_Correctly_Convert_DateTimeString_To_UnspecifiedDateTimeObject()
80+
{
81+
DateTimeTimeZone dateTimeTimeZone = new DateTimeTimeZone
82+
{
83+
TimeZone = "Asia/Jerusalem",
1984
DateTime = "2019-01-25T06:37:39.8058788Z"
2085
};
2186

2287
var actualDateTime = dateTimeTimeZone.ToDateTime();
23-
var expectedDateTime = DateTime.ParseExact(dateTimeTimeZone.DateTime, DateTimeFormat, CultureInfo.InvariantCulture);
88+
var expectedDateTime = DateTime.ParseExact(dateTimeTimeZone.DateTime, DateTimeFormat, CultureInfo.InvariantCulture).ToUniversalTime();
89+
90+
Assert.Equal(expectedDateTime, actualDateTime.ToUniversalTime());
91+
92+
//scenario where the dateTime is Local but the timezone is unspecified
93+
expectedDateTime = DateTime.Now;
94+
var dateTimeString = expectedDateTime.ToString(DateTimeFormat, CultureInfo.InvariantCulture);
95+
dateTimeTimeZone = new DateTimeTimeZone
96+
{
97+
TimeZone = "Asia/Jerusalem",
98+
DateTime = dateTimeString
99+
};
100+
actualDateTime = dateTimeTimeZone.ToDateTime();
101+
Assert.Equal(expectedDateTime, actualDateTime.ToLocalTime());
102+
103+
//scenario where the dateTime has no timezone offset and timezone is unspecified
104+
dateTimeTimeZone = new DateTimeTimeZone
105+
{
106+
TimeZone = "Asia/Jerusalem",
107+
DateTime = "2024-01-16T08:30:00.0000000"
108+
};
24109

110+
actualDateTime = dateTimeTimeZone.ToDateTime();
111+
expectedDateTime = new DateTime(2024, 1, 16, 08, 30, 0, DateTimeKind.Unspecified);
25112
Assert.Equal(expectedDateTime, actualDateTime);
113+
Assert.Equal(DateTimeKind.Unspecified, actualDateTime.Kind);
26114
}
27115

28116
[Fact]

0 commit comments

Comments
 (0)