Skip to content

Commit 1697728

Browse files
committed
Fix for datetime tzinfo conversion
1 parent 3b0f31e commit 1697728

File tree

2 files changed

+66
-2
lines changed

2 files changed

+66
-2
lines changed

src/embed_tests/TestConverter.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,36 @@ def GetNextDay(dateTime):
231231

232232
var expectedDateTime = new DateTime(year, month, day, hour, minute, second);
233233
Assert.AreEqual(expectedDateTime, managedDateTime);
234+
235+
Assert.AreEqual(DateTimeKind.Unspecified, managedDateTime.Kind);
236+
}
237+
}
238+
239+
[Test]
240+
public void ConvertDateTimeWithExplicitUTCTimeZonePythonToCSharp()
241+
{
242+
const int year = 2024;
243+
const int month = 2;
244+
const int day = 27;
245+
const int hour = 12;
246+
const int minute = 30;
247+
const int second = 45;
248+
249+
using (Py.GIL())
250+
{
251+
var csDateTime = new DateTime(year, month, day, hour, minute, second, DateTimeKind.Utc);
252+
// Converter.ToPython will set the datetime tzinfo to UTC using a custom tzinfo class
253+
using var pyDateTime = Converter.ToPython(csDateTime).MoveToPyObject();
254+
var dateTimeResult = default(object);
255+
256+
Assert.DoesNotThrow(() => Converter.ToManaged(pyDateTime, typeof(DateTime), out dateTimeResult, false));
257+
258+
var managedDateTime = (DateTime)dateTimeResult;
259+
260+
var expectedDateTime = new DateTime(year, month, day, hour, minute, second);
261+
Assert.AreEqual(expectedDateTime, managedDateTime);
262+
263+
Assert.AreEqual(DateTimeKind.Utc, managedDateTime.Kind);
234264
}
235265
}
236266

src/runtime/Converter.cs

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1123,13 +1123,31 @@ internal static bool ToPrimitive(BorrowedReference value, Type obType, out objec
11231123
var minute = Runtime.PyObject_GetAttrString(value, minutePtr);
11241124
var second = Runtime.PyObject_GetAttrString(value, secondPtr);
11251125
var microsecond = Runtime.PyObject_GetAttrString(value, microsecondPtr);
1126+
var timeKind = DateTimeKind.Unspecified;
1127+
var tzinfo = Runtime.PyObject_GetAttrString(value, tzinfoPtr);
1128+
1129+
NewReference hours = default;
1130+
NewReference minutes = default;
1131+
if (!ReferenceNullOrNone(tzinfo))
1132+
{
1133+
// We set the datetime kind to UTC if the tzinfo was set to UTC by the ToPthon method
1134+
// using it's custom GMT Python tzinfo class
1135+
hours = Runtime.PyObject_GetAttrString(tzinfo.Borrow(), hoursPtr);
1136+
minutes = Runtime.PyObject_GetAttrString(tzinfo.Borrow(), minutesPtr);
1137+
if (!ReferenceNullOrNone(hours) &&
1138+
!ReferenceNullOrNone(minutes) &&
1139+
Runtime.PyLong_AsLong(hours.Borrow()) == 0 && Runtime.PyLong_AsLong(minutes.Borrow()) == 0)
1140+
{
1141+
timeKind = DateTimeKind.Utc;
1142+
}
1143+
}
11261144

11271145
var convertedHour = 0L;
11281146
var convertedMinute = 0L;
11291147
var convertedSecond = 0L;
11301148
var milliseconds = 0L;
11311149
// could be python date type
1132-
if (!hour.IsNull() && !hour.IsNone())
1150+
if (!ReferenceNullOrNone(hour))
11331151
{
11341152
convertedHour = Runtime.PyLong_AsLong(hour.Borrow());
11351153
convertedMinute = Runtime.PyLong_AsLong(minute.Borrow());
@@ -1143,7 +1161,8 @@ internal static bool ToPrimitive(BorrowedReference value, Type obType, out objec
11431161
(int)convertedHour,
11441162
(int)convertedMinute,
11451163
(int)convertedSecond,
1146-
(int)milliseconds);
1164+
(int)milliseconds,
1165+
timeKind);
11471166

11481167
year.Dispose();
11491168
month.Dispose();
@@ -1153,6 +1172,16 @@ internal static bool ToPrimitive(BorrowedReference value, Type obType, out objec
11531172
second.Dispose();
11541173
microsecond.Dispose();
11551174

1175+
if (!tzinfo.IsNull())
1176+
{
1177+
tzinfo.Dispose();
1178+
if (!tzinfo.IsNone())
1179+
{
1180+
hours.Dispose();
1181+
minutes.Dispose();
1182+
}
1183+
}
1184+
11561185
Exceptions.Clear();
11571186
return true;
11581187
default:
@@ -1183,6 +1212,11 @@ internal static bool ToPrimitive(BorrowedReference value, Type obType, out objec
11831212
return false;
11841213
}
11851214

1215+
private static bool ReferenceNullOrNone(NewReference reference)
1216+
{
1217+
return reference.IsNull() || reference.IsNone();
1218+
}
1219+
11861220

11871221
private static void SetConversionError(BorrowedReference value, Type target)
11881222
{

0 commit comments

Comments
 (0)