Builtin Date_Time, Time_Of_Day, Zone (#3658)

* Builtin Date_Time, Time_Of_Day, Zone

Improved polyglot support for Date_Time (formerly Time), Time_Of_Day and
Zone. This follows the pattern introduced for Enso Date.

Minor caveat - in tests for Date, had to bend a lot for JS Date to pass.
This is because JS Date is not really only a Date, but also a Time and
Timezone, previously we just didn't consider the latter.
Also, JS Date does not deal well with setting timezones so the trick I
used is to first call foreign function returning a polyglot JS Date,
which is converted to ZonedDateTime and only then set the correct
timezone. That way none of the existing tests had to be changes or
special cased.

Additionally, JS deals with milliseconds rather than nanoseconds so
there is loss in precision, as noted in Time_Spec.

* Add tests for Java's LocalTime

* changelog

* Make date formatters in table happy

* PR review, add more tests for zone

* More tests and fixed a bug in column reader

Column reader didn't take into account timezone but that was a mistake
since then it wouldn't map to Enso's Date_Time.
Added tests that check it now.

* remove redundant conversion

* Update distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time.enso

Co-authored-by: Radosław Waśko <radoslaw.wasko@enso.org>

* First round of addressing PR review

* don't leak java exceptions in Zone

* Move Date_Time to top-level module

* PR review

Co-authored-by: Radosław Waśko <radoslaw.wasko@enso.org>
Co-authored-by: Jaroslav Tulach <jaroslav.tulach@enso.org>
This commit is contained in:
Hubert Plociniczak 2022-08-24 12:31:29 +02:00 committed by GitHub
parent 4b9c91626e
commit d87a32d019
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 1854 additions and 672 deletions

View File

@ -327,6 +327,8 @@
- [Support importing module methods][3633] - [Support importing module methods][3633]
- [Support Autosave for open buffers][3637] - [Support Autosave for open buffers][3637]
- [Support pattern matching on constants][3641] - [Support pattern matching on constants][3641]
- [Builtin Date_Time, Time_Of_Day and Zone types for better polyglot
support][3658]
[3227]: https://github.com/enso-org/enso/pull/3227 [3227]: https://github.com/enso-org/enso/pull/3227
[3248]: https://github.com/enso-org/enso/pull/3248 [3248]: https://github.com/enso-org/enso/pull/3248
@ -369,6 +371,7 @@
[3633]: https://github.com/enso-org/enso/pull/3633 [3633]: https://github.com/enso-org/enso/pull/3633
[3637]: https://github.com/enso-org/enso/pull/3637 [3637]: https://github.com/enso-org/enso/pull/3637
[3633]: https://github.com/enso-org/enso/pull/3641 [3633]: https://github.com/enso-org/enso/pull/3641
[3658]: https://github.com/enso-org/enso/pull/3658
# Enso 2.0.0-alpha.18 (2021-10-12) # Enso 2.0.0-alpha.18 (2021-10-12)

View File

@ -1,481 +1,9 @@
from Standard.Base import all
import Standard.Base.Data.Time.Duration
import Standard.Base.Data.Time.Time_Of_Day
import Standard.Base.Data.Time.Zone
polyglot java import java.time.format.DateTimeFormatter
polyglot java import java.time.ZonedDateTime
polyglot java import org.enso.base.Time_Utils
## ALIAS Current Time
Obtains the current date-time from the system clock in the system timezone.
> Example
Get the current time
import Standard.Base.Data.Time
example_now = Time.now
now : Time
now = Time ZonedDateTime.now
## Obtains an instance of `Time` from a year, month, day, hour, minute,
second, nanosecond and timezone.
Arguments:
- month: the month-of-year to represent, from 1 (January) to 12 (December)
- day: the day-of-month to represent, from 1 to 31 and must be valid for the
year and month
- hour: the hour-of-day to represent, from 0 to 23
- minute: the minute-of-hour to represent, from 0 to 59
- second: the second-of-minute to represent, from 0 to 59
- nanosecond: the nano-of-second to represent, from 0 to 999,999,999
- zone: the timezone
Returns a `Time_Error` if the provided time cannot be represented.
> Example
Create a new zoned date time at Unix epoch.
import Standard.Base.Data.Time
import Standard.Base.Data.Time.Zone
example_new = Time.new 1970 (zone = Zone.utc)
> Example
Get the 5 August 1986 at midnight.
import Standard.Base.Data.Time
import Standard.Base.Data.Time.Zone
example_new = Time.new 1986 8 5
new : Integer -> Integer -> Integer -> Integer -> Integer -> Integer -> Integer -> Zone -> Time ! Time_Error
new year (month = 1) (day = 1) (hour = 0) (minute = 0) (second = 0) (nanosecond = 0) (zone = Zone.system) =
Panic.catch_java Any (Time (ZonedDateTime.of year month day hour minute second nanosecond zone.internal_zone_id)) java_exception->
Error.throw (Time_Error java_exception.getMessage)
## ALIAS Time from Text
Obtains an instance of `Time` from a text such as
"2007-12-03T10:15:30+01:00 Europe/Paris".
Arguments:
- text: The text representing the time to be parsed.
- pattern: The pattern to use for parsing the input text.
- locale: The locale in which the pattern should be interpreted.
? Pattern Syntax
For the list of accepted symbols in pattern refer to `Time.format` doc.
? Default Time Format
The text must represent a valid date-time and is parsed using the ISO-8601
extended offset date-time format to add the timezone. The section in square
brackets is not part of the ISO-8601 standard. The format consists of:
- The ISO offset date time.
- If the zone ID is not available or is a zone offset then the format is
complete.
- An open square bracket '['.
- The zone ID. This is not part of the ISO-8601 standard. Parsing is case
sensitive.
- A close square bracket ']'.
This method will return a `Time_Error` if the provided time cannot be parsed
using the above format.
> Example
Parse UTC time.
import Standard.Base.Data.Time
example_parse = Time.parse "2020-10-01T04:11:12Z"
> Example
Parse UTC-04:00 time.
import Standard.Base.Data.Time
example_parse = Time.parse "2020-10-01T04:11:12-04:00"
> Example
Parse UTC-04:00 time specifying New York timezone.
import Standard.Base.Data.Time
example_parse = Time.parse "2020-10-01T04:11:12-04:00[America/New_York]"
> Example
Parse UTC-04:00 time with nanoseconds.
import Standard.Base.Data.Time
example_parse = Time.parse "2020-10-01T04:11:12.177528-04:00"
> Example
Recover from the parse error.
import Standard.Base.Data.Time
example_parse = Time.parse "2020-10-01" . catch Time_Error (_->Time.now)
> Example
Parse "2020-05-06 04:30:20" as Time
import Standard.Base.Data.Time
example_parse = Date.parse "2020-05-06 04:30:20" "yyyy-MM-dd HH:mm:ss"
> Example
Parse "06 of May 2020 at 04:30AM" as Time
import Standard.Base.Data.Time
example_parse =
Date.parse "06 of May 2020 at 04:30AM" "dd 'of' MMMM yyyy 'at' hh:mma"
parse : Text -> Text | Nothing -> Locale -> Time ! Time_Error
parse text pattern=Nothing locale=Locale.default =
result = Panic.recover Any <| case pattern of
Nothing -> Time_Utils.parse_time text
Text -> Time_Utils.parse_time_format text pattern locale.java_locale
_ -> Panic.throw (Time_Error "An invalid pattern was provided.")
Time result . map_error <| case _ of
Polyglot_Error err -> Time_Error err.getMessage
x -> x
type Time
## PRIVATE
A date-time with a timezone in the ISO-8601 calendar system, such as
"2007-12-03T10:15:30+01:00 Europe/Paris".
Arguments:
- internal_zoned_date_time: The internal repreentation of the time.
Time is a representation of a date-time with a timezone. This class
stores all date and time fields, to a precision of nanoseconds, and a
timezone, with a zone offset used to handle ambiguous local
date-times.
For example, the value "2nd October 2007 at 13:45.30.123456789 +02:00 in
the Europe/Paris timezone" can be stored as `Time`.
type Time internal_zoned_date_time
## Get the year portion of the time.
> Example
Get the current year.
import Standard.Base.Data.Time
example_year = Time.now.year
year : Integer
year self = self . internal_zoned_date_time . getYear
## Get the month portion of the time as a number from 1 to 12.
> Example
Get the current month.
import Standard.Base.Data.Time
example_month = Time.now.month
month : Integer
month self = self . internal_zoned_date_time . getMonthValue
## Get the day portion of the time.
> Example
Get the current day.
import Standard.Base.Data.Time
example_day = Time.now.day
day : Integer
day self = self . internal_zoned_date_time . getDayOfMonth
## Get the hour portion of the time.
> Example
Get the current hour.
import Standard.Base.Data.Time
example_hour = Time.now.hour
hour : Integer
hour self = self . internal_zoned_date_time . getHour
## Get the minute portion of the time.
> Example
Get the current minute.
import Standard.Base.Data.Time
example_minute = Time.now.minute
minute : Integer
minute self = self . internal_zoned_date_time . getMinute
## Get the second portion of the time.
> Example
Get the current second.
import Standard.Base.Data.Time
example_second = Time.now.second
second : Integer
second self = self . internal_zoned_date_time . getSecond
## Get the nanosecond portion of the time.
> Example
Get the current nanosecond.
import Standard.Base.Data.Time
example_nanosecond = Time.now.nanosecond
nanosecond : Integer
nanosecond self = self . internal_zoned_date_time . getNano
## Get the timezone for the time.
> Example
Get the current timezone.
import Standard.Base.Data.Time
example_zone = Time.now.zone
zone : Zone
zone self = Zone.Zone self.internal_zoned_date_time.getZone
## Return the number of seconds from the Unix epoch.
> Example
Get the current number of seconds from the Unix epoch.
import Standard.Base.Data.Time
example_epoch = Time.now.to_epoch_seconds
to_epoch_seconds : Integer
to_epoch_seconds self = self . internal_zoned_date_time . toEpochSecond
## Return the number of milliseconds from the Unix epoch.
> Example
Get the current number of milliseconds from the unix epoch.
import Standard.Base.Data.Time
example_epoch = Time.now.to_epoch_milliseconds
to_epoch_milliseconds : Integer
to_epoch_milliseconds self = self . internal_zoned_date_time . toInstant . toEpochMilli
## Convert this point in time to time of day, discarding the time zone
information.
> Example
Convert the current time to a time of day.
import Standard.Base.Data.Time
example_time_of_day = Time.now.time_of_day
time_of_day : Time_Of_Day
time_of_day self = Time_Of_Day.Time_Of_Day self.internal_zoned_date_time.toLocalTime
## ALIAS Time to Date
Convert this point in time to date, discarding the time of day
information.
> Example
Convert the current time to a date.
import Standard.Base.Data.Time
example_date = Time.now.date
date : Date
date self = self.internal_zoned_date_time.toLocalDate
## ALIAS Change Time Zone
Convert the time instant to the same instant in the provided time zone.
Arguments:
- zone: The time-zone to convert the time instant into.
> Example
Convert time instance to -04:00 timezone.
import Standard.Base.Data.Time
import Standard.Base.Data.Time.Zone
exaomple_at_zone = Time.new 2020 . at_zone (Zone.new -4)
at_zone : Zone -> Time
at_zone self zone = Time (self.internal_zoned_date_time . withZoneSameInstant zone.internal_zone_id)
## Add the specified amount of time to this instant to produce a new instant.
Arguments:
- amount: The amount of time to add to this instant.
> Example
Add 15 years and 3 hours to a zoned date time.
import Standard.Base.Data.Time
import Standard.Base.Data.Time.Duration
example_plus = Time.new 2020 + 15.years + 3.hours
+ : Duration -> Time
+ self amount = Time (self . internal_zoned_date_time . plus amount.internal_period . plus amount.internal_duration)
## Subtract the specified amount of time from this instant to get a new
instant.
Arguments:
- amount: The amount of time to subtract from this instant.
> Example
Subtract 1 year and 9 months from a zoned date time.
import Standard.Base.Data.Time
import Standard.Base.Data.Time.Duration
example_minus = Time.new 2020 - 1.year - 9.months
- : Duration -> Time
- self amount = Time (self . internal_zoned_date_time . minus amount.internal_period . minus amount.internal_duration)
## Convert this time to text using the default formatter.
> Example
Convert the current time to text.
import Standard.Base.Data.Time
example_to_text = Time.now.to_text
to_text : Text
to_text self = Time_Utils.default_time_formatter . format self.internal_zoned_date_time
## Convert the time to JSON.
> Example
Convert the current time to JSON.
import Standard.Base.Data.Time
example_to_json = Time.now.to_json
to_json : Json.Object
to_json self = Json.from_pairs [["type", "Time"], ["year", self.year], ["month", self.month], ["day", self.day], ["hour", self.hour], ["minute", self.minute], ["second", self.second], ["nanosecond", self.nanosecond], ["zone", self.zone]]
## Format this time as text using the specified format specifier.
Arguments:
- pattern: The pattern that specifies how to format the time.
? Pattern Syntax
Patterns are based on a simple sequence of letters and symbols. For
example, "d MMM uuuu" will format "2011-12-03" as "3 Dec 2011".
The list of accepted symbols with examples:
- 'G', era, "AD; Anno Domini"
- 'u', year, "2004; 04"
- 'y', year-of-era, "2004; 04"
- 'D', day-of-year, "189"
- 'M/L', month-of-year, "7; 07; Jul; July; J"
- 'd', day-of-month, "10"
- 'g', modified-julian-day, "2451334"
- 'Q/q', quarter-of-year, "3; 03; Q3; 3rd quarter"
- 'Y', week-based-year, "1996; 96"
- 'w', week-of-week-based-year, "27"
- 'W', week-of-month, "4"
- 'E', day-of-week, "Tue; Tuesday; T"
- 'e/c', localized day-of-week, "2; 02; Tue; Tuesday; T"
- 'F', day-of-week-in-month, "3"
- 'a', am-pm-of-day, "PM"
- 'h', clock-hour-of-am-pm (1-12), "12"
- 'K', hour-of-am-pm (0-11), "0"
- 'k', clock-hour-of-day (1-24), "24"
- 'H', hour-of-day (0-23), "0"
- 'm', minute-of-hour, "30"
- 's', second-of-minute, "55"
- 'S', fraction-of-second, "978"
- 'A', milli-of-day, "1234"
- 'n', nano-of-second, "987654321"
- 'N', nano-of-day, "1234000000"
- 'V', time-zone ID, "America/Los_Angeles; Z; -08:30"
- 'v', generic time-zone name, "Pacific Time; PT"
- 'z', time-zone name, "Pacific Standard Time; PST"
- 'O', localized zone-offset, "GMT+8; GMT+08:00; UTC-08:00"
- 'X', zone-offset 'Z' for zero, "Z; -08; -0830; -08:30; -083015; -08:30:15"
- 'x', zone-offset, "+0000; -08; -0830; -08:30; -083015; -08:30:15"
- 'Z', zone-offset, "+0000; -0800; -08:00"
- 'p', pad next, "1"
- ''', (single quote) escape for text, "'Text'"
- '''', (double quote) single quote, "'"
- '[', optional section start
- ']', optional section end
The count of pattern letters determines the format.
> Example
Format "2020-10-08T16:41:13+03:00[Europe/Moscow]" as
"2020-10-08T16:41:13+03:00[Europe/Moscow]".
import Standard.Base.Data.Time
example_format =
Time.parse "2020-10-08T16:41:13+03:00[Europe/Moscow]" . format "yyyy-MM-dd'T'HH:mm:ssZZZZ'['VV']'"
> Example
Format "2020-10-08T16:41:13+03:00[Europe/Moscow]" as
"Thursday October 8 4:41 PM".
import Standard.Base.Data.Time
example_format =
Time.parse "2020-10-08T16:41:13+03:00[Europe/Moscow]" . format "EEEE MMMM d h:mm a"
> Example
Format "2020-10-08T16:41:13+03:00[Europe/Moscow]" as
"Thu Oct 8 (16:41)".
import Standard.Base.Data.Time
example_format =
Time.parse "2020-10-08T16:41:13+03:00[Europe/Moscow]" . format "EEE MMM d (HH:mm)"
format : Text -> Text
format self pattern =
DateTimeFormatter.ofPattern pattern . format self.internal_zoned_date_time
## Compares `self` to `that` to produce an ordering.
Arguments:
- that: The other `Time` to compare against.
> Example
Compare two times for their ordering.
(Time.new 2000).compare_to (Time.new 2001)
compare_to : Time -> Ordering
compare_to self that =
sign = self.internal_zoned_date_time.compareTo that.internal_zoned_date_time
Ordering.from_sign sign
## Compares two Time for equality.
== : Time -> Boolean
== self that = case that of
Time _ -> self.internal_zoned_date_time.equals that.internal_zoned_date_time
_ -> False
type Time_Error type Time_Error
## UNSTABLE ## UNSTABLE
An error produced while working with time. An error produced while working with time- and date-related methods.
Arguments: Arguments:
- error_message: The message for the error. - error_message: The message for the error.
type Time_Error error_message type Time_Error error_message

View File

@ -1,6 +1,7 @@
from Standard.Base import all from Standard.Base import all
import Standard.Base.Data.Time import Standard.Base.Data.Time
import Standard.Base.Data.Time.Date_Time
import Standard.Base.Data.Time.Duration import Standard.Base.Data.Time.Duration
import Standard.Base.Data.Time.Time_Of_Day import Standard.Base.Data.Time.Time_Of_Day
import Standard.Base.Data.Time.Zone import Standard.Base.Data.Time.Zone
@ -193,7 +194,7 @@ type Date
containing the first Thursday of the year. Therefore it is important to containing the first Thursday of the year. Therefore it is important to
properly specify the `locale` argument. properly specify the `locale` argument.
week_of_year : Locale.Locale -> Integer week_of_year : Locale.Locale -> Integer
week_of_year self locale=Locale.default = Time_Utils.week_of_year self locale.java_locale week_of_year self locale=Locale.default = Time_Utils.week_of_year_localdate self locale.java_locale
## ALIAS Date to Time ## ALIAS Date to Time
@ -210,8 +211,8 @@ type Date
import Standard.Base.Data.Time.Zone import Standard.Base.Data.Time.Zone
example_to_time = Date.new 2020 2 3 . to_time Time_Of_Day.new Zone.utc example_to_time = Date.new 2020 2 3 . to_time Time_Of_Day.new Zone.utc
to_time : Time_Of_Day -> Zone -> Time to_time : Time_Of_Day -> Zone -> Date_Time
to_time self time_of_day (zone = Zone.system) = Time.Time (Time_Utils.date_with_time self time_of_day.internal_local_time zone.internal_zone_id) to_time self time_of_day (zone=Zone.system) = self.to_time_builtin time_of_day zone
## Add the specified amount of time to this instant to get another date. ## Add the specified amount of time to this instant to get another date.
@ -226,7 +227,7 @@ type Date
example_add = Date.new 2020 + 6.months example_add = Date.new 2020 + 6.months
+ : Duration -> Date + : Duration -> Date
+ self amount = if amount.is_time then Error.throw (Time.Time_Error "Date does not support time intervals") else + self amount = if amount.is_time then Error.throw (Time.Time_Error "Date does not support time intervals") else
(Time_Utils.date_adjust self 1 amount.internal_period) . internal_local_date (Time_Utils.date_adjust self Time_Utils.AdjustOp.PLUS amount.internal_period) . internal_local_date
## Subtract the specified amount of time from this instant to get another ## Subtract the specified amount of time from this instant to get another
date. date.
@ -243,7 +244,7 @@ type Date
example_subtract = Date.new 2020 - 7.days example_subtract = Date.new 2020 - 7.days
- : Duration -> Date - : Duration -> Date
- self amount = if amount.is_time then Error.throw (Time.Time_Error "Date does not support time intervals") else - self amount = if amount.is_time then Error.throw (Time.Time_Error "Date does not support time intervals") else
(Time_Utils.date_adjust self -1 amount.internal_period) . internal_local_date (Time_Utils.date_adjust self Time_Utils.AdjustOp.MINUS amount.internal_period) . internal_local_date
## A Date to Json conversion. ## A Date to Json conversion.
@ -304,11 +305,11 @@ type Date
(Date.new 2000).compare_to (Date.new 2001) (Date.new 2000).compare_to (Date.new 2001)
compare_to : Date -> Ordering compare_to : Date -> Ordering
compare_to self that = compare_to self that =
sign = Time_Utils.compare_to self that sign = Time_Utils.compare_to_localdate self that
Ordering.from_sign sign Ordering.from_sign sign
## Compares two Dates for equality. ## Compares two Dates for equality.
== : Date -> Boolean == : Date -> Boolean
== self that = == self that =
sign = Time_Utils.compare_to self that sign = Time_Utils.compare_to_localdate self that
0 == sign 0 == sign

View File

@ -0,0 +1,483 @@
from Standard.Base import all
from Standard.Base.Data.Time import Time_Error
import Standard.Base.Data.Time.Duration
import Standard.Base.Data.Time.Time_Of_Day
import Standard.Base.Data.Time.Zone
polyglot java import java.time.format.DateTimeFormatter
polyglot java import java.time.ZonedDateTime
polyglot java import org.enso.base.Time_Utils
## ALIAS Current Time
Obtains the current date-time from the system clock in the system timezone.
> Example
Get the current time
import Standard.Base.Data.Time
example_now = Date_Time.now
now : Date_Time
now = @Builtin_Method "Date_Time.now"
## Obtains an instance of `Date_Time` from a year, month, day, hour, minute,
second, nanosecond and timezone.
Arguments:
- month: the month-of-year to represent, from 1 (January) to 12 (December)
- day: the day-of-month to represent, from 1 to 31 and must be valid for the
year and month
- hour: the hour-of-day to represent, from 0 to 23
- minute: the minute-of-hour to represent, from 0 to 59
- second: the second-of-minute to represent, from 0 to 59
- nanosecond: the nano-of-second to represent, from 0 to 999,999,999
- zone: the timezone
Returns a `Time_Error` if the provided time cannot be represented.
> Example
Create a new zoned date time at Unix epoch.
import Standard.Base.Data.Time.Date_Time
import Standard.Base.Data.Time.Zone
example_new = Date_Time.new 1970 (zone = Zone.utc)
> Example
Get the 5 August 1986 at midnight.
import Standard.Base.Data.Time.Date_Time
import Standard.Base.Data.Time.Zone
example_new = Date_Time.new 1986 8 5
new : Integer -> Integer -> Integer -> Integer -> Integer -> Integer -> Integer -> Zone -> Date_Time ! Time_Error
new year (month = 1) (day = 1) (hour = 0) (minute = 0) (second = 0) (nanosecond = 0) (zone = Zone.system) =
Panic.catch_java Any (Date_Time.new_builtin year month day hour minute second nanosecond zone) java_exception->
Error.throw (Time_Error java_exception.getMessage)
## ALIAS Time from Text
Obtains an instance of `Time` from a text such as
"2007-12-03T10:15:30+01:00 Europe/Paris".
Arguments:
- text: The text representing the time to be parsed.
- pattern: The pattern to use for parsing the input text.
- locale: The locale in which the pattern should be interpreted.
? Pattern Syntax
For the list of accepted symbols in pattern refer to `Time.format` doc.
? Default Date_Time Format
The text must represent a valid date-time and is parsed using the ISO-8601
extended offset date-time format to add the timezone. The section in square
brackets is not part of the ISO-8601 standard. The format consists of:
- The ISO offset date time.
- If the zone ID is not available or is a zone offset then the format is
complete.
- An open square bracket '['.
- The zone ID. This is not part of the ISO-8601 standard. Parsing is case
sensitive.
- A close square bracket ']'.
This method will return a `Time_Error` if the provided time cannot be parsed
using the above format.
> Example
Parse UTC time.
import Standard.Base.Data.Time
example_parse = Date_Time.parse "2020-10-01T04:11:12Z"
> Example
Parse UTC-04:00 time.
import Standard.Base.Data.Time
example_parse = Date_Time.parse "2020-10-01T04:11:12-04:00"
> Example
Parse UTC-04:00 time specifying New York timezone.
import Standard.Base.Data.Time
example_parse = Date_Time.parse "2020-10-01T04:11:12-04:00[America/New_York]"
> Example
Parse UTC-04:00 time with nanoseconds.
import Standard.Base.Data.Time
example_parse = Date_Time.parse "2020-10-01T04:11:12.177528-04:00"
> Example
Recover from the parse error.
import Standard.Base.Data.Time
example_parse = Date_Time.parse "2020-10-01" . catch Time_Error (_->Date_Time.now)
> Example
Parse "2020-05-06 04:30:20" as Date_Time
import Standard.Base.Data.Time
example_parse = Date.parse "2020-05-06 04:30:20" "yyyy-MM-dd HH:mm:ss"
> Example
Parse "06 of May 2020 at 04:30AM" as Date_Tme
import Standard.Base.Data.Time
example_parse =
Date_Time.parse "06 of May 2020 at 04:30AM" "dd 'of' MMMM yyyy 'at' hh:mma"
parse : Text -> Text | Nothing -> Locale -> Date_Time ! Time_Error
parse text pattern=Nothing locale=Locale.default =
Panic.catch_java Any handler=(java_exception -> Error.throw (Time_Error java_exception.getMessage)) <|
case pattern of
Nothing -> Date_Time.parse_builtin text
Text -> Time_Utils.parse_datetime_format text pattern locale.java_locale
type Date_Time
## PRIVATE
A date-time with a timezone in the ISO-8601 calendar system, such as
"2007-12-03T10:15:30+01:00 Europe/Paris".
Time is a representation of a date-time with a timezone. This class
stores all date and time fields, to a precision of nanoseconds, and a
timezone, with a zone offset used to handle ambiguous local
date-times.
For example, the value "2nd October 2007 at 13:45.30.123456789 +02:00 in
the Europe/Paris timezone" can be stored as `Time`.
@Builtin_Type
type Date_Time
## Get the year portion of the time.
> Example
Get the current year.
import Standard.Base.Data.Time
example_year = Date_Time.now.year
year : Integer
year self = @Builtin_Method "Date_Time.year"
## Get the month portion of the time as a number from 1 to 12.
> Example
Get the current month.
import Standard.Base.Data.Time
example_month = Date_Time.now.month
month : Integer
month self = @Builtin_Method "Date_Time.month"
## Get the day portion of the time.
> Example
Get the current day.
import Standard.Base.Data.Time
example_day = Date_Time.now.day
day : Integer
day self = @Builtin_Method "Date_Time.day"
## Get the hour portion of the time.
> Example
Get the current hour.
import Standard.Base.Data.Time
example_hour = Date_Time.now.hour
hour : Integer
hour self = @Builtin_Method "Date_Time.hour"
## Get the minute portion of the time.
> Example
Get the current minute.
import Standard.Base.Data.Time
example_minute = Date_Time.now.minute
minute : Integer
minute self = @Builtin_Method "Date_Time.minute"
## Get the second portion of the time.
> Example
Get the current second.
import Standard.Base.Data.Time
example_second = Date_Time.now.second
second : Integer
second self = @Builtin_Method "Date_Time.second"
## Get the nanosecond portion of the time.
> Example
Get the current nanosecond.
import Standard.Base.Data.Time
example_nanosecond = Date_Time.now.nanosecond
nanosecond : Integer
nanosecond self = @Builtin_Method "Date_Time.nanosecond"
## Get the timezone for the time.
> Example
Get the current timezone.
import Standard.Base.Data.Time
example_zone = Date_Time.now.zone
zone : Zone
zone self = @Builtin_Method "Date_Time.zone"
## Return the number of seconds from the Unix epoch.
> Example
Get the current number of seconds from the Unix epoch.
import Standard.Base.Data.Time
example_epoch = Date_Time.now.to_epoch_seconds
to_epoch_seconds : Integer
to_epoch_seconds self = @Builtin_Method "Date_Time.to_epoch_seconds"
## Return the number of milliseconds from the Unix epoch.
> Example
Get the current number of milliseconds from the unix epoch.
import Standard.Base.Data.Time
example_epoch = Date_Time.now.to_epoch_milliseconds
to_epoch_milliseconds : Integer
to_epoch_milliseconds self = @Builtin_Method "Date_Time.to_epoch_milliseconds"
## Convert this point in time to time of day, discarding the time zone
information.
> Example
Convert the current time to a time of day.
import Standard.Base.Data.Time
example_time_of_day = Date_Time.now.time_of_day
time_of_day : Time_Of_Day
time_of_day self = self.to_localtime_builtin
## Returns the number of week of year this date falls into.
Arguments:
- locale: the locale used to define the notion of weeks of year.
! Locale Dependency
Note that this operation is locale-specific. It varies both by the
local definition of the first day of week and the definition of the
first week of year. For example, in the US, the first day of the week
is Sunday and week 1 is the week containing January 1. In the UK on the
other hand, the first day of the week is Monday, and week 1 is the week
containing the first Thursday of the year. Therefore it is important to
properly specify the `locale` argument.
week_of_year : Locale.Locale -> Integer
week_of_year self locale=Locale.default = Time_Utils.week_of_year_zoneddatetime self locale.java_locale
## ALIAS Time to Date
Convert this point in time to date, discarding the time of day
information.
> Example
Convert the current time to a date.
import Standard.Base.Data.Time
example_date = Date_Time.now.date
date : Date
date self = self.to_localdate_builtin
## ALIAS Change Time Zone
Convert the time instant to the same instant in the provided time zone.
Arguments:
- zone: The timezone to convert the time instant into.
> Example
Convert time instance to -04:00 timezone.
import Standard.Base.Data.Time.Date_Time
import Standard.Base.Data.Time.Zone
example_at_zone = Date_Time.new 2020 . at_zone (Zone.new -4)
at_zone : Zone -> Date_Time
at_zone self zone = @Builtin_Method "Date_Time.at_zone"
## Add the specified amount of time to this instant to produce a new instant.
Arguments:
- amount: The amount of time to add to this instant.
> Example
Add 15 years and 3 hours to a zoned date time.
import Standard.Base.Data.Time.Date_Time
import Standard.Base.Data.Time.Duration
example_plus = Date_Time.new 2020 + 15.years + 3.hours
+ : Duration -> Date_Time
+ self amount =
Time_Utils.datetime_adjust self Time_Utils.AdjustOp.PLUS amount.internal_period amount.internal_duration
## Subtract the specified amount of time from this instant to get a new
instant.
Arguments:
- amount: The amount of time to subtract from this instant.
> Example
Subtract 1 year and 9 months from a zoned date time.
import Standard.Base.Data.Time.Date_Time
import Standard.Base.Data.Time.Duration
example_minus = Date_Time.new 2020 - 1.year - 9.months
- : Duration -> Date_Time
- self amount =
Time_Utils.datetime_adjust self Time_Utils.AdjustOp.MINUS amount.internal_period amount.internal_duration
## Convert this time to text using the default formatter.
> Example
Convert the current time to text.
import Standard.Base.Data.Time
example_to_text = Date_Time.now.to_text
to_text : Text
to_text self = @Builtin_Method "Date_Time.to_text"
## Convert the time to JSON.
> Example
Convert the current time to JSON.
import Standard.Base.Data.Time
example_to_json = Date_Time.now.to_json
to_json : Json.Object
to_json self = Json.from_pairs [["type", "Time"], ["year", self.year], ["month", self.month], ["day", self.day], ["hour", self.hour], ["minute", self.minute], ["second", self.second], ["nanosecond", self.nanosecond], ["zone", self.zone]]
## Format this time as text using the specified format specifier.
Arguments:
- pattern: The pattern that specifies how to format the time.
? Pattern Syntax
Patterns are based on a simple sequence of letters and symbols. For
example, "d MMM uuuu" will format "2011-12-03" as "3 Dec 2011".
The list of accepted symbols with examples:
- 'G', era, "AD; Anno Domini"
- 'u', year, "2004; 04"
- 'y', year-of-era, "2004; 04"
- 'D', day-of-year, "189"
- 'M/L', month-of-year, "7; 07; Jul; July; J"
- 'd', day-of-month, "10"
- 'g', modified-julian-day, "2451334"
- 'Q/q', quarter-of-year, "3; 03; Q3; 3rd quarter"
- 'Y', week-based-year, "1996; 96"
- 'w', week-of-week-based-year, "27"
- 'W', week-of-month, "4"
- 'E', day-of-week, "Tue; Tuesday; T"
- 'e/c', localized day-of-week, "2; 02; Tue; Tuesday; T"
- 'F', day-of-week-in-month, "3"
- 'a', am-pm-of-day, "PM"
- 'h', clock-hour-of-am-pm (1-12), "12"
- 'K', hour-of-am-pm (0-11), "0"
- 'k', clock-hour-of-day (1-24), "24"
- 'H', hour-of-day (0-23), "0"
- 'm', minute-of-hour, "30"
- 's', second-of-minute, "55"
- 'S', fraction-of-second, "978"
- 'A', milli-of-day, "1234"
- 'n', nano-of-second, "987654321"
- 'N', nano-of-day, "1234000000"
- 'V', timezone ID, "America/Los_Angeles; Z; -08:30"
- 'v', generic timezone name, "Pacific Time; PT"
- 'z', timezone name, "Pacific Standard Time; PST"
- 'O', localized zone-offset, "GMT+8; GMT+08:00; UTC-08:00"
- 'X', zone-offset 'Z' for zero, "Z; -08; -0830; -08:30; -083015; -08:30:15"
- 'x', zone-offset, "+0000; -08; -0830; -08:30; -083015; -08:30:15"
- 'Z', zone-offset, "+0000; -0800; -08:00"
- 'p', pad next, "1"
- ''', (single quote) escape for text, "'Text'"
- '''', (double quote) single quote, "'"
- '[', optional section start
- ']', optional section end
The count of pattern letters determines the format.
> Example
Format "2020-10-08T16:41:13+03:00[Europe/Moscow]" as
"2020-10-08T16:41:13+03:00[Europe/Moscow]".
import Standard.Base.Data.Time
example_format =
Date_Time.parse "2020-10-08T16:41:13+03:00[Europe/Moscow]" . format "yyyy-MM-dd'T'HH:mm:ssZZZZ'['VV']'"
> Example
Format "2020-10-08T16:41:13+03:00[Europe/Moscow]" as
"Thursday October 8 4:41 PM".
import Standard.Base.Data.Time
example_format =
Date_Time.parse "2020-10-08T16:41:13+03:00[Europe/Moscow]" . format "EEEE MMMM d h:mm a"
> Example
Format "2020-10-08T16:41:13+03:00[Europe/Moscow]" as
"Thu Oct 8 (16:41)".
import Standard.Base.Data.Time
example_format =
Date_Time.parse "2020-10-08T16:41:13+03:00[Europe/Moscow]" . format "EEE MMM d (HH:mm)"
format : Text -> Text
format self pattern = @Builtin_Method "Date_Time.format"
## Compares `self` to `that` to produce an ordering.
Arguments:
- that: The other `Date_Time` to compare against.
> Example
Compare two times for their ordering.
(Date_Time.new 2000).compare_to (Date_Time.new 2001)
compare_to : Time -> Ordering
compare_to self that =
sign = Time_Utils.compare_to_zoneddatetime self that
Ordering.from_sign sign
## Compares two Date_Time for equality.
== : Date_Time -> Boolean
== self that =
sign = Time_Utils.compare_to_zoneddatetime self that
0 == sign

View File

@ -5,26 +5,26 @@ import Standard.Base.System
polyglot java import java.time.Duration as Java_Duration polyglot java import java.time.Duration as Java_Duration
polyglot java import java.time.Period as Java_Period polyglot java import java.time.Period as Java_Period
polyglot java import org.enso.base.Time_Utils
## Create an interval representing the duration between two points in time. ## Create an interval representing the duration between two points in time.
Arguments: Arguments:
- start_inclusive: The start time of the duration. - start_inclusive: The start time of the duration.
- end_inclusive: The end time of the duration. - end_inclusive: The end time of the duration.
- timezone_aware: Should the creation of the interval be timezone-aware.
> Example > Example
An hour interval between two points in time. An hour interval between two points in time.
import Standard.Base.Data.Time.Duration import Standard.Base.Data.Time.Duration
import Standard.Base.Data.Time import Standard.Base.Data.Time.Date_Time
example_between = Duration.between Time.now (Time.new 2010 10 20) example_between = Duration.between Date_Time.now (Date_Time.new 2010 10 20)
between : Time -> Time -> Duration between : Date_Time -> Date_Time -> Duration
between start_inclusive end_exclusive = between start_inclusive end_exclusive timezone_aware=True =
period = Java_Period.ofDays 0 . normalized period = Java_Period.ofDays 0 . normalized
start = start_inclusive.internal_zoned_date_time duration = Time_Utils.duration_between start_inclusive end_exclusive timezone_aware
end = end_exclusive.internal_zoned_date_time
duration = Java_Duration.between start end
Duration period duration Duration period duration

View File

@ -18,7 +18,7 @@ polyglot java import org.enso.base.Time_Utils
example_now = Time_Of_Day.now example_now = Time_Of_Day.now
now : Time_Of_Day now : Time_Of_Day
now = Time_Of_Day LocalTime.now now = @Builtin_Method "Time_Of_Day.now"
## Obtains an instance of `Time_Of_Day` from an hour, minute, second ## Obtains an instance of `Time_Of_Day` from an hour, minute, second
and nanosecond. and nanosecond.
@ -46,7 +46,7 @@ now = Time_Of_Day LocalTime.now
example_epoch = Time_Of_Day.new hour=9 minute=30 example_epoch = Time_Of_Day.new hour=9 minute=30
new : Integer -> Integer -> Integer -> Integer -> Time_Of_Day ! Time.Time_Error new : Integer -> Integer -> Integer -> Integer -> Time_Of_Day ! Time.Time_Error
new (hour = 0) (minute = 0) (second = 0) (nanosecond = 0) = new (hour = 0) (minute = 0) (second = 0) (nanosecond = 0) =
Panic.catch_java Any (Time_Of_Day (LocalTime.of hour minute second nanosecond)) java_exception-> Panic.catch_java Any (Time_Of_Day.new_builtin hour minute second nanosecond) java_exception->
Error.throw (Time.Time_Error java_exception.getMessage) Error.throw (Time.Time_Error java_exception.getMessage)
## Obtains an instance of `Time_Of_Day` from a text such as "10:15". ## Obtains an instance of `Time_Of_Day` from a text such as "10:15".
@ -112,15 +112,10 @@ new (hour = 0) (minute = 0) (second = 0) (nanosecond = 0) =
example_parse = Time_Of_Day.parse "4:30AM" "h:mma" example_parse = Time_Of_Day.parse "4:30AM" "h:mma"
parse : Text -> Text | Nothing -> Locale -> Time_Of_Day ! Time.Time_Error parse : Text -> Text | Nothing -> Locale -> Time_Of_Day ! Time.Time_Error
parse text pattern=Nothing locale=Locale.default = parse text pattern=Nothing locale=Locale.default =
result = Panic.recover Any <| case pattern of Panic.catch_java Any handler=(java_exception -> Error.throw (Time.Time_Error java_exception.getMessage)) <|
Nothing -> LocalTime.parse text case pattern of
Text -> Nothing -> Time_Of_Day.parse_builtin text
formatter = DateTimeFormatter.ofPattern pattern Text -> Time_Utils.parse_time text pattern locale.java_locale
LocalTime.parse text (formatter.withLocale locale.java_locale)
_ -> Panic.throw (Time.Time_Error "An invalid pattern was provided.")
Time_Of_Day result . map_error <| case _ of
Polyglot_Error err -> Time.Time_Error err.getMessage
x -> x
type Time_Of_Day type Time_Of_Day
@ -129,12 +124,10 @@ type Time_Of_Day
This type is a date-time object that represents a time, often viewed This type is a date-time object that represents a time, often viewed
as hour-minute-second. as hour-minute-second.
Arguments:
- internal_local_time: The internal representation of the time of day.
Time is represented to nanosecond precision. For example, the value Time is represented to nanosecond precision. For example, the value
"13:45.30.123456789" can be stored in a `Time_Of_Day`. "13:45.30.123456789" can be stored in a `Time_Of_Day`.
type Time_Of_Day internal_local_time @Builtin_Type
type Time_Of_Day
## Get the hour portion of the time of day. ## Get the hour portion of the time of day.
@ -145,7 +138,7 @@ type Time_Of_Day
example_hour = Time_Of_Day.now.hour example_hour = Time_Of_Day.now.hour
hour : Integer hour : Integer
hour self = self . internal_local_time . getHour hour self = @Builtin_Method "Time_Of_Day.hour"
## Get the minute portion of the time of day. ## Get the minute portion of the time of day.
@ -156,7 +149,7 @@ type Time_Of_Day
example_minute = Time_Of_Day.now.minute example_minute = Time_Of_Day.now.minute
minute : Integer minute : Integer
minute self = self . internal_local_time . getMinute minute self = @Builtin_Method "Time_Of_Day.minute"
## Get the second portion of the time of day. ## Get the second portion of the time of day.
@ -167,7 +160,7 @@ type Time_Of_Day
example_second = Time_Of_Day.now.second example_second = Time_Of_Day.now.second
second : Integer second : Integer
second self = self . internal_local_time . getSecond second self = @Builtin_Method "Time_Of_Day.second"
## Get the nanosecond portion of the time of day. ## Get the nanosecond portion of the time of day.
@ -178,7 +171,7 @@ type Time_Of_Day
example_nanosecond = Time_Of_Day.now.nanosecond example_nanosecond = Time_Of_Day.now.nanosecond
nanosecond : Integer nanosecond : Integer
nanosecond self = self . internal_local_time . getNano nanosecond self = @Builtin_Method "Time_Of_Day.nanosecond"
## Extracts the time as the number of seconds, from 0 to 24 * 60 * 60 - 1. ## Extracts the time as the number of seconds, from 0 to 24 * 60 * 60 - 1.
@ -189,7 +182,7 @@ type Time_Of_Day
example_to_seconds = Time_Of_Day.now.to_seconds example_to_seconds = Time_Of_Day.now.to_seconds
to_seconds : Integer to_seconds : Integer
to_seconds self = self . internal_local_time . toSecondOfDay to_seconds self = @Builtin_Method "Time_Of_Day.to_seconds"
## Combine this time of day with a date to create a point in time. ## Combine this time of day with a date to create a point in time.
@ -204,8 +197,7 @@ type Time_Of_Day
example_to_time = Time_Of_Day.new 12 30 . to_time (Date.new 2020) example_to_time = Time_Of_Day.new 12 30 . to_time (Date.new 2020)
to_time : Date -> Zone -> Time to_time : Date -> Zone -> Time
to_time self date (zone = Zone.system) = to_time self date (zone=Zone.system) = self.to_time_builtin date zone
Time.Time (self . internal_local_time . atDate date . atZone zone.internal_zone_id)
## Add the specified amount of time to this instant to get a new instant. ## Add the specified amount of time to this instant to get a new instant.
@ -220,7 +212,7 @@ type Time_Of_Day
example_plus = Time_Of_Day.new + 3.seconds example_plus = Time_Of_Day.new + 3.seconds
+ : Duration -> Time_Of_Day + : Duration -> Time_Of_Day
+ self amount = if amount.is_date then Error.throw (Time.Time_Error "Time_Of_Day does not support date intervals") else + self amount = if amount.is_date then Error.throw (Time.Time_Error "Time_Of_Day does not support date intervals") else
Time_Of_Day (self . internal_local_time . plus amount.internal_duration) Time_Utils.time_adjust self Time_Utils.AdjustOp.PLUS amount.internal_duration
## Subtract the specified amount of time from this instant to get a new ## Subtract the specified amount of time from this instant to get a new
instant. instant.
@ -237,7 +229,7 @@ type Time_Of_Day
example_minus = Time_Of_Day.now - 12.hours example_minus = Time_Of_Day.now - 12.hours
- : Duration -> Time_Of_Day - : Duration -> Time_Of_Day
- self amount = if amount.is_date then Error.throw (Time.Time_Error "Time_Of_Day does not support date intervals") else - self amount = if amount.is_date then Error.throw (Time.Time_Error "Time_Of_Day does not support date intervals") else
Time_Of_Day (self . internal_local_time . minus amount.internal_duration) Time_Utils.time_adjust self Time_Utils.AdjustOp.MINUS amount.internal_duration
## Format this time of day as text using the default formatter. ## Format this time of day as text using the default formatter.
@ -248,7 +240,7 @@ type Time_Of_Day
example_to_text = Time_Of_Day.now.to_text example_to_text = Time_Of_Day.now.to_text
to_text : Text to_text : Text
to_text self = Time_Utils.default_time_of_day_formatter . format self.internal_local_time to_text self = @Builtin_Method "Time_Of_Day.to_text"
## A Time_Of_Day to Json conversion. ## A Time_Of_Day to Json conversion.
@ -308,8 +300,7 @@ type Time_Of_Day
example_format = Time_Of_Day.new 16 21 10 . format "'hour:'h" example_format = Time_Of_Day.new 16 21 10 . format "'hour:'h"
format : Text -> Text format : Text -> Text
format self pattern = format self pattern = @Builtin_Method "Time_Of_Day.format"
DateTimeFormatter.ofPattern pattern . format self.internal_local_time
## Compares `self` to `that` to produce an ordering. ## Compares `self` to `that` to produce an ordering.
@ -327,11 +318,11 @@ type Time_Of_Day
time_1.compare_to time_2 time_1.compare_to time_2
compare_to : Time_Of_Day -> Ordering compare_to : Time_Of_Day -> Ordering
compare_to self that = compare_to self that =
sign = self.internal_local_time.compareTo that.internal_local_time sign = Time_Utils.compare_to_localtime self that
Ordering.from_sign sign Ordering.from_sign sign
## Compares two Time_Of_Day for equality. ## Compares two Time_Of_Day for equality.
== : Date -> Boolean == : Date -> Boolean
== self that = case that of == self that =
Time_Of_Day _ -> self.internal_local_time.equals that.internal_local_time sign = Time_Utils.compare_to_localtime self that
_ -> False 0 == sign

View File

@ -1,7 +1,10 @@
from Standard.Base import all from Standard.Base import all
import Standard.Base.Data.Time
polyglot java import java.time.ZoneId polyglot java import java.time.ZoneId
polyglot java import java.time.ZoneOffset polyglot java import java.time.ZoneOffset
polyglot java import org.enso.base.Time_Utils
## The system default timezone. ## The system default timezone.
@ -12,7 +15,7 @@ polyglot java import java.time.ZoneOffset
example_system = Zone.system example_system = Zone.system
system : Zone system : Zone
system = Zone ZoneId.systemDefault system = @Builtin_Method "Zone.system"
## ALIAS Current Time Zone ## ALIAS Current Time Zone
@ -58,7 +61,7 @@ utc = parse "UTC"
example_new = Zone.new 1 1 50 example_new = Zone.new 1 1 50
new : Integer -> Integer -> Integer -> Zone new : Integer -> Integer -> Integer -> Zone
new (hours = 0) (minutes = 0) (seconds = 0) = new (hours = 0) (minutes = 0) (seconds = 0) =
Zone (ZoneOffset.ofHoursMinutesSeconds hours minutes seconds . normalized) Zone.new_builtin hours minutes seconds
## ALIAS Time Zone from Text ## ALIAS Time Zone from Text
@ -95,7 +98,9 @@ new (hours = 0) (minutes = 0) (seconds = 0) =
example_parse = Zone.parse "+03:02:01" example_parse = Zone.parse "+03:02:01"
parse : Text -> Zone parse : Text -> Zone
parse text = Zone (ZoneId.of text) parse text =
Panic.catch_java Any handler=(java_exception -> Error.throw (Time.Time_Error java_exception.getMessage)) <|
Zone.parse_builtin text
type Zone type Zone
@ -109,7 +114,8 @@ type Zone
A time zone can be eiter offset-based like "-06:00" or id-based like A time zone can be eiter offset-based like "-06:00" or id-based like
"Europe/Paris". "Europe/Paris".
type Zone internal_zone_id @Builtin_Type
type Zone
## Get the unique timezone ID. ## Get the unique timezone ID.
@ -120,7 +126,7 @@ type Zone
example_zone_id = Zone.system.zone_id example_zone_id = Zone.system.zone_id
zone_id : Text zone_id : Text
zone_id self = self.internal_zone_id . getId zone_id self = @Builtin_Method "Zone.zone_id"
## Convert the time zone to JSON. ## Convert the time zone to JSON.
@ -132,3 +138,7 @@ type Zone
example_to_json = Zone.system.to_json example_to_json = Zone.system.to_json
to_json : Json.Object to_json : Json.Object
to_json self = Json.from_pairs [["type", "Zone"], ["id", self.zone_id]] to_json self = Json.from_pairs [["type", "Zone"], ["id", self.zone_id]]
## Compares two Zones for equality.
== : Zone -> Boolean
== self that = Time_Utils.equals_zone self that

View File

@ -335,8 +335,7 @@ type File
example_exists = Examples.csv.creation_time example_exists = Examples.csv.creation_time
creation_time : Time ! File_Error creation_time : Time ! File_Error
creation_time self = creation_time self =
handle_java_exceptions self <| handle_java_exceptions self <| self.creation_time_builtin
Time (self.creation_time_builtin)
## PRIVATE ## PRIVATE
@ -356,8 +355,7 @@ type File
example_exists = Examples.csv.last_modified_time example_exists = Examples.csv.last_modified_time
last_modified_time : Time ! File_Error last_modified_time : Time ! File_Error
last_modified_time self = last_modified_time self =
handle_java_exceptions self <| handle_java_exceptions self <| self.last_modified_time_builtin
Time (self.last_modified_time_builtin)
## PRIVATE ## PRIVATE

View File

@ -1,6 +1,6 @@
from Standard.Base import all from Standard.Base import all
import Standard.Base.Data.Time import Standard.Base.Data.Time.Date_Time
import Standard.Base.Network.Http import Standard.Base.Network.Http
import Standard.Base.System.Platform import Standard.Base.System.Platform
import Standard.Base.Data.Time.Duration import Standard.Base.Data.Time.Duration
@ -70,7 +70,7 @@ scratch_file =
## An example duration for experimenting with duration APIs. ## An example duration for experimenting with duration APIs.
duration : Duration duration : Duration
duration = Duration.between (Time.new 2020 10 20) Time.now duration = Duration.between (Date_Time.new 2020 10 20) Date_Time.now
## An example amount of JSON as text. ## An example amount of JSON as text.
json_text : Text json_text : Text

View File

@ -9,7 +9,7 @@
import Standard.Base.Data.Time import Standard.Base.Data.Time
example_now = Time.now example_now = Date_Time.now
> Example > Example
Parse UTC time. Parse UTC time.
@ -21,14 +21,14 @@
> Example > Example
Convert time instance to -04:00 timezone. Convert time instance to -04:00 timezone.
import Standard.Base.Data.Time import Standard.Base.Data.Time.Date_Time
import Standard.Base.Data.Time.Zone import Standard.Base.Data.Time.Zone
exaomple_at_zone = Time.new 2020 . at_zone (Zone.new -4) exaomple_at_zone = Date_Time.new 2020 . at_zone (Zone.new -4)
> Example > Example
Convert the current time to a date. Convert the current time to a date.
import Standard.Base.Data.Time import Standard.Base.Data.Time
example_date = Time.now.date example_date = Date_Time.now.date

View File

@ -1,6 +1,6 @@
from Standard.Base import all from Standard.Base import all
from Standard.Base.Data.Time import Time from Standard.Base.Data.Time import Date_Time
from Standard.Base.Data.Time.Time_Of_Day import Time_Of_Day from Standard.Base.Data.Time.Time_Of_Day import Time_Of_Day
import Standard.Base.Error.Common as Errors import Standard.Base.Error.Common as Errors
@ -67,7 +67,7 @@ type Data_Formatter
By default, a warning is issued, but the operation proceeds. By default, a warning is issued, but the operation proceeds.
If set to `Report_Error`, the operation fails with a dataflow error. If set to `Report_Error`, the operation fails with a dataflow error.
If set to `Ignore`, the operation proceeds without errors or warnings. If set to `Ignore`, the operation proceeds without errors or warnings.
parse : Text -> (Auto|Integer|Number|Date|Time|DateTime|Boolean) -> Problem_Behavior -> Any parse : Text -> (Auto|Integer|Number|Date|Date_Time|Time_Of_Day|Boolean) -> Problem_Behavior -> Any
parse self text datatype=Auto on_problems=Problem_Behavior.Report_Warning = parse self text datatype=Auto on_problems=Problem_Behavior.Report_Warning =
parser = case datatype of parser = case datatype of
Auto -> self.make_auto_parser Auto -> self.make_auto_parser
@ -171,14 +171,13 @@ type Data_Formatter
## PRIVATE ## PRIVATE
make_datatype_parser self datatype = case datatype of make_datatype_parser self datatype = case datatype of
Integer -> self.make_integer_parser Integer -> self.make_integer_parser
Decimal -> self.make_decimal_parser Decimal -> self.make_decimal_parser
Boolean -> self.make_boolean_parser Boolean -> self.make_boolean_parser
_ -> Date -> self.make_date_parser
if datatype == Date then self.make_date_parser else Date_Time -> self.make_datetime_parser
if datatype == Time then self.make_datetime_parser else Time_Of_Day -> self.make_time_parser
if datatype == Time_Of_Day then self.make_time_parser else _ -> Error.throw (Illegal_Argument_Error "Unsupported datatype: "+datatype.to_text)
Error.throw (Illegal_Argument_Error "Unsupported datatype: "+datatype.to_text)
## PRIVATE ## PRIVATE
get_specific_type_parsers self = get_specific_type_parsers self =

View File

@ -1,5 +1,5 @@
from Standard.Base import all from Standard.Base import all
import Standard.Base.Data.Time import Standard.Base.Data.Time.Date_Time
import Standard.Base.Runtime.State import Standard.Base.Runtime.State
import Standard.Base.System import Standard.Base.System
@ -769,7 +769,7 @@ wrap_junit_testsuites config builder ~action =
report_pending_group : Text -> Text -> Suite_Config -> (StringBuilder|Nothing) -> Nothing report_pending_group : Text -> Text -> Suite_Config -> (StringBuilder|Nothing) -> Nothing
report_pending_group name reason config builder = report_pending_group name reason config builder =
if config.should_output_junit then if config.should_output_junit then
builder.append (' <testsuite name="' + (escape_xml name) + '" timestamp="' + (Time.now.format "yyyy-MM-dd'T'HH:mm:ss") + '">\n') builder.append (' <testsuite name="' + (escape_xml name) + '" timestamp="' + (Date_Time.now.format "yyyy-MM-dd'T'HH:mm:ss") + '">\n')
builder.append (' <testcase name="' + (escape_xml name) + '"><skipped message="' + (escape_xml reason) + '" /></testcase>\n') builder.append (' <testcase name="' + (escape_xml name) + '"><skipped message="' + (escape_xml reason) + '" /></testcase>\n')
builder.append ' </testsuite>\n' builder.append ' </testsuite>\n'
@ -782,7 +782,7 @@ report_pending_group name reason config builder =
Spec.print_report : Suite_Config -> (StringBuilder|Nothing) -> Nothing Spec.print_report : Suite_Config -> (StringBuilder|Nothing) -> Nothing
Spec.print_report self config builder = Spec.print_report self config builder =
if config.should_output_junit then if config.should_output_junit then
builder.append (' <testsuite name="' + (escape_xml self.name) + '" timestamp="' + (Time.now.format "yyyy-MM-dd'T'HH:mm:ss") + '"') builder.append (' <testsuite name="' + (escape_xml self.name) + '" timestamp="' + (Date_Time.now.format "yyyy-MM-dd'T'HH:mm:ss") + '"')
builder.append (' tests="' + self.behaviors.length.to_text + '"') builder.append (' tests="' + self.behaviors.length.to_text + '"')
builder.append (' disabled="' + self.behaviors.filter (x->(x.is_a Pending)) . length . to_text + '"') builder.append (' disabled="' + self.behaviors.filter (x->(x.is_a Pending)) . length . to_text + '"')
builder.append (' errors="' + self.behaviors.filter (x->(x.is_a Failure)) . length . to_text + '"') builder.append (' errors="' + self.behaviors.filter (x->(x.is_a Failure)) . length . to_text + '"')

View File

@ -16,6 +16,9 @@ import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage; import com.oracle.truffle.api.library.ExportMessage;
import com.oracle.truffle.api.profiles.BranchProfile; import com.oracle.truffle.api.profiles.BranchProfile;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZoneId;
import org.enso.interpreter.epb.node.ContextRewrapExceptionNode; import org.enso.interpreter.epb.node.ContextRewrapExceptionNode;
import org.enso.interpreter.epb.node.ContextRewrapNode; import org.enso.interpreter.epb.node.ContextRewrapNode;
@ -935,4 +938,54 @@ public class PolyglotProxy implements TruffleObject {
leaveOrigin(node, p); leaveOrigin(node, p);
} }
} }
@ExportMessage
boolean isTime(
@CachedLibrary("this.delegate") InteropLibrary times,
@CachedLibrary("this") InteropLibrary node) {
Object p = enterOrigin(node);
try {
return times.isTime(delegate);
} finally {
leaveOrigin(node, p);
}
}
@ExportMessage
LocalTime asTime(
@CachedLibrary("this.delegate") InteropLibrary times,
@CachedLibrary("this") InteropLibrary node)
throws UnsupportedMessageException {
Object p = enterOrigin(node);
try {
return times.asTime(delegate);
} finally {
leaveOrigin(node, p);
}
}
@ExportMessage
boolean isTimeZone(
@CachedLibrary("this.delegate") InteropLibrary times,
@CachedLibrary("this") InteropLibrary node) {
Object p = enterOrigin(node);
try {
return times.isTimeZone(delegate);
} finally {
leaveOrigin(node, p);
}
}
@ExportMessage
ZoneId asTimeZone(
@CachedLibrary("this.delegate") InteropLibrary times,
@CachedLibrary("this") InteropLibrary node)
throws UnsupportedMessageException {
Object p = enterOrigin(node);
try {
return times.asTimeZone(delegate);
} finally {
leaveOrigin(node, p);
}
}
} }

