Skip to content

Commit fe9c3e8

Browse files
committed
Fix time.tzset() and restore system timezone
Before the fix deleting environ['TZ'] env variable and calling time.tzset() led to switching to UTC
1 parent 2314319 commit fe9c3e8

File tree

3 files changed

+82
-27
lines changed

3 files changed

+82
-27
lines changed

graalpython/com.oracle.graal.python.test/src/tests/test_time.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,30 @@ def test_get_clock_info(self):
6767
self.assertRaises(TypeError, time.get_clock_info, 1)
6868
self.assertRaises(ValueError, time.get_clock_info, 'bogus')
6969

70+
class TzSetTests(unittest.TestCase):
71+
def test_removing_tz(self):
72+
# sets system timezone to the local one
73+
74+
from os import environ
75+
original_TZ = environ.get('TZ', None)
76+
original_timezone = time.timezone
77+
78+
try:
79+
environ['TZ'] = "Asia/Kolkata" # there is no daylight saving time for this timezone
80+
time.tzset()
81+
self.assertEqual(time.timezone, -5.5 * 3600)
82+
83+
del environ['TZ']
84+
time.tzset()
85+
self.assertEqual(time.timezone, original_timezone)
86+
finally:
87+
# Repair TZ environment variable in case any other tests rely on it.
88+
if original_TZ is not None:
89+
environ['TZ'] = original_TZ
90+
elif 'TZ' in environ:
91+
del environ['TZ']
92+
time.tzset()
93+
7094
class StructTimeTests(unittest.TestCase):
7195

7296
def test_new_struct_time(self):

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/TimeModuleBuiltins.java

Lines changed: 55 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
import com.oracle.graal.python.builtins.objects.namespace.PSimpleNamespace;
6868
import com.oracle.graal.python.builtins.objects.tuple.PTuple;
6969
import com.oracle.graal.python.builtins.objects.tuple.StructSequence;
70+
import com.oracle.graal.python.lib.OsEnvironGetNode;
7071
import com.oracle.graal.python.lib.PyFloatAsDoubleNode;
7172
import com.oracle.graal.python.lib.PyImportImport;
7273
import com.oracle.graal.python.lib.PyLongAsLongNode;
@@ -90,7 +91,6 @@
9091
import com.oracle.graal.python.nodes.util.CastToJavaDoubleNode;
9192
import com.oracle.graal.python.runtime.GilNode;
9293
import com.oracle.graal.python.runtime.PythonContext;
93-
import com.oracle.graal.python.runtime.PythonImageBuildOptions;
9494
import com.oracle.graal.python.runtime.object.PFactory;
9595
import com.oracle.graal.python.util.PythonUtils;
9696
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
@@ -112,12 +112,14 @@
112112
import com.oracle.truffle.api.nodes.ExplodeLoop;
113113
import com.oracle.truffle.api.nodes.Node;
114114
import com.oracle.truffle.api.strings.TruffleString;
115+
import com.oracle.truffle.api.strings.TruffleString.ToJavaStringNode;
115116

