Skip to content

Commit 8e16bd1

Browse files
committed
[GR-16588] Python: Support date, time, timezone and duration APIs where applicable.
PullRequest: graalpython/752
2 parents de6d3b2 + 20f8042 commit 8e16bd1

File tree

4 files changed

+510
-6
lines changed

4 files changed

+510
-6
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ language runtime. The main focus is on user-observable behavior of the engine.
77

88
* Jython Compatiblity: Implement `from JavaType import *` to import all static members of a Java class
99
* Jython Compatiblity: Implement importing Python code from inside JAR files by adding `path/to/jarfile.jar!path/inside/jar` to `sys.path`
10+
* Added support for date and time interop.
11+
* Added support for setting the time zone via `Context.Builder.timeZone`.
1012

1113
## Version 19.3.0
1214

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
/*
2+
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* The Universal Permissive License (UPL), Version 1.0
6+
*
7+
* Subject to the condition set forth below, permission is hereby granted to any
8+
* person obtaining a copy of this software, associated documentation and/or
9+
* data (collectively the "Software"), free of charge and under any and all
10+
* copyright rights in the Software, and any and all patent rights owned or
11+
* freely licensable by each licensor hereunder covering either (i) the
12+
* unmodified Software as contributed to or provided by such licensor, or (ii)
13+
* the Larger Works (as defined below), to deal in both
14+
*
15+
* (a) the Software, and
16+
*
17+
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
18+
* one is included with the Software each a "Larger Work" to which the Software
19+
* is contributed by such licensors),
20+
*
21+
* without restriction, including without limitation the rights to copy, create
22+
* derivative works of, display, perform, and distribute the Software and make,
23+
* use, sell, offer for sale, import, export, have made, and have sold the
24+
* Software and the Larger Work(s), and to sublicense the foregoing rights on
25+
* either these or other terms.
26+
*
27+
* This license is subject to the following condition:
28+
*
29+
* The above copyright notice and either this complete permission notice or at a
30+
* minimum a reference to the UPL must be included in all copies or substantial
31+
* portions of the Software.
32+
*
33+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
39+
* SOFTWARE.
40+
*/
41+
package com.oracle.graal.python.test.interop;
42+
43+
import com.oracle.graal.python.test.PythonTests;
44+
import java.io.ByteArrayOutputStream;
45+
import java.time.LocalDate;
46+
import java.time.LocalTime;
47+
import java.time.ZoneId;
48+
import org.graalvm.polyglot.Context;
49+
import org.graalvm.polyglot.Source;
50+
import org.graalvm.polyglot.Value;
51+
import org.junit.After;
52+
import static org.junit.Assert.*;
53+
import org.junit.Before;
54+
import org.junit.Test;
55+
56+
public class TimeDateTest extends PythonTests {
57+
58+
private ByteArrayOutputStream out;
59+
private Context context;
60+
private ByteArrayOutputStream err;
61+
62+
@Before
63+
public void setUpTest() {
64+
out = new ByteArrayOutputStream();
65+
err = new ByteArrayOutputStream();
66+
Context.Builder builder = Context.newBuilder();
67+
builder.allowExperimentalOptions(true);
68+
builder.allowAllAccess(true);
69+
builder.out(out);
70+
builder.err(err);
71+
context = builder.build();
72+
}
73+
74+
@After
75+
public void tearDown() {
76+
context.close();
77+
}
78+
79+
@Test
80+
public void testDateTimeDate01() {
81+
String source = "import datetime as dt; " +
82+
"dt.date.fromisoformat('2019-12-02')";
83+
Value value = getValue(source, ZoneId.of("America/Los_Angeles"));
84+
checkValueDateTimeType(value, true, false, false, false);
85+
checkDate(value, 2019, 12, 02);
86+
try {
87+
value.asTime();
88+
assertFalse("From datetime.date can not be obtained time", true);
89+
} catch (Exception ex) {
90+
}
91+
}
92+
93+
@Test
94+
public void testDateTimeTime01() {
95+
String source = "import datetime as dt; " +
96+
"dt.time.fromisoformat('04:23:01')";
97+
Value value = getValue(source, ZoneId.of("America/Los_Angeles"));
98+
checkValueDateTimeType(value, false, true, false, false);
99+
checkTime(value, 4, 23, 1, 0);
100+
try {
101+
value.asDate();
102+
assertFalse("From datetime.time can not be obtained date", true);
103+
} catch (Exception ex) {
104+
}
105+
}
106+
107+
@Test
108+
public void testDateTimeTime02() {
109+
String source = "import datetime as dt; " +
110+
"dt.time.fromisoformat('04:23:01.000384')";
111+
Value value = getValue(source, ZoneId.of("America/Los_Angeles"));
112+
checkValueDateTimeType(value, false, true, false, false);
113+
checkTime(value, 4, 23, 1, 384);
114+
try {
115+
value.asDate();
116+
assertFalse("From datetime.time can not be obtained date", true);
117+
} catch (Exception ex) {
118+
}
119+
}
120+
121+
// TODO this test is desabled due the issue GR-19849
122+
public void testDateTimeTime03() {
123+
String source = "import datetime as dt; " +
124+
"dt.time.fromisoformat('04:23:01+04:00')";
125+
Value value = getValue(source, ZoneId.of("America/Los_Angeles"));
126+
checkValueDateTimeType(value, false, true, false, true);
127+
checkTime(value, 4, 23, 1, 0);
128+
try {
129+
value.asDate();
130+
assertFalse("From datetime.time can not be obtained date", true);
131+
} catch (Exception ex) {
132+
}
133+
assertEquals(ZoneId.of("UTC+4"), value.asTimeZone());
134+
}
135+
136+
@Test
137+
public void testDateTimeDateTime01() {
138+
String source = "import datetime as dt; " +
139+
"dt.datetime.now()";
140+
Value value = getValue(source, ZoneId.of("America/Los_Angeles"));
141+
checkValueDateTimeType(value, true, true, false, false);
142+
}
143+
144+
@Test
145+
public void testDateTimeDateTime02() {
146+
String source = "import datetime as dt\n" +
147+
"dt.datetime.now(dt.timezone.utc)";
148+
Value value = getValue(source, ZoneId.of("America/Los_Angeles"));
149+
checkValueDateTimeType(value, true, true, true, true);
150+
assertEquals(ZoneId.of("UTC"), value.asTimeZone());
151+
}
152+
153+
@Test
154+
public void testDateTimeDateTime03() {
155+
String source = "from datetime import datetime\n" +
156+
"datetime.fromisoformat('2011-11-04 00:05:23.283+04:00')";
157+
Value value = getValue(source, ZoneId.of("America/Los_Angeles"));
158+
checkValueDateTimeType(value, true, true, true, true);
159+
checkDate(value, 2011, 11, 4);
160+
checkTime(value, 0, 5, 23, 283000);
161+
assertEquals(ZoneId.of("UTC+4"), value.asTimeZone());
162+
}
163+
164+
@Test
165+
public void testStructTime01() {
166+
String source = "import time\n" +
167+
"time.gmtime()";
168+
Value value = getValue(source, ZoneId.of("UTC+1"));
169+
checkValueDateTimeType(value, true, true, true, true);
170+
assertEquals(ZoneId.of("UTC"), value.asTimeZone());
171+
}
172+
173+
@Test
174+
public void testStructTime02() {
175+
String source = "import time\n" +
176+
"time.localtime()";
177+
Value value = getValue(source, ZoneId.of("UTC+1"));
178+
checkValueDateTimeType(value, true, true, true, true);
179+
assertEquals(ZoneId.of("UTC+1"), value.asTimeZone());
180+
}
181+
182+
@Test
183+
public void testStructTime03() {
184+
String source = "import time\n" +
185+
"tup = (2019, 12, 24, 18, 56, 26, 0, 0, 0)\n" +
186+
"tt = time.mktime(tup)\n" +
187+
"time.localtime(tt)";
188+
Value value = getValue(source, ZoneId.of("UTC+8"));
189+
checkValueDateTimeType(value, true, true, true, true);
190+
checkDate(value, 2019, 12, 24);
191+
checkTime(value, 18, 56, 26, 0);
192+
assertEquals(ZoneId.of("UTC+8"), value.asTimeZone());
193+
}
194+
195+
private Value getValue(String source, ZoneId timeZoneId) {
196+
return Context.newBuilder("python").allowAllAccess(true).timeZone(timeZoneId).build().eval(Source.create("python", source));
197+
}
198+
199+
private void checkValueDateTimeType(Value value, boolean isDate, boolean isTime, boolean isInstant, boolean isTimeZone) {
200+
assertEquals(isDate, value.isDate());
201+
assertEquals(isTime, value.isTime());
202+
assertEquals(isTimeZone, value.isTimeZone());
203+
assertEquals(isInstant, value.isInstant());
204+
}
205+
206+
private void checkDate(Value value, int expectedYear, int expectedMonth, int expectedDay) {
207+
LocalDate ld = value.asDate();
208+
assertEquals(expectedYear, ld.getYear());
209+
assertEquals(expectedMonth, ld.getMonthValue());
210+
assertEquals(expectedDay, ld.getDayOfMonth());
211+
}
212+
213+
private void checkTime(Value value, int expectedHour, int expectedMinute, int expectedSecond, int expectedNano) {
214+
LocalTime ld = value.asTime();
215+
assertEquals(expectedHour, ld.getHour());
216+
assertEquals(expectedMinute, ld.getMinute());
217+
assertEquals(expectedSecond, ld.getSecond());
218+
assertEquals(expectedNano, ld.getNano());
219+
}
220+
}

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@
2525
*/
2626
package com.oracle.graal.python.builtins.modules;
2727

