Verify Python date can be used where Enso date (#7396)

Verifies #7387 by running existing suites with various Python objects.
This commit is contained in:
Jaroslav Tulach 2023-07-28 16:17:42 +02:00 committed by GitHub
parent 80c4b1ca06
commit c4d90ae6ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 108 additions and 23 deletions

View File

@ -102,4 +102,9 @@ public final class EnsoTimeZone implements TruffleObject {
Type getType(@CachedLibrary("this") TypesLibrary thisLib) {
return EnsoContext.get(thisLib).getBuiltins().timeZone();
}
@ExportMessage
String toDisplayString(boolean ignore) {
return zone.toString();
}
}

View File

@ -4,7 +4,7 @@ from Standard.Test import Test
import Standard.Test.Extensions
spec name create_new_date =
Test.group (name + "date part tests") <|
Test.group (name + " date part tests") <|
Test.specify "should return if a leap year" <|
create_new_date 2022 8 25 . is_leap_year . should_equal False
create_new_date 1999 12 31 . is_leap_year . should_equal False

View File

@ -14,6 +14,8 @@ polyglot java import java.time.format.DateTimeFormatter
spec =
spec_with "Date" Date.new Date.parse
spec_with "JavaScriptDate" js_date js_parse
if Polyglot.is_language_installed "python" then
spec_with "PythonDate" python_date python_parse
spec_with "JavaDate" java_date java_parse
spec_with "JavaScriptArrayWithADate" js_array_date js_parse
@ -498,7 +500,7 @@ spec_with name create_new_date parse_date =
d1.date_part Date_Period.Day . should_equal 30
Test.expect_panic_with (d1.date_part Time_Period.Day) Type_Error
Test.specify "should allow computing date_diff" <|
d1 = create_new_date 2021 11 3
d2 = create_new_date 2021 12 5
@ -582,6 +584,20 @@ java_parse date_text pattern=Nothing =
java_date year month=1 day=1 =
Panic.catch Any (LocalDate.of year month day) (err -> Error.throw (Time_Error.Error <| err.payload.getMessage))
python_date year month=1 day=1 =
Panic.catch Any (python_date_impl year month day) err->
msg = if err.payload.to_text.contains "month must be in" . not then err.payload else
"Invalid value for MonthOfYear (valid values 1 - 12): " + month.to_text
Error.throw <| Time_Error.Error msg
python_parse text format="" =
d = Date.parse text format
python_date d.year d.month d.day
foreign python python_date_impl year month day = """
import datetime
return datetime.date(year, month, day)
foreign js js_date_impl year month=1 day=1 = """
if (month > 12) {
throw `Invalid value for MonthOfYear (valid values 1 - 12): ${month}`;

View File

@ -11,6 +11,7 @@ import project.Data.Time.Date_Part_Spec
polyglot java import org.enso.base.Time_Utils
polyglot java import java.time.ZonedDateTime
polyglot java import java.time.LocalDateTime
polyglot java import java.time.ZoneId
polyglot java import java.time.ZoneOffset
polyglot java import java.time.format.DateTimeFormatter
polyglot java import java.lang.Exception as JException
@ -18,6 +19,9 @@ polyglot java import java.lang.Exception as JException
spec =
spec_with "Date_Time" enso_datetime Date_Time.parse
spec_with "JavascriptDate" js_datetime js_parse nanoseconds_loss_in_precision=True
if Polyglot.is_language_installed "python" then
skip_check _ _ = True
spec_with "PythonDate" python_datetime python_parse nanoseconds_loss_in_precision=True loose_zone_equal=skip_check
spec_with "JavaZonedDateTime" java_datetime java_parse
spec_with "JavascriptDataInArray" js_array_datetime js_parse nanoseconds_loss_in_precision=True
@ -40,7 +44,11 @@ spec =
(Date_Time.new 2022 12 12).should_equal (Date_Time.new 2022 12 12)
(Date_Time.new 2022 12 12).should_not_equal (Date_Time.new 1996)
spec_with name create_new_datetime parse_datetime nanoseconds_loss_in_precision=False =
default_zone_equal z1 z2 =
z1 . should_equal z2
False
spec_with name create_new_datetime parse_datetime nanoseconds_loss_in_precision=False loose_zone_equal=default_zone_equal =
Test.group name <|
Test.specify "should create time" <|
@ -57,7 +65,9 @@ spec_with name create_new_datetime parse_datetime nanoseconds_loss_in_precision=
Test.specify "should handle errors when creating time" <|
case create_new_datetime 1970 0 0 . catch of
Time_Error.Error msg ->
msg . should_equal "Invalid value for MonthOfYear (valid values 1 - 12): 0"
msg.to_text . contains "0" . should_be_true
msg.to_text . contains "1" . should_be_true
msg.to_text . contains "12" . should_be_true
result ->
Test.fail ("Unexpected result: " + result.to_text)
@ -102,12 +112,12 @@ spec_with name create_new_datetime parse_datetime nanoseconds_loss_in_precision=
time . minute . should_equal 0
time . second . should_equal 1
time . nanosecond . should_equal 0
time . zone . should_equal Time_Zone.system
loose_zone_equal time.zone Time_Zone.system
Test.specify "should parse time Z" <|
time = parse_datetime "1582-10-15T00:00:01Z"
time . to_enso_epoch_seconds . should_equal 1
time . zone . zone_id . should_equal "Z"
loose_zone_equal time.zone.zone_id "Z"
Test.specify "should parse time UTC" <|
time = parse_datetime "1582-10-15T00:00:01Z[UTC]"
@ -133,7 +143,7 @@ spec_with name create_new_datetime parse_datetime nanoseconds_loss_in_precision=
time . millisecond . should_equal 123
time . microsecond . should_equal 456
time . nanosecond . should_equal 789
time . zone . zone_id . should_equal "Z"
loose_zone_equal time.zone.zone_id "Z"
Test.specify "should parse time with offset-based zone" <|
time = parse_datetime "1970-01-01T00:00:01+01:00"
@ -146,7 +156,7 @@ spec_with name create_new_datetime parse_datetime nanoseconds_loss_in_precision=
time . millisecond . should_equal 0
time . microsecond . should_equal 0
time . nanosecond . should_equal 0
time . zone . zone_id . should_equal (Time_Zone.new 1 . zone_id)
loose_zone_equal time.zone.zone_id (Time_Zone.new 1 . zone_id)
Test.specify "should parse time with id-based zone" <|
time = parse_datetime "1970-01-01T00:00:01+01:00[Europe/Paris]"
@ -159,8 +169,8 @@ spec_with name create_new_datetime parse_datetime nanoseconds_loss_in_precision=
time . millisecond . should_equal 0
time . microsecond . should_equal 0
time . nanosecond . should_equal 0
time . zone . zone_id . should_equal "Europe/Paris"
time.to_display_text . should_equal "1970-01-01 00:00:01 Europe/Paris"
loose_zone_equal time.zone.zone_id "Europe/Paris"
loose_zone_equal time.to_display_text "1970-01-01 00:00:01 Europe/Paris"
Test.specify "should throw error when parsing invalid time" <|
case parse_datetime "2008-1-1" . catch of
@ -180,7 +190,7 @@ spec_with name create_new_datetime parse_datetime nanoseconds_loss_in_precision=
time . millisecond . should_equal 0
time . microsecond . should_equal 0
time . nanosecond . should_equal 0
time . zone . zone_id . should_equal "Etc/UTC"
loose_zone_equal time.zone.zone_id "Etc/UTC"
Test.specify "should parse custom format of local time" <|
time = parse_datetime "06 of May 2020 at 04:30AM" "dd 'of' MMMM yyyy 'at' hh:mma"
@ -421,7 +431,7 @@ spec_with name create_new_datetime parse_datetime nanoseconds_loss_in_precision=
Test.specify "will reflect that Time_Period.Day does not reflect daylight saving" <|
tz = Time_Zone.parse "Europe/Warsaw"
dt = Date_Time.new 2023 03 26 01 20 zone=tz
dt1 = dt + Time_Period.Day
dt2 = dt + Date_Period.Day
@ -583,13 +593,14 @@ spec_with name create_new_datetime parse_datetime nanoseconds_loss_in_precision=
offset_1_h = ZoneOffset.ofTotalSeconds 3600
offset_2_h = ZoneOffset.ofTotalSeconds 2*3600
tz = Time_Zone.parse "Europe/Warsaw"
js_dst_pending = if name.contains "Javascript" then
"Javascript implementation does not support time zones correctly, so the tests for conversion around DST switches would fail and thus are disabled. We may revisit once JS gets better time support, see project Temporal: https://tc39.es/proposal-temporal/docs/ and our Pivotal issue tracking our integration: https://www.pivotaltracker.com/story/show/183261296"
Test.specify "should find start/end of a Date_Period or Time_Period containing the current datetime correctly near the spring DST switch" pending=js_dst_pending <|
dst_pending = if name.contains "Javascript" then "Javascript implementation does not support time zones correctly, so the tests for conversion around DST switches would fail and thus are disabled. We may revisit once JS gets better time support, see project Temporal: https://tc39.es/proposal-temporal/docs/ and our issue tracking our integration: https://github.com/enso-org/enso/issues/5384" else
if loose_zone_equal "" "" then "Loose Zone conversions are on, skipping the test"
Test.specify "should find start/end of a Date_Period or Time_Period containing the current datetime correctly near the spring DST switch" pending=dst_pending <|
d1 = create_new_datetime 2022 3 27 1 34 15 0 tz
d2 = create_new_datetime 2022 3 27 3 34 15 0 tz
d1_plus = d1 + (Duration.new hours=1)
d1_plus . should_equal d2
loose_zone_equal d1_plus d2
check_dates_spring date =
date.start_of Date_Period.Day . should_equal (Date_Time.new 2022 3 27 zone=tz)
@ -689,13 +700,13 @@ spec_with name create_new_datetime parse_datetime nanoseconds_loss_in_precision=
Test.specify "should handle shifting dates around spring DST edge cases" <|
# 2022-10-30 and 2022-03-27 are DST switch days, Sundays.
create_new_datetime 2022 10 30 2 30 55 1234 . add_work_days 0 . should_equal (create_new_datetime 2022 10 31 2 30 55 1234)
create_new_datetime 2022 10 30 1 30 . add_work_days 1 . should_equal (Date_Time.new 2022 11 1 1 30)
create_new_datetime 2022 10 30 3 30 . add_work_days 1 . should_equal (Date_Time.new 2022 11 1 3 30)
loose_zone_equal (create_new_datetime 2022 10 30 2 30 55 1234 . add_work_days 0) (create_new_datetime 2022 10 31 2 30 55 1234)
loose_zone_equal (create_new_datetime 2022 10 30 1 30 . add_work_days 1) (Date_Time.new 2022 11 1 1 30)
loose_zone_equal (create_new_datetime 2022 10 30 3 30 . add_work_days 1) (Date_Time.new 2022 11 1 3 30)
tz = Time_Zone.parse "Europe/Warsaw"
create_new_datetime 2022 3 27 1 30 zone=tz . add_work_days 0 . should_equal (Date_Time.new 2022 3 28 1 30 zone=tz)
create_new_datetime 2022 3 27 3 30 zone=tz . add_work_days 1 . should_equal (Date_Time.new 2022 3 29 3 30 zone=tz)
loose_zone_equal (create_new_datetime 2022 3 27 1 30 zone=tz . add_work_days 0) (Date_Time.new 2022 3 28 1 30 zone=tz)
loose_zone_equal (create_new_datetime 2022 3 27 3 30 zone=tz . add_work_days 1) (Date_Time.new 2022 3 29 3 30 zone=tz)
Test.specify "should handle shifting dates around autumn DST edge cases" pending=dst_overlap_message <|
d3 = create_new_datetime 2022 10 30 2 30 15 0 tz
@ -753,7 +764,9 @@ spec_with name create_new_datetime parse_datetime nanoseconds_loss_in_precision=
d1.date_part Time_Period.Microsecond . should_equal 456
d1.date_part Time_Period.Nanosecond . should_equal 789
Test.specify "should allow computing date_diff" <|
pending_date_diff_test = if loose_zone_equal "" "" then "Loose Zone conversions are on, skipping the test"
Test.specify "should allow computing date_diff" pending=pending_date_diff_test <|
t1 = create_new_datetime 2021 11 3 10 15 0
t2 = create_new_datetime 2021 12 5 12 30 20
@ -847,6 +860,19 @@ js_set_zone local_datetime zone =
diff = Duration.between datetime_with_tz local_datetime (timezone_aware=False)
datetime_with_tz + diff
python_datetime year month=1 day=1 hour=0 minute=0 second=0 nanosecond=0 zone=Nothing =
delta = if zone.is_nothing then -1 else
rules = Polyglot.invoke (ZoneId.of (zone.zone_id)) "getRules" []
zone_offset = Polyglot.invoke rules "getOffset" [ LocalDateTime.now ]
Polyglot.invoke zone_offset "getTotalSeconds" []
name = if zone.is_nothing then "Z" else
zone.zone_id
Panic.catch Any (python_datetime_impl year month day hour minute second nanosecond delta name) (err -> Error.throw (Time_Error.Error err.payload))
python_parse text format="" =
d = Date_Time.parse text format
python_datetime d.year d.month d.day d.hour d.minute d.second (d.nanosecond include_milliseconds=True) d.zone
foreign js js_local_datetime_impl year month day hour minute second nanosecond = """
if (month > 12 || month < 1) {
throw `Invalid value for MonthOfYear (valid values 1 - 12): ${month}`;
@ -867,6 +893,27 @@ foreign js js_array_datetimeCreate year month day hour minute second nanosecond
}
return [ new Date(year, month - 1, day, hour, minute, second, nanosecond / 1000000) ];
foreign python python_datetime_impl year month day hour minute second nanosecond zone zone_name = """
import datetime
class Zone(datetime.tzinfo):
def __init__(self, zone, name):
self.zone = zone
self.name = name
def utcoffset(self, dt):
return datetime.timedelta(seconds=zone)
def tzname(self, dt):
return self.name
microseconds = int(nanosecond / 1000000) * 1000
if zone == -1:
return datetime.datetime(year, month, day, hour, minute, second, microseconds)
else:
return datetime.datetime(year, month, day, hour, minute, second, microseconds, Zone(zone, zone_name))
enso_datetime year month=1 day=1 hour=0 minute=0 second=0 nanosecond=0 zone=Time_Zone.system =
Date_Time.new year month day hour minute second nanosecond=nanosecond zone=zone

View File

@ -13,6 +13,8 @@ polyglot java import java.time.format.DateTimeFormatter
spec =
specWith "Time_Of_Day" enso_time Time_Of_Day.parse
specWith "JavaLocalTime" java_time java_parse
if Polyglot.is_language_installed "python" then
specWith "PythonLocalTime" python_time python_parse nanoseconds_loss_in_precision=True
specWith name create_new_time parse_time nanoseconds_loss_in_precision=False =
Test.group name <|
@ -27,7 +29,7 @@ specWith name create_new_time parse_time nanoseconds_loss_in_precision=False =
Test.specify "should handle errors when creating a time" <|
case create_new_time 24 0 0 . catch of
Time_Error.Error msg ->
msg . should_equal "Invalid value for HourOfDay (valid values 0 - 23): 24"
msg.to_text . contains "24" . should_not_equal -1
result ->
Test.fail ("Unexpected result: " + result.to_text)
@ -281,4 +283,19 @@ java_parse time_text pattern=Nothing =
formatter = DateTimeFormatter.ofPattern pattern
LocalTime.parse time_text (formatter.withLocale Locale.default.java_locale)
python_time hour minute=0 second=0 nanoOfSecond=0 =
Panic.catch Any (python_time_impl hour minute second nanoOfSecond) (err -> Error.throw (Time_Error.Error <| err.payload))
python_parse time_text pattern=Nothing =
t = Panic.catch Any handler=(err -> Error.throw (Time_Error.Error err.payload.getMessage)) <|
if pattern.is_nothing then LocalTime.parse time_text else
formatter = DateTimeFormatter.ofPattern pattern
LocalTime.parse time_text (formatter.withLocale Locale.default.java_locale)
python_time t.hour t.minute t.second t.nanosecond
main = Test_Suite.run_main spec
foreign python python_time_impl hour minute second nanoOfSecond = """
import datetime
t = datetime.time(hour, minute, second, int(nanoOfSecond / 1000))
return t