116117
@CoreFunctions(defineModule = "time")
117118
public final class TimeModuleBuiltins extends PythonBuiltins {
118119
private static final int DELAY_NANOS = 10;
119120
private static final String CTIME_FORMAT = "%s %s %2d %02d:%02d:%02d %d";
120121
private static final ZoneId GMT = ZoneId.of("GMT");
122+
private static final TruffleString T_TZ = tsLiteral("TZ");
121123

122124
private static final StructSequence.BuiltinTypeDescriptor STRUCT_TIME_DESC = new StructSequence.BuiltinTypeDescriptor(
123125
PythonBuiltinClassType.PStructTime,
@@ -162,32 +164,66 @@ public void initialize(Python3Core core) {
162164
@Override
163165
public void postInitialize(Python3Core core) {
164166
super.postInitialize(core);
165-
// Should we read TZ env variable?
166-
ZoneId defaultZoneId = core.getContext().getEnv().getTimeZone();
167167
ModuleState moduleState = new ModuleState();
168-
moduleState.currentZoneId = defaultZoneId;
169-
moduleState.timeSlept = 0;
170168
PythonModule timeModule = core.lookupBuiltinModule(T_TIME);
171169
timeModule.setModuleState(moduleState);
172170

173-
TimeZone defaultTimeZone = TimeZone.getTimeZone(defaultZoneId);
174-
TruffleString noDaylightSavingZone = toTruffleStringUncached(defaultTimeZone.getDisplayName(false, TimeZone.SHORT));
175-
TruffleString daylightSavingZone = toTruffleStringUncached(defaultTimeZone.getDisplayName(true, TimeZone.SHORT));
176-
177-
timeModule.setAttribute(T_TZNAME, PFactory.createTuple(core.getLanguage(), new Object[]{noDaylightSavingZone, daylightSavingZone}));
178-
timeModule.setAttribute(T_DAYLIGHT, PInt.intValue(defaultTimeZone.getDSTSavings() != 0));
179-
timeModule.setAttribute(T_TIMEZONE, defaultTimeZone.getRawOffset() / -1000);
180-
timeModule.setAttribute(T_ALTZONE, (defaultTimeZone.getRawOffset() + defaultTimeZone.getDSTSavings()) / -1000);
171+
setGlobalTimeZone(timeModule, core.getLanguage(), core.getContext());
181172

182173
// register_interop_behavior() for time.struct_time
183174
AbstractImportNode.importModule(T_POLYGLOT_TIME);
184175
}
185176

177+
/**
178+
* Determine and set global time zone (for the current Truffle context). Timezone is based on
179+
* the TZ env variable. Intentionally don't change Java default timezone (with
180+
* {@code TimeZone.setDefault}) because it affects all the contexts.
181+
*/
182+
private static void setGlobalTimeZone(PythonModule timeModule, PythonLanguage language, PythonContext context) {
183+
TruffleString tsTzEnv = OsEnvironGetNode.lookupUncached(T_TZ);
184+
185+
TimeZone timeZone;
186+
if (tsTzEnv == null) {
187+
// switch back to the context-specific default timezone
188+
ZoneId zoneId = context.getEnv().getTimeZone();
189+
timeZone = TimeZone.getTimeZone(zoneId);
190+
} else {
191+
String tzEnv = ToJavaStringNode.getUncached().execute(tsTzEnv);
192+
timeZone = TimeZone.getTimeZone(tzEnv);
193+
}
194+
195+
// save in the module state
196+
ModuleState moduleState = timeModule.getModuleState(ModuleState.class);
197+
moduleState.currentZoneId = timeZone.toZoneId();
198+
199+
// update time module attributes
200+
TruffleString noDaylightSavingZone = toTruffleStringUncached(timeZone.getDisplayName(false, TimeZone.SHORT));
201+
TruffleString daylightSavingZone = toTruffleStringUncached(timeZone.getDisplayName(true, TimeZone.SHORT));
202+
203+
timeModule.setAttribute(T_TZNAME, PFactory.createTuple(language, new Object[]{noDaylightSavingZone, daylightSavingZone}));
204+
timeModule.setAttribute(T_DAYLIGHT, PInt.intValue(timeZone.getDSTSavings() != 0));
205+
timeModule.setAttribute(T_TIMEZONE, timeZone.getRawOffset() / -1000);
206+
timeModule.setAttribute(T_ALTZONE, (timeZone.getRawOffset() + timeZone.getDSTSavings()) / -1000);
207+
}
208+
186209
@TruffleBoundary
187210
public static double timeSeconds() {
188211
return System.currentTimeMillis() / 1000.0;
189212
}
190213

214+
/**
215+
* Return current time zone (that can be changed with time.tzset()). The only correct way to get
216+
* it.
217+
*/
218+
public static TimeZone getGlobalTimeZone(PythonContext context) {
219+
PythonModule timeModule = context.lookupBuiltinModule(T_TIME);
220+
221+
ModuleState moduleState = timeModule.getModuleState(ModuleState.class);
222+
ZoneId zoneId = moduleState.currentZoneId;
223+
224+
return TimeZone.getTimeZone(zoneId);
225+
}
226+
191227
private static final int TM_YEAR = 0; /* year */
192228
private static final int TM_MON = 1; /* month */
193229
private static final int TM_MDAY = 2; /* day of the month */
@@ -306,23 +342,15 @@ static PTuple gmtime(VirtualFrame frame, Object seconds,
306342
}
307343
}
308344

309-
@Builtin(name = "tzset")
345+
// time.tzset()
346+
@Builtin(name = "tzset", maxNumOfPositionalArgs = 1, declaresExplicitSelf = true)
310347
@GenerateNodeFactory
311348
public abstract static class TzSetNode extends PythonBuiltinNode {
312-
private static final TruffleString SET_TIMEZONE_ERROR = tsLiteral("Setting timezone was disallowed.");
313349

314350
@Specialization
315351
@TruffleBoundary
316-
Object tzset() {
317-
if (!PythonImageBuildOptions.WITHOUT_PLATFORM_ACCESS) {
318-
String tzEnv = getContext().getEnv().getEnvironment().get("TZ");
319-
if (tzEnv == null) {
320-
tzEnv = "";
321-
}
322-
TimeZone.setDefault(TimeZone.getTimeZone(tzEnv));
323-
} else {
324-
throw PRaiseNode.raiseStatic(this, PythonBuiltinClassType.AttributeError, SET_TIMEZONE_ERROR);
325-
}
352+
Object tzset(PythonModule self) {
353+
setGlobalTimeZone(self, getLanguage(), getContext());
326354
return PNone.NONE;
327355
}
328356
}
@@ -920,7 +948,7 @@ private static TruffleString format(String format, int[] date, TruffleString.Fro
920948
static TruffleString formatTime(PythonModule module, TruffleString format, @SuppressWarnings("unused") PNone time,
921949
@Bind Node inliningTarget,
922950
@Shared("byteIndexOfCp") @Cached TruffleString.ByteIndexOfCodePointNode byteIndexOfCodePointNode,
923-
@Shared("ts2js") @Cached TruffleString.ToJavaStringNode toJavaStringNode,
951+
@Shared("ts2js") @Cached ToJavaStringNode toJavaStringNode,
924952
@Shared("js2ts") @Cached TruffleString.FromJavaStringNode fromJavaStringNode,
925953
@Exclusive @Cached PRaiseNode raiseNode) {
926954
if (byteIndexOfCodePointNode.execute(format, 0, 0, format.byteLength(TS_ENCODING), TS_ENCODING) >= 0) {
@@ -936,7 +964,7 @@ static TruffleString formatTime(VirtualFrame frame, @SuppressWarnings("unused")
936964
@Cached SequenceStorageNodes.GetInternalObjectArrayNode getArray,
937965
@Cached PyNumberAsSizeNode asSizeNode,
938966
@Shared("byteIndexOfCp") @Cached TruffleString.ByteIndexOfCodePointNode byteIndexOfCodePointNode,
939-
@Shared("ts2js") @Cached TruffleString.ToJavaStringNode toJavaStringNode,
967+
@Shared("ts2js") @Cached ToJavaStringNode toJavaStringNode,
940968
@Shared("js2ts") @Cached TruffleString.FromJavaStringNode fromJavaStringNode,
941969
@Exclusive @Cached PRaiseNode raiseNode) {
942970
if (byteIndexOfCodePointNode.execute(format, 0, 0, format.byteLength(TS_ENCODING), TS_ENCODING) >= 0) {

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/BuiltinNames.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,9 @@ private static TruffleString tsLiteral(String s) {
128128

129129
public static final TruffleString T_MODULES = tsLiteral("modules");
130130

131+
// time
132+
public static final TruffleString T_TIME = tsLiteral("time");
133+
131134
// built-in functions
132135
public static final String J_ABS = "abs";
133136
public static final TruffleString T_ABS = tsLiteral(J_ABS);

0 commit comments

Comments
 (0)