diff --git a/src/embed_tests/TestConverter.cs b/src/embed_tests/TestConverter.cs index 9acfbe42d..e86b7f651 100644 --- a/src/embed_tests/TestConverter.cs +++ b/src/embed_tests/TestConverter.cs @@ -187,6 +187,54 @@ public void ConvertDateTimeRoundTrip(DateTimeKind kind) Assert.AreEqual(datetime, result); } + [TestCase("", DateTimeKind.Unspecified)] + [TestCase("America/New_York", DateTimeKind.Unspecified)] + [TestCase("UTC", DateTimeKind.Utc)] + public void ConvertDateTimeWithTimeZonePythonToCSharp(string timeZone, DateTimeKind expectedDateTimeKind) + { + const int year = 2024; + const int month = 2; + const int day = 27; + const int hour = 12; + const int minute = 30; + const int second = 45; + + using (Py.GIL()) + { + dynamic module = PyModule.FromString("module", @$" +from clr import AddReference +AddReference(""Python.EmbeddingTest"") +AddReference(""System"") + +from Python.EmbeddingTest import * + +from datetime import datetime +from pytz import timezone + +tzinfo = timezone('{timeZone}') if '{timeZone}' else None + +def GetPyDateTime(): + return datetime({year}, {month}, {day}, {hour}, {minute}, {second}, tzinfo=tzinfo) \ + if tzinfo else \ + datetime({year}, {month}, {day}, {hour}, {minute}, {second}) + +def GetNextDay(dateTime): + return TestConverter.GetNextDay(dateTime) +"); + + var pyDateTime = module.GetPyDateTime(); + var dateTimeResult = default(object); + + Assert.DoesNotThrow(() => Converter.ToManaged(pyDateTime, typeof(DateTime), out dateTimeResult, false)); + + var managedDateTime = (DateTime)dateTimeResult; + + var expectedDateTime = new DateTime(year, month, day, hour, minute, second); + Assert.AreEqual(expectedDateTime, managedDateTime); + Assert.AreEqual(managedDateTime.Kind, expectedDateTimeKind); + } + } + [Test] public void ConvertTimestampRoundTrip() { @@ -362,7 +410,7 @@ class PyGetListImpl(test.GetListImpl): List result = inst.GetList(); CollectionAssert.AreEqual(new[] { "testing" }, result); } - + [Test] public void PrimitiveIntConversion() { diff --git a/src/runtime/Converter.cs b/src/runtime/Converter.cs index 05afe2f38..fb7f7071b 100644 --- a/src/runtime/Converter.cs +++ b/src/runtime/Converter.cs @@ -76,7 +76,6 @@ static Converter() timeSpanCtor = Runtime.PyObject_GetAttrString(dateTimeMod.Borrow(), "timedelta").MoveToPyObject(); PythonException.ThrowIfIsNull(timeSpanCtor); - tzInfoCtor = new Lazy(() => { var tzInfoMod = PyModule.FromString("custom_tzinfo", @" @@ -1131,9 +1130,13 @@ internal static bool ToPrimitive(BorrowedReference value, Type obType, out objec NewReference minutes = default; if (!tzinfo.IsNone() && !tzinfo.IsNull()) { - hours = Runtime.PyObject_GetAttrString(tzinfo.Borrow(), hoursPtr); - minutes = Runtime.PyObject_GetAttrString(tzinfo.Borrow(), minutesPtr); - if (Runtime.PyLong_AsLong(hours.Borrow()) == 0 && Runtime.PyLong_AsLong(minutes.Borrow()) == 0) + var tznameMethod = Runtime.PyObject_GetAttrString(tzinfo.Borrow(), new StrPtr("tzname", Encoding.UTF8)); + var args = Runtime.PyTuple_New(1); + Runtime.PyTuple_SetItem(args.Borrow(), 0, Runtime.None.Steal()); + var tznameObj = Runtime.PyObject_CallObject(tznameMethod.Borrow(), args.Borrow()); + var tzname = Runtime.GetManagedString(tznameObj.Borrow()); + + if (tzname.Contains("UTC", StringComparison.InvariantCultureIgnoreCase)) { timeKind = DateTimeKind.Utc; }