4545
4646import java .time .LocalDateTime ;
4747import java .time .ZoneId ;
48+ import java .time .ZoneOffset ;
4849import java .time .ZonedDateTime ;
4950import java .time .format .DateTimeFormatter ;
5051import java .util .List ;
@@ -133,8 +134,7 @@ static Object utcoffset(Object self, Object dateTime,
133134 if (!PyDateTimeCheckNode .executeUncached (dateTime )) {
134135 throw PRaiseNode .raiseStatic (inliningTarget , PythonBuiltinClassType .TypeError , ErrorMessages .FROMUTC_ARGUMENT_MUST_BE_A_DATETIME );
135136 }
136- LocalDateTime localDateTime = TemporalValueNodes .GetDateTimeValue .executeUncached (inliningTarget , dateTime ).toLocalDateTime ();
137- ZonedDateTime zonedDateTime = localDateTime .atZone (zoneId );
137+ ZonedDateTime zonedDateTime = resolveDateTimeAtZone (TemporalValueNodes .GetDateTimeValue .executeUncached (inliningTarget , dateTime ), zoneId );
138138 return TimeDeltaNodes .NewNode .getUncached ().execute (inliningTarget , PythonBuiltinClassType .PTimeDelta , 0 , zonedDateTime .getOffset ().getTotalSeconds (), 0 , 0 , 0 , 0 , 0 );
139139 }
140140 }
@@ -153,8 +153,7 @@ static Object dst(Object self, Object dateTime,
153153 if (!PyDateTimeCheckNode .executeUncached (dateTime )) {
154154 throw PRaiseNode .raiseStatic (inliningTarget , PythonBuiltinClassType .TypeError , ErrorMessages .FROMUTC_ARGUMENT_MUST_BE_A_DATETIME );
155155 }
156- LocalDateTime localDateTime = TemporalValueNodes .GetDateTimeValue .executeUncached (inliningTarget , dateTime ).toLocalDateTime ();
157- ZonedDateTime zonedDateTime = localDateTime .atZone (zoneId );
156+ ZonedDateTime zonedDateTime = resolveDateTimeAtZone (TemporalValueNodes .GetDateTimeValue .executeUncached (inliningTarget , dateTime ), zoneId );
158157 int dstSeconds = (int ) zoneId .getRules ().getDaylightSavings (zonedDateTime .toInstant ()).getSeconds ();
159158 return TimeDeltaNodes .NewNode .getUncached ().execute (inliningTarget , PythonBuiltinClassType .PTimeDelta , 0 , dstSeconds , 0 , 0 , 0 , 0 , 0 );
160159 }
@@ -174,8 +173,7 @@ static Object tzname(Object self, Object dateTime,
174173 if (!PyDateTimeCheckNode .executeUncached (dateTime )) {
175174 throw PRaiseNode .raiseStatic (inliningTarget , PythonBuiltinClassType .TypeError , ErrorMessages .FROMUTC_ARGUMENT_MUST_BE_A_DATETIME );
176175 }
177- LocalDateTime localDateTime = TemporalValueNodes .GetDateTimeValue .executeUncached (inliningTarget , dateTime ).toLocalDateTime ();
178- String name = DateTimeFormatter .ofPattern ("z" , Locale .ENGLISH ).format (localDateTime .atZone (zoneId ));
176+ String name = DateTimeFormatter .ofPattern ("z" , Locale .ENGLISH ).format (resolveDateTimeAtZone (TemporalValueNodes .GetDateTimeValue .executeUncached (inliningTarget , dateTime ), zoneId ));
179177 return toTruffleStringUncached (name );
180178 }
181179 }
@@ -191,14 +189,15 @@ static Object fromutc(Object self, Object dateTime,
191189 throw PRaiseNode .raiseStatic (inliningTarget , PythonBuiltinClassType .TypeError , ErrorMessages .FROMUTC_ARGUMENT_MUST_BE_A_DATETIME );
192190 }
193191 DateTimeValue asDateTime = TemporalValueNodes .GetDateTimeValue .executeUncached (inliningTarget , dateTime );
194- if (asDateTime . tzInfo != self ) {
192+ if (! hasMatchingTimeZone ( asDateTime , self ) ) {
195193 throw PRaiseNode .raiseStatic (inliningTarget , PythonBuiltinClassType .ValueError , ErrorMessages .FROMUTC_DT_TZINFO_IS_NOT_SELF );
196194 }
197195 ZoneId zoneId = asZoneId (self );
198196 LocalDateTime utcDateTime = asDateTime .toLocalDateTime ();
199197 ZonedDateTime zonedDateTime = utcDateTime .atOffset (java .time .ZoneOffset .UTC ).atZoneSameInstant (zoneId );
198+ int fold = getFold (zonedDateTime );
200199 return DateTimeNodes .NewUnsafeNode .getUncached ().execute (inliningTarget , PythonBuiltinClassType .PDateTime , zonedDateTime .getYear (), zonedDateTime .getMonthValue (),
201- zonedDateTime .getDayOfMonth (), zonedDateTime .getHour (), zonedDateTime .getMinute (), zonedDateTime .getSecond (), zonedDateTime .getNano () / 1_000 , self , 0 );
200+ zonedDateTime .getDayOfMonth (), zonedDateTime .getHour (), zonedDateTime .getMinute (), zonedDateTime .getSecond (), zonedDateTime .getNano () / 1_000 , self , fold );
202201 }
203202 }
204203
@@ -219,4 +218,44 @@ private static ZoneId asZoneId(Object self) {
219218 throw CompilerDirectives .shouldNotReachHere (e );
220219 }
221220 }
221+
222+ private static boolean hasMatchingTimeZone (DateTimeValue dateTime , Object self ) {
223+ if (dateTime .tzInfo == self ) {
224+ return true ;
225+ }
226+ if (dateTime .zoneId != null ) {
227+ return asZoneId (self ).equals (dateTime .zoneId );
228+ }
229+ if (dateTime .tzInfo == null ) {
230+ return false ;
231+ }
232+ InteropLibrary interop = InteropLibrary .getUncached (dateTime .tzInfo );
233+ if (!interop .isTimeZone (dateTime .tzInfo )) {
234+ return false ;
235+ }
236+ try {
237+ return asZoneId (self ).equals (interop .asTimeZone (dateTime .tzInfo ));
238+ } catch (UnsupportedMessageException e ) {
239+ throw CompilerDirectives .shouldNotReachHere (e );
240+ }
241+ }
242+
243+ private static int getFold (ZonedDateTime zonedDateTime ) {
244+ List <ZoneOffset > validOffsets = zonedDateTime .getZone ().getRules ().getValidOffsets (zonedDateTime .toLocalDateTime ());
245+ if (validOffsets .size () < 2 ) {
246+ return 0 ;
247+ }
248+ return zonedDateTime .getOffset ().equals (validOffsets .get (validOffsets .size () - 1 )) ? 1 : 0 ;
249+ }
250+
251+ @ TruffleBoundary
252+ private static ZonedDateTime resolveDateTimeAtZone (DateTimeValue dateTime , ZoneId zoneId ) {
253+ LocalDateTime localDateTime = dateTime .toLocalDateTime ();
254+ ZonedDateTime zonedDateTime = localDateTime .atZone (zoneId );
255+ List <ZoneOffset > validOffsets = zoneId .getRules ().getValidOffsets (localDateTime );
256+ if (validOffsets .size () < 2 ) {
257+ return zonedDateTime ;
258+ }
259+ return dateTime .fold == 1 ? zonedDateTime .withLaterOffsetAtOverlap () : zonedDateTime .withEarlierOffsetAtOverlap ();
260+ }
222261}
0 commit comments