28+
import com.oracle.graal.python.PythonLanguage;
2829
import java.text.DateFormatSymbols;
2930
import java.time.Instant;
3031
import java.time.LocalDateTime;
3132
import java.time.ZoneId;
32-
import java.time.ZoneOffset;
3333
import java.time.ZonedDateTime;
3434
import java.util.Arrays;
3535
import java.util.Calendar;
@@ -80,7 +80,7 @@ protected List<? extends NodeFactory<? extends PythonBuiltinBaseNode>> getNodeFa
8080
@Override
8181
public void initialize(PythonCore core) {
8282
super.initialize(core);
83-
TimeZone defaultTimeZone = TimeZone.getDefault();
83+
TimeZone defaultTimeZone = TimeZone.getTimeZone(core.getContext().getEnv().getTimeZone());
8484
String noDaylightSavingZone = defaultTimeZone.getDisplayName(false, TimeZone.SHORT);
8585
String daylightSavingZone = defaultTimeZone.getDisplayName(true, TimeZone.SHORT);
8686

@@ -103,7 +103,7 @@ public static double timeSeconds() {
103103
private static Object[] getTimeStruct(double seconds, boolean local) {
104104
Object[] timeStruct = new Object[11];
105105
Instant instant = Instant.ofEpochSecond((long) seconds);
106-
ZoneId zone = (local) ? ZoneId.systemDefault() : ZoneId.of("GMT");
106+
ZoneId zone = (local) ? PythonLanguage.getContext().getEnv().getTimeZone() : ZoneId.of("GMT");
107107
ZonedDateTime zonedDateTime = LocalDateTime.ofInstant(instant, zone).atZone(zone);
108108
timeStruct[0] = zonedDateTime.getYear();
109109
timeStruct[1] = zonedDateTime.getMonth().getValue();
@@ -667,7 +667,8 @@ abstract static class MkTimeNode extends PythonUnaryBuiltinNode {
667667
@TruffleBoundary
668668
private static long op(int[] integers) {
669669
LocalDateTime localtime = LocalDateTime.of(integers[0], integers[1], integers[2], integers[3], integers[4], integers[5]);
670-
return localtime.toEpochSecond(ZoneOffset.UTC);
670+
ZoneId timeZone = PythonLanguage.getContext().getEnv().getTimeZone();
671+
return localtime.toEpochSecond(timeZone.getRules().getOffset(localtime));
671672
}
672673
}
673674
}

0 commit comments

Comments
 (0)