Skip to content

Commit 0678780

Browse files
committed
Fix datetime conversion when tzinfo is used
1 parent 2df3c27 commit 0678780

File tree

2 files changed

+56
-5
lines changed

2 files changed

+56
-5
lines changed

src/embed_tests/TestConverter.cs

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,54 @@ public void ConvertDateTimeRoundTrip(DateTimeKind kind)
187187
Assert.AreEqual(datetime, result);
188188
}
189189

190+
[TestCase("", DateTimeKind.Unspecified)]
191+
[TestCase("America/New_York", DateTimeKind.Unspecified)]
192+
[TestCase("UTC", DateTimeKind.Utc)]
193+
public void ConvertDateTimeWithTimeZonePythonToCSharp(string timeZone, DateTimeKind expectedDateTimeKind)
194+
{
195+
const int year = 2024;
196+
const int month = 2;
197+
const int day = 27;
198+
const int hour = 12;
199+
const int minute = 30;
200+
const int second = 45;
201+
202+
using (Py.GIL())
203+
{
204+
dynamic module = PyModule.FromString("module", @$"
205+
from clr import AddReference
206+
AddReference(""Python.EmbeddingTest"")
207+
AddReference(""System"")
208+
209+
from Python.EmbeddingTest import *
210+
211+
from datetime import datetime
212+
from pytz import timezone
213+
214+
tzinfo = timezone('{timeZone}') if '{timeZone}' else None
215+
216+
def GetPyDateTime():
217+
return datetime({year}, {month}, {day}, {hour}, {minute}, {second}, tzinfo=tzinfo) \
218+
if tzinfo else \
219+
datetime({year}, {month}, {day}, {hour}, {minute}, {second})
220+
221+
def GetNextDay(dateTime):
222+
return TestConverter.GetNextDay(dateTime)
223+
");
224+
225+
var pyDateTime = module.GetPyDateTime();
226+
var dateTimeResult = default(object);
227+
228+
Assert.DoesNotThrow(() => Converter.ToManaged(pyDateTime, typeof(DateTime), out dateTimeResult, false));
229+
230+
var managedDateTime = (DateTime)dateTimeResult;
231+
232+
var expectedDateTime = new DateTime(year, month, day, hour, minute, second);
233+
Assert.AreEqual(expectedDateTime, managedDateTime);
234+
Assert.AreEqual(managedDateTime.Kind, expectedDateTimeKind);
235+
}
236+
}
237+
190238
[Test]
191239
public void ConvertTimestampRoundTrip()
192240
{
@@ -362,7 +410,7 @@ class PyGetListImpl(test.GetListImpl):
362410
List<string> result = inst.GetList();
363411
CollectionAssert.AreEqual(new[] { "testing" }, result);
364412
}
365-
413+
366414
[Test]
367415
public void PrimitiveIntConversion()
368416
{

src/runtime/Converter.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,6 @@ static Converter()
7676
timeSpanCtor = Runtime.PyObject_GetAttrString(dateTimeMod.Borrow(), "timedelta").MoveToPyObject();
7777
PythonException.ThrowIfIsNull(timeSpanCtor);
7878

79-
8079
tzInfoCtor = new Lazy<PyObject>(() =>
8180
{
8281
var tzInfoMod = PyModule.FromString("custom_tzinfo", @"
@@ -1131,9 +1130,13 @@ internal static bool ToPrimitive(BorrowedReference value, Type obType, out objec
11311130
NewReference minutes = default;
11321131
if (!tzinfo.IsNone() && !tzinfo.IsNull())
11331132
{
1134-
hours = Runtime.PyObject_GetAttrString(tzinfo.Borrow(), hoursPtr);
1135-
minutes = Runtime.PyObject_GetAttrString(tzinfo.Borrow(), minutesPtr);
1136-
if (Runtime.PyLong_AsLong(hours.Borrow()) == 0 && Runtime.PyLong_AsLong(minutes.Borrow()) == 0)
1133+
var tznameMethod = Runtime.PyObject_GetAttrString(tzinfo.Borrow(), new StrPtr("tzname", Encoding.UTF8));
1134+
var args = Runtime.PyTuple_New(1);
1135+
Runtime.PyTuple_SetItem(args.Borrow(), 0, Runtime.None.Steal());
1136+
var tznameObj = Runtime.PyObject_CallObject(tznameMethod.Borrow(), args.Borrow());
1137+
var tzname = Runtime.GetManagedString(tznameObj.Borrow());
1138+
1139+
if (tzname.Contains("UTC", StringComparison.InvariantCultureIgnoreCase))
11371140
{
11381141
timeKind = DateTimeKind.Utc;
11391142
}

0 commit comments

Comments
 (0)