diff --git a/src/embed_tests/TestConverter.cs b/src/embed_tests/TestConverter.cs
index 3d68456e3..40ed9ff48 100644
--- a/src/embed_tests/TestConverter.cs
+++ b/src/embed_tests/TestConverter.cs
@@ -231,6 +231,36 @@ def GetNextDay(dateTime):
var expectedDateTime = new DateTime(year, month, day, hour, minute, second);
Assert.AreEqual(expectedDateTime, managedDateTime);
+
+ Assert.AreEqual(DateTimeKind.Unspecified, managedDateTime.Kind);
+ }
+ }
+
+ [Test]
+ public void ConvertDateTimeWithExplicitUTCTimeZonePythonToCSharp()
+ {
+ 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())
+ {
+ var csDateTime = new DateTime(year, month, day, hour, minute, second, DateTimeKind.Utc);
+ // Converter.ToPython will set the datetime tzinfo to UTC using a custom tzinfo class
+ using var pyDateTime = Converter.ToPython(csDateTime).MoveToPyObject();
+ 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(DateTimeKind.Utc, managedDateTime.Kind);
}
}
diff --git a/src/perf_tests/Python.PerformanceTests.csproj b/src/perf_tests/Python.PerformanceTests.csproj
index 373383145..708d6572e 100644
--- a/src/perf_tests/Python.PerformanceTests.csproj
+++ b/src/perf_tests/Python.PerformanceTests.csproj
@@ -13,7 +13,7 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
compile
@@ -25,7 +25,7 @@
-
+
diff --git a/src/runtime/Converter.cs b/src/runtime/Converter.cs
index 3f249f0aa..d42ff958a 100644
--- a/src/runtime/Converter.cs
+++ b/src/runtime/Converter.cs
@@ -1123,13 +1123,31 @@ internal static bool ToPrimitive(BorrowedReference value, Type obType, out objec
var minute = Runtime.PyObject_GetAttrString(value, minutePtr);
var second = Runtime.PyObject_GetAttrString(value, secondPtr);
var microsecond = Runtime.PyObject_GetAttrString(value, microsecondPtr);
+ var timeKind = DateTimeKind.Unspecified;
+ var tzinfo = Runtime.PyObject_GetAttrString(value, tzinfoPtr);
+
+ NewReference hours = default;
+ NewReference minutes = default;
+ if (!ReferenceNullOrNone(tzinfo))
+ {
+ // We set the datetime kind to UTC if the tzinfo was set to UTC by the ToPthon method
+ // using it's custom GMT Python tzinfo class
+ hours = Runtime.PyObject_GetAttrString(tzinfo.Borrow(), hoursPtr);
+ minutes = Runtime.PyObject_GetAttrString(tzinfo.Borrow(), minutesPtr);
+ if (!ReferenceNullOrNone(hours) &&
+ !ReferenceNullOrNone(minutes) &&
+ Runtime.PyLong_AsLong(hours.Borrow()) == 0 && Runtime.PyLong_AsLong(minutes.Borrow()) == 0)
+ {
+ timeKind = DateTimeKind.Utc;
+ }
+ }
var convertedHour = 0L;
var convertedMinute = 0L;
var convertedSecond = 0L;
var milliseconds = 0L;
// could be python date type
- if (!hour.IsNull() && !hour.IsNone())
+ if (!ReferenceNullOrNone(hour))
{
convertedHour = Runtime.PyLong_AsLong(hour.Borrow());
convertedMinute = Runtime.PyLong_AsLong(minute.Borrow());
@@ -1143,7 +1161,8 @@ internal static bool ToPrimitive(BorrowedReference value, Type obType, out objec
(int)convertedHour,
(int)convertedMinute,
(int)convertedSecond,
- (int)milliseconds);
+ (int)milliseconds,
+ timeKind);
year.Dispose();
month.Dispose();
@@ -1153,6 +1172,16 @@ internal static bool ToPrimitive(BorrowedReference value, Type obType, out objec
second.Dispose();
microsecond.Dispose();
+ if (!tzinfo.IsNull())
+ {
+ tzinfo.Dispose();
+ if (!tzinfo.IsNone())
+ {
+ hours.Dispose();
+ minutes.Dispose();
+ }
+ }
+
Exceptions.Clear();
return true;
default:
@@ -1183,6 +1212,11 @@ internal static bool ToPrimitive(BorrowedReference value, Type obType, out objec
return false;
}
+ private static bool ReferenceNullOrNone(NewReference reference)
+ {
+ return reference.IsNull() || reference.IsNone();
+ }
+
private static void SetConversionError(BorrowedReference value, Type target)
{
diff --git a/src/runtime/Properties/AssemblyInfo.cs b/src/runtime/Properties/AssemblyInfo.cs
index a77e40b7a..5eaf718eb 100644
--- a/src/runtime/Properties/AssemblyInfo.cs
+++ b/src/runtime/Properties/AssemblyInfo.cs
@@ -4,5 +4,5 @@
[assembly: InternalsVisibleTo("Python.EmbeddingTest, PublicKey=00240000048000009400000006020000002400005253413100040000110000005ffd8f49fb44ab0641b3fd8d55e749f716e6dd901032295db641eb98ee46063cbe0d4a1d121ef0bc2af95f8a7438d7a80a3531316e6b75c2dae92fb05a99f03bf7e0c03980e1c3cfb74ba690aca2f3339ef329313bcc5dccced125a4ffdc4531dcef914602cd5878dc5fbb4d4c73ddfbc133f840231343e013762884d6143189")]
[assembly: InternalsVisibleTo("Python.Test, PublicKey=00240000048000009400000006020000002400005253413100040000110000005ffd8f49fb44ab0641b3fd8d55e749f716e6dd901032295db641eb98ee46063cbe0d4a1d121ef0bc2af95f8a7438d7a80a3531316e6b75c2dae92fb05a99f03bf7e0c03980e1c3cfb74ba690aca2f3339ef329313bcc5dccced125a4ffdc4531dcef914602cd5878dc5fbb4d4c73ddfbc133f840231343e013762884d6143189")]
-[assembly: AssemblyVersion("2.0.27")]
-[assembly: AssemblyFileVersion("2.0.27")]
+[assembly: AssemblyVersion("2.0.28")]
+[assembly: AssemblyFileVersion("2.0.28")]
diff --git a/src/runtime/Python.Runtime.csproj b/src/runtime/Python.Runtime.csproj
index 25f01f3cb..4677ea191 100644
--- a/src/runtime/Python.Runtime.csproj
+++ b/src/runtime/Python.Runtime.csproj
@@ -5,7 +5,7 @@
Python.Runtime
Python.Runtime
QuantConnect.pythonnet
- 2.0.27
+ 2.0.28
false
LICENSE
https://github.com/pythonnet/pythonnet