diff --git a/CHANGELOG.md b/CHANGELOG.md index cb93d64ee4..276a85af44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ language runtime. The main focus is on user-observable behavior of the engine. * Added support for specifying generics on foreign classes, and inheriting from such classes. Especially when using Java classes that support generics, this allows expressing the generic types in Python type annotations as well. * Added a new `java` backend for the `pyexpat` module that uses a Java XML parser instead of the native `expat` library. It can be useful when running without native access or multiple-context scenarios. This backend is the default when embedding and can be switched back to native `expat` by setting `python.PyExpatModuleBackend` option to `native`. Standalone distribution still defaults to native expat backend. * Add a new context option `python.UnicodeCharacterDatabaseNativeFallback` to control whether the ICU database may fall back to the native unicode character database from CPython for features and characters not supported by ICU. This requires native access to be enabled and is disabled by default for embeddings. +* Foreign temporal objects (dates, times, and timezones) are now given a Python class corresponding to their interop traits, i.e., `date`, `time`, `datetime`, or `tzinfo`. This allows any foreign objects with these traits to be used in place of the native Python types and Python methods available on these types work on the foreign types. ## Version 25.0.1 * Allow users to keep going on unsupported JDK/OS/ARCH combinations at their own risk by opting out of early failure using `-Dtruffle.UseFallbackRuntime=true`, `-Dpolyglot.engine.userResourceCache=/set/to/a/writeable/dir`, `-Dpolyglot.engine.allowUnsupportedPlatform=true`, and `-Dpolyglot.python.UnsupportedPlatformEmulates=[linux|macos|windows]` and `-Dorg.graalvm.python.resources.exclude=native.files`. diff --git a/graalpython/com.oracle.graal.python.test/src/tests/test_interop.py b/graalpython/com.oracle.graal.python.test/src/tests/test_interop.py index ab01a4c18d..ea4c2583bc 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/test_interop.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/test_interop.py @@ -114,6 +114,8 @@ def test_single_trait_classes(self): polyglot.ForeignObject, polyglot.ForeignList, polyglot.ForeignBoolean, + polyglot.ForeignDate, + polyglot.ForeignDateTime, polyglot.ForeignException, polyglot.ForeignExecutable, polyglot.ForeignDict, @@ -124,6 +126,8 @@ def test_single_trait_classes(self): polyglot.ForeignNone, polyglot.ForeignNumber, polyglot.ForeignString, + polyglot.ForeignTime, + polyglot.ForeignTimeZone, ] for c in classes: @@ -131,7 +135,16 @@ def test_single_trait_classes(self): if c is polyglot.ForeignBoolean: self.assertIs(c.__base__, polyglot.ForeignNumber) elif c is not polyglot.ForeignObject: - self.assertIs(c.__base__, polyglot.ForeignObject) + if c is polyglot.ForeignDate: + self.assertIs(c.__base__, __import__("datetime").date) + elif c is polyglot.ForeignTime: + self.assertIs(c.__base__, __import__("datetime").time) + elif c is polyglot.ForeignDateTime: + self.assertIs(c.__base__, __import__("datetime").datetime) + elif c is polyglot.ForeignTimeZone: + self.assertIs(c.__base__, __import__("datetime").tzinfo) + else: + self.assertIs(c.__base__, polyglot.ForeignObject) def test_get_class(self): def wrap(obj): @@ -155,6 +168,7 @@ def t(obj): self.assertEqual(t("abc"), polyglot.ForeignString) from java.lang import Object, Boolean, Integer, Throwable, Thread, Number, String + from java.time import LocalDate, LocalDateTime, LocalTime, ZoneId from java.util import ArrayList, HashMap, ArrayDeque from java.math import BigInteger null = Integer.getInteger("something_that_does_not_exists") @@ -172,6 +186,19 @@ def t(obj): self.assertEqual(type(null), polyglot.ForeignNone) self.assertEqual(type(BigInteger.valueOf(42)), polyglot.ForeignNumber) self.assertEqual(type(wrap(String("abc"))), polyglot.ForeignString) + local_date = LocalDate.of(2025, 3, 23) + self.assertIsInstance(local_date, polyglot.ForeignDate) + self.assertIsInstance(local_date, __import__("datetime").date) + + local_time = LocalTime.of(7, 8, 9) + self.assertIsInstance(local_time, polyglot.ForeignTime) + self.assertIsInstance(local_time, __import__("datetime").time) + + local_date_time = LocalDateTime.of(2025, 3, 23, 7, 8, 9) + self.assertIsInstance(local_date_time, polyglot.ForeignDateTime) + self.assertIsInstance(local_date_time, __import__("datetime").datetime) + + self.assertEqual(type(ZoneId.of("UTC")), polyglot.ForeignTimeZone) def test_import(self): def some_function(): @@ -186,6 +213,146 @@ def some_function(): assert imported_fun1 is some_function assert imported_fun1() == "hello, polyglot world!" + def test_foreign_date_behavior(self): + import datetime + import java + + LocalDate = java.type("java.time.LocalDate") + + d = LocalDate.of(2025, 3, 23) + self.assertEqual(d.year, 2025) + self.assertEqual(d.month, 3) + self.assertEqual(d.day, 23) + self.assertEqual(str(d), "2025-03-23") + self.assertEqual(d.isoformat(), "2025-03-23") + self.assertEqual(d.ctime(), datetime.date(2025, 3, 23).ctime()) + self.assertEqual(d.strftime("%Y-%m-%d"), "2025-03-23") + self.assertEqual(format(d, "%Y-%m-%d"), "2025-03-23") + self.assertEqual(d.toordinal(), datetime.date(2025, 3, 23).toordinal()) + self.assertEqual(d.weekday(), datetime.date(2025, 3, 23).weekday()) + self.assertEqual(d.isoweekday(), datetime.date(2025, 3, 23).isoweekday()) + self.assertEqual(d.isocalendar(), datetime.date(2025, 3, 23).isocalendar()) + self.assertEqual(d.timetuple(), datetime.date(2025, 3, 23).timetuple()) + self.assertEqual(hash(d), hash(datetime.date(2025, 3, 23))) + self.assertEqual(d, datetime.date(2025, 3, 23)) + self.assertEqual(d, LocalDate.of(2025, 3, 23)) + self.assertEqual(d.replace(day=24), datetime.date(2025, 3, 24)) + self.assertEqual(d + datetime.timedelta(days=1), datetime.date(2025, 3, 24)) + self.assertEqual(d - datetime.timedelta(days=1), datetime.date(2025, 3, 22)) + self.assertEqual(d - datetime.date(2025, 3, 20), datetime.timedelta(days=3)) + self.assertEqual(d - LocalDate.of(2025, 3, 20), datetime.timedelta(days=3)) + + def test_foreign_time_behavior(self): + import datetime + import java + + LocalTime = java.type("java.time.LocalTime") + + t = LocalTime.of(7, 8, 9) + self.assertEqual(t.hour, 7) + self.assertEqual(t.minute, 8) + self.assertEqual(t.second, 9) + self.assertEqual(t.microsecond, 0) + self.assertEqual(str(t), "07:08:09") + self.assertEqual(t.isoformat(), "07:08:09") + self.assertEqual(t.strftime("%H:%M:%S"), "07:08:09") + self.assertEqual(format(t, "%H:%M:%S"), "07:08:09") + self.assertEqual(hash(t), hash(datetime.time(7, 8, 9))) + self.assertEqual(t, datetime.time(7, 8, 9)) + self.assertEqual(t, LocalTime.of(7, 8, 9)) + self.assertEqual(t.replace(second=10), datetime.time(7, 8, 10)) + self.assertLess(t, datetime.time(7, 8, 10)) + self.assertIsNone(t.tzinfo) + self.assertIsNone(t.utcoffset()) + self.assertIsNone(t.dst()) + self.assertIsNone(t.tzname()) + + def test_foreign_datetime_behavior(self): + import datetime + import java + + LocalDateTime = java.type("java.time.LocalDateTime") + ZonedDateTime = java.type("java.time.ZonedDateTime") + ZoneId = java.type("java.time.ZoneId") + + dt = LocalDateTime.of(2025, 3, 23, 7, 8, 9) + self.assertEqual(dt.year, 2025) + self.assertEqual(dt.month, 3) + self.assertEqual(dt.day, 23) + self.assertEqual(dt.hour, 7) + self.assertEqual(dt.minute, 8) + self.assertEqual(dt.second, 9) + self.assertEqual(dt.microsecond, 0) + self.assertEqual(str(dt), "2025-03-23 07:08:09") + self.assertEqual(dt.isoformat(), "2025-03-23T07:08:09") + self.assertEqual(dt.date(), datetime.date(2025, 3, 23)) + self.assertEqual(dt.time(), datetime.time(7, 8, 9)) + self.assertEqual(dt.timetz(), datetime.time(7, 8, 9)) + self.assertEqual(dt.timetuple(), datetime.datetime(2025, 3, 23, 7, 8, 9).timetuple()) + self.assertEqual(hash(dt), hash(datetime.datetime(2025, 3, 23, 7, 8, 9))) + self.assertEqual(dt, datetime.datetime(2025, 3, 23, 7, 8, 9)) + self.assertEqual(dt, LocalDateTime.of(2025, 3, 23, 7, 8, 9)) + self.assertEqual(dt.replace(minute=9), datetime.datetime(2025, 3, 23, 7, 9, 9)) + self.assertEqual(dt + datetime.timedelta(days=1), datetime.datetime(2025, 3, 24, 7, 8, 9)) + self.assertEqual(dt - datetime.timedelta(days=1), datetime.datetime(2025, 3, 22, 7, 8, 9)) + self.assertEqual(dt - datetime.datetime(2025, 3, 20, 7, 8, 9), datetime.timedelta(days=3)) + self.assertLess(dt, datetime.datetime(2025, 3, 23, 7, 8, 10)) + self.assertIsNone(dt.tzinfo) + self.assertIsNone(dt.utcoffset()) + self.assertIsNone(dt.dst()) + self.assertIsNone(dt.tzname()) + + berlin = ZoneId.of("Europe/Berlin") + zoned_dt = ZonedDateTime.of(2025, 3, 23, 7, 8, 9, 0, berlin) + self.assertIsInstance(zoned_dt.tzinfo, datetime.tzinfo) + self.assertEqual(zoned_dt.utcoffset(), datetime.timedelta(hours=1)) + self.assertEqual(zoned_dt.dst(), datetime.timedelta()) + self.assertEqual(zoned_dt.tzname(), "CET") + self.assertEqual(zoned_dt.isoformat(), "2025-03-23T07:08:09+01:00") + + def test_foreign_timezone_behavior(self): + import datetime + import java + + ZoneId = java.type("java.time.ZoneId") + ZonedDateTime = java.type("java.time.ZonedDateTime") + + utc = ZoneId.of("UTC") + self.assertIsInstance(utc, datetime.tzinfo) + self.assertEqual(str(utc), "UTC") + self.assertEqual(utc.tzname(None), "UTC") + self.assertEqual(utc.utcoffset(None), datetime.timedelta()) + self.assertIsNone(utc.dst(None)) + + aware = datetime.datetime(2025, 3, 23, 7, 8, 9, tzinfo=utc) + self.assertIs(aware.tzinfo, utc) + self.assertEqual(aware.utcoffset(), datetime.timedelta()) + self.assertEqual(aware.tzname(), "UTC") + self.assertEqual(aware.isoformat(), "2025-03-23T07:08:09+00:00") + + berlin = ZoneId.of("Europe/Berlin") + self.assertIsInstance(berlin, datetime.tzinfo) + self.assertIsNone(berlin.utcoffset(None)) + self.assertIsNone(berlin.dst(None)) + self.assertIsNone(berlin.tzname(None)) + + local = datetime.datetime(2025, 3, 23, 7, 8, 9, tzinfo=berlin) + self.assertIs(local.tzinfo, berlin) + self.assertEqual(local.utcoffset(), datetime.timedelta(hours=1)) + self.assertEqual(local.dst(), datetime.timedelta()) + self.assertEqual(local.tzname(), "CET") + self.assertEqual(berlin.fromutc(datetime.datetime(2025, 3, 23, 6, 8, 9, tzinfo=berlin)), + datetime.datetime(2025, 3, 23, 7, 8, 9, tzinfo=berlin)) + + foreign_aware = ZonedDateTime.of(2025, 3, 23, 6, 8, 9, 0, berlin) + self.assertEqual(berlin.fromutc(foreign_aware), + datetime.datetime(2025, 3, 23, 7, 8, 9, tzinfo=berlin)) + + overlap = berlin.fromutc(datetime.datetime(2025, 10, 26, 1, 30, tzinfo=berlin)) + self.assertEqual(overlap, datetime.datetime(2025, 10, 26, 2, 30, tzinfo=berlin, fold=1)) + self.assertEqual(overlap.fold, 1) + self.assertEqual(overlap.utcoffset(), datetime.timedelta(hours=1)) + def test_read(self): o = CustomObject() assert polyglot.__read__(o, "field") == o.field diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/Python3Core.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/Python3Core.java index 3180fe0133..cabb086c89 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/Python3Core.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/Python3Core.java @@ -268,6 +268,7 @@ import com.oracle.graal.python.builtins.objects.foreign.ForeignIterableBuiltins; import com.oracle.graal.python.builtins.objects.foreign.ForeignNumberBuiltins; import com.oracle.graal.python.builtins.objects.foreign.ForeignObjectBuiltins; +import com.oracle.graal.python.builtins.objects.foreign.ForeignTimeZoneBuiltins; import com.oracle.graal.python.builtins.objects.frame.FrameBuiltins; import com.oracle.graal.python.builtins.objects.function.AbstractFunctionBuiltins; import com.oracle.graal.python.builtins.objects.function.BuiltinFunctionBuiltins; @@ -500,6 +501,7 @@ private static PythonBuiltins[] initializeBuiltins(TruffleLanguage.Env env) { new ForeignObjectBuiltins(), new ForeignNumberBuiltins(), new ForeignBooleanBuiltins(), + new ForeignTimeZoneBuiltins(), new ForeignAbstractClassBuiltins(), new ForeignExecutableBuiltins(), new ForeignInstantiableBuiltins(), diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/PythonBuiltinClassType.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/PythonBuiltinClassType.java index f5d701d729..de0c818540 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/PythonBuiltinClassType.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/PythonBuiltinClassType.java @@ -186,6 +186,7 @@ import com.oracle.graal.python.builtins.objects.foreign.ForeignIterableBuiltins; import com.oracle.graal.python.builtins.objects.foreign.ForeignNumberBuiltins; import com.oracle.graal.python.builtins.objects.foreign.ForeignObjectBuiltins; +import com.oracle.graal.python.builtins.objects.foreign.ForeignTimeZoneBuiltins; import com.oracle.graal.python.builtins.objects.frame.FrameBuiltins; import com.oracle.graal.python.builtins.objects.function.AbstractFunctionBuiltins; import com.oracle.graal.python.builtins.objects.function.FunctionBuiltins; @@ -1230,6 +1231,12 @@ def takewhile(predicate, iterable): PTzInfo, newBuilder().moduleName("datetime").publishInModule("_datetime").slots(TimeZoneBuiltins.SLOTS).doc("Fixed offset from UTC implementation of tzinfo.")), + // foreign datetime + ForeignDate("ForeignDate", PDate, newBuilder().publishInModule(J_POLYGLOT).basetype().addDict().disallowInstantiation()), + ForeignTime("ForeignTime", PTime, newBuilder().publishInModule(J_POLYGLOT).basetype().addDict().disallowInstantiation()), + ForeignDateTime("ForeignDateTime", PDateTime, newBuilder().publishInModule(J_POLYGLOT).basetype().addDict().disallowInstantiation()), + ForeignTimeZone("ForeignTimeZone", PTzInfo, newBuilder().publishInModule(J_POLYGLOT).basetype().addDict().disallowInstantiation().slots(ForeignTimeZoneBuiltins.SLOTS)), + // re PPattern( "Pattern", diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/UnicodeDataModuleBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/UnicodeDataModuleBuiltins.java index 99b88b82fe..7bf5646e6f 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/UnicodeDataModuleBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/UnicodeDataModuleBuiltins.java @@ -150,7 +150,7 @@ public void postInitialize(Python3Core core) { } } - private PythonObject createUCDCompatibilityObject(Python3Core core, PythonModule self) { + private static PythonObject createUCDCompatibilityObject(Python3Core core, PythonModule self) { TruffleString t_ucd = toTruffleStringUncached("UCD"); PythonClass clazz = PFactory.createPythonClassAndFixupSlots(null, core.getLanguage(), t_ucd, PythonBuiltinClassType.PythonObject, new PythonAbstractClass[]{core.lookupType(PythonBuiltinClassType.PythonObject)}); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/DateBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/DateBuiltins.java index 8de40f873c..e38f391955 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/DateBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/DateBuiltins.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -81,6 +81,8 @@ import com.oracle.graal.python.builtins.PythonBuiltinClassType; import com.oracle.graal.python.builtins.PythonBuiltins; import com.oracle.graal.python.builtins.modules.TimeModuleBuiltins; +import com.oracle.graal.python.builtins.modules.datetime.TemporalValueNodes.DateValue; +import com.oracle.graal.python.builtins.modules.datetime.TemporalValueNodes.TimeDeltaValue; import com.oracle.graal.python.builtins.objects.PNone; import com.oracle.graal.python.builtins.objects.PNotImplemented; import com.oracle.graal.python.builtins.objects.bytes.BytesNodes; @@ -96,6 +98,8 @@ import com.oracle.graal.python.builtins.objects.type.slots.TpSlotBinaryOp.BinaryOpBuiltinNode; import com.oracle.graal.python.lib.PyFloatAsDoubleNode; import com.oracle.graal.python.lib.PyFloatCheckNode; +import com.oracle.graal.python.lib.PyDateCheckNode; +import com.oracle.graal.python.lib.PyDeltaCheckNode; import com.oracle.graal.python.lib.PyLongAsLongNode; import com.oracle.graal.python.lib.PyObjectCallMethodObjArgs; import com.oracle.graal.python.lib.PyObjectHashNode; @@ -112,6 +116,7 @@ import com.oracle.graal.python.nodes.function.builtins.PythonUnaryBuiltinNode; import com.oracle.graal.python.nodes.function.builtins.clinic.ArgumentClinicProvider; import com.oracle.graal.python.nodes.object.GetClassNode; +import com.oracle.graal.python.nodes.object.IsForeignObjectNode; import com.oracle.graal.python.nodes.util.CannotCastException; import com.oracle.graal.python.nodes.util.CastToJavaStringNode; import com.oracle.graal.python.nodes.util.CastToTruffleStringNode; @@ -245,8 +250,9 @@ public abstract static class ReprNode extends PythonUnaryBuiltinNode { @Specialization @TruffleBoundary - static TruffleString repr(Object self) { - PDate date = DateNodes.AsManagedDateNode.executeUncached(self); + static TruffleString repr(Object self, + @Bind Node inliningTarget) { + DateValue date = TemporalValueNodes.GetDateValue.executeUncached(inliningTarget, self); TruffleString typeName = TypeNodes.GetTpNameNode.executeUncached(GetClassNode.executeUncached(self)); var string = String.format("%s(%d, %d, %d)", typeName, date.year, date.month, date.day); return TruffleString.FromJavaStringNode.getUncached().execute(string, TS_ENCODING); @@ -259,8 +265,9 @@ public abstract static class StrNode extends PythonUnaryBuiltinNode { @Specialization @TruffleBoundary - static TruffleString str(Object self) { - PDate date = DateNodes.AsManagedDateNode.executeUncached(self); + static TruffleString str(Object self, + @Bind Node inliningTarget) { + DateValue date = TemporalValueNodes.GetDateValue.executeUncached(inliningTarget, self); var string = String.format("%04d-%02d-%02d", date.year, date.month, date.day); return TruffleString.FromJavaStringNode.getUncached().execute(string, TS_ENCODING); } @@ -274,9 +281,9 @@ public abstract static class ReduceNode extends PythonUnaryBuiltinNode { static Object reduce(Object self, @Bind Node inliningTarget, @Bind PythonLanguage language, - @Cached DateNodes.AsManagedDateNode asManagedDateNode, + @Cached TemporalValueNodes.GetDateValue readDateValueNode, @Cached GetClassNode getClassNode) { - PDate date = asManagedDateNode.execute(inliningTarget, self); + DateValue date = readDateValueNode.execute(inliningTarget, self); byte[] bytes = new byte[]{(byte) (date.year / 256), (byte) (date.year % 256), (byte) date.month, (byte) date.day}; PBytes string = PFactory.createBytes(language, bytes); PTuple arguments = PFactory.createTuple(language, new Object[]{string}); @@ -292,11 +299,11 @@ abstract static class RichCmpNode extends RichCmpBuiltinNode { @Specialization static Object richCmp(Object selfObj, Object otherObj, RichCmpOp op, @Bind Node inliningTarget, - @Cached DateNodes.DateCheckNode dateCheckNode, - @Cached DateNodes.AsManagedDateNode asManagedDateNode) { + @Cached PyDateCheckNode dateCheckNode, + @Cached TemporalValueNodes.GetDateValue readDateValueNode) { if (dateCheckNode.execute(inliningTarget, selfObj) && dateCheckNode.execute(inliningTarget, otherObj)) { - PDate self = asManagedDateNode.execute(inliningTarget, selfObj); - PDate other = asManagedDateNode.execute(inliningTarget, otherObj); + DateValue self = readDateValueNode.execute(inliningTarget, selfObj); + DateValue other = readDateValueNode.execute(inliningTarget, otherObj); int result = self.compareTo(other); return op.compareResultToBool(result); } @@ -313,8 +320,8 @@ static long hash(VirtualFrame frame, Object self, @Bind Node inliningTarget, @Bind PythonLanguage language, @Cached PyObjectHashNode hashNode, - @Cached DateNodes.AsManagedDateNode asManaged) { - PDate d = asManaged.execute(inliningTarget, self); + @Cached TemporalValueNodes.GetDateValue readDateValueNode) { + DateValue d = readDateValueNode.execute(inliningTarget, self); var content = new int[]{d.year, d.month, d.day}; return hashNode.execute(frame, inliningTarget, PFactory.createTuple(language, content)); } @@ -341,21 +348,21 @@ static Object add(VirtualFrame frame, Object left, Object right, @TruffleBoundary private static Object addBoundary(Object left, Object right, Node inliningTarget) { Object dateObj, deltaObj; - if (DateNodes.DateCheckNode.executeUncached(left)) { - if (TimeDeltaNodes.TimeDeltaCheckNode.executeUncached(right)) { + if (PyDateCheckNode.executeUncached(left)) { + if (PyDeltaCheckNode.executeUncached(right)) { dateObj = left; deltaObj = right; } else { return PNotImplemented.NOT_IMPLEMENTED; } - } else if (TimeDeltaNodes.TimeDeltaCheckNode.executeUncached(left)) { + } else if (PyDeltaCheckNode.executeUncached(left)) { dateObj = right; deltaObj = left; } else { return PNotImplemented.NOT_IMPLEMENTED; } - PDate date = DateNodes.AsManagedDateNode.executeUncached(dateObj); - PTimeDelta delta = TimeDeltaNodes.AsManagedTimeDeltaNode.executeUncached(deltaObj); + DateValue date = TemporalValueNodes.GetDateValue.executeUncached(inliningTarget, dateObj); + TimeDeltaValue delta = TemporalValueNodes.GetTimeDeltaValue.executeUncached(inliningTarget, deltaObj); LocalDate from = LocalDate.of(1, 1, 1); LocalDate to = LocalDate.of(date.year, date.month, date.day); @@ -372,7 +379,7 @@ private static Object addBoundary(Object left, Object right, Node inliningTarget LocalDate localDate = ChronoUnit.DAYS.addTo(from, days - 1); return DateNodes.SubclassNewNode.getUncached().execute(inliningTarget, - GetClassNode.executeUncached(dateObj), + getResultDateType(dateObj), localDate.getYear(), localDate.getMonthValue(), localDate.getDayOfMonth()); @@ -399,29 +406,29 @@ static Object sub(VirtualFrame frame, Object left, Object right, @TruffleBoundary private static Object subBoundary(Object left, Object right, Node inliningTarget) { - if (!DateNodes.DateCheckNode.executeUncached(left)) { + if (!PyDateCheckNode.executeUncached(left)) { return PNotImplemented.NOT_IMPLEMENTED; } - PDate date = DateNodes.AsManagedDateNode.executeUncached(left); - if (DateNodes.DateCheckNode.executeUncached(right)) { + DateValue date = TemporalValueNodes.GetDateValue.executeUncached(inliningTarget, left); + if (PyDateCheckNode.executeUncached(right)) { LocalDate from = LocalDate.of(1, 1, 1); LocalDate toSelf = LocalDate.of(date.year, date.month, date.day); long daysSelf = ChronoUnit.DAYS.between(from, toSelf) + 1; - PDate other = DateNodes.AsManagedDateNode.executeUncached(right); + DateValue other = TemporalValueNodes.GetDateValue.executeUncached(inliningTarget, right); LocalDate toOther = LocalDate.of(other.year, other.month, other.day); long daysOther = ChronoUnit.DAYS.between(from, toOther) + 1; long days = daysSelf - daysOther; return new PTimeDelta( PythonBuiltinClassType.PTimeDelta, - PythonBuiltinClassType.PTimeDelta.getInstanceShape(PythonLanguage.get(null)), + PythonBuiltinClassType.PTimeDelta.getInstanceShape(PythonLanguage.get(inliningTarget)), (int) days, 0, 0); } - if (TimeDeltaNodes.TimeDeltaCheckNode.executeUncached(right)) { - PTimeDelta timeDelta = TimeDeltaNodes.AsManagedTimeDeltaNode.executeUncached(right); + if (PyDeltaCheckNode.executeUncached(right)) { + TimeDeltaValue timeDelta = TemporalValueNodes.GetTimeDeltaValue.executeUncached(inliningTarget, right); LocalDate from = LocalDate.of(1, 1, 1); LocalDate to = LocalDate.of(date.year, date.month, date.day); long days = ChronoUnit.DAYS.between(from, to) + 1; @@ -437,7 +444,7 @@ private static Object subBoundary(Object left, Object right, Node inliningTarget LocalDate localDate = ChronoUnit.DAYS.addTo(from, days - 1); return DateNodes.SubclassNewNode.getUncached().execute(inliningTarget, - GetClassNode.executeUncached(left), + getResultDateType(left), localDate.getYear(), localDate.getMonthValue(), localDate.getDayOfMonth()); @@ -458,7 +465,14 @@ static int getYear(PDate self) { @Specialization static int getYear(PythonAbstractNativeObject self, @Cached CStructAccess.ReadByteNode readNode) { - return DateNodes.AsManagedDateNode.getYear(self, readNode); + return DateNodes.FromNative.getYear(self, readNode); + } + + @Specialization + static int getYear(Object self, + @Bind Node inliningTarget, + @Cached TemporalValueNodes.GetDateValue readDateValueNode) { + return readDateValueNode.execute(inliningTarget, self).year; } } @@ -474,7 +488,14 @@ static int getMonth(PDate self) { @Specialization static int getMonth(PythonAbstractNativeObject self, @Cached CStructAccess.ReadByteNode readNode) { - return DateNodes.AsManagedDateNode.getMonth(self, readNode); + return DateNodes.FromNative.getMonth(self, readNode); + } + + @Specialization + static int getMonth(Object self, + @Bind Node inliningTarget, + @Cached TemporalValueNodes.GetDateValue readDateValueNode) { + return readDateValueNode.execute(inliningTarget, self).month; } } @@ -490,10 +511,21 @@ static int getDay(PDate self) { @Specialization static int getDay(PythonAbstractNativeObject self, @Cached CStructAccess.ReadByteNode readNode) { - return DateNodes.AsManagedDateNode.getDay(self, readNode); + return DateNodes.FromNative.getDay(self, readNode); + } + + @Specialization + static int getDay(Object self, + @Bind Node inliningTarget, + @Cached TemporalValueNodes.GetDateValue readDateValueNode) { + return readDateValueNode.execute(inliningTarget, self).day; } } + private static Object getResultDateType(Object dateObj) { + return IsForeignObjectNode.executeUncached(dateObj) ? PythonBuiltinClassType.PDate : GetClassNode.executeUncached(dateObj); + } + @Builtin(name = "today", minNumOfPositionalArgs = 1, isClassmethod = true, parameterNames = {"self"}) @GenerateNodeFactory public abstract static class TodayNode extends PythonBuiltinNode { @@ -738,11 +770,11 @@ public abstract static class ReplaceNode extends PythonBuiltinNode { @Specialization static Object replace(VirtualFrame frame, Object self, Object yearObject, Object monthObject, Object dayObject, @Bind Node inliningTarget, - @Cached DateNodes.AsManagedDateNode asManagedDateNode, + @Cached TemporalValueNodes.GetDateValue readDateValueNode, @Cached PyLongAsLongNode longAsLongNode, @Cached GetClassNode getClassNode, @Cached DateNodes.NewNode newNode) { - PDate date = asManagedDateNode.execute(inliningTarget, self); + DateValue date = readDateValueNode.execute(inliningTarget, self); final int year, month, day; if (yearObject instanceof PNone) { @@ -773,8 +805,9 @@ public abstract static class ToOrdinalNode extends PythonUnaryBuiltinNode { @Specialization @TruffleBoundary - static long toOrdinal(Object selfObj) { - PDate self = DateNodes.AsManagedDateNode.executeUncached(selfObj); + static long toOrdinal(Object selfObj, + @Bind Node inliningTarget) { + DateValue self = TemporalValueNodes.GetDateValue.executeUncached(inliningTarget, selfObj); LocalDate from = LocalDate.of(1, 1, 1); LocalDate to = LocalDate.of(self.year, self.month, self.day); return ChronoUnit.DAYS.between(from, to) + 1; @@ -824,8 +857,9 @@ public abstract static class WeekDayNode extends PythonUnaryBuiltinNode { @Specialization @TruffleBoundary - static int weekDay(Object selfObj) { - PDate self = DateNodes.AsManagedDateNode.executeUncached(selfObj); + static int weekDay(Object selfObj, + @Bind Node inliningTarget) { + DateValue self = TemporalValueNodes.GetDateValue.executeUncached(inliningTarget, selfObj); LocalDate localDate = LocalDate.of(self.year, self.month, self.day); DayOfWeek dayOfWeek = localDate.getDayOfWeek(); @@ -840,8 +874,9 @@ public abstract static class IsoWeekDayNode extends PythonUnaryBuiltinNode { @Specialization @TruffleBoundary - static int weekDay(Object selfObj) { - PDate self = DateNodes.AsManagedDateNode.executeUncached(selfObj); + static int weekDay(Object selfObj, + @Bind Node inliningTarget) { + DateValue self = TemporalValueNodes.GetDateValue.executeUncached(inliningTarget, selfObj); LocalDate localDate = LocalDate.of(self.year, self.month, self.day); DayOfWeek dayOfWeek = localDate.getDayOfWeek(); return dayOfWeek.getValue(); @@ -855,8 +890,9 @@ public abstract static class IsoCalendarNode extends PythonUnaryBuiltinNode { @Specialization @TruffleBoundary static PTuple isoCalendar(Object selfObj, - @Bind PythonLanguage language) { - PDate self = DateNodes.AsManagedDateNode.executeUncached(selfObj); + @Bind PythonLanguage language, + @Bind Node inliningTarget) { + DateValue self = TemporalValueNodes.GetDateValue.executeUncached(inliningTarget, selfObj); LocalDate localDate = LocalDate.of(self.year, self.month, self.day); // use week based year ISO-8601 calendar @@ -874,8 +910,9 @@ public abstract static class IsoFormatNode extends PythonUnaryBuiltinNode { @Specialization @TruffleBoundary - static TruffleString isoFormat(Object selfObj) { - PDate self = DateNodes.AsManagedDateNode.executeUncached(selfObj); + static TruffleString isoFormat(Object selfObj, + @Bind Node inliningTarget) { + DateValue self = TemporalValueNodes.GetDateValue.executeUncached(inliningTarget, selfObj); LocalDate locaDate = LocalDate.of(self.year, self.month, self.day); var isoString = locaDate.toString(); return TruffleString.FromJavaStringNode.getUncached().execute(isoString, TS_ENCODING); @@ -888,8 +925,9 @@ public abstract static class CTimeNode extends PythonUnaryBuiltinNode { @Specialization @TruffleBoundary - static TruffleString cTime(Object selfObj) { - PDate self = DateNodes.AsManagedDateNode.executeUncached(selfObj); + static TruffleString cTime(Object selfObj, + @Bind Node inliningTarget) { + DateValue self = TemporalValueNodes.GetDateValue.executeUncached(inliningTarget, selfObj); LocalDate localDate = LocalDate.of(self.year, self.month, self.day); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("EEE LLL ppd 00:00:00 yyyy"); String ctime = localDate.format(formatter); @@ -904,8 +942,9 @@ public abstract static class TimeTupleNode extends PythonUnaryBuiltinNode { @Specialization @TruffleBoundary static PTuple timeTuple(Object selfObj, - @Bind PythonLanguage language) { - PDate self = DateNodes.AsManagedDateNode.executeUncached(selfObj); + @Bind PythonLanguage language, + @Bind Node inliningTarget) { + DateValue self = TemporalValueNodes.GetDateValue.executeUncached(inliningTarget, selfObj); LocalDate localDate = LocalDate.of(self.year, self.month, self.day); // Python's day of week is in range 0-6 @@ -929,8 +968,9 @@ protected ArgumentClinicProvider getArgumentClinic() { @Specialization @TruffleBoundary - static TruffleString strftime(Object selfObj, TruffleString format) { - PDate self = DateNodes.AsManagedDateNode.executeUncached(selfObj); + static TruffleString strftime(Object selfObj, TruffleString format, + @Bind Node inliningTarget) { + DateValue self = TemporalValueNodes.GetDateValue.executeUncached(inliningTarget, selfObj); // Reuse time.strftime(format, time_tuple) method. // construct time_tuple diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/DateNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/DateNodes.java index b13780c403..9d5d0e43fd 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/DateNodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/DateNodes.java @@ -40,14 +40,12 @@ */ package com.oracle.graal.python.builtins.modules.datetime; -import static com.oracle.graal.python.builtins.PythonBuiltinClassType.TypeError; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.ValueError; import static com.oracle.graal.python.builtins.modules.datetime.DatetimeModuleBuiltins.MAX_YEAR; import static com.oracle.graal.python.builtins.modules.datetime.DatetimeModuleBuiltins.MIN_YEAR; import java.time.YearMonth; -import com.oracle.graal.python.PythonLanguage; import com.oracle.graal.python.builtins.PythonBuiltinClassType; import com.oracle.graal.python.builtins.objects.cext.PythonAbstractNativeObject; import com.oracle.graal.python.builtins.objects.cext.capi.CExtNodes; @@ -62,10 +60,8 @@ import com.oracle.graal.python.nodes.PGuards; import com.oracle.graal.python.nodes.PRaiseNode; import com.oracle.graal.python.nodes.call.CallNode; -import com.oracle.graal.python.nodes.object.BuiltinClassProfiles; import com.oracle.graal.python.runtime.PythonContext; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; -import com.oracle.truffle.api.dsl.Bind; import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.Fallback; import com.oracle.truffle.api.dsl.GenerateCached; @@ -185,33 +181,7 @@ static boolean isBuiltinClass(Object cls) { } } - @GenerateUncached - @GenerateInline - @GenerateCached(false) - public abstract static class AsManagedDateNode extends Node { - public abstract PDate execute(Node inliningTarget, Object obj); - - public static PDate executeUncached(Object obj) { - return DateNodesFactory.AsManagedDateNodeGen.getUncached().execute(null, obj); - } - - @Specialization - static PDate asManaged(PDate obj) { - return obj; - } - - @Specialization(guards = "checkNode.execute(inliningTarget, obj)", limit = "1") - static PDate asManagedNative(@SuppressWarnings("unused") Node inliningTarget, PythonAbstractNativeObject obj, - @Bind PythonLanguage language, - @SuppressWarnings("unused") @Cached DateCheckNode checkNode, - @Cached CStructAccess.ReadByteNode readByteNode) { - int year = getYear(obj, readByteNode); - int month = getMonth(obj, readByteNode); - int day = getDay(obj, readByteNode); - PythonBuiltinClassType cls = PythonBuiltinClassType.PDate; - return new PDate(cls, cls.getInstanceShape(language), year, month, day); - } - + public static final class FromNative { static int getYear(PythonAbstractNativeObject self, CStructAccess.ReadByteNode readNode) { int b0 = readNode.readFromObjUnsigned(self, CFields.PyDateTime_Date__data, 0); int b1 = readNode.readFromObjUnsigned(self, CFields.PyDateTime_Date__data, 1); @@ -225,38 +195,6 @@ static int getMonth(PythonAbstractNativeObject self, CStructAccess.ReadByteNode static int getDay(PythonAbstractNativeObject self, CStructAccess.ReadByteNode readNode) { return readNode.readFromObjUnsigned(self, CFields.PyDateTime_Date__data, 3); } - - @Fallback - static PDate error(Object obj, - @Bind Node inliningTarget) { - throw PRaiseNode.raiseStatic(inliningTarget, TypeError, ErrorMessages.S_EXPECTED_GOT_P, "date", obj); - } } - @GenerateUncached - @GenerateInline - @GenerateCached(false) - public abstract static class DateCheckNode extends Node { - public abstract boolean execute(Node inliningTarget, Object obj); - - public static boolean executeUncached(Object obj) { - return DateNodesFactory.DateCheckNodeGen.getUncached().execute(null, obj); - } - - @Specialization - static boolean doManaged(@SuppressWarnings("unused") PDate value) { - return true; - } - - @Specialization - static boolean doNative(Node inliningTarget, PythonAbstractNativeObject value, - @Cached BuiltinClassProfiles.IsBuiltinObjectProfile profile) { - return profile.profileObject(inliningTarget, value, PythonBuiltinClassType.PDate); - } - - @Fallback - static boolean doOther(@SuppressWarnings("unused") Object value) { - return false; - } - } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/DateTimeBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/DateTimeBuiltins.java index 2e9ba79e03..946b5e0ce7 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/DateTimeBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/DateTimeBuiltins.java @@ -100,6 +100,10 @@ import com.oracle.graal.python.builtins.PythonBuiltins; import com.oracle.graal.python.builtins.modules.TimeModuleBuiltins; import com.oracle.graal.python.builtins.modules.WarningsModuleBuiltins.WarnNode; +import com.oracle.graal.python.builtins.modules.datetime.TemporalValueNodes.DateValue; +import com.oracle.graal.python.builtins.modules.datetime.TemporalValueNodes.DateTimeValue; +import com.oracle.graal.python.builtins.modules.datetime.TemporalValueNodes.TimeDeltaValue; +import com.oracle.graal.python.builtins.modules.datetime.TemporalValueNodes.TimeValue; import com.oracle.graal.python.builtins.objects.PNone; import com.oracle.graal.python.builtins.objects.PNotImplemented; import com.oracle.graal.python.builtins.objects.bytes.BytesNodes; @@ -117,9 +121,14 @@ import com.oracle.graal.python.builtins.objects.type.slots.TpSlotRichCompare.RichCmpBuiltinNode; import com.oracle.graal.python.lib.PyFloatAsDoubleNode; import com.oracle.graal.python.lib.PyFloatCheckNode; +import com.oracle.graal.python.lib.PyDateCheckNode; +import com.oracle.graal.python.lib.PyDateTimeCheckNode; +import com.oracle.graal.python.lib.PyDeltaCheckNode; import com.oracle.graal.python.lib.PyLongAsLongNode; import com.oracle.graal.python.lib.PyObjectCallMethodObjArgs; import com.oracle.graal.python.lib.PyObjectReprAsObjectNode; +import com.oracle.graal.python.lib.PyTZInfoCheckNode; +import com.oracle.graal.python.lib.PyTimeCheckNode; import com.oracle.graal.python.lib.PyUnicodeCheckNode; import com.oracle.graal.python.lib.RichCmpOp; import com.oracle.graal.python.nodes.ErrorMessages; @@ -133,6 +142,7 @@ import com.oracle.graal.python.nodes.function.builtins.PythonUnaryBuiltinNode; import com.oracle.graal.python.nodes.function.builtins.clinic.ArgumentClinicProvider; import com.oracle.graal.python.nodes.object.GetClassNode; +import com.oracle.graal.python.nodes.object.IsForeignObjectNode; import com.oracle.graal.python.nodes.util.CannotCastException; import com.oracle.graal.python.nodes.util.CastToJavaDoubleNode; import com.oracle.graal.python.nodes.util.CastToJavaLongExactNode; @@ -247,7 +257,7 @@ private static Object tryToDeserializeDateTime(Object cls, Object bytesObject, O } if (naiveBytesCheck(bytes)) { - if (tzInfo != PNone.NO_VALUE && !TzInfoNodes.TzInfoCheckNode.executeUncached(tzInfo)) { + if (tzInfo != PNone.NO_VALUE && !PyTZInfoCheckNode.executeUncached(tzInfo)) { throw PRaiseNode.raiseStatic(inliningTarget, TypeError, ErrorMessages.BAD_TZINFO_STATE_ARG); } @@ -366,7 +376,7 @@ static Object nowInTimeZone(VirtualFrame frame, Object cls, Object tzInfo, @TruffleBoundary private static Object nowInTimeZoneBoundary(Object cls, Object tzInfo, Node inliningTarget) { - if (!TzInfoNodes.TzInfoCheckNode.executeUncached(tzInfo)) { + if (!PyTZInfoCheckNode.executeUncached(tzInfo)) { throw PRaiseNode.raiseStatic(inliningTarget, TypeError, ErrorMessages.TZINFO_ARGUMENT_MUST_BE_NONE_OR_OF_A_TZINFO_SUBCLASS_NOT_TYPE_P, tzInfo); } // convert current time in UTC to the given time zone with tzinfo.fromutc() @@ -407,11 +417,12 @@ public abstract static class ReprNode extends PythonUnaryBuiltinNode { @Specialization static TruffleString repr(Object selfObj, - @Bind Node inliningTarget) { + @Bind Node inliningTarget, + @Cached DateTimeNodes.TzInfoNode tzInfoNode) { EncapsulatingNodeReference encapsulating = EncapsulatingNodeReference.getCurrent(); Node encapsulatingNode = encapsulating.set(inliningTarget); try { - return reprBoundary(selfObj); + return reprBoundary(inliningTarget, selfObj, tzInfoNode.execute(inliningTarget, selfObj)); } finally { // Some uncached nodes (e.g. PyFloatAsDoubleNode, PyLongAsLongNode, // PyObjectReprAsObjectNode) may raise exceptions that are not @@ -421,8 +432,8 @@ static TruffleString repr(Object selfObj, } @TruffleBoundary - private static TruffleString reprBoundary(Object selfObj) { - PDateTime self = DateTimeNodes.AsManagedDateTimeNode.executeUncached(selfObj); + private static TruffleString reprBoundary(Node inliningTarget, Object selfObj, Object tzInfo) { + DateTimeValue self = TemporalValueNodes.GetDateTimeValue.executeUncached(inliningTarget, selfObj); var builder = new StringBuilder(); TruffleString typeName = TypeNodes.GetTpNameNode.executeUncached(GetClassNode.executeUncached(selfObj)); @@ -438,10 +449,10 @@ private static TruffleString reprBoundary(Object selfObj) { builder.append(", fold=1"); } - if (self.tzInfo != null) { + if (tzInfo != null) { builder.append(", tzinfo="); - Object tzinfoReprObject = PyObjectReprAsObjectNode.executeUncached(self.tzInfo); + Object tzinfoReprObject = PyObjectReprAsObjectNode.executeUncached(tzInfo); String tzinfoRepr = CastToJavaStringNode.getUncached().execute(tzinfoReprObject); builder.append(tzinfoRepr); } @@ -459,8 +470,10 @@ public abstract static class ReduceNode extends PythonUnaryBuiltinNode { static Object reduce(Object selfObj, @Bind Node inliningTarget, @Bind PythonLanguage language, + @Cached DateTimeNodes.TzInfoNode tzInfoNode, @Cached GetClassNode getClassNode) { - PDateTime self = DateTimeNodes.AsManagedDateTimeNode.executeUncached(selfObj); + DateTimeValue self = TemporalValueNodes.GetDateTimeValue.executeUncached(inliningTarget, selfObj); + Object tzInfo = tzInfoNode.execute(inliningTarget, selfObj); // DateTime is serialized in the following format: // ( // bytes(year 1st byte, year 2nd byte, month, day, hours, minutes, seconds, microseconds @@ -484,8 +497,8 @@ static Object reduce(Object selfObj, PBytes baseState = PFactory.createBytes(language, baseStateBytes); final PTuple arguments; - if (self.tzInfo != null) { - arguments = PFactory.createTuple(language, new Object[]{baseState, self.tzInfo}); + if (tzInfo != null) { + arguments = PFactory.createTuple(language, new Object[]{baseState, tzInfo}); } else { arguments = PFactory.createTuple(language, new Object[]{baseState}); } @@ -502,8 +515,10 @@ public abstract static class ReduceExNode extends PythonBinaryBuiltinNode { static Object reduceEx(Object selfObj, int protocol, @Bind Node inliningTarget, @Bind PythonLanguage language, + @Cached DateTimeNodes.TzInfoNode tzInfoNode, @Cached GetClassNode getClassNode) { - PDateTime self = DateTimeNodes.AsManagedDateTimeNode.executeUncached(selfObj); + DateTimeValue self = TemporalValueNodes.GetDateTimeValue.executeUncached(inliningTarget, selfObj); + Object tzInfo = tzInfoNode.execute(inliningTarget, selfObj); byte[] baseStateBytes = new byte[10]; baseStateBytes[0] = (byte) (self.year / 256); baseStateBytes[1] = (byte) (self.year % 256); @@ -523,8 +538,8 @@ static Object reduceEx(Object selfObj, int protocol, PBytes baseState = PFactory.createBytes(language, baseStateBytes); final PTuple arguments; - if (self.tzInfo != null) { - arguments = PFactory.createTuple(language, new Object[]{baseState, self.tzInfo}); + if (tzInfo != null) { + arguments = PFactory.createTuple(language, new Object[]{baseState, tzInfo}); } else { arguments = PFactory.createTuple(language, new Object[]{baseState}); } @@ -541,10 +556,11 @@ abstract static class RichCmpNode extends RichCmpBuiltinNode { @Specialization static Object richCmp(VirtualFrame frame, Object self, Object other, RichCmpOp op, @Bind Node inliningTarget, + @Cached DateTimeNodes.TzInfoNode tzInfoNode, @Cached("createFor($node)") IndirectCallData.BoundaryCallData boundaryCallData) { Object saved = ExecutionContext.BoundaryCallContext.enter(frame, boundaryCallData); try { - return richCmpBoundary(self, other, op, inliningTarget); + return richCmpBoundary(self, other, op, inliningTarget, tzInfoNode); } finally { // A Python method call (using DatetimeModuleBuiltins.callUtcOffset) // should be connected to a current node. @@ -553,8 +569,8 @@ static Object richCmp(VirtualFrame frame, Object self, Object other, RichCmpOp o } @TruffleBoundary - private static Object richCmpBoundary(Object selfObj, Object otherObj, RichCmpOp op, Node inliningTarget) { - if (!DateTimeNodes.DateTimeCheckNode.executeUncached(otherObj)) { + private static Object richCmpBoundary(Object selfObj, Object otherObj, RichCmpOp op, Node inliningTarget, DateTimeNodes.TzInfoNode tzInfoNode) { + if (!PyDateTimeCheckNode.executeUncached(otherObj)) { /* * Prevent invocation of date_richcompare. We want to return NotImplemented here to * give the other object a chance. But since DateTime is a subclass of Date, if the @@ -562,7 +578,7 @@ private static Object richCmpBoundary(Object selfObj, Object otherObj, RichCmpOp * alone, and we don't want that. So force unequal or uncomparable here in that * case. */ - if (DateNodes.DateCheckNode.executeUncached(otherObj)) { + if (PyDateCheckNode.executeUncached(otherObj)) { if (op == RichCmpOp.Py_EQ) { return false; } else if (op == RichCmpOp.Py_NE) { @@ -573,23 +589,25 @@ private static Object richCmpBoundary(Object selfObj, Object otherObj, RichCmpOp } return PNotImplemented.NOT_IMPLEMENTED; } - PDateTime self = DateTimeNodes.AsManagedDateTimeNode.executeUncached(selfObj); - PDateTime other = DateTimeNodes.AsManagedDateTimeNode.executeUncached(otherObj); + DateTimeValue self = TemporalValueNodes.GetDateTimeValue.executeUncached(inliningTarget, selfObj); + DateTimeValue other = TemporalValueNodes.GetDateTimeValue.executeUncached(inliningTarget, otherObj); + Object selfTzInfo = tzInfoNode.execute(inliningTarget, selfObj); + Object otherTzInfo = tzInfoNode.execute(inliningTarget, otherObj); // either naive datetimes (without timezone) or timezones are exactly the same objects - if (self.tzInfo == other.tzInfo) { + if (selfTzInfo == otherTzInfo) { int result = compareDateTimeComponents(self, other); return op.compareResultToBool(result); } - PTimeDelta selfUtcOffset = DatetimeModuleBuiltins.callUtcOffset(self.tzInfo, self, inliningTarget); - PTimeDelta otherUtcOffset = DatetimeModuleBuiltins.callUtcOffset(other.tzInfo, other, inliningTarget); + PTimeDelta selfUtcOffset = DatetimeModuleBuiltins.callUtcOffset(selfTzInfo, selfObj, inliningTarget); + PTimeDelta otherUtcOffset = DatetimeModuleBuiltins.callUtcOffset(otherTzInfo, otherObj, inliningTarget); if (Objects.equals(selfUtcOffset, otherUtcOffset)) { int result = compareDateTimeComponents(self, other); if (result == 0 && (op == RichCmpOp.Py_EQ || op == RichCmpOp.Py_NE) && selfUtcOffset != null) { // if any utc offset is affected by a fold value - return false - if (isExceptionInPep495(selfObj, self, selfUtcOffset, other, otherUtcOffset, inliningTarget)) { + if (isExceptionInPep495(selfObj, self, selfTzInfo, selfUtcOffset, otherObj, other, otherTzInfo, otherUtcOffset, inliningTarget)) { result = 1; } } @@ -618,7 +636,7 @@ private static Object richCmpBoundary(Object selfObj, Object otherObj, RichCmpOp if (result == 0 && (op == RichCmpOp.Py_EQ || op == RichCmpOp.Py_NE)) { // if any utc offset is affected by a fold value - return false - if (isExceptionInPep495(selfObj, self, selfUtcOffset, other, otherUtcOffset, inliningTarget)) { + if (isExceptionInPep495(selfObj, self, selfTzInfo, selfUtcOffset, otherObj, other, otherTzInfo, otherUtcOffset, inliningTarget)) { result = 1; } } @@ -627,7 +645,7 @@ private static Object richCmpBoundary(Object selfObj, Object otherObj, RichCmpOp } @TruffleBoundary - private static int compareDateTimeComponents(PDateTime self, PDateTime other) { + private static int compareDateTimeComponents(DateTimeValue self, DateTimeValue other) { // compare only year, month, day, hours, minutes, ... and ignore fold int[] selfComponents = new int[]{self.year, self.month, self.day, self.hour, self.minute, self.second, self.microsecond}; int[] otherComponents = new int[]{other.year, other.month, other.day, other.hour, other.minute, other.second, other.microsecond}; @@ -640,19 +658,17 @@ private static int compareDateTimeComponents(PDateTime self, PDateTime other) { * 495 – Local Time Disambiguation". See PEP 495 * – Local Time Disambiguation */ - private static boolean isExceptionInPep495(Object selfObj, PDateTime self, PTimeDelta selfUtcOffset, PDateTime other, PTimeDelta otherUtcOffset, Node inliningTarget) { - return isExceptionInPep495(selfObj, self, selfUtcOffset, inliningTarget) || isExceptionInPep495(selfObj, other, otherUtcOffset, inliningTarget); + private static boolean isExceptionInPep495(Object selfObj, DateTimeValue self, Object selfTzInfo, PTimeDelta selfUtcOffset, Object otherObj, DateTimeValue other, Object otherTzInfo, + PTimeDelta otherUtcOffset, Node inliningTarget) { + return isExceptionInPep495(selfObj, self, selfTzInfo, selfUtcOffset, inliningTarget) || isExceptionInPep495(otherObj, other, otherTzInfo, otherUtcOffset, inliningTarget); } - @TruffleBoundary - private static boolean isExceptionInPep495(Object dateTimeObj, PDateTime dateTime, PTimeDelta utcOffset, Node inliningTarget) { - Object cls = GetClassNode.executeUncached(dateTimeObj); - Shape shape = TypeNodes.GetInstanceShape.getUncached().execute(cls); + private static boolean isExceptionInPep495(Object dateTimeObj, DateTimeValue dateTime, Object tzInfo, PTimeDelta utcOffset, Node inliningTarget) { int fold = dateTime.fold == 1 ? 0 : 1; - - PDateTime newDateTime = new PDateTime(cls, shape, dateTime.year, dateTime.month, dateTime.day, dateTime.hour, dateTime.minute, dateTime.second, - dateTime.microsecond, dateTime.tzInfo, fold); - PTimeDelta newUtcOffset = DatetimeModuleBuiltins.callUtcOffset(newDateTime.tzInfo, newDateTime, inliningTarget); + Shape shape = PythonBuiltinClassType.PDateTime.getInstanceShape(PythonLanguage.get(inliningTarget)); + Object newDateTime = new PDateTime(PythonBuiltinClassType.PDateTime, shape, dateTime.year, dateTime.month, dateTime.day, dateTime.hour, dateTime.minute, dateTime.second, + dateTime.microsecond, tzInfo, fold); + PTimeDelta newUtcOffset = DatetimeModuleBuiltins.callUtcOffset(tzInfo, newDateTime, inliningTarget); return !utcOffset.equals(newUtcOffset); } @@ -665,26 +681,27 @@ abstract static class HashNode extends HashBuiltinNode { @Specialization static long hash(VirtualFrame frame, Object selfObj, @Bind Node inliningTarget, + @Cached DateTimeNodes.TzInfoNode tzInfoNode, @Cached PyObjectCallMethodObjArgs callMethodObjArgs, - @Cached PRaiseNode raiseNode, - @Cached TypeNodes.GetInstanceShape getInstanceShape) { - PDateTime self = DateTimeNodes.AsManagedDateTimeNode.executeUncached(selfObj); + @Cached PRaiseNode raiseNode) { + DateTimeValue self = TemporalValueNodes.GetDateTimeValue.executeUncached(inliningTarget, selfObj); + Object tzInfo = tzInfoNode.execute(inliningTarget, selfObj); final PTimeDelta offset; - if (self.tzInfo == null) { + if (tzInfo == null) { offset = null; } else { // ignore fold in calculating utc offset - final PDateTime getUtcOffsetFrom; + final Object getUtcOffsetFrom; if (self.fold == 1) { // reset fold - Object cls = GetClassNode.executeUncached(selfObj); - Shape shape = getInstanceShape.execute(cls); - getUtcOffsetFrom = new PDateTime(cls, shape, self.year, self.month, self.day, self.hour, self.minute, self.second, self.microsecond, self.tzInfo, 0); + Shape shape = PythonBuiltinClassType.PDateTime.getInstanceShape(PythonLanguage.get(inliningTarget)); + getUtcOffsetFrom = new PDateTime(PythonBuiltinClassType.PDateTime, shape, self.year, self.month, self.day, self.hour, self.minute, self.second, self.microsecond, + tzInfo, 0); } else { - getUtcOffsetFrom = self; + getUtcOffsetFrom = selfObj; } - offset = DatetimeModuleBuiltins.callUtcOffset(self.tzInfo, getUtcOffsetFrom, frame, inliningTarget, callMethodObjArgs, raiseNode); + offset = DatetimeModuleBuiltins.callUtcOffset(tzInfo, getUtcOffsetFrom, frame, inliningTarget, callMethodObjArgs, raiseNode); } if (offset == null) { @@ -695,12 +712,12 @@ static long hash(VirtualFrame frame, Object selfObj, } @TruffleBoundary - private static long getHashForDateTime(PDateTime self) { + private static long getHashForDateTime(DateTimeValue self) { return Objects.hash(self.year, self.month, self.day, self.hour, self.minute, self.second, self.microsecond); } @TruffleBoundary - private static long getHashForDateTimeWithOffset(PDateTime self, PTimeDelta offset) { + private static long getHashForDateTimeWithOffset(DateTimeValue self, PTimeDelta offset) { LocalDateTime utc = subtractOffsetFromDateTime(self, offset); return Objects.hash(utc.getYear(), utc.getMonthValue(), utc.getDayOfMonth(), utc.getHour(), utc.getMinute(), utc.getSecond(), utc.getNano() / 1_000); } @@ -713,10 +730,13 @@ abstract static class AddNode extends BinaryOpBuiltinNode { @Specialization static Object add(VirtualFrame frame, Object left, Object right, @Bind Node inliningTarget, + @Cached IsForeignObjectNode isForeignObjectNode, + @Cached GetClassNode getClassNode, + @Cached DateTimeNodes.TzInfoNode tzInfoNode, @Cached("createFor($node)") IndirectCallData.BoundaryCallData boundaryCallData) { Object saved = ExecutionContext.BoundaryCallContext.enter(frame, boundaryCallData); try { - return addBoundary(left, right, inliningTarget); + return addBoundary(left, right, inliningTarget, isForeignObjectNode, getClassNode, tzInfoNode); } finally { // A Python method call (using DateTimeNodes.SubclassNewNode) should be // connected to a current node. @@ -725,32 +745,34 @@ static Object add(VirtualFrame frame, Object left, Object right, } @TruffleBoundary - private static Object addBoundary(Object left, Object right, Node inliningTarget) { + private static Object addBoundary(Object left, Object right, Node inliningTarget, IsForeignObjectNode isForeignObjectNode, GetClassNode getClassNode, + DateTimeNodes.TzInfoNode tzInfoNode) { Object dateTimeObj, deltaObj; - if (DateTimeNodes.DateTimeCheckNode.executeUncached(left)) { - if (TimeDeltaNodes.TimeDeltaCheckNode.executeUncached(right)) { + if (PyDateTimeCheckNode.executeUncached(left)) { + if (PyDeltaCheckNode.executeUncached(right)) { dateTimeObj = left; deltaObj = right; } else { return PNotImplemented.NOT_IMPLEMENTED; } - } else if (TimeDeltaNodes.TimeDeltaCheckNode.executeUncached(left)) { + } else if (PyDeltaCheckNode.executeUncached(left)) { dateTimeObj = right; deltaObj = left; } else { return PNotImplemented.NOT_IMPLEMENTED; } - PDateTime date = DateTimeNodes.AsManagedDateTimeNode.executeUncached(dateTimeObj); - PTimeDelta delta = TimeDeltaNodes.AsManagedTimeDeltaNode.executeUncached(deltaObj); + DateTimeValue date = TemporalValueNodes.GetDateTimeValue.executeUncached(inliningTarget, dateTimeObj); + TimeDeltaValue delta = TemporalValueNodes.GetTimeDeltaValue.executeUncached(inliningTarget, deltaObj); - LocalDateTime local = toLocalDateTime(date); + LocalDateTime local = date.toLocalDateTime(); LocalDateTime localAdjusted = local.plusDays(delta.days).plusSeconds(delta.seconds).plusNanos(delta.microseconds * 1_000L); if (localAdjusted.getYear() < MIN_YEAR || localAdjusted.getYear() > MAX_YEAR) { throw PRaiseNode.raiseStatic(inliningTarget, OverflowError, ErrorMessages.DATE_VALUE_OUT_OF_RANGE); } - return toPDateTime(localAdjusted, date.tzInfo, date.fold, inliningTarget, GetClassNode.executeUncached(dateTimeObj)); + Object tzInfo = tzInfoNode.execute(inliningTarget, dateTimeObj); + return toPDateTime(localAdjusted, tzInfo, date.fold, inliningTarget, getResultDateTimeType(dateTimeObj, inliningTarget, isForeignObjectNode, getClassNode)); } } @@ -761,10 +783,13 @@ abstract static class SubNode extends BinaryOpBuiltinNode { @Specialization static Object sub(VirtualFrame frame, Object left, Object right, @Bind Node inliningTarget, + @Cached IsForeignObjectNode isForeignObjectNode, + @Cached GetClassNode getClassNode, + @Cached DateTimeNodes.TzInfoNode tzInfoNode, @Cached("createFor($node)") IndirectCallData.BoundaryCallData boundaryCallData) { Object saved = ExecutionContext.BoundaryCallContext.enter(frame, boundaryCallData); try { - return subBoundary(left, right, inliningTarget); + return subBoundary(left, right, inliningTarget, isForeignObjectNode, getClassNode, tzInfoNode); } finally { // A Python method call (using DatetimeModuleBuiltins.callUtcOffset) // should be connected to a current node. @@ -773,19 +798,19 @@ static Object sub(VirtualFrame frame, Object left, Object right, } @TruffleBoundary - private static Object subBoundary(Object left, Object right, Node inliningTarget) { - if (!DateTimeNodes.DateTimeCheckNode.executeUncached(left)) { + private static Object subBoundary(Object left, Object right, Node inliningTarget, IsForeignObjectNode isForeignObjectNode, GetClassNode getClassNode, + DateTimeNodes.TzInfoNode tzInfoNode) { + if (!PyDateTimeCheckNode.executeUncached(left)) { return PNotImplemented.NOT_IMPLEMENTED; } - PDateTime self = DateTimeNodes.AsManagedDateTimeNode.executeUncached(left); - if (DateTimeNodes.DateTimeCheckNode.executeUncached(right)) { - PDateTime other = DateTimeNodes.AsManagedDateTimeNode.executeUncached(right); - - final PTimeDelta selfOffset; - final PTimeDelta otherOffset; + DateTimeValue self = TemporalValueNodes.GetDateTimeValue.executeUncached(inliningTarget, left); + Object selfTzInfo = tzInfoNode.execute(inliningTarget, left); + if (PyDateTimeCheckNode.executeUncached(right)) { + DateTimeValue other = TemporalValueNodes.GetDateTimeValue.executeUncached(inliningTarget, right); + Object otherTzInfo = tzInfoNode.execute(inliningTarget, right); - selfOffset = DatetimeModuleBuiltins.callUtcOffset(self.tzInfo, self, inliningTarget); - otherOffset = DatetimeModuleBuiltins.callUtcOffset(other.tzInfo, other, inliningTarget); + final PTimeDelta selfOffset = DatetimeModuleBuiltins.callUtcOffset(selfTzInfo, left, inliningTarget); + final PTimeDelta otherOffset = DatetimeModuleBuiltins.callUtcOffset(otherTzInfo, right, inliningTarget); if ((selfOffset == null) != (otherOffset == null)) { throw PRaiseNode.raiseStatic(inliningTarget, TypeError, ErrorMessages.CANNOT_SUBTRACT_OFFSET_NAIVE_AND_OFFSET_AWARE_DATETIMES); @@ -794,12 +819,12 @@ private static Object subBoundary(Object left, Object right, Node inliningTarget final LocalDateTime selfToCompare; final LocalDateTime otherToCompare; - if (selfOffset != null && self.tzInfo != other.tzInfo) { + if (selfOffset != null && selfTzInfo != otherTzInfo) { selfToCompare = subtractOffsetFromDateTime(self, selfOffset); otherToCompare = subtractOffsetFromDateTime(other, otherOffset); } else { - selfToCompare = toLocalDateTime(self); - otherToCompare = toLocalDateTime(other); + selfToCompare = self.toLocalDateTime(); + otherToCompare = other.toLocalDateTime(); } long selfSeconds = selfToCompare.toEpochSecond(ZoneOffset.UTC); @@ -814,16 +839,16 @@ private static Object subBoundary(Object left, Object right, Node inliningTarget 0, 0, 0); - } else if (TimeDeltaNodes.TimeDeltaCheckNode.executeUncached(right)) { - PTimeDelta timeDelta = TimeDeltaNodes.AsManagedTimeDeltaNode.executeUncached(right); - LocalDateTime local = toLocalDateTime(self); + } else if (PyDeltaCheckNode.executeUncached(right)) { + TimeDeltaValue timeDelta = TemporalValueNodes.GetTimeDeltaValue.executeUncached(inliningTarget, right); + LocalDateTime local = self.toLocalDateTime(); LocalDateTime localAdjusted = local.minusDays(timeDelta.days).minusSeconds(timeDelta.seconds).minusNanos(timeDelta.microseconds * 1_000L); if (localAdjusted.getYear() < MIN_YEAR || localAdjusted.getYear() > MAX_YEAR) { throw PRaiseNode.raiseStatic(inliningTarget, OverflowError, ErrorMessages.DATE_VALUE_OUT_OF_RANGE); } - return toPDateTime(localAdjusted, self.tzInfo, self.fold, inliningTarget, GetClassNode.executeUncached(left)); + return toPDateTime(localAdjusted, selfTzInfo, self.fold, inliningTarget, getResultDateTimeType(left, inliningTarget, isForeignObjectNode, getClassNode)); } else { return PNotImplemented.NOT_IMPLEMENTED; } @@ -841,7 +866,14 @@ static int getHour(PDateTime self) { @Specialization static int getHour(PythonAbstractNativeObject self, @Cached CStructAccess.ReadByteNode readByteNode) { - return DateTimeNodes.AsManagedDateTimeNode.getHour(self, readByteNode); + return DateTimeNodes.FromNative.getHour(self, readByteNode); + } + + @Specialization + static int getHour(Object self, + @Bind Node inliningTarget, + @Cached TemporalValueNodes.GetDateTimeValue readDateTimeValueNode) { + return readDateTimeValueNode.execute(inliningTarget, self).hour; } } @@ -857,7 +889,14 @@ static int getMinute(PDateTime self) { @Specialization static int getMinute(PythonAbstractNativeObject self, @Cached CStructAccess.ReadByteNode readNode) { - return DateTimeNodes.AsManagedDateTimeNode.getMinute(self, readNode); + return DateTimeNodes.FromNative.getMinute(self, readNode); + } + + @Specialization + static int getMinute(Object self, + @Bind Node inliningTarget, + @Cached TemporalValueNodes.GetDateTimeValue readDateTimeValueNode) { + return readDateTimeValueNode.execute(inliningTarget, self).minute; } } @@ -873,7 +912,14 @@ static int getSecond(PDateTime self) { @Specialization static int getSecond(PythonAbstractNativeObject self, @Cached CStructAccess.ReadByteNode readNode) { - return DateTimeNodes.AsManagedDateTimeNode.getSecond(self, readNode); + return DateTimeNodes.FromNative.getSecond(self, readNode); + } + + @Specialization + static int getSecond(Object self, + @Bind Node inliningTarget, + @Cached TemporalValueNodes.GetDateTimeValue readDateTimeValueNode) { + return readDateTimeValueNode.execute(inliningTarget, self).second; } } @@ -889,7 +935,14 @@ static int getMicrosecond(PDateTime self) { @Specialization static int getMicrosecond(PythonAbstractNativeObject self, @Cached CStructAccess.ReadByteNode readNode) { - return DateTimeNodes.AsManagedDateTimeNode.getMicrosecond(self, readNode); + return DateTimeNodes.FromNative.getMicrosecond(self, readNode); + } + + @Specialization + static int getMicrosecond(Object self, + @Bind Node inliningTarget, + @Cached TemporalValueNodes.GetDateTimeValue readDateTimeValueNode) { + return readDateTimeValueNode.execute(inliningTarget, self).microsecond; } } @@ -918,7 +971,14 @@ static int getFold(PDateTime self) { @Specialization static int getFold(PythonAbstractNativeObject self, @Cached CStructAccess.ReadByteNode readNode) { - return DateTimeNodes.AsManagedDateTimeNode.getFold(self, readNode); + return DateTimeNodes.FromNative.getFold(self, readNode); + } + + @Specialization + static int getFold(Object self, + @Bind Node inliningTarget, + @Cached TemporalValueNodes.GetDateTimeValue readDateTimeValueNode) { + return readDateTimeValueNode.execute(inliningTarget, self).fold; } } @@ -974,7 +1034,7 @@ private static Object fromTimestampBoundary(Object cls, Object timestampObject, if (tzInfoObject instanceof PNone) { tzInfo = null; } else { - if (!TzInfoNodes.TzInfoCheckNode.executeUncached(tzInfoObject)) { + if (!PyTZInfoCheckNode.executeUncached(tzInfoObject)) { throw PRaiseNode.raiseStatic(inliningTarget, TypeError, ErrorMessages.TZINFO_ARGUMENT_MUST_BE_NONE_OR_OF_A_TZINFO_SUBCLASS_NOT_TYPE_P, tzInfoObject); } @@ -1147,8 +1207,9 @@ public abstract static class CombineNode extends PythonBuiltinNode { static Object combine(Object cls, Object dateObject, Object timeObject, Object tzInfoObject, @Bind Node inliningTarget, @Cached PRaiseNode raiseNode, + @Cached TimeNodes.TzInfoNode timeTzInfoNode, @Cached DateTimeNodes.SubclassNewNode newNode) { - if (!DateNodes.DateCheckNode.executeUncached(dateObject)) { + if (!PyDateCheckNode.executeUncached(dateObject)) { throw raiseNode.raise(inliningTarget, TypeError, ErrorMessages.ARG_D_MUST_BE_S_NOT_P, @@ -1158,7 +1219,7 @@ static Object combine(Object cls, Object dateObject, Object timeObject, Object t dateObject); } - if (!TimeNodes.TimeCheckNode.executeUncached(timeObject)) { + if (!PyTimeCheckNode.executeUncached(timeObject)) { throw raiseNode.raise(inliningTarget, TypeError, ErrorMessages.ARG_D_MUST_BE_S_NOT_P, @@ -1168,17 +1229,17 @@ static Object combine(Object cls, Object dateObject, Object timeObject, Object t timeObject); } - PDate date = DateNodes.AsManagedDateNode.executeUncached(dateObject); - PTime time = TimeNodes.AsManagedTimeNode.executeUncached(timeObject); + DateValue date = TemporalValueNodes.GetDateValue.executeUncached(inliningTarget, dateObject); + TimeValue time = TemporalValueNodes.GetTimeValue.executeUncached(inliningTarget, timeObject); final Object tzInfo; if (tzInfoObject instanceof PNone) { - tzInfo = time.tzInfo; + tzInfo = timeTzInfoNode.execute(inliningTarget, timeObject); } else { tzInfo = tzInfoObject; } - if (tzInfo != null && !TzInfoNodes.TzInfoCheckNode.executeUncached(tzInfo)) { + if (tzInfo != null && !PyTZInfoCheckNode.executeUncached(tzInfo)) { throw raiseNode.raise(inliningTarget, TypeError, ErrorMessages.TZINFO_ARGUMENT_MUST_BE_NONE_OR_OF_A_TZINFO_SUBCLASS_NOT_TYPE_P, @@ -2525,7 +2586,7 @@ abstract static class DateNode extends PythonUnaryBuiltinNode { static Object getDate(Object selfObj, @Bind Node inliningTarget, @Cached DateNodes.NewNode newDateNode) { - PDateTime self = DateTimeNodes.AsManagedDateTimeNode.executeUncached(selfObj); + DateTimeValue self = TemporalValueNodes.GetDateTimeValue.executeUncached(inliningTarget, selfObj); return newDateNode.execute(inliningTarget, PythonBuiltinClassType.PDate, self.year, @@ -2542,7 +2603,7 @@ abstract static class TimeNode extends PythonUnaryBuiltinNode { static Object getTime(Object selfObj, @Bind Node inliningTarget, @Cached TimeNodes.NewNode newTimeNode) { - PDateTime self = DateTimeNodes.AsManagedDateTimeNode.executeUncached(selfObj); + DateTimeValue self = TemporalValueNodes.GetDateTimeValue.executeUncached(inliningTarget, selfObj); return newTimeNode.execute(inliningTarget, PythonBuiltinClassType.PTime, self.hour, @@ -2561,15 +2622,16 @@ abstract static class TimeTzNode extends PythonUnaryBuiltinNode { @Specialization static Object getTime(Object selfObj, @Bind Node inliningTarget, + @Cached DateTimeNodes.TzInfoNode tzInfoNode, @Cached TimeNodes.NewNode newTimeNode) { - PDateTime self = DateTimeNodes.AsManagedDateTimeNode.executeUncached(selfObj); + DateTimeValue self = TemporalValueNodes.GetDateTimeValue.executeUncached(inliningTarget, selfObj); return newTimeNode.execute(inliningTarget, PythonBuiltinClassType.PTime, self.hour, self.minute, self.second, self.microsecond, - self.tzInfo, + tzInfoNode.execute(inliningTarget, selfObj), self.fold); } } @@ -2582,10 +2644,12 @@ public abstract static class ReplaceNode extends PythonBuiltinNode { static Object replace(VirtualFrame frame, Object selfObj, Object yearObject, Object monthObject, Object dayObject, Object hourObject, Object minuteObject, Object secondObject, Object microsecondObject, Object tzInfoObject, Object foldObject, @Bind Node inliningTarget, + @Cached IsForeignObjectNode isForeignObjectNode, + @Cached DateTimeNodes.TzInfoNode tzInfoNode, @Cached PyLongAsLongNode asLongNode, @Cached GetClassNode getClassNode, @Cached DateTimeNodes.NewNode newDateTimeNode) { - PDateTime self = DateTimeNodes.AsManagedDateTimeNode.executeUncached(selfObj); + DateTimeValue self = TemporalValueNodes.GetDateTimeValue.executeUncached(inliningTarget, selfObj); final long year, month, day; if (yearObject instanceof PNone) { @@ -2634,7 +2698,7 @@ static Object replace(VirtualFrame frame, Object selfObj, Object yearObject, Obj } if (tzInfoObject == PNone.NO_VALUE) { - tzInfo = self.tzInfo; + tzInfo = tzInfoNode.execute(inliningTarget, selfObj); } else if (tzInfoObject == PNone.NONE) { tzInfo = null; } else { @@ -2647,7 +2711,7 @@ static Object replace(VirtualFrame frame, Object selfObj, Object yearObject, Obj fold = asLongNode.execute(frame, inliningTarget, foldObject); } - Object type = getClassNode.execute(inliningTarget, selfObj); + Object type = getResultDateTimeType(selfObj, inliningTarget, isForeignObjectNode, getClassNode); return newDateTimeNode.execute(inliningTarget, type, year, month, day, hour, minute, second, microsecond, tzInfo, fold); } } @@ -2659,10 +2723,13 @@ abstract static class AsTimeZoneNode extends PythonBinaryBuiltinNode { @Specialization static Object inTimeZone(VirtualFrame frame, Object self, Object tzInfo, @Bind Node inliningTarget, + @Cached IsForeignObjectNode isForeignObjectNode, + @Cached GetClassNode getClassNode, + @Cached DateTimeNodes.TzInfoNode tzInfoNode, @Cached("createFor($node)") IndirectCallData.BoundaryCallData boundaryCallData) { Object saved = ExecutionContext.BoundaryCallContext.enter(frame, boundaryCallData); try { - return inTimeZoneBoundary(self, tzInfo, inliningTarget); + return inTimeZoneBoundary(self, tzInfo, inliningTarget, getResultDateTimeType(self, inliningTarget, isForeignObjectNode, getClassNode), tzInfoNode.execute(inliningTarget, self)); } finally { // A Python method call (using DatetimeModuleBuiltins.callUtcOffset // and PyObjectCallMethodObjArgs) should be connected to a current node. @@ -2671,24 +2738,24 @@ static Object inTimeZone(VirtualFrame frame, Object self, Object tzInfo, } @TruffleBoundary - private static Object inTimeZoneBoundary(Object selfObj, Object tzInfo, Node inliningTarget) { - PDateTime self = DateTimeNodes.AsManagedDateTimeNode.executeUncached(selfObj); - if (tzInfo == self.tzInfo) { - return self; + private static Object inTimeZoneBoundary(Object selfObj, Object tzInfo, Node inliningTarget, Object resultType, Object selfTzInfo) { + DateTimeValue self = TemporalValueNodes.GetDateTimeValue.executeUncached(inliningTarget, selfObj); + if (tzInfo == selfTzInfo) { + return selfObj; } Object sourceTimeZone; - if (self.tzInfo != null) { - sourceTimeZone = self.tzInfo; + if (selfTzInfo != null) { + sourceTimeZone = selfTzInfo; } else { - sourceTimeZone = getSystemTimeZoneAt(toLocalDateTime(self), self.fold, inliningTarget); + sourceTimeZone = getSystemTimeZoneAt(self.toLocalDateTime(), self.fold, inliningTarget); } - PTimeDelta sourceOffset = DatetimeModuleBuiltins.callUtcOffset(sourceTimeZone, self, inliningTarget); + PTimeDelta sourceOffset = DatetimeModuleBuiltins.callUtcOffset(sourceTimeZone, selfObj, inliningTarget); if (sourceOffset == null) { - sourceTimeZone = getSystemTimeZoneAt(toLocalDateTime(self), self.fold, inliningTarget); - sourceOffset = DatetimeModuleBuiltins.callUtcOffset(sourceTimeZone, self, inliningTarget); + sourceTimeZone = getSystemTimeZoneAt(self.toLocalDateTime(), self.fold, inliningTarget); + sourceOffset = DatetimeModuleBuiltins.callUtcOffset(sourceTimeZone, selfObj, inliningTarget); } LocalDateTime selfAsLocalDateTimeInUtc = subtractOffsetFromDateTime(self, sourceOffset); @@ -2698,14 +2765,14 @@ private static Object inTimeZoneBoundary(Object selfObj, Object tzInfo, Node inl final Object targetTimeZone; if (tzInfo instanceof PNone) { - targetTimeZone = getSystemTimeZoneAt(toLocalDateTime(self), self.fold, inliningTarget); - } else if (!TzInfoNodes.TzInfoCheckNode.executeUncached(tzInfo)) { + targetTimeZone = getSystemTimeZoneAt(self.toLocalDateTime(), self.fold, inliningTarget); + } else if (!PyTZInfoCheckNode.executeUncached(tzInfo)) { throw PRaiseNode.raiseStatic(inliningTarget, TypeError, ErrorMessages.TZINFO_ARGUMENT_MUST_BE_NONE_OR_OF_A_TZINFO_SUBCLASS_NOT_TYPE_P, tzInfo); } else { targetTimeZone = tzInfo; } - Object selfInUtc = toPDateTime(selfAsLocalDateTimeInUtc, targetTimeZone, 0, inliningTarget, GetClassNode.executeUncached(selfObj)); + Object selfInUtc = toPDateTime(selfAsLocalDateTimeInUtc, targetTimeZone, 0, inliningTarget, resultType); return PyObjectCallMethodObjArgs.executeUncached(targetTimeZone, T_FROMUTC, selfInUtc); } @@ -2836,10 +2903,11 @@ public abstract static class TimeTupleNode extends PythonUnaryBuiltinNode { static PTuple composeTimeTuple(VirtualFrame frame, Object self, @Bind Node inliningTarget, @Bind PythonLanguage language, + @Cached DateTimeNodes.TzInfoNode tzInfoNode, @Cached("createFor($node)") IndirectCallData.BoundaryCallData boundaryCallData) { Object saved = ExecutionContext.BoundaryCallContext.enter(frame, boundaryCallData); try { - return composeTimeTupleBoundary(self, inliningTarget, language); + return composeTimeTupleBoundary(self, inliningTarget, language, tzInfoNode.execute(inliningTarget, self)); } finally { // A Python method call (using DatetimeModuleBuiltins.callDst) should // be connected to a current node. @@ -2848,21 +2916,21 @@ static PTuple composeTimeTuple(VirtualFrame frame, Object self, } @TruffleBoundary - private static PTuple composeTimeTupleBoundary(Object selfObj, Node inliningTarget, PythonLanguage language) { - PDateTime self = DateTimeNodes.AsManagedDateTimeNode.executeUncached(selfObj); + private static PTuple composeTimeTupleBoundary(Object selfObj, Node inliningTarget, PythonLanguage language, Object tzInfo) { + DateTimeValue self = TemporalValueNodes.GetDateTimeValue.executeUncached(inliningTarget, selfObj); LocalDate localDate = LocalDate.of(self.year, self.month, self.day); int dayOfWeek = localDate.getDayOfWeek().getValue() - 1; // Python's day of week range // is 0-6 int dayOfYear = localDate.getDayOfYear(); - int isDst = getIsDst(self, inliningTarget); + int isDst = getIsDst(tzInfo, selfObj, inliningTarget); Object[] fields = new Object[]{self.year, self.month, self.day, self.hour, self.minute, self.second, dayOfWeek, dayOfYear, isDst}; return PFactory.createStructSeq(language, TimeModuleBuiltins.STRUCT_TIME_DESC, fields); } - private static int getIsDst(PDateTime self, Node inliningTarget) { + private static int getIsDst(Object tzInfo, Object selfObj, Node inliningTarget) { int isDst; - PTimeDelta offset = DatetimeModuleBuiltins.callDst(self.tzInfo, self, inliningTarget); + PTimeDelta offset = DatetimeModuleBuiltins.callDst(tzInfo, selfObj, inliningTarget); if (offset == null) { isDst = -1; @@ -2886,10 +2954,11 @@ public abstract static class UtcTimeTupleNode extends PythonUnaryBuiltinNode { static PTuple composeTimeTuple(VirtualFrame frame, Object self, @Bind Node inliningTarget, @Bind PythonLanguage language, + @Cached DateTimeNodes.TzInfoNode tzInfoNode, @Cached("createFor($node)") IndirectCallData.BoundaryCallData boundaryCallData) { Object saved = ExecutionContext.BoundaryCallContext.enter(frame, boundaryCallData); try { - return composeTimeTupleBoundary(self, inliningTarget, language); + return composeTimeTupleBoundary(self, inliningTarget, language, tzInfoNode.execute(inliningTarget, self)); } finally { // A Python method call (using DatetimeModuleBuiltins.callUtcOffset // and PyObjectCallMethodObjArgs) should be connected to a current node. @@ -2898,13 +2967,13 @@ static PTuple composeTimeTuple(VirtualFrame frame, Object self, } @TruffleBoundary - private static PTuple composeTimeTupleBoundary(Object selfObj, Node inliningTarget, PythonLanguage language) { - PDateTime self = DateTimeNodes.AsManagedDateTimeNode.executeUncached(selfObj); + private static PTuple composeTimeTupleBoundary(Object selfObj, Node inliningTarget, PythonLanguage language, Object tzInfo) { + DateTimeValue self = TemporalValueNodes.GetDateTimeValue.executeUncached(inliningTarget, selfObj); final LocalDateTime localDateTime; - PTimeDelta offset = DatetimeModuleBuiltins.callUtcOffset(self.tzInfo, self, inliningTarget); + PTimeDelta offset = DatetimeModuleBuiltins.callUtcOffset(tzInfo, selfObj, inliningTarget); if (offset == null) { - localDateTime = toLocalDateTime(self); + localDateTime = self.toLocalDateTime(); } else { // convert self to UTC localDateTime = subtractOffsetFromDateTime(self, offset); @@ -2932,7 +3001,7 @@ public abstract static class ToOrdinalNode extends PythonUnaryBuiltinNode { @Specialization @TruffleBoundary static long toOrdinal(Object selfObj) { - PDateTime self = DateTimeNodes.AsManagedDateTimeNode.executeUncached(selfObj); + DateTimeValue self = TemporalValueNodes.GetDateTimeValue.executeUncached(null, selfObj); LocalDate from = LocalDate.of(1, 1, 1); LocalDate to = LocalDate.of(self.year, self.month, self.day); return ChronoUnit.DAYS.between(from, to) + 1; @@ -2946,10 +3015,11 @@ public abstract static class TimestampNode extends PythonUnaryBuiltinNode { @Specialization static double toTimestamp(VirtualFrame frame, Object self, @Bind Node inliningTarget, + @Cached DateTimeNodes.TzInfoNode tzInfoNode, @Cached("createFor($node)") IndirectCallData.BoundaryCallData boundaryCallData) { Object saved = ExecutionContext.BoundaryCallContext.enter(frame, boundaryCallData); try { - return toTimestampBoundary(self, inliningTarget); + return toTimestampBoundary(self, inliningTarget, tzInfoNode.execute(inliningTarget, self)); } finally { // A Python method call (using DatetimeModuleBuiltins.callUtcOffset) // should be connected to a current node. @@ -2958,14 +3028,14 @@ static double toTimestamp(VirtualFrame frame, Object self, } @TruffleBoundary - private static double toTimestampBoundary(Object selfObj, Node inliningTarget) { - PDateTime self = DateTimeNodes.AsManagedDateTimeNode.executeUncached(selfObj); - if (self.tzInfo == null) { + private static double toTimestampBoundary(Object selfObj, Node inliningTarget, Object tzInfo) { + DateTimeValue self = TemporalValueNodes.GetDateTimeValue.executeUncached(inliningTarget, selfObj); + if (tzInfo == null) { // CPython: local_to_seconds() TimeZone timeZone = TimeModuleBuiltins.getGlobalTimeZone(getContext(inliningTarget)); ZoneId zoneId = timeZone.toZoneId(); - LocalDateTime localDateTime = toLocalDateTime(self); + LocalDateTime localDateTime = self.toLocalDateTime(); ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, zoneId); if (localDateTime.equals(zonedDateTime.toLocalDateTime())) { @@ -2992,10 +3062,10 @@ private static double toTimestampBoundary(Object selfObj, Node inliningTarget) { } } else { final LocalDateTime localDateTime; - PTimeDelta offset = DatetimeModuleBuiltins.callUtcOffset(self.tzInfo, self, inliningTarget); + PTimeDelta offset = DatetimeModuleBuiltins.callUtcOffset(tzInfo, selfObj, inliningTarget); if (offset == null) { - localDateTime = toLocalDateTime(self); + localDateTime = self.toLocalDateTime(); } else { // convert self to UTC localDateTime = subtractOffsetFromDateTime(self, offset); @@ -3016,10 +3086,11 @@ public abstract static class IsoFormatNode extends PythonTernaryBuiltinNode { @Specialization static TruffleString isoFormat(VirtualFrame frame, Object self, Object separatorObject, Object timespecObject, @Bind Node inliningTarget, + @Cached DateTimeNodes.TzInfoNode tzInfoNode, @Cached("createFor($node)") IndirectCallData.BoundaryCallData boundaryCallData) { Object saved = ExecutionContext.BoundaryCallContext.enter(frame, boundaryCallData); try { - return isoFormatBoundary(self, separatorObject, timespecObject, inliningTarget); + return isoFormatBoundary(self, separatorObject, timespecObject, inliningTarget, tzInfoNode.execute(inliningTarget, self)); } finally { // A Python method call (using DatetimeModuleBuiltins.callUtcOffset) // should be connected to a current node. @@ -3028,8 +3099,8 @@ static TruffleString isoFormat(VirtualFrame frame, Object self, Object separator } @TruffleBoundary - private static TruffleString isoFormatBoundary(Object selfObj, Object separatorObject, Object timespecObject, Node inliningTarget) { - PDateTime self = DateTimeNodes.AsManagedDateTimeNode.executeUncached(selfObj); + private static TruffleString isoFormatBoundary(Object selfObj, Object separatorObject, Object timespecObject, Node inliningTarget, Object tzInfo) { + DateTimeValue self = TemporalValueNodes.GetDateTimeValue.executeUncached(inliningTarget, selfObj); var builder = new StringBuilder(); String dateSection = PythonUtils.formatJString("%04d-%02d-%02d", self.year, self.month, self.day); @@ -3119,7 +3190,7 @@ private static TruffleString isoFormatBoundary(Object selfObj, Object separatorO ErrorMessages.UNKNOWN_TIMESPEC_VALUE); } - Object utcOffsetString = DatetimeModuleBuiltins.formatUtcOffset(self.tzInfo, self, true, inliningTarget); + Object utcOffsetString = DatetimeModuleBuiltins.formatUtcOffset(tzInfo, selfObj, true, inliningTarget); builder.append(utcOffsetString); return TruffleString.FromJavaStringNode.getUncached().execute(builder.toString(), TS_ENCODING); @@ -3133,8 +3204,8 @@ public abstract static class CTimeNode extends PythonUnaryBuiltinNode { @Specialization @TruffleBoundary static TruffleString cTime(Object selfObj) { - PDateTime self = DateTimeNodes.AsManagedDateTimeNode.executeUncached(selfObj); - LocalDateTime localDateTime = LocalDateTime.of(self.year, self.month, self.day, self.hour, self.minute, self.second); + DateTimeValue self = TemporalValueNodes.GetDateTimeValue.executeUncached(null, selfObj); + LocalDateTime localDateTime = self.toLocalDateTime(); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("EEE LLL ppd HH:mm:ss yyyy"); String ctime = localDateTime.format(formatter); return TruffleString.FromJavaStringNode.getUncached().execute(ctime, TS_ENCODING); @@ -3154,10 +3225,11 @@ protected ArgumentClinicProvider getArgumentClinic() { @Specialization static TruffleString strftime(VirtualFrame frame, Object self, TruffleString format, @Bind Node inliningTarget, + @Cached DateTimeNodes.TzInfoNode tzInfoNode, @Cached("createFor($node)") IndirectCallData.BoundaryCallData boundaryCallData) { Object saved = ExecutionContext.BoundaryCallContext.enter(frame, boundaryCallData); try { - return strftimeBoundary(self, format, inliningTarget); + return strftimeBoundary(self, format, inliningTarget, tzInfoNode.execute(inliningTarget, self)); } finally { // A Python method call (using PyObjectCallMethodObjArgs and // DatetimeModuleBuiltins.callUtcOffset) should be connected to a @@ -3167,8 +3239,8 @@ static TruffleString strftime(VirtualFrame frame, Object self, TruffleString for } @TruffleBoundary - private static TruffleString strftimeBoundary(Object selfObj, TruffleString format, Node inliningTarget) { - PDateTime self = DateTimeNodes.AsManagedDateTimeNode.executeUncached(selfObj); + private static TruffleString strftimeBoundary(Object selfObj, TruffleString format, Node inliningTarget, Object tzInfo) { + DateTimeValue self = TemporalValueNodes.GetDateTimeValue.executeUncached(inliningTarget, selfObj); // Reuse time.strftime(format, time_tuple) method. // construct time_tuple @@ -3177,7 +3249,7 @@ private static TruffleString strftimeBoundary(Object selfObj, TruffleString form int dayOfYear = localDate.getDayOfYear(); int[] timeTuple = new int[]{self.year, self.month, self.day, self.hour, self.minute, self.second, dayOfWeek, dayOfYear, -1}; - String formatPreprocessed = preprocessFormat(format, self, inliningTarget); + String formatPreprocessed = preprocessFormat(format, self, selfObj, inliningTarget, tzInfo); return TimeModuleBuiltins.StrfTimeNode.format(formatPreprocessed, timeTuple, TruffleString.FromJavaStringNode.getUncached()); } @@ -3185,7 +3257,7 @@ private static TruffleString strftimeBoundary(Object selfObj, TruffleString form // The datetime.datetime.strftime() method supports some extra formatters - %f, %z, %:z, // and %Z so handle them here. // CPython: wrap_strftime() - private static String preprocessFormat(TruffleString tsformat, PDateTime self, Node inliningTarget) { + private static String preprocessFormat(TruffleString tsformat, DateTimeValue self, Object selfObj, Node inliningTarget, Object tzInfo) { String format = tsformat.toString(); StringBuilder builder = new StringBuilder(); int i = 0; @@ -3208,13 +3280,13 @@ private static String preprocessFormat(TruffleString tsformat, PDateTime self, N char c = format.charAt(p + 1); if (c == 'z') { - Object utcOffsetString = DatetimeModuleBuiltins.formatUtcOffset(self.tzInfo, self, false, inliningTarget); + Object utcOffsetString = DatetimeModuleBuiltins.formatUtcOffset(tzInfo, selfObj, false, inliningTarget); builder.append(utcOffsetString); i = p + 2; } else if (c == 'Z') { - if (self.tzInfo != null) { + if (tzInfo != null) { // call tzname() - Object tzNameObject = PyObjectCallMethodObjArgs.executeUncached(self.tzInfo, T_TZNAME, self); + Object tzNameObject = PyObjectCallMethodObjArgs.executeUncached(tzInfo, T_TZNAME, selfObj); // ignore None value if (tzNameObject != PNone.NONE) { @@ -3245,7 +3317,7 @@ private static String preprocessFormat(TruffleString tsformat, PDateTime self, N char d = format.charAt(p + 2); if (d == 'z') { - Object utcOffsetString = DatetimeModuleBuiltins.formatUtcOffset(self.tzInfo, self, true, inliningTarget); + Object utcOffsetString = DatetimeModuleBuiltins.formatUtcOffset(tzInfo, selfObj, true, inliningTarget); builder.append(utcOffsetString); i = p + 3; @@ -3261,13 +3333,13 @@ private static String preprocessFormat(TruffleString tsformat, PDateTime self, N } @TruffleBoundary - private static LocalDateTime subtractOffsetFromDateTime(PDateTime self, PTimeDelta offset) { - return toLocalDateTime(self).minusDays(offset.days).minusSeconds(offset.seconds).minusNanos(offset.microseconds * 1_000L); + private static LocalDateTime subtractOffsetFromDateTime(DateTimeValue self, PTimeDelta offset) { + return self.toLocalDateTime().minusDays(offset.days).minusSeconds(offset.seconds).minusNanos(offset.microseconds * 1_000L); } @TruffleBoundary private static LocalDateTime toLocalDateTime(PDateTime dateTime) { - return LocalDateTime.of(dateTime.year, dateTime.month, dateTime.day, dateTime.hour, dateTime.minute, dateTime.second, dateTime.microsecond * 1_000); + return DateTimeValue.of(dateTime).toLocalDateTime(); } private static Object toPDateTime(LocalDateTime local, Object tzInfo, int fold, Node inliningTarget, Object cls) { @@ -3303,6 +3375,10 @@ private static Object toPDateTime(ZonedDateTime local, Object tzInfo, int fold, fold); } + private static Object getResultDateTimeType(Object selfObj, Node inliningTarget, IsForeignObjectNode isForeignObjectNode, GetClassNode getClassNode) { + return isForeignObjectNode.execute(inliningTarget, selfObj) ? PythonBuiltinClassType.PDateTime : getClassNode.execute(inliningTarget, selfObj); + } + /** * Check whether there was setting clocks back due to daylight saving time transition. CPython: * datetime_from_timet_and_us() diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/DateTimeNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/DateTimeNodes.java index f6b3eddafb..b2ad472e00 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/DateTimeNodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/DateTimeNodes.java @@ -48,7 +48,6 @@ import java.time.YearMonth; -import com.oracle.graal.python.PythonLanguage; import com.oracle.graal.python.builtins.PythonBuiltinClassType; import com.oracle.graal.python.builtins.objects.PNone; import com.oracle.graal.python.builtins.objects.cext.PythonAbstractNativeObject; @@ -60,15 +59,14 @@ import com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess; import com.oracle.graal.python.builtins.objects.function.PKeyword; import com.oracle.graal.python.builtins.objects.type.TypeNodes; +import com.oracle.graal.python.lib.PyTZInfoCheckNode; import com.oracle.graal.python.lib.PyLongAsIntNode; import com.oracle.graal.python.nodes.ErrorMessages; import com.oracle.graal.python.nodes.PGuards; import com.oracle.graal.python.nodes.PRaiseNode; import com.oracle.graal.python.nodes.call.CallNode; -import com.oracle.graal.python.nodes.object.BuiltinClassProfiles; import com.oracle.graal.python.runtime.PythonContext; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; -import com.oracle.truffle.api.dsl.Bind; import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.Fallback; import com.oracle.truffle.api.dsl.GenerateCached; @@ -96,7 +94,7 @@ static Object newDateTime(VirtualFrame frame, Node inliningTarget, Object cls, O Object secondObject, Object microsecondObject, Object tzInfoObject, Object foldObject, @Cached PyLongAsIntNode asIntNode, @Cached DateTimeNodes.NewUnsafeNode newUnsafeNode, - @Cached TzInfoNodes.TzInfoCheckNode tzInfoCheckNode, + @Cached PyTZInfoCheckNode tzInfoCheckNode, @Cached PRaiseNode raiseNode) { int year = asIntNode.execute(frame, inliningTarget, yearObject); int month = asIntNode.execute(frame, inliningTarget, monthObject); @@ -174,7 +172,7 @@ private static void validateDateComponents(Node inliningTarget, PRaiseNode raise } private static void validateTimeComponents(Node inliningTarget, PRaiseNode raiseNode, long hour, long minute, long second, long microsecond, Object tzInfo, long fold, - TzInfoNodes.TzInfoCheckNode tzInfoCheckNode) { + PyTZInfoCheckNode tzInfoCheckNode) { if (hour < 0 || hour >= 24) { throw raiseNode.raise(inliningTarget, ValueError, ErrorMessages.HOUR_MUST_BE_IN); } @@ -221,7 +219,7 @@ public static NewUnsafeNode getUncached() { @Specialization static Object newDateTime(Node inliningTarget, Object cls, int year, int month, int day, int hour, int minute, int second, int microsecond, Object tzInfoObject, int fold, @Cached PRaiseNode raiseNode, - @Cached TzInfoNodes.TzInfoCheckNode tzInfoCheckNode, + @Cached PyTZInfoCheckNode tzInfoCheckNode, @Cached TypeNodes.GetInstanceShape getInstanceShape, @Cached TypeNodes.NeedsNativeAllocationNode needsNativeAllocationNode, @Cached CExtNodes.PCallCapiFunction callCapiFunction, @@ -292,50 +290,7 @@ static boolean isBuiltinClass(Object cls) { } } - @GenerateUncached - @GenerateInline - @GenerateCached(false) - public abstract static class AsManagedDateTimeNode extends Node { - - public abstract PDateTime execute(Node inliningTarget, Object obj); - - public static PDateTime executeUncached(Object obj) { - return DateTimeNodesFactory.AsManagedDateTimeNodeGen.getUncached().execute(null, obj); - } - - @Specialization - static PDateTime asManaged(PDateTime obj) { - return obj; - } - - @Specialization(guards = "checkNode.execute(inliningTarget, obj)", limit = "1") - static PDateTime asManagedNative(@SuppressWarnings("unused") Node inliningTarget, PythonAbstractNativeObject obj, - @Bind PythonLanguage language, - @SuppressWarnings("unused") @Cached DateTimeCheckNode checkNode, - @Cached CStructAccess.ReadByteNode readByteNode, - @Cached CStructAccess.ReadObjectNode readObjectNode) { - int year = getYear(obj, readByteNode); - int month = getMonth(obj, readByteNode); - int day = getDay(obj, readByteNode); - - int hour = getHour(obj, readByteNode); - int minute = getMinute(obj, readByteNode); - int second = getSecond(obj, readByteNode); - int microsecond = getMicrosecond(obj, readByteNode); - - Object tzInfo = getTzInfo(obj, readByteNode, readObjectNode); - int fold = getFold(obj, readByteNode); - - PythonBuiltinClassType cls = PythonBuiltinClassType.PDateTime; - return new PDateTime(cls, cls.getInstanceShape(language), year, month, day, hour, minute, second, microsecond, tzInfo, fold); - } - - @Fallback - static PDateTime error(Object obj, - @Bind Node inliningTarget) { - throw PRaiseNode.raiseStatic(inliningTarget, TypeError, ErrorMessages.S_EXPECTED_GOT_P, "datetime", obj); - } - + public static final class FromNative { static int getYear(PythonAbstractNativeObject self, CStructAccess.ReadByteNode readNode) { int b0 = readNode.readFromObjUnsigned(self, CFields.PyDateTime_DateTime__data, 0); int b1 = readNode.readFromObjUnsigned(self, CFields.PyDateTime_DateTime__data, 1); @@ -369,7 +324,7 @@ static int getMicrosecond(PythonAbstractNativeObject self, CStructAccess.ReadByt return (b3 << 16) | (b4 << 8) | b5; } - private static Object getTzInfo(PythonAbstractNativeObject obj, CStructAccess.ReadByteNode readByteNode, CStructAccess.ReadObjectNode readObjectNode) { + static Object getTzInfo(PythonAbstractNativeObject obj, CStructAccess.ReadByteNode readByteNode, CStructAccess.ReadObjectNode readObjectNode) { Object tzInfo = null; if (readByteNode.readFromObj(obj, CFields.PyDateTime_DateTime__hastzinfo) != 0) { Object tzinfoObj = readObjectNode.readFromObj(obj, CFields.PyDateTime_DateTime__tzinfo); @@ -391,6 +346,10 @@ static int getFold(PythonAbstractNativeObject self, CStructAccess.ReadByteNode r abstract static class TzInfoNode extends Node { public abstract Object execute(Node inliningTarget, Object obj); + public static Object executeUncached(Node inliningTarget, Object obj) { + return DateTimeNodesFactory.TzInfoNodeGen.getUncached().execute(inliningTarget, obj); + } + @Specialization static Object getTzInfo(PDateTime self) { return self.tzInfo; @@ -401,34 +360,15 @@ static Object getTzInfo(PDateTime self) { static Object getTzInfo(PythonAbstractNativeObject self, @Cached CStructAccess.ReadByteNode readByteNode, @Cached CStructAccess.ReadObjectNode readObjectNode) { - return AsManagedDateTimeNode.getTzInfo(self, readByteNode, readObjectNode); - } - } - - @GenerateUncached - @GenerateInline - @GenerateCached(false) - public abstract static class DateTimeCheckNode extends Node { - public abstract boolean execute(Node inliningTarget, Object obj); - - public static boolean executeUncached(Object obj) { - return DateTimeNodesFactory.DateTimeCheckNodeGen.getUncached().execute(null, obj); - } - - @Specialization - static boolean doManaged(@SuppressWarnings("unused") PDateTime value) { - return true; + return FromNative.getTzInfo(self, readByteNode, readObjectNode); } @Specialization - static boolean doNative(Node inliningTarget, PythonAbstractNativeObject value, - @Cached BuiltinClassProfiles.IsBuiltinObjectProfile profile) { - return profile.profileObject(inliningTarget, value, PythonBuiltinClassType.PDateTime); - } - - @Fallback - static boolean doOther(@SuppressWarnings("unused") Object value) { - return false; + static Object getTzInfo(Node inliningTarget, Object self, + @Cached TemporalValueNodes.GetDateTimeValue readDateTimeValueNode) { + TemporalValueNodes.DateTimeValue value = readDateTimeValueNode.execute(inliningTarget, self); + return TemporalValueNodes.toPythonTzInfo(value.tzInfo, value.zoneId, inliningTarget); } } + } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/DatetimeModuleBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/DatetimeModuleBuiltins.java index 3af9fa1ccc..1348d28ff8 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/DatetimeModuleBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/DatetimeModuleBuiltins.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -328,8 +328,8 @@ public static PTimeDelta callDst(Object tzInfo, Object dateTime, VirtualFrame fr @TruffleBoundary public static Object addOffsetToDateTime(Object dateTimeObj, PTimeDelta offset, DateTimeNodes.SubclassNewNode subclassNewNode, Node inliningTarget) { - PDateTime dateTime = DateTimeNodes.AsManagedDateTimeNode.executeUncached(dateTimeObj); - LocalDateTime utc = LocalDateTime.of(dateTime.year, dateTime.month, dateTime.day, dateTime.hour, dateTime.minute, dateTime.second, dateTime.microsecond * 1_000).plusDays( + TemporalValueNodes.DateTimeValue dateTime = TemporalValueNodes.GetDateTimeValue.executeUncached(inliningTarget, dateTimeObj); + LocalDateTime utc = dateTime.toLocalDateTime().plusDays( offset.days).plusSeconds(offset.seconds).plusNanos(offset.microseconds * 1_000L); return subclassNewNode.execute(inliningTarget, diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/TemporalValueNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/TemporalValueNodes.java new file mode 100644 index 0000000000..0ec7b88917 --- /dev/null +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/TemporalValueNodes.java @@ -0,0 +1,424 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.graal.python.builtins.modules.datetime; + +import static com.oracle.graal.python.builtins.PythonBuiltinClassType.TypeError; + +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZoneOffset; + +import com.oracle.graal.python.builtins.PythonBuiltinClassType; +import com.oracle.graal.python.builtins.objects.PNone; +import com.oracle.graal.python.builtins.objects.cext.PythonAbstractNativeObject; +import com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess; +import com.oracle.graal.python.lib.PyDateCheckNode; +import com.oracle.graal.python.lib.PyDateTimeCheckNode; +import com.oracle.graal.python.lib.PyDeltaCheckNode; +import com.oracle.graal.python.lib.PyTimeCheckNode; +import com.oracle.graal.python.nodes.ErrorMessages; +import com.oracle.graal.python.nodes.PRaiseNode; +import com.oracle.graal.python.nodes.object.IsForeignObjectNode; +import com.oracle.graal.python.runtime.PythonContext; +import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.CompilerDirectives.ValueType; +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.dsl.Bind; +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.Fallback; +import com.oracle.truffle.api.dsl.GenerateCached; +import com.oracle.truffle.api.dsl.GenerateInline; +import com.oracle.truffle.api.dsl.GenerateUncached; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.interop.UnsupportedMessageException; +import com.oracle.truffle.api.library.CachedLibrary; +import com.oracle.truffle.api.nodes.Node; + +public final class TemporalValueNodes { + private TemporalValueNodes() { + } + + @ValueType + public static final class DateValue { + public final int year; + public final int month; + public final int day; + + public DateValue(int year, int month, int day) { + this.year = year; + this.month = month; + this.day = day; + } + + public static DateValue of(PDate date) { + return new DateValue(date.year, date.month, date.day); + } + + public LocalDate toLocalDate() { + return LocalDate.of(year, month, day); + } + + public int compareTo(DateValue other) { + if (year < other.year) { + return -1; + } + if (year > other.year) { + return 1; + } + if (month < other.month) { + return -1; + } + if (month > other.month) { + return 1; + } + if (day < other.day) { + return -1; + } + if (day > other.day) { + return 1; + } + return 0; + } + } + + @ValueType + public static final class TimeDeltaValue { + public final int days; + public final int seconds; + public final int microseconds; + + public TimeDeltaValue(int days, int seconds, int microseconds) { + this.days = days; + this.seconds = seconds; + this.microseconds = microseconds; + } + + public static TimeDeltaValue of(PTimeDelta delta) { + return new TimeDeltaValue(delta.days, delta.seconds, delta.microseconds); + } + + public boolean isZero() { + return days == 0 && seconds == 0 && microseconds == 0; + } + + public int compareTo(TimeDeltaValue other) { + if (days < other.days) { + return -1; + } + if (days > other.days) { + return 1; + } + if (seconds < other.seconds) { + return -1; + } + if (seconds > other.seconds) { + return 1; + } + if (microseconds < other.microseconds) { + return -1; + } + if (microseconds > other.microseconds) { + return 1; + } + return 0; + } + } + + @ValueType + public static class TimeValue { + public final int hour; + public final int minute; + public final int second; + public final int microsecond; + public final Object tzInfo; + public final ZoneId zoneId; + public final int fold; + + public TimeValue(int hour, int minute, int second, int microsecond, Object tzInfo, ZoneId zoneId, int fold) { + this.hour = hour; + this.minute = minute; + this.second = second; + this.microsecond = microsecond; + this.tzInfo = tzInfo; + this.zoneId = zoneId; + this.fold = fold; + } + + public static TimeValue of(PTime time) { + return new TimeValue(time.hour, time.minute, time.second, time.microsecond, time.tzInfo, null, time.fold); + } + + public LocalTime toLocalTime() { + return LocalTime.of(hour, minute, second, microsecond * 1_000); + } + + public boolean hasTimeZone() { + return tzInfo != null || zoneId != null; + } + } + + @ValueType + public static final class DateTimeValue extends TimeValue { + public final int year; + public final int month; + public final int day; + + public DateTimeValue(int year, int month, int day, int hour, int minute, int second, int microsecond, Object tzInfo, ZoneId zoneId, int fold) { + super(hour, minute, second, microsecond, tzInfo, zoneId, fold); + this.year = year; + this.month = month; + this.day = day; + } + + public static DateTimeValue of(PDateTime dateTime) { + return new DateTimeValue(dateTime.year, dateTime.month, dateTime.day, dateTime.hour, dateTime.minute, dateTime.second, dateTime.microsecond, dateTime.tzInfo, null, + dateTime.fold); + } + + public DateValue getDateValue() { + return new DateValue(year, month, day); + } + + public LocalDateTime toLocalDateTime() { + return LocalDateTime.of(year, month, day, hour, minute, second, microsecond * 1_000); + } + } + + public static Object toPythonTzInfo(Object tzInfo, ZoneId zoneId, Node inliningTarget) { + if (tzInfo != null) { + return tzInfo; + } + if (zoneId == null) { + return null; + } + Object fixedOffsetTimeZone = toFixedOffsetTimeZone(zoneId, inliningTarget); + if (fixedOffsetTimeZone != null) { + return fixedOffsetTimeZone; + } + return PythonContext.get(inliningTarget).getEnv().asGuestValue(zoneId); + } + + @TruffleBoundary + public static Object toFixedOffsetTimeZone(ZoneId zoneId, Node inliningTarget) { + if (zoneId == null) { + return null; + } + final ZoneOffset offset; + if (zoneId instanceof ZoneOffset zoneOffset) { + offset = zoneOffset; + } else if (zoneId.getRules().isFixedOffset()) { + offset = zoneId.getRules().getOffset(Instant.EPOCH); + } else { + return null; + } + PTimeDelta delta = TimeDeltaNodes.NewNode.getUncached().executeBuiltin(inliningTarget, 0, offset.getTotalSeconds(), 0, 0, 0, 0, 0); + return TimeZoneNodes.NewNode.getUncached().execute(inliningTarget, PythonContext.get(inliningTarget), PythonBuiltinClassType.PTimezone, delta, PNone.NO_VALUE); + } + + @GenerateUncached + @GenerateInline + @GenerateCached(false) + public abstract static class GetTimeDeltaValue extends Node { + public abstract TimeDeltaValue execute(Node inliningTarget, Object obj); + + public static TimeDeltaValue executeUncached(Node inliningTarget, Object obj) { + return TemporalValueNodesFactory.GetTimeDeltaValueNodeGen.getUncached().execute(inliningTarget, obj); + } + + @Specialization + static TimeDeltaValue doManaged(PTimeDelta value) { + return TimeDeltaValue.of(value); + } + + @Specialization(guards = "checkNode.execute(inliningTarget, value)", limit = "1") + static TimeDeltaValue doNative(@SuppressWarnings("unused") Node inliningTarget, PythonAbstractNativeObject value, + @SuppressWarnings("unused") @Cached PyDeltaCheckNode checkNode, + @Cached CStructAccess.ReadI32Node readIntNode) { + return new TimeDeltaValue(TimeDeltaNodes.FromNative.getDays(value, readIntNode), TimeDeltaNodes.FromNative.getSeconds(value, readIntNode), + TimeDeltaNodes.FromNative.getMicroseconds(value, readIntNode)); + } + + @Fallback + static TimeDeltaValue error(Object obj, + @Bind Node inliningTarget) { + throw PRaiseNode.raiseStatic(inliningTarget, TypeError, ErrorMessages.S_EXPECTED_GOT_P, "timedelta", obj); + } + } + + @GenerateUncached + @GenerateInline + @GenerateCached(false) + public abstract static class GetDateValue extends Node { + public abstract DateValue execute(Node inliningTarget, Object obj); + + public static DateValue executeUncached(Node inliningTarget, Object obj) { + return TemporalValueNodesFactory.GetDateValueNodeGen.getUncached().execute(inliningTarget, obj); + } + + @Specialization + static DateValue doManaged(PDate value) { + return DateValue.of(value); + } + + @Specialization(guards = "checkNode.execute(inliningTarget, value)", limit = "1") + static DateValue doNative(@SuppressWarnings("unused") Node inliningTarget, PythonAbstractNativeObject value, + @SuppressWarnings("unused") @Cached PyDateCheckNode checkNode, + @Cached CStructAccess.ReadByteNode readNode) { + return new DateValue(DateNodes.FromNative.getYear(value, readNode), DateNodes.FromNative.getMonth(value, readNode), DateNodes.FromNative.getDay(value, readNode)); + } + + @Specialization(guards = {"isForeignObjectNode.execute(inliningTarget, value)", "interop.isDate(value)"}, limit = "1") + static DateValue doForeign(Node inliningTarget, Object value, + @SuppressWarnings("unused") @Cached IsForeignObjectNode isForeignObjectNode, + @CachedLibrary("value") InteropLibrary interop) { + try { + LocalDate date = interop.asDate(value); + return new DateValue(date.getYear(), date.getMonthValue(), date.getDayOfMonth()); + } catch (UnsupportedMessageException e) { + throw CompilerDirectives.shouldNotReachHere(e); + } + } + + @Fallback + static DateValue error(Object obj, + @Bind Node inliningTarget) { + throw PRaiseNode.raiseStatic(inliningTarget, TypeError, ErrorMessages.S_EXPECTED_GOT_P, "date", obj); + } + } + + @GenerateUncached + @GenerateInline + @GenerateCached(false) + public abstract static class GetTimeValue extends Node { + public abstract TimeValue execute(Node inliningTarget, Object obj); + + public static TimeValue executeUncached(Node inliningTarget, Object obj) { + return TemporalValueNodesFactory.GetTimeValueNodeGen.getUncached().execute(inliningTarget, obj); + } + + @Specialization + static TimeValue doManaged(PTime value) { + return TimeValue.of(value); + } + + @Specialization(guards = "checkNode.execute(inliningTarget, value)", limit = "1") + static TimeValue doNative(@SuppressWarnings("unused") Node inliningTarget, PythonAbstractNativeObject value, + @SuppressWarnings("unused") @Cached PyTimeCheckNode checkNode, + @Cached CStructAccess.ReadByteNode readByteNode, + @Cached CStructAccess.ReadObjectNode readObjectNode) { + return new TimeValue(TimeNodes.FromNative.getHour(value, readByteNode), TimeNodes.FromNative.getMinute(value, readByteNode), + TimeNodes.FromNative.getSecond(value, readByteNode), TimeNodes.FromNative.getMicrosecond(value, readByteNode), + TimeNodes.FromNative.getTzInfo(value, readByteNode, readObjectNode), null, TimeNodes.FromNative.getFold(value, readByteNode)); + } + + @Specialization(guards = {"isForeignObjectNode.execute(inliningTarget, value)", "interop.isTime(value)"}, limit = "1") + static TimeValue doForeign(Node inliningTarget, Object value, + @SuppressWarnings("unused") @Cached IsForeignObjectNode isForeignObjectNode, + @CachedLibrary("value") InteropLibrary interop) { + try { + LocalTime time = interop.asTime(value); + ZoneId zoneId = interop.isTimeZone(value) ? interop.asTimeZone(value) : null; + return new TimeValue(time.getHour(), time.getMinute(), time.getSecond(), time.getNano() / 1_000, null, zoneId, 0); + } catch (UnsupportedMessageException e) { + throw CompilerDirectives.shouldNotReachHere(e); + } + } + + @Fallback + static TimeValue error(Object obj, + @Bind Node inliningTarget) { + throw PRaiseNode.raiseStatic(inliningTarget, TypeError, ErrorMessages.S_EXPECTED_GOT_P, "time", obj); + } + } + + @GenerateUncached + @GenerateInline + @GenerateCached(false) + public abstract static class GetDateTimeValue extends Node { + public abstract DateTimeValue execute(Node inliningTarget, Object obj); + + public static DateTimeValue executeUncached(Node inliningTarget, Object obj) { + return TemporalValueNodesFactory.GetDateTimeValueNodeGen.getUncached().execute(inliningTarget, obj); + } + + @Specialization + static DateTimeValue doManaged(PDateTime value) { + return DateTimeValue.of(value); + } + + @Specialization(guards = "checkNode.execute(inliningTarget, value)", limit = "1") + static DateTimeValue doNative(@SuppressWarnings("unused") Node inliningTarget, PythonAbstractNativeObject value, + @SuppressWarnings("unused") @Cached PyDateTimeCheckNode checkNode, + @Cached CStructAccess.ReadByteNode readByteNode, + @Cached CStructAccess.ReadObjectNode readObjectNode) { + return new DateTimeValue(DateTimeNodes.FromNative.getYear(value, readByteNode), DateTimeNodes.FromNative.getMonth(value, readByteNode), + DateTimeNodes.FromNative.getDay(value, readByteNode), DateTimeNodes.FromNative.getHour(value, readByteNode), + DateTimeNodes.FromNative.getMinute(value, readByteNode), DateTimeNodes.FromNative.getSecond(value, readByteNode), + DateTimeNodes.FromNative.getMicrosecond(value, readByteNode), DateTimeNodes.FromNative.getTzInfo(value, readByteNode, readObjectNode), null, + DateTimeNodes.FromNative.getFold(value, readByteNode)); + } + + @Specialization(guards = {"isForeignObjectNode.execute(inliningTarget, value)", "interop.isDate(value)", "interop.isTime(value)"}, limit = "1") + static DateTimeValue doForeign(Node inliningTarget, Object value, + @SuppressWarnings("unused") @Cached IsForeignObjectNode isForeignObjectNode, + @CachedLibrary("value") InteropLibrary interop) { + try { + LocalDate date = interop.asDate(value); + LocalTime time = interop.asTime(value); + ZoneId zoneId = interop.isTimeZone(value) ? interop.asTimeZone(value) : null; + return new DateTimeValue(date.getYear(), date.getMonthValue(), date.getDayOfMonth(), time.getHour(), time.getMinute(), time.getSecond(), time.getNano() / 1_000, null, zoneId, + 0); + } catch (UnsupportedMessageException e) { + throw CompilerDirectives.shouldNotReachHere(e); + } + } + + @Fallback + static DateTimeValue error(Object obj, + @Bind Node inliningTarget) { + throw PRaiseNode.raiseStatic(inliningTarget, TypeError, ErrorMessages.S_EXPECTED_GOT_P, "datetime", obj); + } + } +} diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/TimeBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/TimeBuiltins.java index d306a9552c..78789de622 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/TimeBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/TimeBuiltins.java @@ -68,6 +68,7 @@ import com.oracle.graal.python.builtins.PythonBuiltinClassType; import com.oracle.graal.python.builtins.PythonBuiltins; import com.oracle.graal.python.builtins.modules.TimeModuleBuiltins; +import com.oracle.graal.python.builtins.modules.datetime.TemporalValueNodes.TimeValue; import com.oracle.graal.python.builtins.objects.PNone; import com.oracle.graal.python.builtins.objects.PNotImplemented; import com.oracle.graal.python.builtins.objects.bytes.BytesNodes; @@ -88,6 +89,8 @@ import com.oracle.graal.python.lib.PyObjectReprAsObjectNode; import com.oracle.graal.python.lib.PyObjectStrAsObjectNode; import com.oracle.graal.python.lib.PyUnicodeCheckNode; +import com.oracle.graal.python.lib.PyTZInfoCheckNode; +import com.oracle.graal.python.lib.PyTimeCheckNode; import com.oracle.graal.python.lib.RichCmpOp; import com.oracle.graal.python.nodes.ErrorMessages; import com.oracle.graal.python.nodes.PRaiseNode; @@ -98,6 +101,7 @@ import com.oracle.graal.python.nodes.function.builtins.PythonUnaryBuiltinNode; import com.oracle.graal.python.nodes.function.builtins.clinic.ArgumentClinicProvider; import com.oracle.graal.python.nodes.object.GetClassNode; +import com.oracle.graal.python.nodes.object.IsForeignObjectNode; import com.oracle.graal.python.nodes.util.CannotCastException; import com.oracle.graal.python.nodes.util.CastToJavaStringNode; import com.oracle.graal.python.nodes.util.CastToTruffleStringNode; @@ -200,7 +204,7 @@ private static Object tryToDeserializeTime(Object cls, Object bytesObject, Objec if (naiveBytesCheck(bytes)) { // slightly different error message - if (tzInfo != PNone.NO_VALUE && !TzInfoNodes.TzInfoCheckNode.executeUncached(tzInfo)) { + if (tzInfo != PNone.NO_VALUE && !PyTZInfoCheckNode.executeUncached(tzInfo)) { throw PRaiseNode.raiseStatic(inliningTarget, TypeError, ErrorMessages.BAD_TZINFO_STATE_ARG); } @@ -255,10 +259,12 @@ public abstract static class ReprNode extends PythonUnaryBuiltinNode { @Specialization static TruffleString repr(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached TimeNodes.TzInfoNode tzInfoNode, @Cached("createFor($node)") IndirectCallData.BoundaryCallData boundaryCallData) { Object saved = ExecutionContext.BoundaryCallContext.enter(frame, boundaryCallData); try { - return reprBoundary(self); + return reprBoundary(inliningTarget, self, tzInfoNode.execute(inliningTarget, self)); } finally { // A Python method call (using PyObjectReprAsObjectNode) should be // connected to a current node. @@ -267,8 +273,8 @@ static TruffleString repr(VirtualFrame frame, Object self, } @TruffleBoundary - private static TruffleString reprBoundary(Object selfObj) { - PTime self = TimeNodes.AsManagedTimeNode.executeUncached(selfObj); + private static TruffleString reprBoundary(Node inliningTarget, Object selfObj, Object tzInfo) { + TimeValue self = TemporalValueNodes.GetTimeValue.executeUncached(null, selfObj); var builder = new StringBuilder(); TruffleString typeName = TypeNodes.GetTpNameNode.executeUncached(GetClassNode.executeUncached(selfObj)); @@ -280,10 +286,10 @@ private static TruffleString reprBoundary(Object selfObj) { builder.append(PythonUtils.formatJString(", %d", self.second)); } - if (self.tzInfo != null) { + if (tzInfo != null) { builder.append(", tzinfo="); - Object tzinfoReprObject = PyObjectReprAsObjectNode.executeUncached(self.tzInfo); + Object tzinfoReprObject = PyObjectReprAsObjectNode.executeUncached(tzInfo); String tzinfoRepr = CastToJavaStringNode.getUncached().execute(tzinfoReprObject); builder.append(tzinfoRepr); } @@ -306,9 +312,11 @@ public abstract static class ReduceNode extends PythonUnaryBuiltinNode { static Object reduce(Object self, @Bind Node inliningTarget, @Bind PythonLanguage language, - @Cached TimeNodes.AsManagedTimeNode asManagedTimeNode, + @Cached TemporalValueNodes.GetTimeValue asManagedTimeNode, + @Cached TimeNodes.TzInfoNode tzInfoNode, @Cached GetClassNode getClassNode) { - PTime time = asManagedTimeNode.execute(inliningTarget, self); + TimeValue time = asManagedTimeNode.execute(inliningTarget, self); + Object tzInfo = tzInfoNode.execute(inliningTarget, self); // Time is serialized in the following format: // ( // bytes(hours, minutes, seconds, microseconds 1st byte, microseconds 2nd byte, @@ -327,8 +335,8 @@ static Object reduce(Object self, PBytes baseState = PFactory.createBytes(language, baseStateBytes); final PTuple arguments; - if (time.tzInfo != null) { - arguments = PFactory.createTuple(language, new Object[]{baseState, time.tzInfo}); + if (tzInfo != null) { + arguments = PFactory.createTuple(language, new Object[]{baseState, tzInfo}); } else { arguments = PFactory.createTuple(language, new Object[]{baseState}); } @@ -346,9 +354,11 @@ public abstract static class ReduceExNode extends PythonBinaryBuiltinNode { static Object reduceEx(Object self, int protocol, @Bind Node inliningTarget, @Bind PythonLanguage language, - @Cached TimeNodes.AsManagedTimeNode asManagedTimeNode, + @Cached TemporalValueNodes.GetTimeValue asManagedTimeNode, + @Cached TimeNodes.TzInfoNode tzInfoNode, @Cached GetClassNode getClassNode) { - PTime time = asManagedTimeNode.execute(inliningTarget, self); + TimeValue time = asManagedTimeNode.execute(inliningTarget, self); + Object tzInfo = tzInfoNode.execute(inliningTarget, self); byte[] baseStateBytes = new byte[6]; baseStateBytes[0] = (byte) time.hour; baseStateBytes[1] = (byte) time.minute; @@ -364,8 +374,8 @@ static Object reduceEx(Object self, int protocol, PBytes baseState = PFactory.createBytes(language, baseStateBytes); final PTuple arguments; - if (time.tzInfo != null) { - arguments = PFactory.createTuple(language, new Object[]{baseState, time.tzInfo}); + if (tzInfo != null) { + arguments = PFactory.createTuple(language, new Object[]{baseState, tzInfo}); } else { arguments = PFactory.createTuple(language, new Object[]{baseState}); } @@ -382,10 +392,11 @@ abstract static class RichCmpNode extends RichCmpBuiltinNode { @Specialization static Object richCmp(VirtualFrame frame, Object self, Object other, RichCmpOp op, @Bind Node inliningTarget, + @Cached TimeNodes.TzInfoNode tzInfoNode, @Cached("createFor($node)") IndirectCallData.BoundaryCallData boundaryCallData) { Object saved = ExecutionContext.BoundaryCallContext.enter(frame, boundaryCallData); try { - return richCmpBoundary(self, other, op, inliningTarget); + return richCmpBoundary(self, other, op, inliningTarget, tzInfoNode); } finally { // A Python method call (using DatetimeModuleBuiltins.callUtcOffset) // should be connected to a current node. @@ -394,19 +405,21 @@ static Object richCmp(VirtualFrame frame, Object self, Object other, RichCmpOp o } @TruffleBoundary - private static Object richCmpBoundary(Object selfObj, Object otherObj, RichCmpOp op, Node inliningTarget) { - if (!TimeNodes.TimeCheckNode.executeUncached(selfObj) || !TimeNodes.TimeCheckNode.executeUncached(otherObj)) { + private static Object richCmpBoundary(Object selfObj, Object otherObj, RichCmpOp op, Node inliningTarget, TimeNodes.TzInfoNode tzInfoNode) { + if (!PyTimeCheckNode.executeUncached(selfObj) || !PyTimeCheckNode.executeUncached(otherObj)) { return PNotImplemented.NOT_IMPLEMENTED; } - PTime self = TimeNodes.AsManagedTimeNode.executeUncached(selfObj); - PTime other = TimeNodes.AsManagedTimeNode.executeUncached(otherObj); + TimeValue self = TemporalValueNodes.GetTimeValue.executeUncached(inliningTarget, selfObj); + TimeValue other = TemporalValueNodes.GetTimeValue.executeUncached(inliningTarget, otherObj); + Object selfTzInfo = tzInfoNode.execute(inliningTarget, selfObj); + Object otherTzInfo = tzInfoNode.execute(inliningTarget, otherObj); // either naive times (without timezone) or timezones are exactly the same objects - if (self.tzInfo == other.tzInfo) { + if (selfTzInfo == otherTzInfo) { return compareTimeComponents(self, other, op); } - PTimeDelta selfUtcOffset = DatetimeModuleBuiltins.callUtcOffset(self.tzInfo, PNone.NONE, inliningTarget); - PTimeDelta otherUtcOffset = DatetimeModuleBuiltins.callUtcOffset(other.tzInfo, PNone.NONE, inliningTarget); + PTimeDelta selfUtcOffset = DatetimeModuleBuiltins.callUtcOffset(selfTzInfo, PNone.NONE, inliningTarget); + PTimeDelta otherUtcOffset = DatetimeModuleBuiltins.callUtcOffset(otherTzInfo, PNone.NONE, inliningTarget); if (Objects.equals(selfUtcOffset, otherUtcOffset)) { return compareTimeComponents(self, other, op); @@ -432,7 +445,7 @@ private static Object richCmpBoundary(Object selfObj, Object otherObj, RichCmpOp return op.compareResultToBool(result); } - private static boolean compareTimeComponents(PTime self, PTime other, RichCmpOp op) { + private static boolean compareTimeComponents(TimeValue self, TimeValue other, RichCmpOp op) { // compare only hours, minutes, ... and ignore fold int[] selfComponents = new int[]{self.hour, self.minute, self.second, self.microsecond}; int[] otherComponents = new int[]{other.hour, other.minute, other.second, other.microsecond}; @@ -451,12 +464,14 @@ abstract static class HashNode extends HashBuiltinNode { static long hash(VirtualFrame frame, Object selfObj, @Bind Node inliningTarget, @Bind PythonLanguage language, - @Cached TimeNodes.AsManagedTimeNode asManagedTimeNode, + @Cached TemporalValueNodes.GetTimeValue asManagedTimeNode, + @Cached TimeNodes.TzInfoNode tzInfoNode, @Cached PyObjectCallMethodObjArgs callMethodObjArgs, @Cached PRaiseNode raiseNode, @Cached PyObjectHashNode hashNode) { - PTime self = asManagedTimeNode.execute(inliningTarget, selfObj); - PTimeDelta utcOffset = DatetimeModuleBuiltins.callUtcOffset(self.tzInfo, PNone.NONE, frame, inliningTarget, callMethodObjArgs, raiseNode); + TimeValue self = asManagedTimeNode.execute(inliningTarget, selfObj); + Object tzInfo = tzInfoNode.execute(inliningTarget, selfObj); + PTimeDelta utcOffset = DatetimeModuleBuiltins.callUtcOffset(tzInfo, PNone.NONE, frame, inliningTarget, callMethodObjArgs, raiseNode); if (utcOffset == null) { var content = new int[]{self.hour, self.minute, self.second, self.microsecond}; @@ -482,7 +497,14 @@ static int getHour(PTime self) { @Specialization static int getHour(PythonAbstractNativeObject self, @Cached CStructAccess.ReadByteNode readNode) { - return TimeNodes.AsManagedTimeNode.getHour(self, readNode); + return TimeNodes.FromNative.getHour(self, readNode); + } + + @Specialization + static int getHour(Object self, + @Bind Node inliningTarget, + @Cached TemporalValueNodes.GetTimeValue readTimeValueNode) { + return readTimeValueNode.execute(inliningTarget, self).hour; } } @@ -498,7 +520,14 @@ static int getMinute(PTime self) { @Specialization static int getMinute(PythonAbstractNativeObject self, @Cached CStructAccess.ReadByteNode readNode) { - return TimeNodes.AsManagedTimeNode.getMinute(self, readNode); + return TimeNodes.FromNative.getMinute(self, readNode); + } + + @Specialization + static int getMinute(Object self, + @Bind Node inliningTarget, + @Cached TemporalValueNodes.GetTimeValue readTimeValueNode) { + return readTimeValueNode.execute(inliningTarget, self).minute; } } @@ -514,7 +543,14 @@ static int getSecond(PTime self) { @Specialization static int getSecond(PythonAbstractNativeObject self, @Cached CStructAccess.ReadByteNode readNode) { - return TimeNodes.AsManagedTimeNode.getSecond(self, readNode); + return TimeNodes.FromNative.getSecond(self, readNode); + } + + @Specialization + static int getSecond(Object self, + @Bind Node inliningTarget, + @Cached TemporalValueNodes.GetTimeValue readTimeValueNode) { + return readTimeValueNode.execute(inliningTarget, self).second; } } @@ -530,7 +566,14 @@ static int getMicrosecond(PTime self) { @Specialization static int getMicrosecond(PythonAbstractNativeObject self, @Cached CStructAccess.ReadByteNode readNode) { - return TimeNodes.AsManagedTimeNode.getMicrosecond(self, readNode); + return TimeNodes.FromNative.getMicrosecond(self, readNode); + } + + @Specialization + static int getMicrosecond(Object self, + @Bind Node inliningTarget, + @Cached TemporalValueNodes.GetTimeValue readTimeValueNode) { + return readTimeValueNode.execute(inliningTarget, self).microsecond; } } @@ -559,7 +602,14 @@ static int getFold(PTime self) { @Specialization static int getFold(PythonAbstractNativeObject self, @Cached CStructAccess.ReadByteNode readNode) { - return TimeNodes.AsManagedTimeNode.getFold(self, readNode); + return TimeNodes.FromNative.getFold(self, readNode); + } + + @Specialization + static int getFold(Object self, + @Bind Node inliningTarget, + @Cached TemporalValueNodes.GetTimeValue readTimeValueNode) { + return readTimeValueNode.execute(inliningTarget, self).fold; } } @@ -946,11 +996,13 @@ public abstract static class ReplaceNode extends PythonBuiltinNode { @Specialization static Object replace(VirtualFrame frame, Object self, Object hourObject, Object minuteObject, Object secondObject, Object microsecondObject, Object tzInfoObject, Object foldObject, @Bind Node inliningTarget, - @Cached TimeNodes.AsManagedTimeNode asManagedTimeNode, + @Cached TemporalValueNodes.GetTimeValue asManagedTimeNode, @Cached PyLongAsLongNode asLongNode, + @Cached IsForeignObjectNode isForeignObjectNode, + @Cached TimeNodes.TzInfoNode tzInfoNode, @Cached GetClassNode getClassNode, @Cached TimeNodes.NewNode newTimeNode) { - PTime time = asManagedTimeNode.execute(inliningTarget, self); + TimeValue time = asManagedTimeNode.execute(inliningTarget, self); final long hour, minute, second, microsecond, fold; final Object tzInfo; @@ -979,7 +1031,7 @@ static Object replace(VirtualFrame frame, Object self, Object hourObject, Object } if (tzInfoObject == PNone.NO_VALUE) { - tzInfo = time.tzInfo; + tzInfo = tzInfoNode.execute(inliningTarget, self); } else if (tzInfoObject == PNone.NONE) { tzInfo = null; } else { @@ -992,7 +1044,7 @@ static Object replace(VirtualFrame frame, Object self, Object hourObject, Object fold = asLongNode.execute(frame, inliningTarget, foldObject); } - Object type = getClassNode.execute(inliningTarget, self); + Object type = getResultTimeType(self, inliningTarget, isForeignObjectNode, getClassNode); return newTimeNode.execute(inliningTarget, type, hour, minute, second, microsecond, tzInfo, fold); } } @@ -1004,10 +1056,11 @@ public abstract static class IsoFormatNode extends PythonBinaryBuiltinNode { @Specialization static TruffleString isoFormat(VirtualFrame frame, Object self, Object timespecObject, @Bind Node inliningTarget, + @Cached TimeNodes.TzInfoNode tzInfoNode, @Cached("createFor($node)") IndirectCallData.BoundaryCallData boundaryCallData) { Object saved = ExecutionContext.BoundaryCallContext.enter(frame, boundaryCallData); try { - return isoFormatBoundary(self, timespecObject, inliningTarget); + return isoFormatBoundary(self, timespecObject, inliningTarget, tzInfoNode.execute(inliningTarget, self)); } finally { // A Python method call (using PyObjectCallMethodObjArgs and // DatetimeModuleBuiltins.callUtcOffset) should be connected to a @@ -1017,8 +1070,8 @@ static TruffleString isoFormat(VirtualFrame frame, Object self, Object timespecO } @TruffleBoundary - private static TruffleString isoFormatBoundary(Object selfObj, Object timespecObject, Node inliningTarget) { - PTime self = TimeNodes.AsManagedTimeNode.executeUncached(selfObj); + private static TruffleString isoFormatBoundary(Object selfObj, Object timespecObject, Node inliningTarget, Object tzInfo) { + TimeValue self = TemporalValueNodes.GetTimeValue.executeUncached(inliningTarget, selfObj); var builder = new StringBuilder(); final String timespec; @@ -1075,7 +1128,7 @@ private static TruffleString isoFormatBoundary(Object selfObj, Object timespecOb ErrorMessages.UNKNOWN_TIMESPEC_VALUE); } - Object utcOffsetString = DatetimeModuleBuiltins.formatUtcOffset(self.tzInfo, PNone.NONE, true, inliningTarget); + Object utcOffsetString = DatetimeModuleBuiltins.formatUtcOffset(tzInfo, PNone.NONE, true, inliningTarget); builder.append(utcOffsetString); return FromJavaStringNode.getUncached().execute(builder.toString(), TS_ENCODING); @@ -1170,10 +1223,11 @@ protected ArgumentClinicProvider getArgumentClinic() { @Specialization static TruffleString strftime(VirtualFrame frame, Object self, TruffleString format, @Bind Node inliningTarget, + @Cached TimeNodes.TzInfoNode tzInfoNode, @Cached("createFor($node)") IndirectCallData.BoundaryCallData boundaryCallData) { Object saved = ExecutionContext.BoundaryCallContext.enter(frame, boundaryCallData); try { - return strftimeBoundary(self, format, inliningTarget); + return strftimeBoundary(self, format, inliningTarget, tzInfoNode.execute(inliningTarget, self)); } finally { // A Python method call (using PyObjectCallMethodObjArgs and // DatetimeModuleBuiltins.formatUtcOffset) should be connected to a @@ -1183,11 +1237,11 @@ static TruffleString strftime(VirtualFrame frame, Object self, TruffleString for } @TruffleBoundary - private static TruffleString strftimeBoundary(Object selfObj, TruffleString format, Node inliningTarget) { - PTime self = TimeNodes.AsManagedTimeNode.executeUncached(selfObj); + private static TruffleString strftimeBoundary(Object selfObj, TruffleString format, Node inliningTarget, Object tzInfo) { + TimeValue self = TemporalValueNodes.GetTimeValue.executeUncached(inliningTarget, selfObj); // Reuse time.strftime(format, time_tuple) method. int[] timeTuple = new int[]{1900, 1, 1, self.hour, self.minute, self.second, 0, 1, -1}; - String formatPreprocessed = preprocessFormat(format, self, inliningTarget); + String formatPreprocessed = preprocessFormat(format, self, inliningTarget, tzInfo); return TimeModuleBuiltins.StrfTimeNode.format(formatPreprocessed, timeTuple, TruffleString.FromJavaStringNode.getUncached()); } @@ -1195,7 +1249,7 @@ private static TruffleString strftimeBoundary(Object selfObj, TruffleString form // %Z so handle them here. // CPython: wrap_strftime() @TruffleBoundary - private static String preprocessFormat(TruffleString tsformat, PTime self, Node inliningTarget) { + private static String preprocessFormat(TruffleString tsformat, TimeValue self, Node inliningTarget, Object tzInfo) { String format = tsformat.toString(); StringBuilder builder = new StringBuilder(); int i = 0; @@ -1218,13 +1272,13 @@ private static String preprocessFormat(TruffleString tsformat, PTime self, Node char c = format.charAt(p + 1); if (c == 'z') { - Object utcOffsetString = DatetimeModuleBuiltins.formatUtcOffset(self.tzInfo, PNone.NONE, false, inliningTarget); + Object utcOffsetString = DatetimeModuleBuiltins.formatUtcOffset(tzInfo, PNone.NONE, false, inliningTarget); builder.append(utcOffsetString); i = p + 2; } else if (c == 'Z') { - if (self.tzInfo != null) { + if (tzInfo != null) { // call tzname() - Object tzNameObject = PyObjectCallMethodObjArgs.executeUncached(self.tzInfo, T_TZNAME, PNone.NONE); + Object tzNameObject = PyObjectCallMethodObjArgs.executeUncached(tzInfo, T_TZNAME, PNone.NONE); // ignore None value if (tzNameObject != PNone.NONE) { @@ -1255,7 +1309,7 @@ private static String preprocessFormat(TruffleString tsformat, PTime self, Node char d = format.charAt(p + 2); if (d == 'z') { - Object utcOffsetString = DatetimeModuleBuiltins.formatUtcOffset(self.tzInfo, PNone.NONE, true, inliningTarget); + Object utcOffsetString = DatetimeModuleBuiltins.formatUtcOffset(tzInfo, PNone.NONE, true, inliningTarget); builder.append(utcOffsetString); i = p + 3; @@ -1295,7 +1349,7 @@ static Object format(VirtualFrame frame, Object self, TruffleString format, } } - private static long toMicroseconds(PTime self, PTimeDelta utcOffset) { + private static long toMicroseconds(TimeValue self, PTimeDelta utcOffset) { return (long) self.hour * 3600 * 1_000_000 + (long) self.minute * 60 * 1_000_000 + (long) self.second * 1_000_000 + @@ -1304,4 +1358,8 @@ private static long toMicroseconds(PTime self, PTimeDelta utcOffset) { (long) utcOffset.seconds * 1_000_000 - (long) utcOffset.microseconds; } + + private static Object getResultTimeType(Object self, Node inliningTarget, IsForeignObjectNode isForeignObjectNode, GetClassNode getClassNode) { + return isForeignObjectNode.execute(inliningTarget, self) ? PythonBuiltinClassType.PTime : getClassNode.execute(inliningTarget, self); + } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/TimeDeltaBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/TimeDeltaBuiltins.java index 88cfccc818..86bde99c63 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/TimeDeltaBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/TimeDeltaBuiltins.java @@ -76,7 +76,9 @@ import com.oracle.graal.python.builtins.objects.type.slots.TpSlotHashFun; import com.oracle.graal.python.builtins.objects.type.slots.TpSlotInquiry; import com.oracle.graal.python.builtins.objects.type.slots.TpSlotRichCompare.RichCmpBuiltinNode; +import com.oracle.graal.python.builtins.modules.datetime.TemporalValueNodes.TimeDeltaValue; import com.oracle.graal.python.lib.PyFloatCheckNode; +import com.oracle.graal.python.lib.PyDeltaCheckNode; import com.oracle.graal.python.lib.PyLongCheckNode; import com.oracle.graal.python.lib.PyNumberAddNode; import com.oracle.graal.python.lib.PyNumberFloorDivideNode; @@ -163,8 +165,8 @@ abstract static class BoolNode extends TpSlotInquiry.NbBoolBuiltinNode { @Specialization static boolean bool(Object selfObj, @Bind Node inliningTarget, - @Cached TimeDeltaNodes.AsManagedTimeDeltaNode asManagedTimeDeltaNode) { - PTimeDelta self = asManagedTimeDeltaNode.execute(inliningTarget, selfObj); + @Cached TemporalValueNodes.GetTimeDeltaValue readTimeDeltaValueNode) { + TimeDeltaValue self = readTimeDeltaValueNode.execute(inliningTarget, selfObj); return self.days != 0 || self.seconds != 0 || self.microseconds != 0; } } @@ -176,10 +178,10 @@ public abstract static class ReprNode extends PythonUnaryBuiltinNode { @Specialization @TruffleBoundary static TruffleString repr(Object selfObj) { - PTimeDelta self = TimeDeltaNodes.AsManagedTimeDeltaNode.executeUncached(selfObj); + TimeDeltaValue self = TemporalValueNodes.GetTimeDeltaValue.executeUncached(null, selfObj); var builder = new StringBuilder(); - builder.append(TypeNodes.GetTpNameNode.executeUncached(GetClassNode.executeUncached(self))); + builder.append(TypeNodes.GetTpNameNode.executeUncached(GetClassNode.executeUncached(selfObj))); builder.append("("); @@ -223,8 +225,9 @@ public abstract static class StrNode extends PythonUnaryBuiltinNode { @Specialization @TruffleBoundary - static TruffleString str(Object selfObj) { - PTimeDelta self = TimeDeltaNodes.AsManagedTimeDeltaNode.executeUncached(selfObj); + static TruffleString str(Object selfObj, + @Bind Node inliningTarget) { + TimeDeltaValue self = TemporalValueNodes.GetTimeDeltaValue.executeUncached(inliningTarget, selfObj); var builder = new StringBuilder(); // optional prefix with days, e.g. '1 day' or '5 days' @@ -269,8 +272,8 @@ static Object reduce(Object selfObj, @Bind Node inliningTarget, @Bind PythonLanguage language, @Cached GetClassNode getClassNode, - @Cached TimeDeltaNodes.AsManagedTimeDeltaNode asManagedTimeDeltaNode) { - PTimeDelta self = asManagedTimeDeltaNode.execute(inliningTarget, selfObj); + @Cached TemporalValueNodes.GetTimeDeltaValue readTimeDeltaValueNode) { + TimeDeltaValue self = readTimeDeltaValueNode.execute(inliningTarget, selfObj); Object type = getClassNode.execute(inliningTarget, selfObj); PTuple arguments = PFactory.createTuple(language, new Object[]{self.days, self.seconds, self.microseconds}); return PFactory.createTuple(language, new Object[]{type, arguments}); @@ -284,13 +287,13 @@ abstract static class RichCmpNode extends RichCmpBuiltinNode { @Specialization static Object richCmp(Object left, Object right, RichCmpOp op, @Bind Node inliningTarget, - @Cached TimeDeltaNodes.TimeDeltaCheckNode checkNode, - @Cached TimeDeltaNodes.AsManagedTimeDeltaNode asManagedTimeDeltaNode) { + @Cached PyDeltaCheckNode checkNode, + @Cached TemporalValueNodes.GetTimeDeltaValue readTimeDeltaValueNode) { if (!checkNode.execute(inliningTarget, left) || !checkNode.execute(inliningTarget, right)) { return PNotImplemented.NOT_IMPLEMENTED; } - PTimeDelta self = asManagedTimeDeltaNode.execute(inliningTarget, left); - PTimeDelta other = asManagedTimeDeltaNode.execute(inliningTarget, right); + TimeDeltaValue self = readTimeDeltaValueNode.execute(inliningTarget, left); + TimeDeltaValue other = readTimeDeltaValueNode.execute(inliningTarget, right); int result = self.compareTo(other); return op.compareResultToBool(result); } @@ -305,8 +308,8 @@ static long hash(VirtualFrame frame, Object selfObj, @Bind Node inliningTarget, @Bind PythonLanguage language, @Cached PyObjectHashNode hashNode, - @Cached TimeDeltaNodes.AsManagedTimeDeltaNode asManagedTimeDeltaNode) { - PTimeDelta self = asManagedTimeDeltaNode.execute(inliningTarget, selfObj); + @Cached TemporalValueNodes.GetTimeDeltaValue readTimeDeltaValueNode) { + TimeDeltaValue self = readTimeDeltaValueNode.execute(inliningTarget, selfObj); var content = new int[]{self.days, self.seconds, self.microseconds}; return hashNode.execute(frame, inliningTarget, PFactory.createTuple(language, content)); } @@ -321,13 +324,13 @@ abstract static class AddNode extends BinaryOpBuiltinNode { static Object add(Object left, Object right, @Bind Node inliningTarget, @Cached TimeDeltaNodes.NewNode newNode, - @Cached TimeDeltaNodes.TimeDeltaCheckNode checkNode, - @Cached TimeDeltaNodes.AsManagedTimeDeltaNode asManagedTimeDeltaNode) { + @Cached PyDeltaCheckNode checkNode, + @Cached TemporalValueNodes.GetTimeDeltaValue readTimeDeltaValueNode) { if (!checkNode.execute(inliningTarget, left) || !checkNode.execute(inliningTarget, right)) { return PNotImplemented.NOT_IMPLEMENTED; } - PTimeDelta self = asManagedTimeDeltaNode.execute(inliningTarget, left); - PTimeDelta other = asManagedTimeDeltaNode.execute(inliningTarget, right); + TimeDeltaValue self = readTimeDeltaValueNode.execute(inliningTarget, left); + TimeDeltaValue other = readTimeDeltaValueNode.execute(inliningTarget, right); return newNode.executeBuiltin(inliningTarget, self.days + other.days, self.seconds + other.seconds, self.microseconds + other.microseconds, 0, 0, 0, 0); } } @@ -341,13 +344,13 @@ abstract static class SubNode extends BinaryOpBuiltinNode { static Object sub(Object left, Object rigth, @Bind Node inliningTarget, @Cached TimeDeltaNodes.NewNode newNode, - @Cached TimeDeltaNodes.TimeDeltaCheckNode checkNode, - @Cached TimeDeltaNodes.AsManagedTimeDeltaNode asManagedTimeDeltaNode) { + @Cached PyDeltaCheckNode checkNode, + @Cached TemporalValueNodes.GetTimeDeltaValue readTimeDeltaValueNode) { if (!checkNode.execute(inliningTarget, left) || !checkNode.execute(inliningTarget, rigth)) { return PNotImplemented.NOT_IMPLEMENTED; } - PTimeDelta self = asManagedTimeDeltaNode.execute(inliningTarget, left); - PTimeDelta other = asManagedTimeDeltaNode.execute(inliningTarget, rigth); + TimeDeltaValue self = readTimeDeltaValueNode.execute(inliningTarget, left); + TimeDeltaValue other = readTimeDeltaValueNode.execute(inliningTarget, rigth); return newNode.executeBuiltin(inliningTarget, self.days - other.days, self.seconds - other.seconds, self.microseconds - other.microseconds, 0, 0, 0, 0); } } @@ -413,15 +416,15 @@ static Object mul(VirtualFrame frame, Object left, Object right, @Cached PyNumberAddNode addNode, @Cached PyNumberMultiplyNode multiplyNode, @Cached TimeDeltaNodes.NewNode newNode, - @Cached TimeDeltaNodes.TimeDeltaCheckNode checkNode, - @Cached TimeDeltaNodes.AsManagedTimeDeltaNode asManagedTimeDeltaNode) { - PTimeDelta date; + @Cached PyDeltaCheckNode checkNode, + @Cached TemporalValueNodes.GetTimeDeltaValue readTimeDeltaValueNode) { + TimeDeltaValue date; Object other; if (checkNode.execute(inliningTarget, left)) { - date = asManagedTimeDeltaNode.execute(inliningTarget, left); + date = readTimeDeltaValueNode.execute(inliningTarget, left); other = right; } else { - date = asManagedTimeDeltaNode.execute(inliningTarget, right); + date = readTimeDeltaValueNode.execute(inliningTarget, right); other = left; } if (longCheckNode.execute(inliningTarget, other)) { @@ -465,15 +468,15 @@ static Object div(VirtualFrame frame, Object left, Object right, @Cached PyNumberMultiplyNode multiplyNode, @Cached PyNumberTrueDivideNode trueDivideNode, @Cached TimeDeltaNodes.NewNode newNode, - @Cached TimeDeltaNodes.TimeDeltaCheckNode checkLeft, - @Cached TimeDeltaNodes.TimeDeltaCheckNode checkRight, - @Cached TimeDeltaNodes.AsManagedTimeDeltaNode asManagedTimeDeltaNode) { + @Cached PyDeltaCheckNode checkLeft, + @Cached PyDeltaCheckNode checkRight, + @Cached TemporalValueNodes.GetTimeDeltaValue readTimeDeltaValueNode) { if (!checkLeft.execute(inliningTarget, left)) { return PNotImplemented.NOT_IMPLEMENTED; } - PTimeDelta self = asManagedTimeDeltaNode.execute(inliningTarget, left); + TimeDeltaValue self = readTimeDeltaValueNode.execute(inliningTarget, left); if (checkRight.execute(inliningTarget, right)) { - PTimeDelta otherTimeDelta = asManagedTimeDeltaNode.execute(inliningTarget, right); + TimeDeltaValue otherTimeDelta = readTimeDeltaValueNode.execute(inliningTarget, right); Object microsecondsSelf = toMicroseconds(self, addNode, multiplyNode); Object microsecondsOther = toMicroseconds(otherTimeDelta, addNode, multiplyNode); return trueDivideNode.execute(frame, microsecondsSelf, microsecondsOther); @@ -513,15 +516,15 @@ static Object div(VirtualFrame frame, Object left, Object right, @Cached PyNumberAddNode addNode, @Cached PyNumberMultiplyNode multiplyNode, @Cached PyNumberFloorDivideNode floorDivideNode, - @Cached TimeDeltaNodes.TimeDeltaCheckNode checkLeft, - @Cached TimeDeltaNodes.TimeDeltaCheckNode checkRight, - @Cached TimeDeltaNodes.AsManagedTimeDeltaNode asManagedTimeDeltaNode) { + @Cached PyDeltaCheckNode checkLeft, + @Cached PyDeltaCheckNode checkRight, + @Cached TemporalValueNodes.GetTimeDeltaValue readTimeDeltaValueNode) { if (!checkLeft.execute(inliningTarget, left)) { return PNotImplemented.NOT_IMPLEMENTED; } - PTimeDelta self = asManagedTimeDeltaNode.execute(inliningTarget, left); + TimeDeltaValue self = readTimeDeltaValueNode.execute(inliningTarget, left); if (checkRight.execute(inliningTarget, right)) { - PTimeDelta otherTimeDelta = asManagedTimeDeltaNode.execute(inliningTarget, right); + TimeDeltaValue otherTimeDelta = readTimeDeltaValueNode.execute(inliningTarget, right); Object microsecondsSelf = toMicroseconds(self, addNode, multiplyNode); Object microsecondsOther = toMicroseconds(otherTimeDelta, addNode, multiplyNode); return floorDivideNode.execute(frame, microsecondsSelf, microsecondsOther); @@ -544,11 +547,11 @@ abstract static class DivModNode extends BinaryOpBuiltinNode { static Object divmod(Object left, Object right, @Bind Node inliningTarget, @Bind PythonLanguage language) { - if (!TimeDeltaNodes.TimeDeltaCheckNode.executeUncached(left) || !TimeDeltaNodes.TimeDeltaCheckNode.executeUncached(right)) { + if (!PyDeltaCheckNode.executeUncached(left) || !PyDeltaCheckNode.executeUncached(right)) { return PNotImplemented.NOT_IMPLEMENTED; } - PTimeDelta self = TimeDeltaNodes.AsManagedTimeDeltaNode.executeUncached(left); - PTimeDelta other = TimeDeltaNodes.AsManagedTimeDeltaNode.executeUncached(right); + TimeDeltaValue self = TemporalValueNodes.GetTimeDeltaValue.executeUncached(inliningTarget, left); + TimeDeltaValue other = TemporalValueNodes.GetTimeDeltaValue.executeUncached(inliningTarget, right); EncapsulatingNodeReference encapsulating = EncapsulatingNodeReference.getCurrent(); Node encapsulatingNode = encapsulating.set(inliningTarget); @@ -576,14 +579,14 @@ abstract static class ModNode extends BinaryOpBuiltinNode { @TruffleBoundary static Object mod(Object left, Object right, @Bind Node inliningTarget) { - if (!TimeDeltaNodes.TimeDeltaCheckNode.executeUncached(left) || !TimeDeltaNodes.TimeDeltaCheckNode.executeUncached(right)) { + if (!PyDeltaCheckNode.executeUncached(left) || !PyDeltaCheckNode.executeUncached(right)) { return PNotImplemented.NOT_IMPLEMENTED; } EncapsulatingNodeReference encapsulating = EncapsulatingNodeReference.getCurrent(); Node encapsulatingNode = encapsulating.set(inliningTarget); try { - PTimeDelta self = TimeDeltaNodes.AsManagedTimeDeltaNode.executeUncached(left); - PTimeDelta other = TimeDeltaNodes.AsManagedTimeDeltaNode.executeUncached(right); + TimeDeltaValue self = TemporalValueNodes.GetTimeDeltaValue.executeUncached(inliningTarget, left); + TimeDeltaValue other = TemporalValueNodes.GetTimeDeltaValue.executeUncached(inliningTarget, right); Object microsecondsSelf = toMicrosecondsUncached(self); Object microsecondsOther = toMicrosecondsUncached(other); Object remainder = PyNumberRemainderNode.getUncached().execute(null, microsecondsSelf, microsecondsOther); @@ -603,8 +606,8 @@ abstract static class AbsNode extends PythonUnaryBuiltinNode { static PTimeDelta abs(PTimeDelta selfObj, @Bind Node inliningTarget, @Cached TimeDeltaNodes.NewNode newNode, - @Cached TimeDeltaNodes.AsManagedTimeDeltaNode asManagedTimeDeltaNode) { - PTimeDelta self = asManagedTimeDeltaNode.execute(inliningTarget, selfObj); + @Cached TemporalValueNodes.GetTimeDeltaValue readTimeDeltaValueNode) { + TimeDeltaValue self = readTimeDeltaValueNode.execute(inliningTarget, selfObj); if (self.days >= 0) { return newNode.executeBuiltin(inliningTarget, self.days, self.seconds, self.microseconds, 0, 0, 0, 0); } else { @@ -621,8 +624,8 @@ abstract static class PosNode extends PythonUnaryBuiltinNode { static PTimeDelta pos(PTimeDelta selfObj, @Bind Node inliningTarget, @Cached TimeDeltaNodes.NewNode newNode, - @Cached TimeDeltaNodes.AsManagedTimeDeltaNode asManagedTimeDeltaNode) { - PTimeDelta self = asManagedTimeDeltaNode.execute(inliningTarget, selfObj); + @Cached TemporalValueNodes.GetTimeDeltaValue readTimeDeltaValueNode) { + TimeDeltaValue self = readTimeDeltaValueNode.execute(inliningTarget, selfObj); return newNode.executeBuiltin(inliningTarget, self.days, self.seconds, self.microseconds, 0, 0, 0, 0); } } @@ -635,8 +638,8 @@ abstract static class NegNode extends PythonUnaryBuiltinNode { static PTimeDelta neg(Object selfObj, @Bind Node inliningTarget, @Cached TimeDeltaNodes.NewNode newNode, - @Cached TimeDeltaNodes.AsManagedTimeDeltaNode asManagedTimeDeltaNode) { - PTimeDelta self = asManagedTimeDeltaNode.execute(inliningTarget, selfObj); + @Cached TemporalValueNodes.GetTimeDeltaValue readTimeDeltaValueNode) { + TimeDeltaValue self = readTimeDeltaValueNode.execute(inliningTarget, selfObj); return newNode.executeBuiltin(inliningTarget, -self.days, -self.seconds, -self.microseconds, 0, 0, 0, 0); } } @@ -653,7 +656,7 @@ static int getDays(PTimeDelta self) { @Specialization static int getDays(PythonAbstractNativeObject self, @Cached CStructAccess.ReadI32Node readNode) { - return TimeDeltaNodes.AsManagedTimeDeltaNode.getDays(self, readNode); + return TimeDeltaNodes.FromNative.getDays(self, readNode); } } @@ -669,7 +672,7 @@ static int getSeconds(PTimeDelta self) { @Specialization static int getSeconds(PythonAbstractNativeObject self, @Cached CStructAccess.ReadI32Node readNode) { - return TimeDeltaNodes.AsManagedTimeDeltaNode.getSeconds(self, readNode); + return TimeDeltaNodes.FromNative.getSeconds(self, readNode); } } @@ -685,7 +688,7 @@ static int getMicroseconds(PTimeDelta self) { @Specialization static int getMicroseconds(PythonAbstractNativeObject self, @Cached CStructAccess.ReadI32Node readNode) { - return TimeDeltaNodes.AsManagedTimeDeltaNode.getMicroseconds(self, readNode); + return TimeDeltaNodes.FromNative.getMicroseconds(self, readNode); } } @@ -696,24 +699,24 @@ abstract static class TotalSecondsNode extends PythonUnaryBuiltinNode { @Specialization static Object getTotalSeconds(Object selfObj, @Bind Node inliningTarget, - @Cached TimeDeltaNodes.AsManagedTimeDeltaNode asManagedTimeDeltaNode, + @Cached TemporalValueNodes.GetTimeDeltaValue readTimeDeltaValueNode, @Cached PyNumberAddNode addNode, @Cached PyNumberMultiplyNode multiplyNode, @Cached PyNumberTrueDivideNode trueDivideNode) { - PTimeDelta self = asManagedTimeDeltaNode.execute(inliningTarget, selfObj); + TimeDeltaValue self = readTimeDeltaValueNode.execute(inliningTarget, selfObj); Object microseconds = toMicroseconds(self, addNode, multiplyNode); return trueDivideNode.execute(null, microseconds, 1_000_000); } } - private static Object toMicroseconds(PTimeDelta timeDelta, PyNumberAddNode addNode, PyNumberMultiplyNode multiplyNode) { + private static Object toMicroseconds(TimeDeltaValue timeDelta, PyNumberAddNode addNode, PyNumberMultiplyNode multiplyNode) { Object x = multiplyNode.execute(null, timeDelta.days, 24 * 3600); x = addNode.execute(null, x, timeDelta.seconds); x = multiplyNode.execute(null, x, 1_000_000); return addNode.execute(null, x, timeDelta.microseconds); } - private static Object toMicrosecondsUncached(PTimeDelta timeDelta) { + private static Object toMicrosecondsUncached(TimeDeltaValue timeDelta) { return toMicroseconds(timeDelta, PyNumberAddNode.getUncached(), PyNumberMultiplyNode.getUncached()); } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/TimeDeltaNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/TimeDeltaNodes.java index 6dcd2390f9..4ee7d60e23 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/TimeDeltaNodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/TimeDeltaNodes.java @@ -45,7 +45,6 @@ import java.math.BigInteger; -import com.oracle.graal.python.PythonLanguage; import com.oracle.graal.python.builtins.PythonBuiltinClassType; import com.oracle.graal.python.builtins.objects.PNone; import com.oracle.graal.python.builtins.objects.cext.PythonAbstractNativeObject; @@ -64,13 +63,10 @@ import com.oracle.graal.python.lib.PyNumberMultiplyNode; import com.oracle.graal.python.nodes.ErrorMessages; import com.oracle.graal.python.nodes.PRaiseNode; -import com.oracle.graal.python.nodes.object.BuiltinClassProfiles; import com.oracle.graal.python.nodes.util.CastToJavaBigIntegerNode; import com.oracle.graal.python.runtime.PythonContext; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; -import com.oracle.truffle.api.dsl.Bind; import com.oracle.truffle.api.dsl.Cached; -import com.oracle.truffle.api.dsl.Fallback; import com.oracle.truffle.api.dsl.GenerateCached; import com.oracle.truffle.api.dsl.GenerateInline; import com.oracle.truffle.api.dsl.GenerateUncached; @@ -239,61 +235,7 @@ public BigInteger getTotalMicroseconds() { } } - @GenerateUncached - @GenerateInline - @GenerateCached(false) - public abstract static class TimeDeltaCheckNode extends Node { - public abstract boolean execute(Node inliningTarget, Object obj); - - public static boolean executeUncached(Object obj) { - return TimeDeltaNodesFactory.TimeDeltaCheckNodeGen.getUncached().execute(null, obj); - } - - @Specialization - static boolean doManaged(@SuppressWarnings("unused") PTimeDelta value) { - return true; - } - - @Specialization - static boolean doNative(Node inliningTarget, PythonAbstractNativeObject value, - @Cached BuiltinClassProfiles.IsBuiltinObjectProfile profile) { - return profile.profileObject(inliningTarget, value, PythonBuiltinClassType.PTimeDelta); - } - - @Fallback - static boolean doOther(@SuppressWarnings("unused") Object value) { - return false; - } - } - - @GenerateUncached - @GenerateInline - @GenerateCached(false) - public abstract static class AsManagedTimeDeltaNode extends Node { - public abstract PTimeDelta execute(Node inliningTarget, Object obj); - - public static PTimeDelta executeUncached(Object obj) { - return TimeDeltaNodesFactory.AsManagedTimeDeltaNodeGen.getUncached().execute(null, obj); - } - - @Specialization - static PTimeDelta doPTimeDelta(PTimeDelta value) { - return value; - } - - @Specialization(guards = "checkNode.execute(inliningTarget, nativeDelta)", limit = "1") - static PTimeDelta doNative(@SuppressWarnings("unused") Node inliningTarget, PythonAbstractNativeObject nativeDelta, - @Bind PythonLanguage language, - @SuppressWarnings("unused") @Cached TimeDeltaCheckNode checkNode, - @Cached CStructAccess.ReadI32Node readIntNode) { - int days = getDays(nativeDelta, readIntNode); - int seconds = getSeconds(nativeDelta, readIntNode); - int microseconds = getMicroseconds(nativeDelta, readIntNode); - - PythonBuiltinClassType cls = PythonBuiltinClassType.PTimeDelta; - return new PTimeDelta(cls, cls.getInstanceShape(language), days, seconds, microseconds); - } - + public static final class FromNative { static int getDays(PythonAbstractNativeObject self, CStructAccess.ReadI32Node readNode) { return readNode.readFromObj(self, CFields.PyDateTime_Delta__days); } @@ -305,11 +247,5 @@ static int getSeconds(PythonAbstractNativeObject self, CStructAccess.ReadI32Node static int getMicroseconds(PythonAbstractNativeObject self, CStructAccess.ReadI32Node readNode) { return readNode.readFromObj(self, CFields.PyDateTime_Delta__microseconds); } - - @Fallback - static PTimeDelta error(Object obj, - @Bind Node inliningTarget) { - throw PRaiseNode.raiseStatic(inliningTarget, TypeError, ErrorMessages.S_EXPECTED_GOT_P, "timedelta", obj); - } } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/TimeNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/TimeNodes.java index e68f2781e5..c79fb86e0c 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/TimeNodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/TimeNodes.java @@ -44,7 +44,6 @@ import static com.oracle.graal.python.builtins.PythonBuiltinClassType.ValueError; import static com.oracle.graal.python.util.PythonUtils.tsLiteral; -import com.oracle.graal.python.PythonLanguage; import com.oracle.graal.python.builtins.PythonBuiltinClassType; import com.oracle.graal.python.builtins.objects.PNone; import com.oracle.graal.python.builtins.objects.cext.PythonAbstractNativeObject; @@ -57,15 +56,14 @@ import com.oracle.graal.python.builtins.objects.function.PKeyword; import com.oracle.graal.python.builtins.objects.type.TypeNodes; import com.oracle.graal.python.builtins.objects.type.TypeNodes.GetInstanceShape; +import com.oracle.graal.python.lib.PyTZInfoCheckNode; import com.oracle.graal.python.lib.PyLongAsIntNode; import com.oracle.graal.python.nodes.ErrorMessages; import com.oracle.graal.python.nodes.PGuards; import com.oracle.graal.python.nodes.PRaiseNode; import com.oracle.graal.python.nodes.call.CallNode; -import com.oracle.graal.python.nodes.object.BuiltinClassProfiles; import com.oracle.graal.python.runtime.PythonContext; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; -import com.oracle.truffle.api.dsl.Bind; import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.Fallback; import com.oracle.truffle.api.dsl.GenerateCached; @@ -185,7 +183,7 @@ private static void validateTimeComponents(Node inliningTarget, long hour, long throw PRaiseNode.raiseStatic(inliningTarget, ValueError, ErrorMessages.MICROSECOND_MUST_BE_IN); } - if (tzInfo != null && !TzInfoNodes.TzInfoCheckNode.executeUncached(tzInfo)) { + if (tzInfo != null && !PyTZInfoCheckNode.executeUncached(tzInfo)) { throw PRaiseNode.raiseStatic(inliningTarget, TypeError, ErrorMessages.TZINFO_ARGUMENT_MUST_BE_NONE_OR_OF_A_TZINFO_SUBCLASS_NOT_TYPE_P, tzInfo); } @@ -230,39 +228,7 @@ static boolean isBuiltinClass(Object cls) { } } - @GenerateUncached - @GenerateInline - @GenerateCached(false) - public abstract static class AsManagedTimeNode extends Node { - public abstract PTime execute(Node inliningTarget, Object obj); - - public static PTime executeUncached(Object obj) { - return TimeNodesFactory.AsManagedTimeNodeGen.getUncached().execute(null, obj); - } - - @Specialization - static PTime doPTime(PTime value) { - return value; - } - - @Specialization(guards = "checkNode.execute(inliningTarget, nativeTime)", limit = "1") - static PTime doNative(@SuppressWarnings("unused") Node inliningTarget, PythonAbstractNativeObject nativeTime, - @Bind PythonLanguage language, - @SuppressWarnings("unused") @Cached TimeCheckNode checkNode, - @Cached CStructAccess.ReadByteNode readByteNode, - @Cached CStructAccess.ReadObjectNode readObjectNode) { - int hour = getHour(nativeTime, readByteNode); - int minute = getMinute(nativeTime, readByteNode); - int second = getSecond(nativeTime, readByteNode); - int microsecond = getMicrosecond(nativeTime, readByteNode); - - Object tzinfo = getTzInfo(nativeTime, readByteNode, readObjectNode); - int fold = getFold(nativeTime, readByteNode); - - PythonBuiltinClassType cls = PythonBuiltinClassType.PTime; - return new PTime(cls, cls.getInstanceShape(language), hour, minute, second, microsecond, tzinfo, fold); - } - + public static final class FromNative { static int getHour(PythonAbstractNativeObject self, CStructAccess.ReadByteNode readNode) { return readNode.readFromObjUnsigned(self, CFields.PyDateTime_Time__data, 0); } @@ -282,7 +248,7 @@ static int getMicrosecond(PythonAbstractNativeObject self, CStructAccess.ReadByt return (b3 << 16) | (b4 << 8) | b5; } - private static Object getTzInfo(PythonAbstractNativeObject nativeTime, CStructAccess.ReadByteNode readByteNode, CStructAccess.ReadObjectNode readObjectNode) { + static Object getTzInfo(PythonAbstractNativeObject nativeTime, CStructAccess.ReadByteNode readByteNode, CStructAccess.ReadObjectNode readObjectNode) { Object tzinfo = null; if (readByteNode.readFromObj(nativeTime, CFields.PyDateTime_Time__hastzinfo) != 0) { Object tzinfoObj = readObjectNode.readFromObj(nativeTime, CFields.PyDateTime_Time__tzinfo); @@ -296,39 +262,6 @@ private static Object getTzInfo(PythonAbstractNativeObject nativeTime, CStructAc static int getFold(PythonAbstractNativeObject self, CStructAccess.ReadByteNode readNode) { return readNode.readFromObjUnsigned(self, CFields.PyDateTime_Time__fold); } - - @Fallback - static PTime error(Object obj, - @Bind Node inliningTarget) { - throw PRaiseNode.raiseStatic(inliningTarget, TypeError, ErrorMessages.S_EXPECTED_GOT_P, "time", obj); - } - } - - @GenerateUncached - @GenerateInline - @GenerateCached(false) - public abstract static class TimeCheckNode extends Node { - public abstract boolean execute(Node inliningTarget, Object obj); - - public static boolean executeUncached(Object obj) { - return TimeNodesFactory.TimeCheckNodeGen.getUncached().execute(null, obj); - } - - @Specialization - static boolean doManaged(@SuppressWarnings("unused") PTime value) { - return true; - } - - @Specialization - static boolean doNative(Node inliningTarget, PythonAbstractNativeObject value, - @Cached BuiltinClassProfiles.IsBuiltinObjectProfile profile) { - return profile.profileObject(inliningTarget, value, PythonBuiltinClassType.PTime); - } - - @Fallback - static boolean doOther(@SuppressWarnings("unused") Object value) { - return false; - } } @GenerateInline @@ -337,6 +270,10 @@ static boolean doOther(@SuppressWarnings("unused") Object value) { abstract static class TzInfoNode extends Node { public abstract Object execute(Node inliningTarget, Object obj); + public static Object executeUncached(Node inliningTarget, Object obj) { + return TimeNodesFactory.TzInfoNodeGen.getUncached().execute(inliningTarget, obj); + } + @Specialization static Object getTzInfo(PTime self) { return self.tzInfo; @@ -347,7 +284,14 @@ static Object getTzInfo(PTime self) { static Object getTzInfo(PythonAbstractNativeObject self, @Cached CStructAccess.ReadByteNode readByteNode, @Cached CStructAccess.ReadObjectNode readObjectNode) { - return AsManagedTimeNode.getTzInfo(self, readByteNode, readObjectNode); + return FromNative.getTzInfo(self, readByteNode, readObjectNode); + } + + @Specialization + static Object getTzInfo(Node inliningTarget, Object self, + @Cached TemporalValueNodes.GetTimeValue readTimeValueNode) { + TemporalValueNodes.TimeValue timeValue = readTimeValueNode.execute(inliningTarget, self); + return TemporalValueNodes.toPythonTzInfo(timeValue.tzInfo, timeValue.zoneId, inliningTarget); } } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/TimeZoneBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/TimeZoneBuiltins.java index d0ff0aa76d..835c58cccb 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/TimeZoneBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/TimeZoneBuiltins.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -71,6 +71,7 @@ import com.oracle.graal.python.builtins.objects.type.TpSlots; import com.oracle.graal.python.builtins.objects.type.slots.TpSlotHashFun; import com.oracle.graal.python.builtins.objects.type.slots.TpSlotRichCompare.RichCmpBuiltinNode; +import com.oracle.graal.python.lib.PyDateTimeCheckNode; import com.oracle.graal.python.lib.PyObjectCallMethodObjArgs; import com.oracle.graal.python.lib.PyObjectHashNode; import com.oracle.graal.python.lib.PyObjectReprAsObjectNode; @@ -247,25 +248,18 @@ static long hash(VirtualFrame frame, PTimeZone self, @GenerateNodeFactory public abstract static class UtcOffsetNode extends PythonBinaryBuiltinNode { - @Specialization - static PTimeDelta utcOffset(PTimeZone self, PDateTime dt) { - return self.offset; - } - @Specialization(guards = {"isNone(dt)"}) static PTimeDelta utcOffsetForNone(PTimeZone self, PNone dt) { return self.offset; } - @Fallback - static void doGeneric(Object self, Object dt, + @Specialization(guards = {"!isNone(dt)"}) + static PTimeDelta utcOffset(PTimeZone self, Object dt, @Bind Node inliningTarget, + @Cached PyDateTimeCheckNode dateTimeCheckNode, @Cached PRaiseNode raiseNode) { - throw raiseNode.raise(inliningTarget, TypeError, - ErrorMessages.S_ARGUMENT_MUST_BE_A_S_INSTANCE_OR_NONE_NOT_P, - "utcoffset(dt)", - "datetime", - dt); + validateDateTimeOrNone(dt, "utcoffset(dt)", inliningTarget, dateTimeCheckNode, raiseNode); + return self.offset; } } @@ -273,26 +267,18 @@ static void doGeneric(Object self, Object dt, @GenerateNodeFactory public abstract static class DstNode extends PythonBinaryBuiltinNode { - @Specialization - static Object dst(PTimeZone self, PDateTime dt) { - return PNone.NONE; - } - @Specialization(guards = {"isNone(dt)"}) static Object dst(PTimeZone self, PNone dt) { return PNone.NONE; } - @Fallback - static void doGeneric(Object self, Object dt, + @Specialization(guards = {"!isNone(dt)"}) + static Object dst(PTimeZone self, Object dt, @Bind Node inliningTarget, + @Cached PyDateTimeCheckNode dateTimeCheckNode, @Cached PRaiseNode raiseNode) { - throw raiseNode.raise(inliningTarget, - TypeError, - ErrorMessages.S_ARGUMENT_MUST_BE_A_S_INSTANCE_OR_NONE_NOT_P, - "dst(dt)", - "datetime", - dt); + validateDateTimeOrNone(dt, "dst(dt)", inliningTarget, dateTimeCheckNode, raiseNode); + return PNone.NONE; } } @@ -300,26 +286,18 @@ static void doGeneric(Object self, Object dt, @GenerateNodeFactory public abstract static class TzNameNode extends PythonBinaryBuiltinNode { - @Specialization - static TruffleString tzName(PTimeZone self, PDateTime dt) { - return getTzName(self); - } - @Specialization(guards = {"isNone(dt)"}) static TruffleString tzName(PTimeZone self, PNone dt) { return getTzName(self); } - @Fallback - static void doGeneric(Object self, Object dt, + @Specialization(guards = {"!isNone(dt)"}) + static TruffleString tzName(PTimeZone self, Object dt, @Bind Node inliningTarget, + @Cached PyDateTimeCheckNode dateTimeCheckNode, @Cached PRaiseNode raiseNode) { - throw raiseNode.raise(inliningTarget, - TypeError, - ErrorMessages.S_ARGUMENT_MUST_BE_A_S_INSTANCE_OR_NONE_NOT_P, - "tzname(dt)", - "datetime", - dt); + validateDateTimeOrNone(dt, "tzname(dt)", inliningTarget, dateTimeCheckNode, raiseNode); + return getTzName(self); } } @@ -350,7 +328,7 @@ public abstract static class FromUtcNode extends PythonBinaryBuiltinNode { @Specialization static Object fromUtc(PTimeZone self, Object dateTime, @Bind Node inliningTarget, - @Cached DateTimeNodes.DateTimeCheckNode dateTimeCheckNode, + @Cached PyDateTimeCheckNode dateTimeCheckNode, @Cached DateTimeNodes.TzInfoNode tzInfoNode, @Cached PRaiseNode raiseNode, @Cached DateTimeNodes.SubclassNewNode dateTimeSubclassNewNode) { @@ -400,4 +378,15 @@ static TruffleString getTzName(PTimeZone timezone) { return TruffleString.FromJavaStringNode.getUncached().execute(builder.toString(), TS_ENCODING); } + + private static void validateDateTimeOrNone(Object dt, String methodName, Node inliningTarget, PyDateTimeCheckNode dateTimeCheckNode, PRaiseNode raiseNode) { + if (!dateTimeCheckNode.execute(inliningTarget, dt)) { + throw raiseNode.raise(inliningTarget, + TypeError, + ErrorMessages.S_ARGUMENT_MUST_BE_A_S_INSTANCE_OR_NONE_NOT_P, + methodName, + "datetime", + dt); + } + } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/TimeZoneNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/TimeZoneNodes.java index 792e4de29b..dd9c59d40f 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/TimeZoneNodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/TimeZoneNodes.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -42,13 +42,18 @@ import static com.oracle.graal.python.builtins.PythonBuiltinClassType.TypeError; +import com.oracle.graal.python.PythonLanguage; +import com.oracle.graal.python.builtins.PythonBuiltinClassType; +import com.oracle.graal.python.builtins.modules.datetime.TemporalValueNodes.TimeDeltaValue; import com.oracle.graal.python.builtins.objects.PNone; import com.oracle.graal.python.builtins.objects.type.TypeNodes; +import com.oracle.graal.python.lib.PyDeltaCheckNode; import com.oracle.graal.python.nodes.ErrorMessages; import com.oracle.graal.python.nodes.PRaiseNode; import com.oracle.graal.python.nodes.util.CannotCastException; import com.oracle.graal.python.nodes.util.CastToTruffleStringNode; import com.oracle.graal.python.runtime.PythonContext; +import com.oracle.truffle.api.dsl.Bind; import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.GenerateCached; import com.oracle.truffle.api.dsl.GenerateInline; @@ -72,8 +77,8 @@ public static NewNode getUncached() { @Specialization static PTimeZone newTimezone(Node inliningTarget, PythonContext context, Object cls, Object offsetObj, Object nameObject, - @Cached TimeDeltaNodes.TimeDeltaCheckNode timeDeltaCheckNode, - @Cached TimeDeltaNodes.AsManagedTimeDeltaNode asManagedTimeDeltaNode, + @Bind PythonLanguage language, + @Cached PyDeltaCheckNode timeDeltaCheckNode, @Cached CastToTruffleStringNode castToTruffleStringNode, @Cached PRaiseNode raiseNode, @Cached TypeNodes.GetInstanceShape getInstanceShape) { @@ -86,7 +91,14 @@ static PTimeZone newTimezone(Node inliningTarget, PythonContext context, Object "datetime.timedelta", offsetObj); } - PTimeDelta offset = asManagedTimeDeltaNode.execute(inliningTarget, offsetObj); + PTimeDelta offset; + if (offsetObj instanceof PTimeDelta value) { + offset = value; + } else { + TimeDeltaValue offsetValue = TemporalValueNodes.GetTimeDeltaValue.executeUncached(inliningTarget, offsetObj); + PythonBuiltinClassType tdcls = PythonBuiltinClassType.PTimeDelta; + offset = new PTimeDelta(tdcls, tdcls.getInstanceShape(language), offsetValue.days, offsetValue.seconds, offsetValue.microseconds); + } final TruffleString name; if (nameObject == PNone.NO_VALUE) { name = null; diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/TzInfoBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/TzInfoBuiltins.java index 08ebb60de0..b4cb72b416 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/TzInfoBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/TzInfoBuiltins.java @@ -66,6 +66,7 @@ import com.oracle.graal.python.builtins.objects.type.TpSlots; import com.oracle.graal.python.builtins.objects.type.TypeNodes; import com.oracle.graal.python.builtins.objects.type.TypeNodes.GetInstanceShape; +import com.oracle.graal.python.lib.PyDateTimeCheckNode; import com.oracle.graal.python.lib.PyObjectCallMethodObjArgs; import com.oracle.graal.python.lib.PyObjectGetStateNode; import com.oracle.graal.python.lib.PyObjectLookupAttr; @@ -174,7 +175,7 @@ public abstract static class FromUtcNode extends PythonBinaryBuiltinNode { @Specialization static Object fromUtc(VirtualFrame frame, Object self, Object dateTime, @Bind Node inliningTarget, - @Cached DateTimeNodes.DateTimeCheckNode dateTimeCheckNode, + @Cached PyDateTimeCheckNode dateTimeCheckNode, @Cached DateTimeNodes.TzInfoNode tzInfoNode, @Cached PyObjectCallMethodObjArgs callMethodObjArgs, @Cached PRaiseNode raiseNode, diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/foreign/ForeignTimeZoneBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/foreign/ForeignTimeZoneBuiltins.java new file mode 100644 index 0000000000..7ab72d1417 --- /dev/null +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/foreign/ForeignTimeZoneBuiltins.java @@ -0,0 +1,261 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.graal.python.builtins.objects.foreign; + +import static com.oracle.graal.python.nodes.SpecialMethodNames.J___REDUCE__; +import static com.oracle.graal.python.util.PythonUtils.toTruffleStringUncached; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.Locale; + +import com.oracle.graal.python.annotations.Builtin; +import com.oracle.graal.python.annotations.Slot; +import com.oracle.graal.python.annotations.Slot.SlotKind; +import com.oracle.graal.python.builtins.CoreFunctions; +import com.oracle.graal.python.builtins.PythonBuiltinClassType; +import com.oracle.graal.python.builtins.PythonBuiltins; +import com.oracle.graal.python.builtins.modules.datetime.DateTimeNodes; +import com.oracle.graal.python.builtins.modules.datetime.DatetimeModuleBuiltins; +import com.oracle.graal.python.builtins.modules.datetime.TemporalValueNodes; +import com.oracle.graal.python.builtins.modules.datetime.TimeDeltaNodes; +import com.oracle.graal.python.builtins.modules.datetime.TemporalValueNodes.DateTimeValue; +import com.oracle.graal.python.builtins.objects.PNone; +import com.oracle.graal.python.builtins.objects.type.TpSlots; +import com.oracle.graal.python.builtins.objects.type.TypeNodes; +import com.oracle.graal.python.lib.PyDateTimeCheckNode; +import com.oracle.graal.python.nodes.ErrorMessages; +import com.oracle.graal.python.nodes.PRaiseNode; +import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode; +import com.oracle.graal.python.nodes.function.builtins.PythonBinaryBuiltinNode; +import com.oracle.graal.python.nodes.function.builtins.PythonUnaryBuiltinNode; +import com.oracle.graal.python.nodes.object.GetClassNode; +import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.dsl.Bind; +import com.oracle.truffle.api.dsl.GenerateNodeFactory; +import com.oracle.truffle.api.dsl.NodeFactory; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.interop.UnsupportedMessageException; +import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.api.strings.TruffleString; + +@CoreFunctions(extendClasses = PythonBuiltinClassType.ForeignTimeZone) +public final class ForeignTimeZoneBuiltins extends PythonBuiltins { + public static final TpSlots SLOTS = ForeignTimeZoneBuiltinsSlotsGen.SLOTS; + + @Override + protected List> getNodeFactories() { + return ForeignTimeZoneBuiltinsFactory.getFactories(); + } + + @Slot(value = SlotKind.tp_repr, isComplex = true) + @GenerateNodeFactory + abstract static class ReprNode extends PythonUnaryBuiltinNode { + @Specialization + @TruffleBoundary + static TruffleString repr(Object self, + @Bind Node inliningTarget) { + ZoneId zoneId = asZoneId(self); + TruffleString typeName = TypeNodes.GetTpNameNode.executeUncached(GetClassNode.executeUncached(self)); + String value = String.format("%s('%s')", typeName, zoneId.getId()); + return toTruffleStringUncached(value); + } + } + + @Slot(value = SlotKind.tp_str, isComplex = true) + @GenerateNodeFactory + abstract static class StrNode extends PythonUnaryBuiltinNode { + @Specialization + @TruffleBoundary + static TruffleString str(Object self) { + return toTruffleStringUncached(asZoneId(self).getId()); + } + } + + @Builtin(name = "utcoffset", minNumOfPositionalArgs = 1, parameterNames = {"$self", "dt"}) + @GenerateNodeFactory + abstract static class UtcOffsetNode extends PythonBinaryBuiltinNode { + @Specialization + @TruffleBoundary + static Object utcoffset(Object self, Object dateTime, + @Bind Node inliningTarget) { + ZoneId zoneId = asZoneId(self); + if (dateTime == PNone.NONE) { + Object fixed = TemporalValueNodes.toFixedOffsetTimeZone(zoneId, inliningTarget); + if (fixed == null) { + return PNone.NONE; + } + return DatetimeModuleBuiltins.callUtcOffset(fixed, PNone.NONE, inliningTarget); + } + if (!PyDateTimeCheckNode.executeUncached(dateTime)) { + throw PRaiseNode.raiseStatic(inliningTarget, PythonBuiltinClassType.TypeError, ErrorMessages.FROMUTC_ARGUMENT_MUST_BE_A_DATETIME); + } + ZonedDateTime zonedDateTime = resolveDateTimeAtZone(TemporalValueNodes.GetDateTimeValue.executeUncached(inliningTarget, dateTime), zoneId); + return TimeDeltaNodes.NewNode.getUncached().execute(inliningTarget, PythonBuiltinClassType.PTimeDelta, 0, zonedDateTime.getOffset().getTotalSeconds(), 0, 0, 0, 0, 0); + } + } + + @Builtin(name = "dst", minNumOfPositionalArgs = 1, parameterNames = {"$self", "dt"}) + @GenerateNodeFactory + abstract static class DstNode extends PythonBinaryBuiltinNode { + @Specialization + @TruffleBoundary + static Object dst(Object self, Object dateTime, + @Bind Node inliningTarget) { + ZoneId zoneId = asZoneId(self); + if (dateTime == PNone.NONE) { + return PNone.NONE; + } + if (!PyDateTimeCheckNode.executeUncached(dateTime)) { + throw PRaiseNode.raiseStatic(inliningTarget, PythonBuiltinClassType.TypeError, ErrorMessages.FROMUTC_ARGUMENT_MUST_BE_A_DATETIME); + } + ZonedDateTime zonedDateTime = resolveDateTimeAtZone(TemporalValueNodes.GetDateTimeValue.executeUncached(inliningTarget, dateTime), zoneId); + int dstSeconds = (int) zoneId.getRules().getDaylightSavings(zonedDateTime.toInstant()).getSeconds(); + return TimeDeltaNodes.NewNode.getUncached().execute(inliningTarget, PythonBuiltinClassType.PTimeDelta, 0, dstSeconds, 0, 0, 0, 0, 0); + } + } + + @Builtin(name = "tzname", minNumOfPositionalArgs = 1, parameterNames = {"$self", "dt"}) + @GenerateNodeFactory + abstract static class TzNameNode extends PythonBinaryBuiltinNode { + @Specialization + @TruffleBoundary + static Object tzname(Object self, Object dateTime, + @Bind Node inliningTarget) { + ZoneId zoneId = asZoneId(self); + if (dateTime == PNone.NONE) { + return zoneId.getRules().isFixedOffset() ? toTruffleStringUncached(zoneId.getId()) : PNone.NONE; + } + if (!PyDateTimeCheckNode.executeUncached(dateTime)) { + throw PRaiseNode.raiseStatic(inliningTarget, PythonBuiltinClassType.TypeError, ErrorMessages.FROMUTC_ARGUMENT_MUST_BE_A_DATETIME); + } + String name = DateTimeFormatter.ofPattern("z", Locale.ENGLISH).format(resolveDateTimeAtZone(TemporalValueNodes.GetDateTimeValue.executeUncached(inliningTarget, dateTime), zoneId)); + return toTruffleStringUncached(name); + } + } + + @Builtin(name = "fromutc", minNumOfPositionalArgs = 1, parameterNames = {"$self", "dt"}) + @GenerateNodeFactory + abstract static class FromUtcNode extends PythonBinaryBuiltinNode { + @Specialization + @TruffleBoundary + static Object fromutc(Object self, Object dateTime, + @Bind Node inliningTarget) { + if (!PyDateTimeCheckNode.executeUncached(dateTime)) { + throw PRaiseNode.raiseStatic(inliningTarget, PythonBuiltinClassType.TypeError, ErrorMessages.FROMUTC_ARGUMENT_MUST_BE_A_DATETIME); + } + DateTimeValue asDateTime = TemporalValueNodes.GetDateTimeValue.executeUncached(inliningTarget, dateTime); + if (!hasMatchingTimeZone(asDateTime, self)) { + throw PRaiseNode.raiseStatic(inliningTarget, PythonBuiltinClassType.ValueError, ErrorMessages.FROMUTC_DT_TZINFO_IS_NOT_SELF); + } + ZoneId zoneId = asZoneId(self); + LocalDateTime utcDateTime = asDateTime.toLocalDateTime(); + ZonedDateTime zonedDateTime = utcDateTime.atOffset(java.time.ZoneOffset.UTC).atZoneSameInstant(zoneId); + int fold = getFold(zonedDateTime); + return DateTimeNodes.NewUnsafeNode.getUncached().execute(inliningTarget, PythonBuiltinClassType.PDateTime, zonedDateTime.getYear(), zonedDateTime.getMonthValue(), + zonedDateTime.getDayOfMonth(), zonedDateTime.getHour(), zonedDateTime.getMinute(), zonedDateTime.getSecond(), zonedDateTime.getNano() / 1_000, self, fold); + } + } + + @Builtin(name = J___REDUCE__, minNumOfPositionalArgs = 1) + @GenerateNodeFactory + abstract static class ReduceNode extends PythonUnaryBuiltinNode { + @Specialization + @TruffleBoundary + static Object reduce(Object self) { + return toTruffleStringUncached(asZoneId(self).getId()); + } + } + + private static ZoneId asZoneId(Object self) { + try { + return InteropLibrary.getUncached(self).asTimeZone(self); + } catch (UnsupportedMessageException e) { + throw CompilerDirectives.shouldNotReachHere(e); + } + } + + private static boolean hasMatchingTimeZone(DateTimeValue dateTime, Object self) { + if (dateTime.tzInfo == self) { + return true; + } + if (dateTime.zoneId != null) { + return asZoneId(self).equals(dateTime.zoneId); + } + if (dateTime.tzInfo == null) { + return false; + } + InteropLibrary interop = InteropLibrary.getUncached(dateTime.tzInfo); + if (!interop.isTimeZone(dateTime.tzInfo)) { + return false; + } + try { + return asZoneId(self).equals(interop.asTimeZone(dateTime.tzInfo)); + } catch (UnsupportedMessageException e) { + throw CompilerDirectives.shouldNotReachHere(e); + } + } + + private static int getFold(ZonedDateTime zonedDateTime) { + List validOffsets = zonedDateTime.getZone().getRules().getValidOffsets(zonedDateTime.toLocalDateTime()); + if (validOffsets.size() < 2) { + return 0; + } + return zonedDateTime.getOffset().equals(validOffsets.get(validOffsets.size() - 1)) ? 1 : 0; + } + + @TruffleBoundary + private static ZonedDateTime resolveDateTimeAtZone(DateTimeValue dateTime, ZoneId zoneId) { + LocalDateTime localDateTime = dateTime.toLocalDateTime(); + ZonedDateTime zonedDateTime = localDateTime.atZone(zoneId); + List validOffsets = zoneId.getRules().getValidOffsets(localDateTime); + if (validOffsets.size() < 2) { + return zonedDateTime; + } + return dateTime.fold == 1 ? zonedDateTime.withLaterOffsetAtOverlap() : zonedDateTime.withEarlierOffsetAtOverlap(); + } +} diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyDateCheckNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyDateCheckNode.java new file mode 100644 index 0000000000..04943d47e1 --- /dev/null +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyDateCheckNode.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.graal.python.lib; + +import com.oracle.graal.python.builtins.modules.datetime.PDate; +import com.oracle.graal.python.builtins.objects.cext.PythonAbstractNativeObject; +import com.oracle.graal.python.nodes.object.BuiltinClassProfiles.IsBuiltinObjectProfile; +import com.oracle.graal.python.nodes.object.IsForeignObjectNode; +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.Fallback; +import com.oracle.truffle.api.dsl.GenerateCached; +import com.oracle.truffle.api.dsl.GenerateInline; +import com.oracle.truffle.api.dsl.GenerateUncached; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.library.CachedLibrary; +import com.oracle.truffle.api.nodes.Node; + +/** Equivalent of CPython's {@code PyDate_Check}. */ +@GenerateUncached +@GenerateInline +@GenerateCached(false) +public abstract class PyDateCheckNode extends Node { + public static boolean executeUncached(Object object) { + return PyDateCheckNodeGen.getUncached().execute(null, object); + } + + public abstract boolean execute(Node inliningTarget, Object object); + + @Specialization + static boolean doManaged(@SuppressWarnings("unused") PDate value) { + return true; + } + + @Specialization + static boolean doNative(Node inliningTarget, PythonAbstractNativeObject value, + @Cached IsBuiltinObjectProfile profile) { + return profile.profileObject(inliningTarget, value, com.oracle.graal.python.builtins.PythonBuiltinClassType.PDate); + } + + @Specialization(guards = "isForeignObjectNode.execute(inliningTarget, value)", limit = "1") + static boolean doForeign(Node inliningTarget, Object value, + @SuppressWarnings("unused") @Cached IsForeignObjectNode isForeignObjectNode, + @CachedLibrary("value") InteropLibrary interop) { + return interop.isDate(value); + } + + @Fallback + static boolean doOther(@SuppressWarnings("unused") Object value) { + return false; + } +} diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyDateTimeCheckNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyDateTimeCheckNode.java new file mode 100644 index 0000000000..1874d96dc0 --- /dev/null +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyDateTimeCheckNode.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.graal.python.lib; + +import com.oracle.graal.python.builtins.modules.datetime.PDateTime; +import com.oracle.graal.python.builtins.objects.cext.PythonAbstractNativeObject; +import com.oracle.graal.python.nodes.object.BuiltinClassProfiles.IsBuiltinObjectProfile; +import com.oracle.graal.python.nodes.object.IsForeignObjectNode; +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.Fallback; +import com.oracle.truffle.api.dsl.GenerateCached; +import com.oracle.truffle.api.dsl.GenerateInline; +import com.oracle.truffle.api.dsl.GenerateUncached; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.library.CachedLibrary; +import com.oracle.truffle.api.nodes.Node; + +/** Equivalent of CPython's {@code PyDateTime_Check}. */ +@GenerateUncached +@GenerateInline +@GenerateCached(false) +public abstract class PyDateTimeCheckNode extends Node { + public static boolean executeUncached(Object object) { + return PyDateTimeCheckNodeGen.getUncached().execute(null, object); + } + + public abstract boolean execute(Node inliningTarget, Object object); + + @Specialization + static boolean doManaged(@SuppressWarnings("unused") PDateTime value) { + return true; + } + + @Specialization + static boolean doNative(Node inliningTarget, PythonAbstractNativeObject value, + @Cached IsBuiltinObjectProfile profile) { + return profile.profileObject(inliningTarget, value, com.oracle.graal.python.builtins.PythonBuiltinClassType.PDateTime); + } + + @Specialization(guards = "isForeignObjectNode.execute(inliningTarget, value)", limit = "1") + static boolean doForeign(Node inliningTarget, Object value, + @SuppressWarnings("unused") @Cached IsForeignObjectNode isForeignObjectNode, + @CachedLibrary("value") InteropLibrary interop) { + return interop.isDate(value) && interop.isTime(value); + } + + @Fallback + static boolean doOther(@SuppressWarnings("unused") Object value) { + return false; + } +} diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/TzInfoNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyDeltaCheckNode.java similarity index 71% rename from graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/TzInfoNodes.java rename to graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyDeltaCheckNode.java index fbed81342d..9a03066472 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/datetime/TzInfoNodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyDeltaCheckNode.java @@ -38,11 +38,12 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -package com.oracle.graal.python.builtins.modules.datetime; +package com.oracle.graal.python.lib; import com.oracle.graal.python.builtins.PythonBuiltinClassType; +import com.oracle.graal.python.builtins.modules.datetime.PTimeDelta; import com.oracle.graal.python.builtins.objects.cext.PythonAbstractNativeObject; -import com.oracle.graal.python.nodes.object.BuiltinClassProfiles; +import com.oracle.graal.python.nodes.object.BuiltinClassProfiles.IsBuiltinObjectProfile; import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.Fallback; import com.oracle.truffle.api.dsl.GenerateCached; @@ -51,31 +52,30 @@ import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.nodes.Node; -public class TzInfoNodes { - @GenerateUncached - @GenerateInline - @GenerateCached(false) - public abstract static class TzInfoCheckNode extends Node { - public abstract boolean execute(Node inliningTarget, Object obj); +/** Equivalent of CPython's {@code PyDelta_Check}. */ +@GenerateUncached +@GenerateInline +@GenerateCached(false) +public abstract class PyDeltaCheckNode extends Node { + public static boolean executeUncached(Object object) { + return PyDeltaCheckNodeGen.getUncached().execute(null, object); + } - public static boolean executeUncached(Object obj) { - return TzInfoNodesFactory.TzInfoCheckNodeGen.getUncached().execute(null, obj); - } + public abstract boolean execute(Node inliningTarget, Object object); - @Specialization - static boolean doManaged(@SuppressWarnings("unused") PTzInfo value) { - return true; - } + @Specialization + static boolean doManaged(@SuppressWarnings("unused") PTimeDelta value) { + return true; + } - @Specialization - static boolean doNative(Node inliningTarget, PythonAbstractNativeObject value, - @Cached BuiltinClassProfiles.IsBuiltinObjectProfile profile) { - return profile.profileObject(inliningTarget, value, PythonBuiltinClassType.PTzInfo); - } + @Specialization + static boolean doNative(Node inliningTarget, PythonAbstractNativeObject value, + @Cached IsBuiltinObjectProfile profile) { + return profile.profileObject(inliningTarget, value, PythonBuiltinClassType.PTimeDelta); + } - @Fallback - static boolean doOther(@SuppressWarnings("unused") Object value) { - return false; - } + @Fallback + static boolean doOther(@SuppressWarnings("unused") Object value) { + return false; } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyTZInfoCheckNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyTZInfoCheckNode.java new file mode 100644 index 0000000000..cc05e56e6a --- /dev/null +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyTZInfoCheckNode.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.graal.python.lib; + +import com.oracle.graal.python.builtins.modules.datetime.PTzInfo; +import com.oracle.graal.python.builtins.objects.cext.PythonAbstractNativeObject; +import com.oracle.graal.python.nodes.object.BuiltinClassProfiles.IsBuiltinObjectProfile; +import com.oracle.graal.python.nodes.object.IsForeignObjectNode; +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.Fallback; +import com.oracle.truffle.api.dsl.GenerateCached; +import com.oracle.truffle.api.dsl.GenerateInline; +import com.oracle.truffle.api.dsl.GenerateUncached; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.library.CachedLibrary; +import com.oracle.truffle.api.nodes.Node; + +/** Equivalent of CPython's {@code PyTZInfo_Check}. */ +@GenerateUncached +@GenerateInline +@GenerateCached(false) +public abstract class PyTZInfoCheckNode extends Node { + public static boolean executeUncached(Object object) { + return PyTZInfoCheckNodeGen.getUncached().execute(null, object); + } + + public abstract boolean execute(Node inliningTarget, Object object); + + @Specialization + static boolean doManaged(@SuppressWarnings("unused") PTzInfo value) { + return true; + } + + @Specialization + static boolean doNative(Node inliningTarget, PythonAbstractNativeObject value, + @Cached IsBuiltinObjectProfile profile) { + return profile.profileObject(inliningTarget, value, com.oracle.graal.python.builtins.PythonBuiltinClassType.PTzInfo); + } + + @Specialization(guards = "isForeignObjectNode.execute(inliningTarget, value)", limit = "1") + static boolean doForeign(Node inliningTarget, Object value, + @SuppressWarnings("unused") @Cached IsForeignObjectNode isForeignObjectNode, + @CachedLibrary("value") InteropLibrary interop) { + return interop.isTimeZone(value) && !interop.isDate(value) && !interop.isTime(value); + } + + @Fallback + static boolean doOther(@SuppressWarnings("unused") Object value) { + return false; + } +} diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyTimeCheckNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyTimeCheckNode.java new file mode 100644 index 0000000000..a7502b49e8 --- /dev/null +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyTimeCheckNode.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.graal.python.lib; + +import com.oracle.graal.python.builtins.modules.datetime.PTime; +import com.oracle.graal.python.builtins.objects.cext.PythonAbstractNativeObject; +import com.oracle.graal.python.nodes.object.BuiltinClassProfiles.IsBuiltinObjectProfile; +import com.oracle.graal.python.nodes.object.IsForeignObjectNode; +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.Fallback; +import com.oracle.truffle.api.dsl.GenerateCached; +import com.oracle.truffle.api.dsl.GenerateInline; +import com.oracle.truffle.api.dsl.GenerateUncached; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.library.CachedLibrary; +import com.oracle.truffle.api.nodes.Node; + +/** Equivalent of CPython's {@code PyTime_Check}. */ +@GenerateUncached +@GenerateInline +@GenerateCached(false) +public abstract class PyTimeCheckNode extends Node { + public static boolean executeUncached(Object object) { + return PyTimeCheckNodeGen.getUncached().execute(null, object); + } + + public abstract boolean execute(Node inliningTarget, Object object); + + @Specialization + static boolean doManaged(@SuppressWarnings("unused") PTime value) { + return true; + } + + @Specialization + static boolean doNative(Node inliningTarget, PythonAbstractNativeObject value, + @Cached IsBuiltinObjectProfile profile) { + return profile.profileObject(inliningTarget, value, com.oracle.graal.python.builtins.PythonBuiltinClassType.PTime); + } + + @Specialization(guards = "isForeignObjectNode.execute(inliningTarget, value)", limit = "1") + static boolean doForeign(Node inliningTarget, Object value, + @SuppressWarnings("unused") @Cached IsForeignObjectNode isForeignObjectNode, + @CachedLibrary("value") InteropLibrary interop) { + return interop.isTime(value) && !interop.isDate(value); + } + + @Fallback + static boolean doOther(@SuppressWarnings("unused") Object value) { + return false; + } +} diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/object/GetForeignObjectClassNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/object/GetForeignObjectClassNode.java index 36fcaa99da..0d63dfa5b9 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/object/GetForeignObjectClassNode.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/object/GetForeignObjectClassNode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -95,8 +95,12 @@ public enum Trait { // Interop types first as they are the most concrete/specific types NULL("None", PythonBuiltinClassType.PNone), BOOLEAN("Boolean", PythonBuiltinClassType.ForeignBoolean), + DATE("Date", PythonBuiltinClassType.ForeignDate), + DATETIME("DateTime", PythonBuiltinClassType.ForeignDateTime), NUMBER("Number", PythonBuiltinClassType.ForeignNumber), // int, float, complex STRING("String", PythonBuiltinClassType.PString), + TIME("Time", PythonBuiltinClassType.ForeignTime), + TIME_ZONE("TimeZone", PythonBuiltinClassType.ForeignTimeZone), EXCEPTION("Exception", PythonBuiltinClassType.PBaseException), META_OBJECT("AbstractClass", PythonBuiltinClassType.ForeignAbstractClass), @@ -154,9 +158,16 @@ PythonManagedClass uncached(Object object, } protected static int getTraits(Object object, InteropLibrary interop) { + // Temporal types are a bit special since some traits should exclude each other to match + // the split in Python's datetime module + boolean isDate = interop.isDate(object); + boolean isTime = interop.isTime(object); + boolean isTimeZone = interop.isTimeZone(object); // Alphabetic order here as it does not matter return (interop.hasArrayElements(object) ? Trait.ARRAY.bit : 0) + (interop.isBoolean(object) ? Trait.BOOLEAN.bit : 0) + + (isDate && isTime ? Trait.DATETIME.bit : 0) + + (isDate && !isTime ? Trait.DATE.bit : 0) + (interop.isException(object) ? Trait.EXCEPTION.bit : 0) + (interop.isExecutable(object) ? Trait.EXECUTABLE.bit : 0) + (interop.hasHashEntries(object) ? Trait.HASH.bit : 0) + @@ -166,7 +177,9 @@ protected static int getTraits(Object object, InteropLibrary interop) { (interop.isMetaObject(object) ? Trait.META_OBJECT.bit : 0) + (interop.isNull(object) ? Trait.NULL.bit : 0) + (interop.isNumber(object) ? Trait.NUMBER.bit : 0) + - (interop.isString(object) ? Trait.STRING.bit : 0); + (interop.isString(object) ? Trait.STRING.bit : 0) + + (!isDate && isTime ? Trait.TIME.bit : 0) + + (!isDate && !isTime && isTimeZone ? Trait.TIME_ZONE.bit : 0); } private PythonManagedClass classForTraits(PythonContext context, int traits) { diff --git a/mx.graalpython/mx_graalpython_python_benchmarks.py b/mx.graalpython/mx_graalpython_python_benchmarks.py index 953dca2c42..ad178e5d2b 100644 --- a/mx.graalpython/mx_graalpython_python_benchmarks.py +++ b/mx.graalpython/mx_graalpython_python_benchmarks.py @@ -207,6 +207,41 @@ def create_asv_benchmark_selection(benchmarks, skipped=()): return '^(?!' + '|'.join(negative_lookaheads) + ')(' + regex + ')' +def patch_asv_for_cpython_312(workdir, vm_venv): + pattern = join(workdir, vm_venv, "lib", "python*", "site-packages", "asv", "plugins", "virtualenv.py") + candidates = glob.glob(pattern) + if not candidates: + mx.abort(f"Could not find ASV virtualenv plugin to patch: {pattern}") + + if len(candidates) != 1: + mx.abort(f"Found multiple ASV virtualenv plugins to patch: {candidates}") + + virtualenv_py = candidates[0] + with open(virtualenv_py) as f: + content = f.read() + + patched_import = "from packaging.version import parse as LooseVersion" + if patched_import in content: + mx.log(f"ASV virtualenv plugin already patched: {virtualenv_py}") + return + + distutils_import = "from distutils.version import LooseVersion" + if distutils_import not in content: + mx.abort(f"Unexpected ASV virtualenv plugin contents, cannot patch: {virtualenv_py}") + + content = content.replace( + distutils_import, + "try:\n" + " from packaging.version import parse as LooseVersion\n" + "except Exception:\n" + " from distutils.version import LooseVersion", + 1, + ) + with open(virtualenv_py, "w") as f: + f.write(content) + mx.log(f"Patched ASV virtualenv plugin for CPython 3.12+: {virtualenv_py}") + + class PyPerfJsonRule(mx_benchmark.Rule): """Parses a JSON file produced by PyPerf and creates a measurement result.""" @@ -638,6 +673,8 @@ def _vmRun(self, vm, workdir, command, benchmarks, bmSuiteArgs): vm.run(workdir, ["-m", "venv", join(workdir, vm_venv)]) pip = join(workdir, vm_venv, "bin", "pip") mx.run([pip, "install", *self.BENCHMARK_REQ], cwd=workdir) + if vm.name() == "cpython": + patch_asv_for_cpython_312(workdir, vm_venv) mx.run( [join(workdir, vm_venv, "bin", "asv"), "machine", "--yes"], cwd=benchdir ) @@ -773,6 +810,8 @@ def _vmRun(self, vm, workdir, command, benchmarks, bmSuiteArgs): env = os.environ.copy() env['PIP_CONSTRAINT'] = constraints.name mx.run([pip, "install", *self.BENCHMARK_REQ], cwd=workdir, env=env) + if vm.name() == "cpython": + patch_asv_for_cpython_312(workdir, vm_venv) mx.run( [join(workdir, vm_venv, "bin", "asv"), "machine", "--yes"], cwd=benchdir )