View File

@ -12,6 +12,7 @@ import com.oracle.truffle.api.profiles.BranchProfile;
import com.oracle.truffle.api.profiles.ConditionProfile; import com.oracle.truffle.api.profiles.ConditionProfile;
import com.oracle.truffle.api.source.SourceSection; import com.oracle.truffle.api.source.SourceSection;
import java.time.ZoneId;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
@ -23,8 +24,7 @@ import org.enso.interpreter.runtime.Context;
import org.enso.interpreter.runtime.callable.UnresolvedSymbol; import org.enso.interpreter.runtime.callable.UnresolvedSymbol;
import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo; import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo;
import org.enso.interpreter.runtime.callable.function.Function; import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.data.ArrayRope; import org.enso.interpreter.runtime.data.*;
import org.enso.interpreter.runtime.data.EnsoDate;
import org.enso.interpreter.runtime.data.text.Text; import org.enso.interpreter.runtime.data.text.Text;
import org.enso.interpreter.runtime.error.*; import org.enso.interpreter.runtime.error.*;
import org.enso.interpreter.runtime.library.dispatch.MethodDispatchLibrary; import org.enso.interpreter.runtime.library.dispatch.MethodDispatchLibrary;
@ -260,6 +260,118 @@ public abstract class InvokeMethodNode extends BaseNode {
} }
} }
@Specialization(
guards = {
"!methods.hasFunctionalDispatch(self)",
"!methods.hasSpecialDispatch(self)",
"getPolyglotCallType(self, symbol.getName(), interop) == CONVERT_TO_DATE_TIME"
})
Stateful doConvertDateTime(
VirtualFrame frame,
Object state,
UnresolvedSymbol symbol,
Object self,
Object[] arguments,
@CachedLibrary(limit = "10") MethodDispatchLibrary methods,
@CachedLibrary(limit = "1") MethodDispatchLibrary dateDispatch,
@CachedLibrary(limit = "10") InteropLibrary interop) {
var ctx = Context.get(this);
try {
var hostLocalDate = interop.asDate(self);
var hostLocalTime = interop.asTime(self);
var hostZonedDateTime = hostLocalDate.atTime(hostLocalTime).atZone(ZoneId.systemDefault());
var dateTime = new EnsoDateTime(hostZonedDateTime);
Function function = dateDispatch.getFunctionalDispatch(dateTime, symbol);
arguments[0] = dateTime;
return invokeFunctionNode.execute(function, frame, state, arguments);
} catch (MethodDispatchLibrary.NoSuchMethodException | UnsupportedMessageException e) {
throw new PanicException(ctx.getBuiltins().error().makeNoSuchMethodError(self, symbol), this);
}
}
@Specialization(
guards = {
"!methods.hasFunctionalDispatch(self)",
"!methods.hasSpecialDispatch(self)",
"getPolyglotCallType(self, symbol.getName(), interop) == CONVERT_TO_ZONED_DATE_TIME"
})
Stateful doConvertZonedDateTime(
VirtualFrame frame,
Object state,
UnresolvedSymbol symbol,
Object self,
Object[] arguments,
@CachedLibrary(limit = "10") MethodDispatchLibrary methods,
@CachedLibrary(limit = "1") MethodDispatchLibrary dateDispatch,
@CachedLibrary(limit = "10") InteropLibrary interop) {
var ctx = Context.get(this);
try {
var hostLocalDate = interop.asDate(self);
var hostLocalTime = interop.asTime(self);
var hostZone = interop.asTimeZone(self);
var dateTime = new EnsoDateTime(hostLocalDate.atTime(hostLocalTime).atZone(hostZone));
Function function = dateDispatch.getFunctionalDispatch(dateTime, symbol);
arguments[0] = dateTime;
return invokeFunctionNode.execute(function, frame, state, arguments);
} catch (MethodDispatchLibrary.NoSuchMethodException | UnsupportedMessageException e) {
throw new PanicException(ctx.getBuiltins().error().makeNoSuchMethodError(self, symbol), this);
}
}
@Specialization(
guards = {
"!methods.hasFunctionalDispatch(self)",
"!methods.hasSpecialDispatch(self)",
"getPolyglotCallType(self, symbol.getName(), interop) == CONVERT_TO_ZONE"
})
Stateful doConvertZone(
VirtualFrame frame,
Object state,
UnresolvedSymbol symbol,
Object self,
Object[] arguments,
@CachedLibrary(limit = "10") MethodDispatchLibrary methods,
@CachedLibrary(limit = "1") MethodDispatchLibrary dateDispatch,
@CachedLibrary(limit = "10") InteropLibrary interop) {
var ctx = Context.get(this);
try {
var hostZone = interop.asTimeZone(self);
var dateTime = new EnsoZone(hostZone);
Function function = dateDispatch.getFunctionalDispatch(dateTime, symbol);
arguments[0] = dateTime;
return invokeFunctionNode.execute(function, frame, state, arguments);
} catch (MethodDispatchLibrary.NoSuchMethodException | UnsupportedMessageException e) {
throw new PanicException(ctx.getBuiltins().error().makeNoSuchMethodError(self, symbol), this);
}
}
@Specialization(
guards = {
"!methods.hasFunctionalDispatch(self)",
"!methods.hasSpecialDispatch(self)",
"getPolyglotCallType(self, symbol.getName(), interop) == CONVERT_TO_TIME_OF_DAY"
})
Stateful doConvertTimeOfDay(
VirtualFrame frame,
Object state,
UnresolvedSymbol symbol,
Object self,
Object[] arguments,
@CachedLibrary(limit = "10") MethodDispatchLibrary methods,
@CachedLibrary(limit = "1") MethodDispatchLibrary dateDispatch,
@CachedLibrary(limit = "10") InteropLibrary interop) {
var ctx = Context.get(this);
try {
var hostLocalTime = interop.asTime(self);
var dateTime = new EnsoTimeOfDay(hostLocalTime);
Function function = dateDispatch.getFunctionalDispatch(dateTime, symbol);
arguments[0] = dateTime;
return invokeFunctionNode.execute(function, frame, state, arguments);
} catch (MethodDispatchLibrary.NoSuchMethodException | UnsupportedMessageException e) {
throw new PanicException(ctx.getBuiltins().error().makeNoSuchMethodError(self, symbol), this);
}
}
@Specialization( @Specialization(
guards = { guards = {
"!methods.hasFunctionalDispatch(self)", "!methods.hasFunctionalDispatch(self)",

View File

@ -48,6 +48,26 @@ public abstract class HostMethodCallNode extends Node {
* Standard.Base.Data.Time.Date} and dispatching natively. * Standard.Base.Data.Time.Date} and dispatching natively.
*/ */
CONVERT_TO_DATE, CONVERT_TO_DATE,
/**
* The method call should be handled by converting {@code self} to a {@code
* Standard.Base.Data.DateTime} and dispatching natively.
*/
CONVERT_TO_ZONED_DATE_TIME,
/**
* The method call should be handled by converting {@code self} to a {@code
* Standard.Base.Data.DateTime} with a system Zone and dispatching natively.
*/
CONVERT_TO_DATE_TIME,
/**
* The method call should be handled by converting {@code self} to a {@code
* Standard.Base.Data.Time.Time_Of_Day} and dispatching natively.
*/
CONVERT_TO_TIME_OF_DAY,
/**
* The method call should be handled by converting {@code self} to a {@code
* Standard.Base.Data.Time.Zone} and dispatching natively.
*/
CONVERT_TO_ZONE,
/** The method call should be handled by dispatching through the {@code Any} type. */ /** The method call should be handled by dispatching through the {@code Any} type. */
NOT_SUPPORTED; NOT_SUPPORTED;
@ -59,7 +79,13 @@ public abstract class HostMethodCallNode extends Node {
* @return true if one can directly pass this object to {@link InteropLibrary} * @return true if one can directly pass this object to {@link InteropLibrary}
*/ */
public boolean isInteropLibrary() { public boolean isInteropLibrary() {
return this != NOT_SUPPORTED && this != CONVERT_TO_TEXT && this != CONVERT_TO_DATE; return this != NOT_SUPPORTED
&& this != CONVERT_TO_TEXT
&& this != CONVERT_TO_DATE
&& this != CONVERT_TO_DATE_TIME
&& this != CONVERT_TO_ZONED_DATE_TIME
&& this != CONVERT_TO_TIME_OF_DAY
&& this != CONVERT_TO_ZONE;
} }
} }
@ -80,8 +106,20 @@ public abstract class HostMethodCallNode extends Node {
*/ */
public static PolyglotCallType getPolyglotCallType( public static PolyglotCallType getPolyglotCallType(
Object self, String methodName, InteropLibrary library) { Object self, String methodName, InteropLibrary library) {
if (library.isDate(self) && !library.isTime(self)) { if (library.isDate(self)) {
return PolyglotCallType.CONVERT_TO_DATE; if (library.isTime(self)) {
if (library.isTimeZone(self)) {
return PolyglotCallType.CONVERT_TO_ZONED_DATE_TIME;
} else {
return PolyglotCallType.CONVERT_TO_DATE_TIME;
}
} else {
return PolyglotCallType.CONVERT_TO_DATE;
}
} else if (library.isTime(self)) {
return PolyglotCallType.CONVERT_TO_TIME_OF_DAY;
} else if (library.isTimeZone(self)) {
return PolyglotCallType.CONVERT_TO_ZONE;
} else if (library.isString(self)) { } else if (library.isString(self)) {
return PolyglotCallType.CONVERT_TO_TEXT; return PolyglotCallType.CONVERT_TO_TEXT;
} else if (library.isMemberInvocable(self, methodName)) { } else if (library.isMemberInvocable(self, methodName)) {

View File

@ -76,6 +76,9 @@ public class Builtins {
private final BuiltinAtomConstructor projectDescription; private final BuiltinAtomConstructor projectDescription;
private final BuiltinAtomConstructor file; private final BuiltinAtomConstructor file;
private final BuiltinAtomConstructor date; private final BuiltinAtomConstructor date;
private final BuiltinAtomConstructor dateTime;
private final BuiltinAtomConstructor timeOfDay;
private final BuiltinAtomConstructor zone;
private final BuiltinAtomConstructor warning; private final BuiltinAtomConstructor warning;
/** /**
@ -117,6 +120,15 @@ public class Builtins {
date = date =
new BuiltinAtomConstructor( new BuiltinAtomConstructor(
this, org.enso.interpreter.node.expression.builtin.date.Date.class); this, org.enso.interpreter.node.expression.builtin.date.Date.class);
dateTime =
new BuiltinAtomConstructor(
this, org.enso.interpreter.node.expression.builtin.date.DateTime.class);
timeOfDay =
new BuiltinAtomConstructor(
this, org.enso.interpreter.node.expression.builtin.date.TimeOfDay.class);
zone =
new BuiltinAtomConstructor(
this, org.enso.interpreter.node.expression.builtin.date.Zone.class);
special = new Special(language); special = new Special(language);
warning = new BuiltinAtomConstructor(this, Warning.class); warning = new BuiltinAtomConstructor(this, Warning.class);
} }
@ -430,6 +442,33 @@ public class Builtins {
return date.constructor(); return date.constructor();
} }
/**
* Returns the {@code DateTime} atom constructor.
*
* @return the {@code DateTime} atom constructor
*/
public AtomConstructor dateTime() {
return dateTime.constructor();
}
/**
* Returns the {@code Zone} atom constructor.
*
* @return the {@code Zone} atom constructor
*/
public AtomConstructor timeOfDay() {
return timeOfDay.constructor();
}
/**
* Returns the {@code Zone} atom constructor.
*
* @return the {@code Zone} atom constructor
*/
public AtomConstructor zone() {
return zone.constructor();
}
/** /**
* Returns the {@code Debug} atom constructor. TODO: this is redundant, figure out a way to avoid * Returns the {@code Debug} atom constructor. TODO: this is redundant, figure out a way to avoid
* createing spurious Debug builtin type * createing spurious Debug builtin type

View File

@ -5,12 +5,14 @@ import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.interop.InteropLibrary; import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.TruffleObject; import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.library.CachedLibrary; import com.oracle.truffle.api.library.CachedLibrary;
import com.oracle.truffle.api.library.ExportLibrary; import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage; import com.oracle.truffle.api.library.ExportMessage;
import java.time.DateTimeException; import java.time.DateTimeException;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException; import java.time.format.DateTimeParseException;
@ -24,7 +26,7 @@ import org.enso.interpreter.runtime.library.dispatch.MethodDispatchLibrary;
@ExportLibrary(InteropLibrary.class) @ExportLibrary(InteropLibrary.class)
@ExportLibrary(MethodDispatchLibrary.class) @ExportLibrary(MethodDispatchLibrary.class)
@Builtin(pkg = "date", name = "Date") @Builtin(pkg = "date", name = "Date", stdlibName = "Standard.Base.Data.Time.Date")
public final class EnsoDate implements TruffleObject { public final class EnsoDate implements TruffleObject {
private final LocalDate date; private final LocalDate date;
@ -71,6 +73,11 @@ public final class EnsoDate implements TruffleObject {
return date.getDayOfMonth(); return date.getDayOfMonth();
} }
@Builtin.Method(name = "to_time_builtin", description = "Combine this day with time to create a point in time.")
public EnsoDateTime toTime(EnsoTimeOfDay timeOfDay, EnsoZone zone) {
return new EnsoDateTime(date.atTime(timeOfDay.asTime()).atZone(zone.asTimeZone()));
}
@ExportMessage @ExportMessage
boolean isDate() { boolean isDate() {
return true; return true;
@ -81,6 +88,16 @@ public final class EnsoDate implements TruffleObject {
return date; return date;
} }
@ExportMessage
boolean isTime() {
return false;
}
@ExportMessage
LocalTime asTime() throws UnsupportedMessageException {
throw UnsupportedMessageException.create();
}
@ExportMessage @ExportMessage
boolean hasFunctionalDispatch() { boolean hasFunctionalDispatch() {
return true; return true;

View File

@ -0,0 +1,284 @@
package org.enso.interpreter.runtime.data;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.library.CachedLibrary;
import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage;
import org.enso.interpreter.dsl.Builtin;
import org.enso.interpreter.node.expression.builtin.error.PolyglotError;
import org.enso.interpreter.runtime.Context;
import org.enso.interpreter.runtime.callable.UnresolvedSymbol;
import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.data.text.Text;
import org.enso.interpreter.runtime.library.dispatch.MethodDispatchLibrary;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.format.DateTimeParseException;
import java.time.temporal.TemporalAccessor;
@ExportLibrary(InteropLibrary.class)
@ExportLibrary(MethodDispatchLibrary.class)
@Builtin(pkg = "date", name = "DateTime", stdlibName = "Standard.Base.Data.Time.Date_Time")
public final class EnsoDateTime implements TruffleObject {
private final ZonedDateTime dateTime;
public EnsoDateTime(ZonedDateTime dateTime) {
this.dateTime = dateTime;
}
@Builtin.Method(description = "Return current DateTime")
public static EnsoDateTime now() {
return new EnsoDateTime(ZonedDateTime.now());
}
/**
* Obtains an instance of EnsoDateTime (ZonedDateTime) from a text string.
*
* <p>Accepts:
*
* <ul>
* <li>Local date time, such as '2011-12-03T10:15:30' adding system dafault timezone.
* <li>Offset date time, such as '2011-12-03T10:15:30+01:00' parsing offset as a timezone.
* <li>Zoned date time, such as '2011-12-03T10:15:30+01:00[Europe/Paris]' with optional region
* id in square brackets.
* </ul>
*
* @param text the string to parse.
* @return parsed ZonedDateTime instance wrapped in EnsoDateTime.
*/
@Builtin.Method(
name = "parse_builtin",
description = "Constructs a new DateTime from text with optional pattern")
@Builtin.Specialize
@Builtin.WrapException(
from = DateTimeParseException.class,
to = PolyglotError.class,
propagate = true)
public static EnsoDateTime parse(String text) {
TemporalAccessor time = TIME_FORMAT.parseBest(text, ZonedDateTime::from, LocalDateTime::from);
if (time instanceof ZonedDateTime) {
return new EnsoDateTime((ZonedDateTime) time);
} else if (time instanceof LocalDateTime) {
return new EnsoDateTime(((LocalDateTime) time).atZone(ZoneId.systemDefault()));
}
throw new DateTimeException("Text '" + text + "' could not be parsed as Time.");
}
@Builtin.Method(
name = "new_builtin",
description = "Constructs a new Date from a year, month, and day")
@Builtin.WrapException(from = DateTimeException.class, to = PolyglotError.class, propagate = true)
public static EnsoDateTime create(
long year,
long month,
long day,
long hour,
long minute,
long second,
long nanosecond,
EnsoZone zone) {
return new EnsoDateTime(
ZonedDateTime.of(
Math.toIntExact(year),
Math.toIntExact(month),
Math.toIntExact(day),
Math.toIntExact(hour),
Math.toIntExact(minute),
Math.toIntExact(second),
Math.toIntExact(nanosecond),
zone.asTimeZone()));
}
@Builtin.Method(description = "Gets the year")
@CompilerDirectives.TruffleBoundary
public long year() {
return dateTime.getYear();
}
@Builtin.Method(description = "Gets the month")
@CompilerDirectives.TruffleBoundary
public long month() {
return dateTime.getMonthValue();
}
@Builtin.Method(description = "Gets the day")
@CompilerDirectives.TruffleBoundary
public long day() {
return dateTime.getDayOfMonth();
}
@Builtin.Method(description = "Gets the hour")
@CompilerDirectives.TruffleBoundary
public long hour() {
return dateTime.getHour();
}
@Builtin.Method(description = "Gets the minute")
@CompilerDirectives.TruffleBoundary
public long minute() {
return dateTime.getMinute();
}
@Builtin.Method(description = "Gets the second")
@CompilerDirectives.TruffleBoundary
public long second() {
return dateTime.getSecond();
}
@Builtin.Method(description = "Gets the nanosecond")
@CompilerDirectives.TruffleBoundary
public long nanosecond() {
return dateTime.getNano();
}
@Builtin.Method(name = "zone", description = "Gets the zone")
public EnsoZone zone() {
return new EnsoZone(dateTime.getZone());
}
@Builtin.Method(description = "Return the number of seconds from the Unix epoch.")
@CompilerDirectives.TruffleBoundary
public long toEpochSeconds() {
return dateTime.toEpochSecond();
}
@Builtin.Method(description = "Return the number of milliseconds from the Unix epoch.")
@CompilerDirectives.TruffleBoundary
public long toEpochMilliseconds() {
return dateTime.toInstant().toEpochMilli();
}
@Builtin.Method(
name = "to_localtime_builtin",
description = "Return the localtime of this date time value.")
public EnsoTimeOfDay toLocalTime() {
return new EnsoTimeOfDay(dateTime.toLocalTime());
}
@Builtin.Method(
name = "to_localdate_builtin",
description = "Return the localdate of this date time value.")
public EnsoDate toLocalDate() {
return new EnsoDate(dateTime.toLocalDate());
}
@Builtin.Method(description = "Return this datetime in the provided time zone.")
public EnsoDateTime atZone(EnsoZone zone) {
return new EnsoDateTime(dateTime.withZoneSameInstant(zone.asTimeZone()));
}
@Builtin.Method(
name = "to_time_builtin",
description = "Combine this day with time to create a point in time.")
public EnsoDateTime toTime(EnsoTimeOfDay timeOfDay, EnsoZone zone) {
return new EnsoDateTime(
dateTime.toLocalDate().atTime(timeOfDay.asTime()).atZone(zone.asTimeZone()));
}
@Builtin.Method(description = "Return this datetime to the datetime in the provided time zone.")
public Text toText() {
return Text.create(DateTimeFormatter.ISO_ZONED_DATE_TIME.format(dateTime));
}
@Builtin.Method(description = "Return this datetime to the datetime in the provided time zone.")
@Builtin.Specialize
public Text format(String pattern) {
return Text.create(DateTimeFormatter.ofPattern(pattern).format(dateTime));
}
@ExportMessage
boolean isDate() {
return true;
}
@ExportMessage
LocalDate asDate() {
return dateTime.toLocalDate();
}
@ExportMessage
boolean isTime() {
return true;
}
@ExportMessage
LocalTime asTime() {
return dateTime.toLocalTime();
}
@ExportMessage
boolean isTimeZone() {
return true;
}
@ExportMessage
ZoneId asTimeZone() {
return dateTime.getZone();
}
@ExportMessage
boolean hasFunctionalDispatch() {
return true;
}
@ExportMessage
static class GetFunctionalDispatch {
@CompilerDirectives.TruffleBoundary
static Function doResolve(InteropLibrary my, UnresolvedSymbol symbol) {
Context context = Context.get(my);
return symbol.resolveFor(context.getBuiltins().dateTime(), context.getBuiltins().any());
}
@Specialization(
guards = {"cachedSymbol == symbol", "function != null"},
limit = "3")
static Function resolveCached(
EnsoDateTime self,
UnresolvedSymbol symbol,
@Cached("symbol") UnresolvedSymbol cachedSymbol,
@CachedLibrary("self") InteropLibrary mySelf,
@Cached("doResolve(mySelf, cachedSymbol)") Function function) {
return function;
}
@Specialization(replaces = "resolveCached")
static Function resolve(
EnsoDateTime self, UnresolvedSymbol symbol, @CachedLibrary("self") InteropLibrary mySelf)
throws MethodDispatchLibrary.NoSuchMethodException {
Function function = doResolve(mySelf, symbol);
if (function == null) {
throw new MethodDispatchLibrary.NoSuchMethodException();
}
return function;
}
}
@ExportMessage
@CompilerDirectives.TruffleBoundary
public final Object toDisplayString(boolean allowSideEffects) {
return DateTimeFormatter.ISO_ZONED_DATE_TIME.format(dateTime);
}
private static final DateTimeFormatter TIME_FORMAT =
new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
.parseLenient()
.optionalStart()
.appendZoneOrOffsetId()
.optionalEnd()
.parseStrict()
.optionalStart()
.appendLiteral('[')
.parseCaseSensitive()
.appendZoneRegionId()
.appendLiteral(']')
.optionalEnd()
.toFormatter();
}

View File

@ -91,15 +91,17 @@ public class EnsoFile implements TruffleObject {
@Builtin.Method(name = "creation_time_builtin") @Builtin.Method(name = "creation_time_builtin")
@Builtin.WrapException(from = IOException.class, to = PolyglotError.class, propagate = true) @Builtin.WrapException(from = IOException.class, to = PolyglotError.class, propagate = true)
@Builtin.ReturningGuestObject @Builtin.ReturningGuestObject
public ZonedDateTime getCreationTime() throws IOException { public EnsoDateTime getCreationTime() throws IOException {
return ZonedDateTime.ofInstant(truffleFile.getCreationTime().toInstant(), ZoneOffset.UTC); return new EnsoDateTime(
ZonedDateTime.ofInstant(truffleFile.getCreationTime().toInstant(), ZoneOffset.UTC));
} }
@Builtin.Method(name = "last_modified_time_builtin") @Builtin.Method(name = "last_modified_time_builtin")
@Builtin.WrapException(from = IOException.class, to = PolyglotError.class, propagate = true) @Builtin.WrapException(from = IOException.class, to = PolyglotError.class, propagate = true)
@Builtin.ReturningGuestObject @Builtin.ReturningGuestObject
public ZonedDateTime getLastModifiedTime() throws IOException { public EnsoDateTime getLastModifiedTime() throws IOException {
return ZonedDateTime.ofInstant(truffleFile.getLastModifiedTime().toInstant(), ZoneOffset.UTC); return new EnsoDateTime(
ZonedDateTime.ofInstant(truffleFile.getLastModifiedTime().toInstant(), ZoneOffset.UTC));
} }
@Builtin.Method(name = "posix_permissions_builtin") @Builtin.Method(name = "posix_permissions_builtin")

View File

@ -0,0 +1,163 @@
package org.enso.interpreter.runtime.data;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.library.CachedLibrary;
import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage;
import org.enso.interpreter.dsl.Builtin;
import org.enso.interpreter.node.expression.builtin.error.PolyglotError;
import org.enso.interpreter.runtime.Context;
import org.enso.interpreter.runtime.callable.UnresolvedSymbol;
import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.data.text.Text;
import org.enso.interpreter.runtime.library.dispatch.MethodDispatchLibrary;
import java.time.DateTimeException;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
@ExportLibrary(InteropLibrary.class)
@ExportLibrary(MethodDispatchLibrary.class)
@Builtin(pkg = "date", name = "TimeOfDay", stdlibName = "Standard.Base.Data.Time.Time_Of_Day")
public class EnsoTimeOfDay implements TruffleObject {
private LocalTime localTime;
public EnsoTimeOfDay(LocalTime localTime) {
this.localTime = localTime;
}
@Builtin.Method(
name = "parse_builtin",
description = "Constructs a new DateTime from text with optional pattern")
@Builtin.Specialize
@Builtin.WrapException(
from = DateTimeParseException.class,
to = PolyglotError.class,
propagate = true)
public static EnsoTimeOfDay parse(String text) {
return new EnsoTimeOfDay(LocalTime.parse(text));
}
@Builtin.Method(name = "new_builtin", description = "Constructs a new Time_OF_Day from an hour")
@Builtin.WrapException(from = DateTimeException.class, to = PolyglotError.class, propagate = true)
public static EnsoTimeOfDay create(long hour, long minute, long second, long nanosecond) {
return new EnsoTimeOfDay(
LocalTime.of(
Math.toIntExact(hour),
Math.toIntExact(minute),
Math.toIntExact(second),
Math.toIntExact(nanosecond)));
}
@Builtin.Method(description = "Gets a value of hour")
public static EnsoTimeOfDay now() {
return new EnsoTimeOfDay(LocalTime.now());
}
@Builtin.Method(description = "Gets a value of hour")
public long hour() {
return localTime.getHour();
}
@Builtin.Method(description = "Gets a value minute")
public long minute() {
return localTime.getMinute();
}
@Builtin.Method(description = "Gets a value second")
public long second() {
return localTime.getSecond();
}
@Builtin.Method(description = "Gets a value nanosecond")
public long nanosecond() {
return localTime.getNano();
}
@Builtin.Method(description = "Gets a value second")
public long toSeconds() {
return localTime.toSecondOfDay();
}
@Builtin.Method(
name = "to_time_builtin",
description = "Combine this time of day with a date to create a point in time.")
public EnsoDateTime toTime(EnsoDate date, EnsoZone zone) {
return new EnsoDateTime(localTime.atDate(date.asDate()).atZone(zone.asTimeZone()));
}
@Builtin.Method(description = "Return this datetime to the datetime in the provided time zone.")
public Text toText() {
return Text.create(DateTimeFormatter.ISO_LOCAL_TIME.format(localTime));
}
@Builtin.Method(description = "Return this datetime to the datetime in the provided time zone.")
@Builtin.Specialize
public Text format(String pattern) {
return Text.create(DateTimeFormatter.ofPattern(pattern).format(localTime));
}
@ExportMessage
boolean isTime() {
return true;
}
@ExportMessage
LocalTime asTime() {
return localTime;
}
@ExportMessage
final boolean isDate() {
return false;
}
@ExportMessage
LocalDate asDate() throws UnsupportedMessageException {
throw UnsupportedMessageException.create();
}
@ExportMessage
boolean hasFunctionalDispatch() {
return true;
}
@ExportMessage
static class GetFunctionalDispatch {
@CompilerDirectives.TruffleBoundary
static Function doResolve(InteropLibrary my, UnresolvedSymbol symbol) {
Context context = Context.get(my);
return symbol.resolveFor(context.getBuiltins().timeOfDay(), context.getBuiltins().any());
}
@Specialization(
guards = {"cachedSymbol == symbol", "function != null"},
limit = "3")
static Function resolveCached(
EnsoTimeOfDay self,
UnresolvedSymbol symbol,
@Cached("symbol") UnresolvedSymbol cachedSymbol,
@CachedLibrary("self") InteropLibrary mySelf,
@Cached("doResolve(mySelf, cachedSymbol)") Function function) {
return function;
}
@Specialization(replaces = "resolveCached")
static Function resolve(
EnsoTimeOfDay self, UnresolvedSymbol symbol, @CachedLibrary("self") InteropLibrary mySelf)
throws MethodDispatchLibrary.NoSuchMethodException {
Function function = doResolve(mySelf, symbol);
if (function == null) {
throw new MethodDispatchLibrary.NoSuchMethodException();
}
return function;
}
}
}

View File

@ -0,0 +1,112 @@
package org.enso.interpreter.runtime.data;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.library.CachedLibrary;
import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage;
import org.enso.interpreter.dsl.Builtin;
import org.enso.interpreter.node.expression.builtin.error.PolyglotError;
import org.enso.interpreter.runtime.Context;
import org.enso.interpreter.runtime.callable.UnresolvedSymbol;
import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.data.text.Text;
import org.enso.interpreter.runtime.library.dispatch.MethodDispatchLibrary;
import java.time.DateTimeException;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.format.DateTimeParseException;
import java.time.zone.ZoneRulesException;
@ExportLibrary(InteropLibrary.class)
@ExportLibrary(MethodDispatchLibrary.class)
@Builtin(pkg = "date", name = "Zone", stdlibName = "Standard.Base.Data.Time.Zone")
public final class EnsoZone implements TruffleObject {
private final ZoneId zone;
public EnsoZone(ZoneId zone) {
this.zone = zone;
}
@Builtin.Method(description = "Get the unique identifier for your system's current timezone.")
public Text zoneId() {
return Text.create(this.zone.getId());
}
@Builtin.Method(name = "parse_builtin", description = "Parse the ID producing EnsoZone.")
@Builtin.Specialize
@Builtin.WrapException(
from = ZoneRulesException.class,
to = PolyglotError.class,
propagate = true)
public static EnsoZone parse(String text) {
return new EnsoZone(ZoneId.of(text));
}
@Builtin.Method(
name = "new_builtin",
description =
"Obtains an instance of `Zone` using an offset in hours, minutes and seconds from the UTC zone.")
@Builtin.WrapException(from = DateTimeException.class, to = PolyglotError.class, propagate = true)
public static EnsoZone create(long hours, long minutes, long seconds) {
return new EnsoZone(
ZoneOffset.ofHoursMinutesSeconds(
Math.toIntExact(hours), Math.toIntExact(minutes), Math.toIntExact(seconds)));
}
@Builtin.Method(name = "system", description = "The system default timezone.")
public static EnsoZone system() {
return new EnsoZone(ZoneId.systemDefault());
}
@ExportMessage
boolean isTimeZone() {
return true;
}
@ExportMessage
ZoneId asTimeZone() {
return zone;
}
@ExportMessage
boolean hasFunctionalDispatch() {
return true;
}
@ExportMessage
static class GetFunctionalDispatch {
@CompilerDirectives.TruffleBoundary
static Function doResolve(InteropLibrary my, UnresolvedSymbol symbol) {
Context context = Context.get(my);
return symbol.resolveFor(context.getBuiltins().zone(), context.getBuiltins().any());
}
@Specialization(
guards = {"cachedSymbol == symbol", "function != null"},
limit = "3")
static Function resolveCached(
EnsoZone self,
UnresolvedSymbol symbol,
@Cached("symbol") UnresolvedSymbol cachedSymbol,
@CachedLibrary("self") InteropLibrary mySelf,
@Cached("doResolve(mySelf, cachedSymbol)") Function function) {
return function;
}
@Specialization(replaces = "resolveCached")
static Function resolve(
EnsoZone self, UnresolvedSymbol symbol, @CachedLibrary("self") InteropLibrary mySelf)
throws MethodDispatchLibrary.NoSuchMethodException {
Function function = doResolve(mySelf, symbol);
if (function == null) {
throw new MethodDispatchLibrary.NoSuchMethodException();
}
return function;
}
}
}

View File

@ -50,7 +50,10 @@ import org.enso.polyglot.data.TypeGraph;
PanicSentinel.class, PanicSentinel.class,
Warning.class, Warning.class,
EnsoFile.class, EnsoFile.class,
EnsoDate.class EnsoDate.class,
EnsoDateTime.class,
EnsoTimeOfDay.class,
EnsoZone.class,
}) })
public class Types { public class Types {
@ -221,6 +224,10 @@ public class Types {
graph.insert(ConstantsGen.PANIC, ConstantsGen.ANY); graph.insert(ConstantsGen.PANIC, ConstantsGen.ANY);
graph.insert(ConstantsGen.REF, ConstantsGen.ANY); graph.insert(ConstantsGen.REF, ConstantsGen.ANY);
graph.insert(ConstantsGen.TEXT, ConstantsGen.ANY); graph.insert(ConstantsGen.TEXT, ConstantsGen.ANY);
graph.insert(ConstantsGen.DATE, ConstantsGen.ANY);
graph.insert(ConstantsGen.DATE_TIME, ConstantsGen.ANY);
graph.insert(ConstantsGen.TIME_OF_DAY, ConstantsGen.ANY);
graph.insert(ConstantsGen.ZONE, ConstantsGen.ANY);
graph.insertWithoutParent(ConstantsGen.PANIC); graph.insertWithoutParent(ConstantsGen.PANIC);
graph.insertWithoutParent(Constants.THUNK); graph.insertWithoutParent(Constants.THUNK);
graph.insertWithoutParent(Constants.UNRESOLVED_SYMBOL); graph.insertWithoutParent(Constants.UNRESOLVED_SYMBOL);

View File

@ -33,23 +33,6 @@ class DateTest extends InterpreterTest {
consumeOut shouldEqual List("2022-04-01") consumeOut shouldEqual List("2022-04-01")
} }
"send enso date into java" in {
val code =
s"""import Standard.Base.IO
|polyglot java import java.time.LocalTime
|import Standard.Base.Data.Time.Date
|
|main =
| ensodate = Date.new 2022 04 01
| javatime = LocalTime.of 10 26
| javatimedate = javatime.atDate ensodate
| javadate = javatimedate . toLocalDate
| IO.println javadate
|""".stripMargin
eval(code)
consumeOut shouldEqual List("2022-04-01")
}
"check java date has enso methods" in { "check java date has enso methods" in {
val code = val code =
s"""import Standard.Base.IO s"""import Standard.Base.IO

View File

@ -43,6 +43,9 @@ public record TypeWithKind(String baseType, TypeKind kind) {
"org.enso.interpreter.runtime.data.ArrayOverBuffer", "org.enso.interpreter.runtime.data.ArrayOverBuffer",
"org.enso.interpreter.runtime.data.EnsoFile", "org.enso.interpreter.runtime.data.EnsoFile",
"org.enso.interpreter.runtime.data.EnsoDate", "org.enso.interpreter.runtime.data.EnsoDate",
"org.enso.interpreter.runtime.data.EnsoDateTime",
"org.enso.interpreter.runtime.data.EnsoTimeOfDay",
"org.enso.interpreter.runtime.data.EnsoZone",
"org.enso.interpreter.runtime.data.ManagedResource", "org.enso.interpreter.runtime.data.ManagedResource",
"org.enso.interpreter.runtime.data.Ref", "org.enso.interpreter.runtime.data.Ref",
"org.enso.interpreter.runtime.data.text.Text", "org.enso.interpreter.runtime.data.text.Text",

View File

@ -1,12 +1,6 @@
package org.enso.base; package org.enso.base;
import java.time.DateTimeException; import java.time.*;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Period;
import java.time.ZonedDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder; import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalAccessor;
@ -41,6 +35,11 @@ public class Time_Utils {
.toFormatter(); .toFormatter();
} }
public enum AdjustOp {
PLUS,
MINUS
}
/** @return default Time formatter. */ /** @return default Time formatter. */
public static DateTimeFormatter default_time_formatter() { public static DateTimeFormatter default_time_formatter() {
return DateTimeFormatter.ISO_ZONED_DATE_TIME; return DateTimeFormatter.ISO_ZONED_DATE_TIME;
@ -64,18 +63,71 @@ public class Time_Utils {
return date.atTime(time).atZone(zone); return date.atTime(time).atZone(zone);
} }
public static LocalDate date_adjust(LocalDate date, long add, Period duration) { public static LocalDate date_adjust(LocalDate date, AdjustOp op, Period duration) {
return add == 1 ? date.plus(duration) : date.minus(duration); switch (op) {
case PLUS:
return date.plus(duration);
case MINUS:
return date.minus(duration);
default:
throw new DateTimeException("Unknown adjust operation");
}
} }
public static long week_of_year(LocalDate date, Locale locale) { public static ZonedDateTime datetime_adjust(
ZonedDateTime datetime, AdjustOp op, Period period, Duration duration) {
switch (op) {
case PLUS:
return datetime.plus(period).plus(duration);
case MINUS:
return datetime.minus(period).minus(duration);
default:
throw new DateTimeException("Unknown adjust operation");
}
}
public static LocalTime time_adjust(LocalTime time, AdjustOp op, Duration duration) {
switch (op) {
case PLUS:
return time.plus(duration);
case MINUS:
return time.minus(duration);
default:
throw new DateTimeException("Unknown adjust operation");
}
}
public static long week_of_year_localdate(LocalDate date, Locale locale) {
return WeekFields.of(locale).weekOfYear().getFrom(date); return WeekFields.of(locale).weekOfYear().getFrom(date);
} }
public static int compare_to(LocalDate self, LocalDate that) { public static long week_of_year_zoneddatetime(ZonedDateTime date, Locale locale) {
return WeekFields.of(locale).weekOfYear().getFrom(date);
}
public static Duration duration_between(
ZonedDateTime start, ZonedDateTime end, boolean timezoneAware) {
return timezoneAware
? Duration.between(start, end)
: Duration.between(start.toLocalDateTime(), end.toLocalDateTime());
}
public static int compare_to_localdate(LocalDate self, LocalDate that) {
return self.compareTo(that); return self.compareTo(that);
} }
public static int compare_to_zoneddatetime(ZonedDateTime self, ZonedDateTime that) {
return self.compareTo(that);
}
public static int compare_to_localtime(LocalTime self, LocalTime that) {
return self.compareTo(that);
}
public static boolean equals_zone(ZoneId self, ZoneId that) {
return self.equals(that);
}
/** /**
* Obtains an instance of ZonedDateTime from a text string. * Obtains an instance of ZonedDateTime from a text string.
* *
@ -91,7 +143,7 @@ public class Time_Utils {
* @param text the string to parse. * @param text the string to parse.
* @return parsed ZonedDateTime instance. * @return parsed ZonedDateTime instance.
*/ */
public static ZonedDateTime parse_time(String text) { public static ZonedDateTime parse_datetime(String text) {
TemporalAccessor time = TIME_FORMAT.parseBest(text, ZonedDateTime::from, LocalDateTime::from); TemporalAccessor time = TIME_FORMAT.parseBest(text, ZonedDateTime::from, LocalDateTime::from);
if (time instanceof ZonedDateTime) { if (time instanceof ZonedDateTime) {
return (ZonedDateTime) time; return (ZonedDateTime) time;
@ -109,9 +161,10 @@ public class Time_Utils {
* *
* @param text the string to parse. * @param text the string to parse.
* @param pattern the format string. * @param pattern the format string.
* @param locale localization config to be uses in the formatter.
* @return parsed ZonedDateTime instance. * @return parsed ZonedDateTime instance.
*/ */
public static ZonedDateTime parse_time_format(String text, String pattern, Locale locale) { public static ZonedDateTime parse_datetime_format(String text, String pattern, Locale locale) {
TemporalAccessor time = TemporalAccessor time =
DateTimeFormatter.ofPattern(pattern) DateTimeFormatter.ofPattern(pattern)
.withLocale(locale) .withLocale(locale)
@ -123,4 +176,17 @@ public class Time_Utils {
} }
throw new DateTimeException("Text '" + text + "' could not be parsed as Time."); throw new DateTimeException("Text '" + text + "' could not be parsed as Time.");
} }
/**
* Obtains an instance of LocalTime from a text string using a custom string.
*
* @param text the string to parse.
* @param pattern the format string.
* @param locale localization config to be uses in the formatter.
* @return parsed LocalTime instance.
*/
public static LocalTime parse_time(String text, String pattern, Locale locale) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
return (LocalTime.parse(text, formatter.withLocale(locale)));
}
} }

View File

@ -1,5 +1,7 @@
package org.enso.table.data.table; package org.enso.table.data.table;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.BitSet; import java.util.BitSet;
import java.util.List; import java.util.List;
import java.util.function.Function; import java.util.function.Function;
@ -120,11 +122,30 @@ public class Column {
public static Column fromItems(String name, List<Value> items) { public static Column fromItems(String name, List<Value> items) {
InferredBuilder builder = new InferredBuilder(items.size()); InferredBuilder builder = new InferredBuilder(items.size());
for (var item : items) { for (var item : items) {
builder.appendNoGrow(item.isDate() ? item.asDate() : item.as(Object.class)); builder.appendNoGrow(convertDateOrTime(item));
} }
return new Column(name, new DefaultIndex(items.size()), builder.seal()); return new Column(name, new DefaultIndex(items.size()), builder.seal());
} }
private static Object convertDateOrTime(Value item) {
if (item.isDate()) {
LocalDate d = item.asDate();
if (item.isTime()) {
LocalDateTime dtime = d.atTime(item.asTime());
if (item.isTimeZone()) {
return dtime.atZone(item.asTimeZone());
} else {
return dtime;
}
} else {
return d;
}
} else if (item.isTime()) {
return item.asTime();
}
return item.as(Object.class);
}
/** /**
* Changes the index of this column. * Changes the index of this column.
* *

View File

@ -31,6 +31,6 @@ public class DateFormatter implements DataFormatter {
@Override @Override
public boolean canFormat(Object value) { public boolean canFormat(Object value) {
return value instanceof LocalDate || (value instanceof Value v && v.isDate()); return value instanceof LocalDate || (value instanceof Value v && v.isDate() && !v.isTime());
} }
} }

View File

@ -1,5 +1,7 @@
package org.enso.table.formatting; package org.enso.table.formatting;
import org.graalvm.polyglot.Value;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.Locale; import java.util.Locale;
@ -17,13 +19,19 @@ public class DateTimeFormatter implements DataFormatter {
return NULL_REPRESENTATION; return NULL_REPRESENTATION;
} }
if (value instanceof Value v && v.isDate() && v.isTime()) {
value = v.asDate().atTime(v.asTime());
if (v.isTimeZone()) {
value = ((LocalDateTime) value).atZone(v.asTimeZone());
}
}
if (value instanceof LocalDateTime date) { if (value instanceof LocalDateTime date) {
return date.format(formatter); return date.format(formatter);
} }
// Currently Enso uses ZonedDateTime for the date-time type. This should be revisited along with the Datetime API.
if (value instanceof ZonedDateTime date) { if (value instanceof ZonedDateTime date) {
return date.toLocalDateTime().format(formatter); return date.format(formatter);
} }
throw new IllegalArgumentException("Unsupported type for DateTimeFormatter."); throw new IllegalArgumentException("Unsupported type for DateTimeFormatter.");
@ -31,6 +39,6 @@ public class DateTimeFormatter implements DataFormatter {
@Override @Override
public boolean canFormat(Object value) { public boolean canFormat(Object value) {
return value instanceof LocalDateTime || value instanceof ZonedDateTime; return value instanceof LocalDateTime || value instanceof ZonedDateTime || (value instanceof Value v && v.isDate() && v.isTime());
} }
} }

View File

@ -1,5 +1,7 @@
package org.enso.table.formatting; package org.enso.table.formatting;
import org.graalvm.polyglot.Value;
import java.time.LocalTime; import java.time.LocalTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.Locale; import java.util.Locale;
@ -17,6 +19,10 @@ public class TimeFormatter implements DataFormatter {
return NULL_REPRESENTATION; return NULL_REPRESENTATION;
} }
if (value instanceof Value v && v.isTime()) {
value = v.asTime();
}
if (value instanceof LocalTime date) { if (value instanceof LocalTime date) {
return date.format(formatter); return date.format(formatter);
} }
@ -26,6 +32,6 @@ public class TimeFormatter implements DataFormatter {
@Override @Override
public boolean canFormat(Object value) { public boolean canFormat(Object value) {
return value instanceof LocalTime; return value instanceof LocalTime || (value instanceof Value v && !v.isDate() && v.isTime());
} }
} }

View File

@ -0,0 +1,7 @@
Serial number,Movement type,Posting date
2LMXK1,101,2015-01-05 09:00:00
2LMXK1,301,2015-01-05 14:00:00
JEMLP3,101,2015-01-06 09:00:00
JEMLP3,203,2015-01-07 17:30:00
BR83GP,101,2011-01-05 09:00:00
BR83GP,301,2011-01-09 15:30:00
1 Serial number Movement type Posting date
2 2LMXK1 101 2015-01-05 09:00:00
3 2LMXK1 301 2015-01-05 14:00:00
4 JEMLP3 101 2015-01-06 09:00:00
5 JEMLP3 203 2015-01-07 17:30:00
6 BR83GP 101 2011-01-05 09:00:00
7 BR83GP 301 2011-01-09 15:30:00

View File

@ -0,0 +1,7 @@
Serial number,Movement type,Posting time
2LMXK1,101,09:00:00
2LMXK1,301,14:00:12
JEMLP3,101,09:00:00
JEMLP3,203,17:30:00
BR83GP,101,09:00:04
BR83GP,301,15:30:00
1 Serial number Movement type Posting time
2 2LMXK1 101 09:00:00
3 2LMXK1 301 14:00:12
4 JEMLP3 101 09:00:00
5 JEMLP3 203 17:30:00
6 BR83GP 101 09:00:04
7 BR83GP 301 15:30:00

View File

@ -1,6 +1,8 @@
from Standard.Base import all from Standard.Base import all
import Standard.Base.Data.Time import Standard.Base.Data.Time
import Standard.Base.Data.Time.Date_Time
import Standard.Base.Data.Time.Time_Of_Day import Standard.Base.Data.Time.Time_Of_Day
import Standard.Base.Data.Time.Zone
import Standard.Table import Standard.Table
from Standard.Table import Column, Data_Formatter, Quote_Style from Standard.Table import Column, Data_Formatter, Quote_Style
@ -137,14 +139,17 @@ spec =
Test.specify "should format dates" <| Test.specify "should format dates" <|
formatter = Data_Formatter formatter = Data_Formatter
formatter.format (Date.new 2022) . should_equal "2022-01-01" formatter.format (Date.new 2022) . should_equal "2022-01-01"
formatter.format (Time.new 1999 . internal_zoned_date_time) . should_equal "1999-01-01 00:00:00" formatter.format (Date_Time.new 1999) . should_equal "1999-01-01 00:00:00"
formatter.format (Time_Of_Day.new . internal_local_time) . should_equal "00:00:00" formatter.format (Date_Time.new 1999 zone=Zone.utc) . should_equal "1999-01-01 00:00:00"
formatter.format (Date_Time.new 1999 zone=(Zone.parse "America/Los_Angeles")) . should_equal "1999-01-01 00:00:00"
formatter.format (Time_Of_Day.new) . should_equal "00:00:00"
Test.specify "should allow custom date formats" <| Test.specify "should allow custom date formats" <|
formatter = Data_Formatter date_formats=["E, d MMM y", "d MMM y[ G]"] datetime_formats=["dd/MM/yyyy HH:mm"] time_formats=["h:mma"] datetime_locale=Locale.uk formatter = Data_Formatter date_formats=["E, d MMM y", "d MMM y[ G]"] datetime_formats=["dd/MM/yyyy HH:mm [z]"] time_formats=["h:mma"] datetime_locale=Locale.uk
formatter.format (Date.new 2022 06 21) . should_equal "Tue, 21 Jun 2022" formatter.format (Date.new 2022 06 21) . should_equal "Tue, 21 Jun 2022"
formatter.format (Time.new 1999 02 03 04 56 11 . internal_zoned_date_time) . should_equal "03/02/1999 04:56" formatter.format (Date_Time.new 1999 02 03 04 56 11 zone=Zone.utc) . should_equal "03/02/1999 04:56 UTC"
formatter.format (Time_Of_Day.new 13 55 . internal_local_time) . should_equal "1:55pm" formatter.format (Date_Time.new 1999 02 03 04 56 11 zone=(Zone.parse "America/Los_Angeles")) . should_equal "03/02/1999 04:56 GMT-08:00"
formatter.format (Time_Of_Day.new 13 55) . should_equal "1:55pm"
Test.specify "should act as identity on Text" <| Test.specify "should act as identity on Text" <|
formatter = Data_Formatter formatter = Data_Formatter

View File

@ -166,7 +166,7 @@ spec =
Test.specify 'should allow to always quote text and custom values, but for non-text primitves only if absolutely necessary' <| Test.specify 'should allow to always quote text and custom values, but for non-text primitves only if absolutely necessary' <|
format = Delimited "," value_formatter=(Data_Formatter thousand_separator='"' date_formats=["E, d MMM y"]) . with_quotes always_quote=True quote_escape='\\' format = Delimited "," value_formatter=(Data_Formatter thousand_separator='"' date_formats=["E, d MMM y"]) . with_quotes always_quote=True quote_escape='\\'
table = Table.new [['The Column "Name"', ["foo","'bar'",'"baz"', 'one, two, three']], ["B", [1.0, 1000000.5, 2.2, -1.5]], ["C", ["foo", My_Type 44, (Date.new 2022 06 21), 42]], ["D", [1,2,3,4000]], ["E", [Nothing, (Time_Of_Day.new 13 55 . internal_local_time), Nothing, Nothing]]] table = Table.new [['The Column "Name"', ["foo","'bar'",'"baz"', 'one, two, three']], ["B", [1.0, 1000000.5, 2.2, -1.5]], ["C", ["foo", My_Type 44, (Date.new 2022 06 21), 42]], ["D", [1,2,3,4000]], ["E", [Nothing, (Time_Of_Day.new 13 55), Nothing, Nothing]]]
file = (enso_project.data / "transient" / "quote_always.csv") file = (enso_project.data / "transient" / "quote_always.csv")
file.delete_if_exists file.delete_if_exists
table.write file format on_problems=Report_Error . should_succeed table.write file format on_problems=Report_Error . should_succeed

View File

@ -0,0 +1,53 @@
from Standard.Base import all
import Standard.Base.Data.Text.Line_Ending_Style
import Standard.Base.Data.Time.Time_Of_Day
import Standard.Table
import Standard.Table.Data.Column
import Standard.Table.IO.File_Format
from Standard.Table.Data.Data_Formatter as Data_Formatter_Module import Data_Formatter
import Standard.Test
from project.Util import all
spec =
c_number = ["Serial number", ["2LMXK1", "2LMXK1", "JEMLP3", "JEMLP3", "BR83GP", "BR83GP"]]
c_type = ["Movement type", [101, 301, 101, 203, 101, 301]]
c_time = ["Posting time", [Time_Of_Day.new 9 0, Time_Of_Day.new 14 0 12, Time_Of_Day.new 9 0, Time_Of_Day.new 17 30, Time_Of_Day.new 9 0 4, Time_Of_Day.new 15 30]]
expected = Table.new [c_number, c_type, c_time]
Test.group "File.read (Delimited) should work with Time_Of_Days" <|
table = (enso_project.data / "time_of_day_sample.csv").read
Test.specify "should be able to read in a table with dates" <|
table.column_count.should_equal 3
table.columns.map (.name) . should_equal ['Serial number','Movement type', 'Posting time']
table.row_count.should_equal 6
Test.specify "should be able to treat a single value as a Time_Of_Days" <|
from_column = table.at 'Posting time'
from_column.at 5 . hour . should_equal 15
from_column.at 5 . minute . should_equal 30
from_column.at 5 . should_equal (Time_Of_Day.new 15 30)
Test.specify "should be able to compare columns and table" <|
table.at 'Serial number' . should_equal (Column.from_vector c_number.first c_number.second)
table.at 'Movement type' . should_equal (Column.from_vector c_type.first c_type.second)
table.at 'Posting time' . should_equal (Column.from_vector c_time.first c_time.second)
table.should_equal expected
Test.group "Should be able to serialise a table with Time_Of_Days to Text" <|
Test.specify "should serialise back to input" <|
expected_text = normalize_lines <|
(enso_project.data / "time_of_day_sample.csv").read_text
delimited = Text.from expected format=(File_Format.Delimited "," line_endings=Line_Ending_Style.Unix)
delimited.should_equal expected_text
Test.specify "should serialise dates with format" <|
test_table = Table.new [c_time]
expected_text = 'Posting time\n09-00-00\n14-00-12\n09-00-00\n17-30-00\n09-00-04\n15-30-00\n'
data_formatter = Data_Formatter . with_datetime_formats time_formats=["HH-mm-ss"]
delimited = Text.from test_table format=(File_Format.Delimited "," value_formatter=data_formatter line_endings=Line_Ending_Style.Unix)
delimited.should_equal expected_text
main = Test.Suite.run_main spec

View File

@ -0,0 +1,52 @@
from Standard.Base import all
import Standard.Base.Data.Text.Line_Ending_Style
import Standard.Base.Data.Time
import Standard.Table
import Standard.Table.Data.Column
import Standard.Table.IO.File_Format
from Standard.Table.Data.Data_Formatter as Data_Formatter_Module import Data_Formatter
import Standard.Test
from project.Util import all
spec =
c_number = ["Serial number", ["2LMXK1", "2LMXK1", "JEMLP3", "JEMLP3", "BR83GP", "BR83GP"]]
c_type = ["Movement type", [101, 301, 101, 203, 101, 301]]
c_date = ["Posting date", [Date_Time.new 2015 1 5 9 0, Date_Time.new 2015 1 5 14 0, Date_Time.new 2015 1 6 9 0, Date_Time.new 2015 1 7 17 30, Date_Time.new 2011 1 5 9 0, Date_Time.new 2011 1 9 15 30]]
expected = Table.new [c_number, c_type, c_date]
Test.group "File.read (Delimited) should work with Date_Time" <|
table = (enso_project.data / "datetime_sample.csv").read
Test.specify "should be able to read in a table with dates" <|
table.column_count.should_equal 3
table.columns.map (.name) . should_equal ['Serial number','Movement type', 'Posting date']
table.row_count.should_equal 6
Test.specify "should be able to treat a single value as a Date_Time" <|
from_column = table.at 'Posting date'
from_column.at 5 . year . should_equal 2011
from_column.at 5 . should_equal (Date_Time.new 2011 1 9 15 30)
Test.specify "should be able to compare columns and table" <|
table.at 'Serial number' . should_equal (Column.from_vector c_number.first c_number.second)
table.at 'Movement type' . should_equal (Column.from_vector c_type.first c_type.second)
table.at 'Posting date' . should_equal (Column.from_vector c_date.first c_date.second)
table.should_equal expected
Test.group "Should be able to serialise a table with DateTimes to Text" <|
Test.specify "should serialise back to input" <|
expected_text = normalize_lines <|
(enso_project.data / "datetime_sample.csv").read_text
delimited = Text.from expected format=(File_Format.Delimited "," line_endings=Line_Ending_Style.Unix)
delimited.should_equal expected_text
Test.specify "should serialise dates with format" <|
test_table = Table.new [c_date]
expected_text = 'Posting date\n05.01.2015 09-00\n05.01.2015 14-00\n06.01.2015 09-00\n07.01.2015 17-30\n05.01.2011 09-00\n09.01.2011 15-30\n'
data_formatter = Data_Formatter . with_datetime_formats datetime_formats=["dd.MM.yyyy HH-mm"]
delimited = Text.from test_table format=(File_Format.Delimited "," value_formatter=data_formatter line_endings=Line_Ending_Style.Unix)
delimited.should_equal expected_text
main = Test.Suite.run_main spec

View File

@ -148,12 +148,20 @@ js_parse text format=Nothing =
d = Date.parse text format d = Date.parse text format
js_date d.year d.month d.day js_date d.year d.month d.day
## JSDate is not only a date but also time and timezone.
## Here, we explicitly convert JS ZonedDateTime to LocalDate
js_set_zone local_datetime =
zone = Zone.utc
datetime_with_tz = local_datetime.at_zone(zone)
diff = Duration.between datetime_with_tz local_datetime (timezone_aware=False)
(datetime_with_tz + diff).to_localdate_builtin
js_date year month=1 day=1 = js_date year month=1 day=1 =
Panic.catch Any (js_date_impl year month day) (err -> Error.throw (Time.Time_Error err.payload.cause)) Panic.catch Any (js_set_zone (js_date_impl year month day)) (err -> Error.throw (Time.Time_Error err.payload.cause))
js_array_date year month=1 day=1 = js_array_date year month=1 day=1 =
arr = Panic.catch Any (js_array_dateCreate year month day) (err -> Error.throw (Time.Time_Error err.payload.cause)) arr = Panic.catch Any (js_array_dateCreate year month day) (err -> Error.throw (Time.Time_Error err.payload.cause))
arr.at(0) js_set_zone arr.at(0)
java_parse date_text pattern=Nothing = java_parse date_text pattern=Nothing =
if pattern == Nothing then Panic.catch Polyglot_Error (LocalDate.parse date_text) (err -> Error.throw (Time.Time_Error err.payload.cause.getMessage)) else if pattern == Nothing then Panic.catch Polyglot_Error (LocalDate.parse date_text) (err -> Error.throw (Time.Time_Error err.payload.cause.getMessage)) else

View File

@ -1,7 +1,7 @@
from Standard.Base import all from Standard.Base import all
import Standard.Base.Data.Time.Duration import Standard.Base.Data.Time.Duration
import Standard.Base.Data.Time import Standard.Base.Data.Time.Date_Time
import Standard.Test import Standard.Test
@ -37,8 +37,8 @@ spec =
interval.to_vector . should_equal [-11, 0, 0, 2, 0, 12, 0] interval.to_vector . should_equal [-11, 0, 0, 2, 0, 12, 0]
Test.specify "should create interval between two points in time" <| Test.specify "should create interval between two points in time" <|
time1 = Time.new 2001 1 2 time1 = Date_Time.new 2001 1 2
time2 = Time.new 2001 2 1 time2 = Date_Time.new 2001 2 1
interval = Duration.between time1 time2 interval = Duration.between time1 time2
interval.to_vector . should_equal [0, 0, 0, 720, 0, 0, 0] interval.to_vector . should_equal [0, 0, 0, 720, 0, 0, 0]
@ -99,3 +99,5 @@ spec =
duration_1!=duration_2 . should_be_true duration_1!=duration_2 . should_be_true
duration_1>duration_2 . should_be_true duration_1>duration_2 . should_be_true
duration_1<duration_2 . should_be_false duration_1<duration_2 . should_be_false
main = Test.Suite.run_main spec

View File

@ -7,59 +7,66 @@ import Standard.Base.Data.Time.Zone
import Standard.Test import Standard.Test
polyglot java import java.time.LocalTime
polyglot java import java.time.format.DateTimeFormatter
spec = spec =
Test.group "Time_Of_Day" <| specWith "Time_Of_Day" enso_time Time_Of_Day.parse
specWith "JavaLocalTime" java_time java_parse
specWith name create_new_time parse_time =
Test.group name <|
Test.specify "should create local time" <| Test.specify "should create local time" <|
time = Time_Of_Day.new 1 0 0 time = create_new_time 1 0 0
time . hour . should_equal 1 time . hour . should_equal 1
time . minute . should_equal 0 time . minute . should_equal 0
time . second . should_equal 0 time . second . should_equal 0
time . to_seconds . should_equal 3600 time . to_seconds . should_equal 3600
Test.specify "should handle errors when creating a time" <| Test.specify "should handle errors when creating a time" <|
case Time_Of_Day.new 24 0 0 . catch of case create_new_time 24 0 0 . catch of
Time.Time_Error msg -> Time.Time_Error msg ->
msg . should_equal "Invalid value for HourOfDay (valid values 0 - 23): 24" msg . should_equal "Invalid value for HourOfDay (valid values 0 - 23): 24"
result -> result ->
Test.fail ("Unexpected result: " + result.to_text) Test.fail ("Unexpected result: " + result.to_text)
Test.specify "should format local time using provided pattern" <| Test.specify "should format local time using provided pattern" <|
text = Time_Of_Day.new 12 20 44 . format "HHmmss" text = create_new_time 12 20 44 . format "HHmmss"
text . should_equal "122044" text . should_equal "122044"
Test.specify "should format local time using default pattern" <| Test.specify "should format local time using default pattern" <|
text = Time_Of_Day.new 12 20 44 . to_text text = create_new_time 12 20 44 . to_text
text . should_equal "12:20:44" text . should_equal "12:20:44"
Test.specify "should convert to Json" <| Test.specify "should convert to Json" <|
time = Time_Of_Day.new 1 2 3 time = create_new_time 1 2 3
time.to_json.should_equal <| time.to_json.should_equal <|
time_pairs = [["hour", time.hour], ["minute", time.minute], ["second", time.second], ["nanosecond", time.nanosecond]] time_pairs = [["hour", time.hour], ["minute", time.minute], ["second", time.second], ["nanosecond", time.nanosecond]]
Json.from_pairs ([["type", "Time_Of_Day"]] + time_pairs) Json.from_pairs ([["type", "Time_Of_Day"]] + time_pairs)
Test.specify "should parse default time format" <| Test.specify "should parse default time format" <|
text = Time_Of_Day.new 12 20 44 . to_text text = create_new_time 12 20 44 . to_text
time = Time_Of_Day.parse text time = Time_Of_Day.parse text
time.to_seconds . should_equal 44444 time.to_seconds . should_equal 44444
Test.specify "should parse local time" <| Test.specify "should parse local time" <|
time = Time_Of_Day.parse "10:00:00" time = parse_time "10:00:00"
time.to_seconds . should_equal 36000 time.to_seconds . should_equal 36000
Test.specify "should throw error when parsing invalid time" <| Test.specify "should throw error when parsing invalid time" <|
case Time_Of_Day.parse "1200" . catch of case parse_time "1200" . catch of
Time.Time_Error msg -> Time.Time_Error msg ->
msg . should_equal "Text '1200' could not be parsed at index 2" msg . should_equal "Text '1200' could not be parsed at index 2"
result -> result ->
Test.fail ("Unexpected result: " + result.to_text) Test.fail ("Unexpected result: " + result.to_text)
Test.specify "should parse custom format" <| Test.specify "should parse custom format" <|
time = Time_Of_Day.parse "12:30AM" "hh:mma" time = parse_time "12:30AM" "hh:mma"
time.to_seconds . should_equal 1800 time.to_seconds . should_equal 1800
Test.specify "should throw error when parsing custom format" <| Test.specify "should throw error when parsing custom format" <|
time = Time_Of_Day.parse "12:30" "HH:mm:ss" time = parse_time "12:30" "HH:mm:ss"
case time.catch of case time.catch of
Time.Time_Error msg -> Time.Time_Error msg ->
msg . should_equal "Text '12:30' could not be parsed at index 5" msg . should_equal "Text '12:30' could not be parsed at index 5"
@ -67,48 +74,60 @@ spec =
Test.fail ("Unexpected result: " + result.to_text) Test.fail ("Unexpected result: " + result.to_text)
Test.specify "should convert to time" <| Test.specify "should convert to time" <|
time = Time_Of_Day.new 1 0 0 . to_time (Date.new 2000 12 21) Zone.utc datetime = create_new_time 1 0 0 . to_time (Date.new 2000 12 21) Zone.utc
time . year . should_equal 2000 datetime . year . should_equal 2000
time . month . should_equal 12 datetime . month . should_equal 12
time . day . should_equal 21 datetime . day . should_equal 21
time . hour . should_equal 1 datetime . hour . should_equal 1
time . minute . should_equal 0 datetime . minute . should_equal 0
time . second . should_equal 0 datetime . second . should_equal 0
time . nanosecond . should_equal 0 datetime . nanosecond . should_equal 0
time . zone . zone_id . should_equal Zone.utc.zone_id datetime . zone . zone_id . should_equal Zone.utc.zone_id
Test.specify "should add time-based interval" <| Test.specify "should add time-based interval" <|
time = Time_Of_Day.new + 1.minute time = create_new_time 0 + 1.minute
time . to_seconds . should_equal 60 time . to_seconds . should_equal 60
Test.specify "should subtract time-based interval" <| Test.specify "should subtract time-based interval" <|
time = Time_Of_Day.new - 1.minute time = create_new_time 0 - 1.minute
time . to_seconds . should_equal 86340 time . to_seconds . should_equal 86340
Test.specify "should support mixed interval operators" <| Test.specify "should support mixed interval operators" <|
time = Time_Of_Day.new + 1.hour - 1.second time = create_new_time 0 + 1.hour - 1.second
time . to_seconds . should_equal 3599 time . to_seconds . should_equal 3599
Test.specify "should throw error when adding date-based interval" <| Test.specify "should throw error when adding date-based interval" <|
case (Time_Of_Day.new + 1.day) . catch of case (create_new_time 0 + 1.day) . catch of
Time.Time_Error message -> Time.Time_Error message ->
message . should_equal "Time_Of_Day does not support date intervals" message . should_equal "Time_Of_Day does not support date intervals"
result -> result ->
Test.fail ("Unexpected result: " + result.to_text) Test.fail ("Unexpected result: " + result.to_text)
Test.specify "should throw error when subtracting date-based interval" <| Test.specify "should throw error when subtracting date-based interval" <|
case (Time_Of_Day.new - (1.day - 1.minute)) . catch of case (create_new_time 0 - (1.day - 1.minute)) . catch of
Time.Time_Error message -> Time.Time_Error message ->
message . should_equal "Time_Of_Day does not support date intervals" message . should_equal "Time_Of_Day does not support date intervals"
result -> result ->
Test.fail ("Unexpected result: " + result.to_text) Test.fail ("Unexpected result: " + result.to_text)
Test.specify "should be comparable" <| Test.specify "should be comparable" <|
time_1 = Time_Of_Day.parse "12:30:12.7102" time_1 = parse_time "12:30:12.7102"
time_2 = Time_Of_Day.parse "04:00:10.0" time_2 = parse_time "04:00:10.0"
(time_1 == time_2) . should_be_false (time_1 == time_2) . should_be_false
time_1==time_1 . should_be_true time_1==time_1 . should_be_true
time_1!=time_2 . should_be_true time_1!=time_2 . should_be_true
time_1>time_2 . should_be_true time_1>time_2 . should_be_true
time_1<time_2 . should_be_false time_1<time_2 . should_be_false
enso_time hour minute=0 second=0 nanoOfSecond=0 =
Time_Of_Day.new hour minute second nanoOfSecond
java_time hour minute=0 second=0 nanoOfSecond=0 =
Panic.catch Any (LocalTime.of hour minute second nanoOfSecond) (err -> Error.throw (Time.Time_Error <| err.payload.to_display_text.drop (Text_Sub_Range.First 16)))
java_parse time_text pattern=Nothing =
if pattern == Nothing then Panic.catch Polyglot_Error (LocalTime.parse time_text) (err -> Error.throw (Time.Time_Error err.payload.cause.getMessage)) else
formatter = DateTimeFormatter.ofPattern(pattern)
Panic.catch Polyglot_Error (LocalTime.parse time_text formatter) (err -> Error.throw (Time.Time_Error err.payload.cause.getMessage))
main = Test.Suite.run_main spec

View File

@ -1,16 +1,27 @@
from Standard.Base import all from Standard.Base import all
import Standard.Base.Data.Time.Duration
import Standard.Base.Data.Time import Standard.Base.Data.Time
import Standard.Base.Data.Time.Date_Time
import Standard.Base.Data.Time.Duration
import Standard.Base.Data.Time.Zone import Standard.Base.Data.Time.Zone
import Standard.Test import Standard.Test
polyglot java import java.time.ZonedDateTime
polyglot java import java.time.LocalDateTime
polyglot java import java.time.format.DateTimeFormatter
spec = spec =
Test.group "Time" <| specWith "Date_Time" Date_Time.new Date_Time.parse
specWith "JavascriptDate" js_datetime js_parse nanoseconds_loss_in_precision=True
specWith "JavaZonedDateTime" java_datetime java_parse
specWith "JavascriptDataInArray" js_array_datetime js_parse nanoseconds_loss_in_precision=True
specWith name create_new_datetime parse_datetime nanoseconds_loss_in_precision=False =
Test.group name <|
Test.specify "should create time" <| Test.specify "should create time" <|
time = Time.new 1970 (zone = Zone.utc) time = create_new_datetime 1970 (zone = Zone.utc)
time . year . should_equal 1970 time . year . should_equal 1970
time . month . should_equal 1 time . month . should_equal 1
time . day . should_equal 1 time . day . should_equal 1
@ -21,30 +32,30 @@ spec =
time . zone . zone_id . should_equal Zone.utc.zone_id time . zone . zone_id . should_equal Zone.utc.zone_id
Test.specify "should handle errors when creating time" <| Test.specify "should handle errors when creating time" <|
case Time.new 1970 0 0 . catch of case create_new_datetime 1970 0 0 . catch of
Time.Time_Error msg -> Time.Time_Error msg ->
msg . should_equal "Invalid value for MonthOfYear (valid values 1 - 12): 0" msg . should_equal "Invalid value for MonthOfYear (valid values 1 - 12): 0"
result -> result ->
Test.fail ("Unexpected result: " + result.to_text) Test.fail ("Unexpected result: " + result.to_text)
Test.specify "should format using provided pattern" <| Test.specify "should format using provided pattern" <|
text = Time.new 1970 (zone = Zone.utc) . format "yyyy-MM-dd'T'HH:mm:ss" text = create_new_datetime 1970 (zone = Zone.utc) . format "yyyy-MM-dd'T'HH:mm:ss"
text . should_equal "1970-01-01T00:00:00" text . should_equal "1970-01-01T00:00:00"
Test.specify "should format using default pattern" <| Test.specify "should format using default pattern" <|
text = Time.new 1970 (zone = Zone.utc) . to_text text = create_new_datetime 1970 (zone = Zone.utc) . to_text
text . should_equal "1970-01-01T00:00:00Z[UTC]" text . should_equal "1970-01-01T00:00:00Z[UTC]"
Test.specify "should convert to Json" <| Test.specify "should convert to Json" <|
time = Time.new 1970 12 21 (zone = Zone.utc) time = create_new_datetime 1970 12 21 (zone = Zone.utc)
time.to_json.should_equal <| time.to_json.should_equal <|
zone_pairs = [["zone", Zone.utc]] zone_pairs = [["zone", Zone.utc]]
time_pairs = [["year", time.year], ["month", time.month], ["day", time.day], ["hour", time.hour], ["minute", time.minute], ["second", time.second], ["nanosecond", time.nanosecond]] time_pairs = [["year", time.year], ["month", time.month], ["day", time.day], ["hour", time.hour], ["minute", time.minute], ["second", time.second], ["nanosecond", time.nanosecond]]
Json.from_pairs ([["type", "Time"]] + time_pairs + zone_pairs) Json.from_pairs ([["type", "Time"]] + time_pairs + zone_pairs)
Test.specify "should parse default time format" <| Test.specify "should parse default time format" <|
text = Time.new 1970 (zone = Zone.utc) . to_text text = create_new_datetime 1970 (zone = Zone.utc) . to_text
time = Time.parse text time = parse_datetime text
time . year . should_equal 1970 time . year . should_equal 1970
time . month . should_equal 1 time . month . should_equal 1
time . day . should_equal 1 time . day . should_equal 1
@ -55,7 +66,7 @@ spec =
time . zone . zone_id . should_equal Zone.utc.zone_id time . zone . zone_id . should_equal Zone.utc.zone_id
Test.specify "should parse local time adding system zone" <| Test.specify "should parse local time adding system zone" <|
time = Time.parse "1970-01-01T00:00:01" time = parse_datetime "1970-01-01T00:00:01"
time . year . should_equal 1970 time . year . should_equal 1970
time . month . should_equal 1 time . month . should_equal 1
time . day . should_equal 1 time . day . should_equal 1
@ -63,31 +74,32 @@ spec =
time . minute . should_equal 0 time . minute . should_equal 0
time . second . should_equal 1 time . second . should_equal 1
time . nanosecond . should_equal 0 time . nanosecond . should_equal 0
time . zone . zone_id . should_equal Zone.system.zone_id time . zone . should_equal Zone.system
Test.specify "should parse time Z" <| Test.specify "should parse time Z" <|
time = Time.parse "1970-01-01T00:00:01Z" time = parse_datetime "1970-01-01T00:00:01Z"
time . to_epoch_seconds . should_equal 1 time . to_epoch_seconds . should_equal 1
time . zone . zone_id . should_equal "Z" time . zone . zone_id . should_equal "Z"
Test.specify "should parse time UTC" <| Test.specify "should parse time UTC" <|
time = Time.parse "1970-01-01T00:00:01Z[UTC]" time = parse_datetime "1970-01-01T00:00:01Z[UTC]"
time . to_epoch_seconds . should_equal 1 time . to_epoch_seconds . should_equal 1
time . zone . zone_id . should_equal "UTC" time . zone . zone_id . should_equal "UTC"
Test.specify "should parse time with nanoseconds" <| Test.specify "should parse time with nanoseconds" <|
time = Time.parse "1970-01-01T00:00:01.123456789Z" time = parse_datetime "1970-01-01T00:00:01.123456789Z"
time . year . should_equal 1970 time . year . should_equal 1970
time . month . should_equal 1 time . month . should_equal 1
time . day . should_equal 1 time . day . should_equal 1
time . hour . should_equal 0 time . hour . should_equal 0
time . minute . should_equal 0 time . minute . should_equal 0
time . second . should_equal 1 time . second . should_equal 1
time . nanosecond . should_equal 123456789 if nanoseconds_loss_in_precision then time . nanosecond . should_equal 123000000 else
time . nanosecond . should_equal 123456789
time . zone . zone_id . should_equal "Z" time . zone . zone_id . should_equal "Z"
Test.specify "should parse time with offset-based zone" <| Test.specify "should parse time with offset-based zone" <|
time = Time.parse "1970-01-01T00:00:01+01:00" time = parse_datetime "1970-01-01T00:00:01+01:00"
time . year . should_equal 1970 time . year . should_equal 1970
time . month . should_equal 1 time . month . should_equal 1
time . day . should_equal 1 time . day . should_equal 1
@ -98,7 +110,7 @@ spec =
time . zone . zone_id . should_equal (Zone.new 1 . zone_id) time . zone . zone_id . should_equal (Zone.new 1 . zone_id)
Test.specify "should parse time with id-based zone" <| Test.specify "should parse time with id-based zone" <|
time = Time.parse "1970-01-01T00:00:01+01:00[Europe/Paris]" time = parse_datetime "1970-01-01T00:00:01+01:00[Europe/Paris]"
time . year . should_equal 1970 time . year . should_equal 1970
time . month . should_equal 1 time . month . should_equal 1
time . day . should_equal 1 time . day . should_equal 1
@ -109,14 +121,14 @@ spec =
time . zone . zone_id . should_equal "Europe/Paris" time . zone . zone_id . should_equal "Europe/Paris"
Test.specify "should throw error when parsing invalid time" <| Test.specify "should throw error when parsing invalid time" <|
case Time.parse "2008-1-1" . catch of case parse_datetime "2008-1-1" . catch of
Time.Time_Error msg -> Time.Time_Error msg ->
msg . should_equal "Text '2008-1-1' could not be parsed at index 5" msg . should_equal "Text '2008-1-1' could not be parsed at index 5"
result -> result ->
Test.fail ("Unexpected result: " + result.to_text) Test.fail ("Unexpected result: " + result.to_text)
Test.specify "should parse custom format of zoned time" <| Test.specify "should parse custom format of zoned time" <|
time = Time.parse "2020-05-06 04:30:20 UTC" "yyyy-MM-dd HH:mm:ss z" time = parse_datetime "2020-05-06 04:30:20 UTC" "yyyy-MM-dd HH:mm:ss z"
time . year . should_equal 2020 time . year . should_equal 2020
time . month . should_equal 5 time . month . should_equal 5
time . day . should_equal 6 time . day . should_equal 6
@ -127,7 +139,7 @@ spec =
time . zone . zone_id . should_equal "Etc/UTC" time . zone . zone_id . should_equal "Etc/UTC"
Test.specify "should parse custom format of local time" <| Test.specify "should parse custom format of local time" <|
time = Time.parse "06 of May 2020 at 04:30AM" "dd 'of' MMMM yyyy 'at' hh:mma" time = parse_datetime "06 of May 2020 at 04:30AM" "dd 'of' MMMM yyyy 'at' hh:mma"
time . year . should_equal 2020 time . year . should_equal 2020
time . month . should_equal 5 time . month . should_equal 5
time . day . should_equal 6 time . day . should_equal 6
@ -137,7 +149,7 @@ spec =
time . nanosecond . should_equal 0 time . nanosecond . should_equal 0
Test.specify "should throw error when parsing custom format" <| Test.specify "should throw error when parsing custom format" <|
time = Time.parse "2008-01-01" "yyyy-MM-dd'T'HH:mm:ss'['z']'" time = parse_datetime "2008-01-01" "yyyy-MM-dd'T'HH:mm:ss'['z']'"
case time.catch of case time.catch of
Time.Time_Error msg -> Time.Time_Error msg ->
msg . should_equal "Text '2008-01-01' could not be parsed at index 10" msg . should_equal "Text '2008-01-01' could not be parsed at index 10"
@ -145,16 +157,16 @@ spec =
Test.fail ("Unexpected result: " + result.to_text) Test.fail ("Unexpected result: " + result.to_text)
Test.specify "should get epoch seconds" <| Test.specify "should get epoch seconds" <|
time = Time.new 1970 1 1 0 0 8 (zone = Zone.utc) time = create_new_datetime 1970 1 1 0 0 8 (zone = Zone.utc)
time . to_epoch_seconds . should_equal 8 time . to_epoch_seconds . should_equal 8
Test.specify "should get epoch millis" <| Test.specify "should get epoch millis" <|
time = Time.new 1970 1 1 0 0 8 (zone = Zone.utc) time = create_new_datetime 1970 1 1 0 0 8 (zone = Zone.utc)
time . to_epoch_milliseconds . should_equal 8000 time . to_epoch_milliseconds . should_equal 8000
Test.specify "should set offset-based timezone" <| Test.specify "should set offset-based timezone" <|
tz = Zone.new 1 1 1 tz = Zone.new 1 1 1
time = Time.new 1970 (zone = Zone.utc) . at_zone tz time = create_new_datetime 1970 (zone = Zone.utc) . at_zone tz
time . year . should_equal 1970 time . year . should_equal 1970
time . month . should_equal 1 time . month . should_equal 1
time . day . should_equal 1 time . day . should_equal 1
@ -166,7 +178,7 @@ spec =
Test.specify "should set id-based timezone" <| Test.specify "should set id-based timezone" <|
tz = Zone.parse "Europe/Moscow" tz = Zone.parse "Europe/Moscow"
time = Time.new 1970 (zone = Zone.utc) . at_zone tz time = create_new_datetime 1970 (zone = Zone.utc) . at_zone tz
time . year . should_equal 1970 time . year . should_equal 1970
time . month . should_equal 1 time . month . should_equal 1
time . day . should_equal 1 time . day . should_equal 1
@ -177,33 +189,33 @@ spec =
time . zone . zone_id . should_equal tz.zone_id time . zone . zone_id . should_equal tz.zone_id
Test.specify "should get time of day from offsed-based time" <| Test.specify "should get time of day from offsed-based time" <|
time = Time.parse "1970-01-01T00:00:01+01:00" . time_of_day time = parse_datetime "1970-01-01T00:00:01+01:00" . time_of_day
time . hour . should_equal 0 time . hour . should_equal 0
time . minute . should_equal 0 time . minute . should_equal 0
time . second . should_equal 1 time . second . should_equal 1
time . nanosecond . should_equal 0 time . nanosecond . should_equal 0
Test.specify "should get time of day from id-based time" <| Test.specify "should get time of day from id-based time" <|
time = Time.parse "1970-01-01T00:00:01+01:00[Europe/Paris]" . time_of_day time = parse_datetime "1970-01-01T00:00:01+01:00[Europe/Paris]" . time_of_day
time . hour . should_equal 0 time . hour . should_equal 0
time . minute . should_equal 0 time . minute . should_equal 0
time . second . should_equal 1 time . second . should_equal 1
time . nanosecond . should_equal 0 time . nanosecond . should_equal 0
Test.specify "should get date from offsed-based time" <| Test.specify "should get date from offsed-based time" <|
time = Time.parse "1970-01-01T00:00:01+01:00" . date time = parse_datetime "1970-01-01T00:00:01+01:00" . date
time . year . should_equal 1970 time . year . should_equal 1970
time . month . should_equal 1 time . month . should_equal 1
time . day . should_equal 1 time . day . should_equal 1
Test.specify "should get date from id-based time" <| Test.specify "should get date from id-based time" <|
time = Time.parse "1970-01-01T00:00:01+01:00[Europe/Paris]" . date time = parse_datetime "1970-01-01T00:00:01+01:00[Europe/Paris]" . date
time . year . should_equal 1970 time . year . should_equal 1970
time . month . should_equal 1 time . month . should_equal 1
time . day . should_equal 1 time . day . should_equal 1
Test.specify "should add time interval" <| Test.specify "should add time interval" <|
time = Time.new 1970 (zone = Zone.utc) + 1.nanosecond time = create_new_datetime 1970 (zone = Zone.utc) + 1.nanosecond
time . year . should_equal 1970 time . year . should_equal 1970
time . month . should_equal 1 time . month . should_equal 1
time . day . should_equal 1 time . day . should_equal 1
@ -211,10 +223,10 @@ spec =
time . minute . should_equal 0 time . minute . should_equal 0
time . second . should_equal 0 time . second . should_equal 0
time . nanosecond . should_equal 1 time . nanosecond . should_equal 1
time . zone . zone_id . should_equal Zone.utc.zone_id time . zone . should_equal Zone.utc
Test.specify "should add date interval" <| Test.specify "should add date interval" <|
time = Time.new 1970 (zone = Zone.utc) + 1.month time = create_new_datetime 1970 (zone = Zone.utc) + 1.month
time . year . should_equal 1970 time . year . should_equal 1970
time . month . should_equal 2 time . month . should_equal 2
time . day . should_equal 1 time . day . should_equal 1
@ -225,7 +237,7 @@ spec =
time . zone . zone_id . should_equal Zone.utc.zone_id time . zone . zone_id . should_equal Zone.utc.zone_id
Test.specify "should add mixed date time interval" <| Test.specify "should add mixed date time interval" <|
time = Time.new 1970 (zone = Zone.utc) + (1.month + 3.hours) time = create_new_datetime 1970 (zone = Zone.utc) + (1.month + 3.hours)
time . year . should_equal 1970 time . year . should_equal 1970
time . month . should_equal 2 time . month . should_equal 2
time . day . should_equal 1 time . day . should_equal 1
@ -236,7 +248,7 @@ spec =
time . zone . zone_id . should_equal Zone.utc.zone_id time . zone . zone_id . should_equal Zone.utc.zone_id
Test.specify "should subtract time interval" <| Test.specify "should subtract time interval" <|
time = Time.new 1970 (zone = Zone.utc) - 1.hour time = create_new_datetime 1970 (zone = Zone.utc) - 1.hour
time . year . should_equal 1969 time . year . should_equal 1969
time . month . should_equal 12 time . month . should_equal 12
time . day . should_equal 31 time . day . should_equal 31
@ -247,7 +259,7 @@ spec =
time . zone . zone_id . should_equal Zone.utc.zone_id time . zone . zone_id . should_equal Zone.utc.zone_id
Test.specify "should subtract date interval" <| Test.specify "should subtract date interval" <|
time = Time.new 1970 (zone = Zone.utc) - 1.month time = create_new_datetime 1970 (zone = Zone.utc) - 1.month
time . year . should_equal 1969 time . year . should_equal 1969
time . month . should_equal 12 time . month . should_equal 12
time . day . should_equal 1 time . day . should_equal 1
@ -258,7 +270,7 @@ spec =
time . zone . zone_id . should_equal Zone.utc.zone_id time . zone . zone_id . should_equal Zone.utc.zone_id
Test.specify "should subtract mixed date time interval" <| Test.specify "should subtract mixed date time interval" <|
time = Time.new 1970 (zone = Zone.utc) - (1.month - 3.hours) time = create_new_datetime 1970 (zone = Zone.utc) - (1.month - 3.hours)
time . year . should_equal 1969 time . year . should_equal 1969
time . month . should_equal 12 time . month . should_equal 12
time . day . should_equal 1 time . day . should_equal 1
@ -269,7 +281,7 @@ spec =
time . zone . zone_id . should_equal Zone.utc.zone_id time . zone . zone_id . should_equal Zone.utc.zone_id
Test.specify "should support mixed interval operators" <| Test.specify "should support mixed interval operators" <|
time = Time.new 1970 (zone = Zone.utc) - 1.month + 12.hours time = create_new_datetime 1970 (zone = Zone.utc) - 1.month + 12.hours
time . year . should_equal 1969 time . year . should_equal 1969
time . month . should_equal 12 time . month . should_equal 12
time . day . should_equal 1 time . day . should_equal 1
@ -280,11 +292,64 @@ spec =
time . zone . zone_id . should_equal Zone.utc.zone_id time . zone . zone_id . should_equal Zone.utc.zone_id
Test.specify "should be comparable" <| Test.specify "should be comparable" <|
time_1 = Time.parse "2021-01-01T00:30:12.7102[UTC]" time_1 = parse_datetime "2021-01-01T00:30:12.7102[UTC]"
time_2 = Time.parse "2021-01-01T04:00:10.0+04:00" time_2 = parse_datetime "2021-01-01T04:00:10.0+04:00"
(time_1 == time_2) . should_be_false (time_1 == time_2) . should_be_false
time_1==time_1 . should_be_true time_1==time_1 . should_be_true
time_1!=time_2 . should_be_true time_1!=time_2 . should_be_true
time_1>time_2 . should_be_true time_1>time_2 . should_be_true
time_1<time_2 . should_be_false time_1<time_2 . should_be_false
js_datetime year month=1 day=1 hour=0 minute=0 second=0 nanosecond=0 zone=Zone.system =
Panic.catch Any (js_datetime_with_zone year month day hour minute second nanosecond zone) (err -> Error.throw (Time.Time_Error err.payload.cause))
# This ensures that date returned by javascript has the right timezone specified by the zone parameter.
# Javascript's toLocaleString will accept the timezone but it will just adapt the datetime while keeping the local timezone.
js_datetime_with_zone year month day hour minute second nanosecond zone =
js_set_zone (js_local_datetime_impl year month day hour minute second nanosecond) zone
js_set_zone local_datetime zone =
datetime_with_tz = local_datetime.at_zone(zone)
diff = Duration.between datetime_with_tz local_datetime (timezone_aware=False)
datetime_with_tz + diff
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}`;
}
return new Date(year, month - 1, day, hour, minute, second, nanosecond / 1000000);
js_parse text format=Nothing =
d = Date_Time.parse text format
js_datetime d.year d.month d.day d.hour d.minute d.second d.nanosecond d.zone
js_array_datetime year month=1 day=1 hour=0 minute=0 second=0 nanosecond=0 zone=Zone.system =
arr = Panic.catch Any (js_array_datetimeCreate year month day hour minute second nanosecond) (err -> Error.throw (Time.Time_Error err.payload.cause))
js_set_zone arr.at(0) zone
foreign js js_array_datetimeCreate year month day hour minute second nanosecond = """
if (month > 12 || month < 1) {
throw `Invalid value for MonthOfYear (valid values 1 - 12): ${month}`;
}
return [ new Date(year, month - 1, day, hour, minute, second, nanosecond / 1000000) ];
java_datetime year month=1 day=1 hour=0 minute=0 second=0 nanosecond=0 zone=Zone.system =
Panic.catch Any (ZonedDateTime.of year month day hour minute second nanosecond zone) (err -> Error.throw (Time.Time_Error <| err.payload.to_display_text.drop (Text_Sub_Range.First 16)))
maybe_parse_java_zoned text pattern=Nothing =
if pattern == Nothing then ZonedDateTime.parse text else
ZonedDateTime.parse text pattern
parse_java_local original_error text pattern=Nothing =
if pattern == Nothing then Panic.catch Polyglot_Error (LocalDateTime.parse text) (_ -> Error.throw (Time.Time_Error original_error.payload.cause.getMessage)) else
formatter = DateTimeFormatter.ofPattern(pattern)
Panic.catch Polyglot_Error (LocalDateTime.parse text formatter) (_ -> Error.throw (Time.Time_Error original_error.payload.cause.getMessage))
java_parse date_text_raw pattern=Nothing =
utc_replaced = date_text_raw.replace "[UTC]" "Z"
date_text = if utc_replaced.ends_with "ZZ" then date_text_raw else utc_replaced
if pattern == Nothing then Panic.catch Polyglot_Error (maybe_parse_java_zoned date_text) (err -> parse_java_local err date_text pattern) else
formatter = DateTimeFormatter.ofPattern(pattern)
Panic.catch Polyglot_Error (maybe_parse_java_zoned date_text formatter) (err -> parse_java_local err date_text pattern)
main = Test.Suite.run_main spec

View File

@ -1,9 +1,13 @@
from Standard.Base import all from Standard.Base import all
import Standard.Base.Data.Time
import Standard.Base.Data.Time.Zone import Standard.Base.Data.Time.Zone
import Standard.Test import Standard.Test
polyglot java import java.time.ZoneId
polyglot java import java.time.ZoneOffset
spec = spec =
Test.group "Zone" <| Test.group "Zone" <|
Test.specify "should get system zone id" <| Test.specify "should get system zone id" <|
@ -29,3 +33,36 @@ spec =
Json.from_pairs [["type", "Zone"], ["id", "+01:02:03"]] Json.from_pairs [["type", "Zone"], ["id", "+01:02:03"]]
Zone.utc.to_json.should_equal <| Zone.utc.to_json.should_equal <|
Json.from_pairs [["type", "Zone"], ["id", "UTC"]] Json.from_pairs [["type", "Zone"], ["id", "UTC"]]
Test.specify "should throw error when parsing invalid zone id" <|
case Zone.parse "foo" . catch of
Time.Time_Error msg ->
msg . should_equal "Unknown time-zone ID: foo"
result ->
Test.fail ("Unexpected result: " + result.to_text)
Test.group "JavaZoneId" <|
Test.specify "should get system zone id" <|
defaultZone = ZoneId.systemDefault
Zone.system . should_equal defaultZone
Test.specify "should parse UTC zone" <|
zone = "UTC"
id = ZoneId.of zone
id . should_equal Zone.utc
Test.specify "should parse id-based zone" <|
zone = "Europe/Warsaw"
id = ZoneId.of zone
id . zone_id . should_equal zone
Test.specify "should parse offset-based zone" <|
zone = "+01:02:03"
id = ZoneId.of zone
id . zone_id . should_equal zone
Test.specify "should get utc zone id" <|
zone = ZoneId.of "UTC"
zone . should_equal Zone.utc
Test.specify "should convert to Json" <|
zone = ZoneOffset.ofHoursMinutesSeconds 1 2 3
zone.to_json.should_equal <|
Json.from_pairs [["type", "Zone"], ["id", "+01:02:03"]]
(ZoneId.of "UTC").to_json.should_equal <|
Json.from_pairs [["type", "Zone"], ["id", "UTC"]]
main = Test.Suite.run_main spec

View File

@ -47,8 +47,8 @@ spec =
Test.specify "send Enso date into Java" <| Test.specify "send Enso date into Java" <|
ensodate = Date.new 2022 04 01 ensodate = Date.new 2022 04 01
javatime = LocalTime.of 10 26 javatime = LocalTime.of 10 26
javatimedate = javatime.atDate ensodate javatimedate = javatime . to_time ensodate
april1st = javatimedate . toLocalDate april1st = javatimedate . date
april1st.year.should_equal 2022 april1st.year.should_equal 2022
april1st.month.should_equal 4 april1st.month.should_equal 4
april1st.day.should_equal 1 april1st.day.should_equal 1