From 35ccbed8a20a3b4d50c5adbe9850bdc0c5459114 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Wed, 13 Sep 2023 01:25:57 +0200 Subject: [PATCH 01/76] Adding structure for date time format --- .../Data/Time/Date_Time_Format_Pattern.enso | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso new file mode 100644 index 000000000000..1c9c56da9033 --- /dev/null +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso @@ -0,0 +1,81 @@ +type Date_Time_Format_Pattern + ## A simple date-time format pattern. + + Every letter in the pattern is interpreted as a pattern character as + described in the table below. Any character that is not a letter in the + pattern is treated as a literal character. If a sequence of letters needs + to be put in as a literal, it can be escaped using single quotes. Use two + single quotes in a row to represent a single quote in the result. + + Pattern characters are interpreted case insensitively, with the exception + of `M/m' and 'H/h'. + + Pattern characters: + - y: Year. The number of pattern letters determines the minimum number of + digits. If only two digits are supplied, TODO how do we interpret '90? + - M: Month of year. The number of pattern letters determines the format. + Single letter will accept any number (1-12), two letters will require + zero padding (01-12), three letters will use the short name, and four + letters will use the full name. The names that are expected depend on + the locale set when parsing a date. + - d: Day of month. If two digits are supplied, zero padding is used. + - E: Day of week. It will use a short name if repeated 3 or less times + and a full name otherwise. Providing just a day of week is usually not + enough to uniquely identify a date, but it can be used as a sanity + check together with other fields. + - H: 24h hour of day (0-23). + - h: 12h hour of day (0-12). The `a` pattern is needed to disambiguate + between am and pm. TODO possible to have both 0 and 12?? + - m: Minute of hour. + - s: Second of minute. + - f: Fractional part of the second. The number of pattern letters + determines the number of digits. If one letter is used, any number of + digits will be accepted. + - a: AM/PM marker. Case insensitive. + - T: If repeated 3 or less times - Time zone ID (e.g. Europe/Warsaw, Z, + -08:30), otherwise - Time zone name (e.g. Central European Time, CET). + - Z: Zone offset (e.g. +0000, -0830, +08:30:15). + + Some parts, like fractions of a second may not be required. The square + brackets `[]` can be used to surround such optional sections. + + > Example + Date_Time.parse "yyyy-MM-dd'T'HH:mm:ss.fZ" "2021-10-12T12:34:56.789+0200" == (Date_Time.new 2021 10 12 12 34 56 milliseconds=789 (Time_Zone.new hours=2)) + Date.parse "EEE, d MMM yyyy" "Tue, 12 Oct 2021" == (Date.new 2021 10 12) + Date.parse "EEEE, dd MMMM ''98" "Thursday, 1 October '98" == (Date.new 1998 10 01) + Date_Time.parse "d/M/Y h:mm a" "12/10/2021 5:34 PM" == (Date_Time.new 2021 10 12 17 34 00) + Simple pattern:Text + + ## A pattern for formats related to the ISO 8601 leap week calendar. + + The ISO 8601 leap week calendar is a variation of the ISO 8601 calendar + that defines a leap week as the week that contains the 29th of February. + This calendar is used by some European and Middle Eastern countries. + + The pattern is a sequence of letters and symbols that are interpreted as + follows: + - Y: The week based year. + - W: Week of year. + - d: Numeric day of week (1-7). + - E: Named day of week. It will use a short name if repeated 3 or less + times and a full name otherwise. + + The same as in the `Simple` pattern, the single quotes can be used to + escape letter literals and square brackets can be used to indicate + optional sections. + + > Example + Date.parse (ISO_Week_Date "YYYY-'W'WW-d" "1976-W53-6") == (Date.new 1977 01 01) + Date.parse (ISO_Week_Date "YYYY-'W'WW, eee" "1978-W01, Mon") == (Date.new 1978 01 02) + ISO_Week_Date pattern:Text + + ## ADVANCED + A pattern built using the Java pattern syntax. + + This relies on the Java `DateTimeFormatter.ofPattern` method to build the + date time format. See the Java documentation for explanation of the + pattern format: https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/format/DateTimeFormatter.html#patterns + Java pattern:Text + +## PRIVATE +Date_Time_Format_Pattern.from (that:Text) = Date_Time_Format_Pattern.Simple that From dc0dec466833363fa8fbf8b95faccb406ccffda6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Wed, 13 Sep 2023 01:30:47 +0200 Subject: [PATCH 02/76] updates --- .../Data/Time/Date_Time_Format_Pattern.enso | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso index 1c9c56da9033..3a6626465718 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso @@ -12,17 +12,23 @@ type Date_Time_Format_Pattern Pattern characters: - y: Year. The number of pattern letters determines the minimum number of - digits. If only two digits are supplied, TODO how do we interpret '90? - - M: Month of year. The number of pattern letters determines the format. - Single letter will accept any number (1-12), two letters will require - zero padding (01-12), three letters will use the short name, and four - letters will use the full name. The names that are expected depend on - the locale set when parsing a date. - - d: Day of month. If two digits are supplied, zero padding is used. - - E: Day of week. It will use a short name if repeated 3 or less times - and a full name otherwise. Providing just a day of week is usually not - enough to uniquely identify a date, but it can be used as a sanity - check together with other fields. + digits. + - y: The year using any number of digits. + - yy: The year, using at most two digits. TODO mapping + - yyyy: The year, using exactly four digits. + - M: Month of year. The number of pattern letters determines the format: + - M: Any number (1-12). + - MM: Month number with zero padding required (01-12). + - MMM: Short name of the month (Jan-Dec). + - MMMM: Full name of the month (January-December). + The names that are expected depend on the locale. + - d: Day. The number of pattern letters determines the format: + - d: Any number (1-31). + - dd: Day number with zero padding required (01-31). + - ddd: Short name of the day of week (Mon-Sun). + - dddd: Full name of the day of week (Monday-Sunday). + Both day of week and day of month may be included in a single pattern - + in such case the day of week is used as a sanity check. - H: 24h hour of day (0-23). - h: 12h hour of day (0-12). The `a` pattern is needed to disambiguate between am and pm. TODO possible to have both 0 and 12?? @@ -41,8 +47,8 @@ type Date_Time_Format_Pattern > Example Date_Time.parse "yyyy-MM-dd'T'HH:mm:ss.fZ" "2021-10-12T12:34:56.789+0200" == (Date_Time.new 2021 10 12 12 34 56 milliseconds=789 (Time_Zone.new hours=2)) - Date.parse "EEE, d MMM yyyy" "Tue, 12 Oct 2021" == (Date.new 2021 10 12) - Date.parse "EEEE, dd MMMM ''98" "Thursday, 1 October '98" == (Date.new 1998 10 01) + Date.parse "ddd, d MMM yyyy" "Tue, 12 Oct 2021" == (Date.new 2021 10 12) + Date.parse "dddd, dd MMMM ''98" "Thursday, 1 October '98" == (Date.new 1998 10 01) Date_Time.parse "d/M/Y h:mm a" "12/10/2021 5:34 PM" == (Date_Time.new 2021 10 12 17 34 00) Simple pattern:Text From 4aa2d622fee0f1bcb431f722be57966cea1d1800 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Wed, 13 Sep 2023 12:39:29 +0200 Subject: [PATCH 03/76] clarifications --- .../Data/Time/Date_Time_Format_Pattern.enso | 54 +++++++++++++++---- 1 file changed, 43 insertions(+), 11 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso index 3a6626465718..7bb075e40f99 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso @@ -10,7 +10,7 @@ type Date_Time_Format_Pattern Pattern characters are interpreted case insensitively, with the exception of `M/m' and 'H/h'. - Pattern characters: + Date pattern characters: - y: Year. The number of pattern letters determines the minimum number of digits. - y: The year using any number of digits. @@ -21,14 +21,17 @@ type Date_Time_Format_Pattern - MM: Month number with zero padding required (01-12). - MMM: Short name of the month (Jan-Dec). - MMMM: Full name of the month (January-December). - The names that are expected depend on the locale. + The month names depend on the selected locale. - d: Day. The number of pattern letters determines the format: - d: Any number (1-31). - dd: Day number with zero padding required (01-31). - ddd: Short name of the day of week (Mon-Sun). - dddd: Full name of the day of week (Monday-Sunday). + The weekday names depend on the selected locale. Both day of week and day of month may be included in a single pattern - in such case the day of week is used as a sanity check. + + Time pattern characters: - H: 24h hour of day (0-23). - h: 12h hour of day (0-12). The `a` pattern is needed to disambiguate between am and pm. TODO possible to have both 0 and 12?? @@ -38,6 +41,8 @@ type Date_Time_Format_Pattern determines the number of digits. If one letter is used, any number of digits will be accepted. - a: AM/PM marker. Case insensitive. + + Time zone pattern characters: - T: If repeated 3 or less times - Time zone ID (e.g. Europe/Warsaw, Z, -08:30), otherwise - Time zone name (e.g. Central European Time, CET). - Z: Zone offset (e.g. +0000, -0830, +08:30:15). @@ -46,10 +51,22 @@ type Date_Time_Format_Pattern brackets `[]` can be used to surround such optional sections. > Example - Date_Time.parse "yyyy-MM-dd'T'HH:mm:ss.fZ" "2021-10-12T12:34:56.789+0200" == (Date_Time.new 2021 10 12 12 34 56 milliseconds=789 (Time_Zone.new hours=2)) - Date.parse "ddd, d MMM yyyy" "Tue, 12 Oct 2021" == (Date.new 2021 10 12) - Date.parse "dddd, dd MMMM ''98" "Thursday, 1 October '98" == (Date.new 1998 10 01) - Date_Time.parse "d/M/Y h:mm a" "12/10/2021 5:34 PM" == (Date_Time.new 2021 10 12 17 34 00) + Parsing date/time values + + Date_Time.parse "yyyy-MM-dd'T'HH:mm:ss.fZ" "2021-10-12T12:34:56.789+0200" == (Date_Time.new 2021 10 12 12 34 56 milliseconds=789 (Time_Zone.new hours=2)) + Date.parse "ddd, d MMM yyyy" "Tue, 12 Oct 2021" == (Date.new 2021 10 12) + Date.parse "dddd, dd MMMM ''98" "Thursday, 1 October '98" == (Date.new 1998 10 01) + Date_Time.parse "d/M/Y h:mm a" "12/10/2021 5:34 PM" == (Date_Time.new 2021 10 12 17 34 00) + + > Example + Omitting the day will yield the first day of the month. + + Date.parse "yyyy-MM" "2021-10" == (Date.new 2021 10 01) + + > Example + Omitting the year will yield the current year. + + Date.parse "MM-dd" "10-12" == (Date.new (Date.today.year) 10 12) Simple pattern:Text ## A pattern for formats related to the ISO 8601 leap week calendar. @@ -62,17 +79,32 @@ type Date_Time_Format_Pattern follows: - Y: The week based year. - W: Week of year. - - d: Numeric day of week (1-7). - - E: Named day of week. It will use a short name if repeated 3 or less - times and a full name otherwise. + - d: Day of week. + - d: Numeric day of week (1-7). 1 is Monday. + - dd: Numeric day of week with zero padding (01-07). + - ddd: Short name of the day of week (Mon-Sun). + - dddd: Full name of the day of week (Monday-Sunday). + The weekday names depend on the selected locale. + + Moreover, all time and timezone pattern characters like in `Simple` case + are supported too - in case you need to parse a date time value with the + date part in ISO week date format. The same as in the `Simple` pattern, the single quotes can be used to escape letter literals and square brackets can be used to indicate optional sections. > Example - Date.parse (ISO_Week_Date "YYYY-'W'WW-d" "1976-W53-6") == (Date.new 1977 01 01) - Date.parse (ISO_Week_Date "YYYY-'W'WW, eee" "1978-W01, Mon") == (Date.new 1978 01 02) + Parsing a date in the ISO week date format + + Date.parse (ISO_Week_Date "YYYY-'W'WW-d" "1976-W53-6") == (Date.new 1977 01 01) + Date.parse (ISO_Week_Date "YYYY-'W'WW, eee" "1978-W01, Mon") == (Date.new 1978 01 02) + Date_Time.parse (ISO_Week_Date "YYYY-'W'WW-d HH:mm:ss" "1978-W01-4 12:34:56") == (Date_Time.new 1978 01 05 12 34 56) + + > Example + Omitting the day of the week will result in the first day of that week. + + Date.parse (ISO_Week_Date "YYYY-'W'WW" "1978-W01") == (Date.new 1978 01 02) ISO_Week_Date pattern:Text ## ADVANCED From 68dfa254519b78f23f8b291adc2225a25c36089a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Wed, 13 Sep 2023 14:23:04 +0200 Subject: [PATCH 04/76] improvements --- .../Data/Time/Date_Time_Format_Pattern.enso | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso index 7bb075e40f99..aa18f9796888 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso @@ -1,3 +1,7 @@ +import project.Data.Text.Text + +polyglot java import java.time.format.DateTimeFormatter + type Date_Time_Format_Pattern ## A simple date-time format pattern. @@ -14,7 +18,9 @@ type Date_Time_Format_Pattern - y: Year. The number of pattern letters determines the minimum number of digits. - y: The year using any number of digits. - - yy: The year, using at most two digits. TODO mapping + - yy: The year, using at most two digits. The default range is + 1950-2049, but this can be changed by including the end year in + braces e.g. `yy{2099}`. - yyyy: The year, using exactly four digits. - M: Month of year. The number of pattern letters determines the format: - M: Any number (1-12). @@ -34,13 +40,13 @@ type Date_Time_Format_Pattern Time pattern characters: - H: 24h hour of day (0-23). - h: 12h hour of day (0-12). The `a` pattern is needed to disambiguate - between am and pm. TODO possible to have both 0 and 12?? + between AM and PM. - m: Minute of hour. - s: Second of minute. - f: Fractional part of the second. The number of pattern letters determines the number of digits. If one letter is used, any number of digits will be accepted. - - a: AM/PM marker. Case insensitive. + - a: AM/PM marker. Time zone pattern characters: - T: If repeated 3 or less times - Time zone ID (e.g. Europe/Warsaw, Z, @@ -55,7 +61,7 @@ type Date_Time_Format_Pattern Date_Time.parse "yyyy-MM-dd'T'HH:mm:ss.fZ" "2021-10-12T12:34:56.789+0200" == (Date_Time.new 2021 10 12 12 34 56 milliseconds=789 (Time_Zone.new hours=2)) Date.parse "ddd, d MMM yyyy" "Tue, 12 Oct 2021" == (Date.new 2021 10 12) - Date.parse "dddd, dd MMMM ''98" "Thursday, 1 October '98" == (Date.new 1998 10 01) + Date.parse "dddd, dd MMMM ''yy" "Thursday, 1 October '98" == (Date.new 1998 10 01) Date_Time.parse "d/M/Y h:mm a" "12/10/2021 5:34 PM" == (Date_Time.new 2021 10 12 17 34 00) > Example @@ -67,6 +73,11 @@ type Date_Time_Format_Pattern Omitting the year will yield the current year. Date.parse "MM-dd" "10-12" == (Date.new (Date.today.year) 10 12) + + > Example + Parsing a two-digit year with a custom base year. + + Date.parse "dd MMMM ''yy{2099}" "1 November '95" == (Date.new 2095 11 01) Simple pattern:Text ## A pattern for formats related to the ISO 8601 leap week calendar. @@ -78,6 +89,9 @@ type Date_Time_Format_Pattern The pattern is a sequence of letters and symbols that are interpreted as follows: - Y: The week based year. + - In case the year is parsed in two digit mode (`YY`), the default + range is 1950-2049, but this can be changed by including the end year + in braces e.g. `yy{2099}` - W: Week of year. - d: Day of week. - d: Numeric day of week (1-7). 1 is Monday. @@ -108,12 +122,14 @@ type Date_Time_Format_Pattern ISO_Week_Date pattern:Text ## ADVANCED - A pattern built using the Java pattern syntax. - - This relies on the Java `DateTimeFormatter.ofPattern` method to build the - date time format. See the Java documentation for explanation of the - pattern format: https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/format/DateTimeFormatter.html#patterns - Java pattern:Text + A pattern built using the Java pattern syntax or a Java DateTimeFormatter + instance. + + This method parses the pattern using the Java `DateTimeFormatter.ofPattern` + method to build the date time format. See the Java documentation for + explanation of the pattern format: + https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/format/DateTimeFormatter.html#patterns + Java (pattern : Text | DateTimeFormatter) ## PRIVATE Date_Time_Format_Pattern.from (that:Text) = Date_Time_Format_Pattern.Simple that From 265c9f5d93629d0961ef822c6c9d8c7e9bbda7f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Wed, 13 Sep 2023 17:26:31 +0200 Subject: [PATCH 05/76] improvements 2 --- .../src/Data/Time/Date_Time_Format_Pattern.enso | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso index aa18f9796888..069e76d107e9 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso @@ -9,7 +9,9 @@ type Date_Time_Format_Pattern described in the table below. Any character that is not a letter in the pattern is treated as a literal character. If a sequence of letters needs to be put in as a literal, it can be escaped using single quotes. Use two - single quotes in a row to represent a single quote in the result. + single quotes in a row to represent a single quote in the result. As + explained below, curly braces can have special meaning (see 'yy'); to + enter a literal curly brace, put it inside a quoted literal. Pattern characters are interpreted case insensitively, with the exception of `M/m' and 'H/h'. @@ -133,3 +135,8 @@ type Date_Time_Format_Pattern ## PRIVATE Date_Time_Format_Pattern.from (that:Text) = Date_Time_Format_Pattern.Simple that + +type Date_Time_Format_Parse_Error + ## PRIVATE + Indicates an error during parsing of a `Date_Time_Format_Pattern`. + Error message From 51913c92c1329639e96728019a1d8cd2fbac210f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Wed, 13 Sep 2023 17:27:14 +0200 Subject: [PATCH 06/76] initial tokenizer implementation --- .../Time/Date_Time_Format_Parser.enso | 123 ++++++++++++++++++ .../main/java/org/enso/base/Text_Utils.java | 7 + 2 files changed, 130 insertions(+) create mode 100644 distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Date_Time_Format_Parser.enso diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Date_Time_Format_Parser.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Date_Time_Format_Parser.enso new file mode 100644 index 000000000000..3a46f5355d79 --- /dev/null +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Date_Time_Format_Parser.enso @@ -0,0 +1,123 @@ +import project.Data.Numbers.Integer +import project.Data.Text.Text +import project.Data.Time.Date_Time_Format_Pattern.Date_Time_Format_Parse_Error +import project.Data.Vector.Vector +from project.Data.Boolean import Boolean, False, True +from project.Data.Text.Extensions import all + +polyglot java import org.enso.base.Text_Utils + +## PRIVATE +type Format_Token + ## PRIVATE + A format pattern described by a single character and count. + Pattern character:Text count:Integer + + ## PRIVATE + A literal text string. + Literal text:Text + + ## PRIVATE + Indicates beginning of an optional section. + Optional_Section_Start + + ## PRIVATE + Indicates end of an optional section. + Optional_Section_End + + ## PRIVATE + A special parameter in curly braces. + + Currently only used to customize base year for `yy`, i.e. `yy{2099}`. + Curly_Section (inner_text : Text) + + +## PRIVATE +tokenize : Text -> Vector Format_Token +tokenize text = Panic.recover Date_Time_Format_Parse_Error <| + # Nothing is a guard to finish parsing + chars = text.characters+[Nothing] + tokens_builder = Vector.new_builder + is_in_optional = Ref.new False + + finalize_token current_token = case current_token of + Nothing -> Nothing + _ -> tokens_builder.append current_token + + parse_normal position current_token = case chars.at position of + Nothing -> + if is_in_optional.get then + Panic.throw (Date_Time_Format_Parse_Error.Error "Unterminated optional section within the pattern "+text.to_display_text) + finalize_token current_token + Nothing + "'" -> + finalize_token current_token + @Tail_Call parse_quoted position+1 "" + "[" -> + if is_in_optional.get then + Panic.throw (Date_Time_Format_Parse_Error.Error "Nested optional sections are not allowed (at position "+position.to_text+" in pattern "+text.to_display_text+").") + finalize_token current_token + tokens_builder.append Optional_Section_Start + is_in_optional.put True + @Tail_Call parse_normal position+1 Nothing + "]" -> + if is_in_optional.get.not then + Panic.throw (Date_Time_Format_Parse_Error.Error "Unmatched closing bracket ] (at position "+position.to_text+" in pattern "+text.to_display_text+").") + finalize_token current_token + tokens_builder.append Optional_Section_End + is_in_optional.put False + @Tail_Call parse_normal position+1 Nothing + "{" -> + finalize_token current_token + @Tail_Call parse_curly position+1 "" + new_character -> + case Text_Utils.is_all_letters new_character of + True -> + is_matching_current_token = case current_token of + Format_Token.Pattern current_pattern_character _ -> + current_pattern_character == new_character + _ -> False + case is_matching_current_token of + True -> + @Tail_Call parse_normal position+1 (Format_Token.Pattern current_token.character current_token.count+1) + False -> + finalize_token current_token + @Tail_Call parse_normal position+1 (Format_Token.Pattern new_character 1) + False -> + finalize_token current_token + tokens_builder.append (Format_Token.Literal other_character) + @Tail_Call parse_normal position+1 Nothing + + parse_quoted position text_accumulator = case chars.at position of + Nothing -> + Panic.throw (Date_Time_Format_Parse_Error.Error "Unterminated quoted sequence within the pattern "+text.to_display_text) + "'" -> + # Next letter is always accessible, but it may be Nothing. + next_letter = chars.at position+1 + case next_letter of + # If the next letter is a quote, that means an escaped single quote within a quoted section. + "'" -> + @Tail_Call parse_quoted position+2 text_accumulator+"'" + + # If the next letter is not a quote, that means the end of the quoted sequence. + _ -> + case text_accumulator.is_empty of + # If there is no text between the quotes, that means this whole quoted sequence was just an escaped single quote OUTSIDE a quoted section. + True -> + tokens_builder.append (Format_Token.Literal "'") + False -> + tokens_builder.append (Format_Token.Literal text_accumulator) + @Tail_Call parse_normal position+1 Nothing + other_character -> @Tail_Call parse_quoted position+1 text_accumulator+other_character + + parse_curly position text_accumulator = case chars.at position of + Nothing -> + Panic.throw (Date_Time_Format_Parse_Error.Error "Unterminated curly sequence within the pattern "+text.to_display_text) + "}" -> + tokens_builder.append (Format_Token.Curly_Section text_accumulator) + @Tail_Call parse_normal position+1 Nothing + other_character -> + @Tail_Call parse_curly position+1 text_accumulator+other_character + + parse_normal 0 Nothing + tokens_builder.to_vector diff --git a/std-bits/base/src/main/java/org/enso/base/Text_Utils.java b/std-bits/base/src/main/java/org/enso/base/Text_Utils.java index 60aea905fc92..a236a05a505c 100644 --- a/std-bits/base/src/main/java/org/enso/base/Text_Utils.java +++ b/std-bits/base/src/main/java/org/enso/base/Text_Utils.java @@ -635,6 +635,13 @@ public static boolean is_all_whitespace(String text) { return text.codePoints().allMatch(UCharacter::isUWhiteSpace); } + /** + * Checks if the given string consists only of letters. + */ + public static boolean is_all_letters(String text) { + return text.codePoints().allMatch(UCharacter::isLetter); + } + /** * Replaces all provided spans within the text with {@code newSequence}. * From dbf523396fcb78f41537e4a39df4904758897878 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Wed, 13 Sep 2023 17:50:43 +0200 Subject: [PATCH 07/76] make it compile --- .../Time/Date_Time_Format_Parser.enso | 121 ++++++++++-------- 1 file changed, 71 insertions(+), 50 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Date_Time_Format_Parser.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Date_Time_Format_Parser.enso index 3a46f5355d79..3cd73c03aa19 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Date_Time_Format_Parser.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Date_Time_Format_Parser.enso @@ -1,7 +1,12 @@ import project.Data.Numbers.Integer import project.Data.Text.Text import project.Data.Time.Date_Time_Format_Pattern.Date_Time_Format_Parse_Error +import project.Data.Vector.Builder as Vector_Builder import project.Data.Vector.Vector +import project.Error.Error +import project.Nothing.Nothing +import project.Panic.Panic +import project.Runtime.Ref.Ref from project.Data.Boolean import Boolean, False, True from project.Data.Text.Extensions import all @@ -31,45 +36,55 @@ type Format_Token Currently only used to customize base year for `yy`, i.e. `yy{2099}`. Curly_Section (inner_text : Text) - ## PRIVATE -tokenize : Text -> Vector Format_Token -tokenize text = Panic.recover Date_Time_Format_Parse_Error <| - # Nothing is a guard to finish parsing - chars = text.characters+[Nothing] - tokens_builder = Vector.new_builder - is_in_optional = Ref.new False +type Tokenizer + ## PRIVATE + A helper type to hold the state of the tokenizer. + Normally, we could keep these in the closure, inside of a method. + But our 3 parse methods need to be able to call each other, and mutual + recursion of variables defined inside of a method is not supported in + Enso. So to achieve the mutual recursion, we instead define these as + member methods. + Instance (original_text : Text) (chars : Vector Text) (tokens_builder : Vector_Builder Format_Token) (is_in_optional : Ref Boolean) - finalize_token current_token = case current_token of + ## PRIVATE + new : Text -> Tokenizer + new text = + # Nothing is appended at the and as a guard to avoid checking for length. + Tokenizer.Instance text text.characters+[Nothing] Vector.new_builder (Ref.new False) + + ## PRIVATE + finalize_token self current_token = case current_token of Nothing -> Nothing - _ -> tokens_builder.append current_token + _ -> self.tokens_builder.append current_token - parse_normal position current_token = case chars.at position of + ## PRIVATE + parse_normal self position current_token = case self.chars.at position of Nothing -> - if is_in_optional.get then - Panic.throw (Date_Time_Format_Parse_Error.Error "Unterminated optional section within the pattern "+text.to_display_text) - finalize_token current_token + if self.is_in_optional.get then + Panic.throw (Date_Time_Format_Parse_Error.Error "Unterminated optional section within the pattern "+self.original_text.to_display_text) + self.finalize_token current_token Nothing "'" -> - finalize_token current_token - @Tail_Call parse_quoted position+1 "" + self.finalize_token current_token + @Tail_Call self.parse_quoted position+1 "" "[" -> - if is_in_optional.get then - Panic.throw (Date_Time_Format_Parse_Error.Error "Nested optional sections are not allowed (at position "+position.to_text+" in pattern "+text.to_display_text+").") - finalize_token current_token - tokens_builder.append Optional_Section_Start - is_in_optional.put True - @Tail_Call parse_normal position+1 Nothing + if self.is_in_optional.get then + Panic.throw (Date_Time_Format_Parse_Error.Error "Nested optional sections are not allowed (at position "+position.to_text+" in pattern "+self.original_text.to_display_text+").") + self.finalize_token current_token + self.tokens_builder.append Format_Token.Optional_Section_Start + self.is_in_optional.put True + @Tail_Call self.parse_normal position+1 Nothing "]" -> - if is_in_optional.get.not then - Panic.throw (Date_Time_Format_Parse_Error.Error "Unmatched closing bracket ] (at position "+position.to_text+" in pattern "+text.to_display_text+").") - finalize_token current_token - tokens_builder.append Optional_Section_End - is_in_optional.put False - @Tail_Call parse_normal position+1 Nothing + if self.is_in_optional.get.not then + Panic.throw (Date_Time_Format_Parse_Error.Error "Unmatched closing bracket ] (at position "+position.to_text+" in pattern "+self.original_text.to_display_text+").") + self.finalize_token current_token + self.tokens_builder.append Format_Token.Optional_Section_End + self.is_in_optional.put False + @Tail_Call self.parse_normal position+1 Nothing "{" -> - finalize_token current_token - @Tail_Call parse_curly position+1 "" + self.finalize_token current_token + @Tail_Call self.parse_curly position+1 "" new_character -> case Text_Utils.is_all_letters new_character of True -> @@ -79,45 +94,51 @@ tokenize text = Panic.recover Date_Time_Format_Parse_Error <| _ -> False case is_matching_current_token of True -> - @Tail_Call parse_normal position+1 (Format_Token.Pattern current_token.character current_token.count+1) + @Tail_Call self.parse_normal position+1 (Format_Token.Pattern current_token.character current_token.count+1) False -> - finalize_token current_token - @Tail_Call parse_normal position+1 (Format_Token.Pattern new_character 1) + self.finalize_token current_token + @Tail_Call self.parse_normal position+1 (Format_Token.Pattern new_character 1) False -> - finalize_token current_token - tokens_builder.append (Format_Token.Literal other_character) - @Tail_Call parse_normal position+1 Nothing + self.finalize_token current_token + self.tokens_builder.append (Format_Token.Literal new_character) + @Tail_Call self.parse_normal position+1 Nothing - parse_quoted position text_accumulator = case chars.at position of + ## PRIVATE + parse_quoted self position text_accumulator = case self.chars.at position of Nothing -> - Panic.throw (Date_Time_Format_Parse_Error.Error "Unterminated quoted sequence within the pattern "+text.to_display_text) + Panic.throw (Date_Time_Format_Parse_Error.Error "Unterminated quoted sequence within the pattern "+self.original_text.to_display_text) "'" -> # Next letter is always accessible, but it may be Nothing. - next_letter = chars.at position+1 + next_letter = self.chars.at position+1 case next_letter of # If the next letter is a quote, that means an escaped single quote within a quoted section. "'" -> - @Tail_Call parse_quoted position+2 text_accumulator+"'" + @Tail_Call self.parse_quoted position+2 text_accumulator+"'" # If the next letter is not a quote, that means the end of the quoted sequence. _ -> case text_accumulator.is_empty of # If there is no text between the quotes, that means this whole quoted sequence was just an escaped single quote OUTSIDE a quoted section. True -> - tokens_builder.append (Format_Token.Literal "'") + self.tokens_builder.append (Format_Token.Literal "'") False -> - tokens_builder.append (Format_Token.Literal text_accumulator) - @Tail_Call parse_normal position+1 Nothing - other_character -> @Tail_Call parse_quoted position+1 text_accumulator+other_character + self.tokens_builder.append (Format_Token.Literal text_accumulator) + @Tail_Call self.parse_normal position+1 Nothing + other_character -> @Tail_Call self.parse_quoted position+1 text_accumulator+other_character - parse_curly position text_accumulator = case chars.at position of + ## PRIVATE + parse_curly self position text_accumulator = case self.chars.at position of Nothing -> - Panic.throw (Date_Time_Format_Parse_Error.Error "Unterminated curly sequence within the pattern "+text.to_display_text) + Panic.throw (Date_Time_Format_Parse_Error.Error "Unterminated curly sequence within the pattern "+self.original_text.to_display_text) "}" -> - tokens_builder.append (Format_Token.Curly_Section text_accumulator) - @Tail_Call parse_normal position+1 Nothing + self.tokens_builder.append (Format_Token.Curly_Section text_accumulator) + @Tail_Call self.parse_normal position+1 Nothing other_character -> - @Tail_Call parse_curly position+1 text_accumulator+other_character + @Tail_Call self.parse_curly position+1 text_accumulator+other_character - parse_normal 0 Nothing - tokens_builder.to_vector + ## PRIVATE + tokenize : Text -> Vector Format_Token + tokenize text = Panic.recover Date_Time_Format_Parse_Error <| + tokenizer = Tokenizer.new text + tokenizer.parse_normal 0 Nothing + tokenizer.tokens_builder.to_vector From 9ecf7ee404623f5c2948c4d7cf968672a734b3c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Wed, 13 Sep 2023 18:17:03 +0200 Subject: [PATCH 08/76] updates to doc --- .../0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso index 069e76d107e9..f28c5cd8bb4a 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso @@ -38,6 +38,8 @@ type Date_Time_Format_Pattern The weekday names depend on the selected locale. Both day of week and day of month may be included in a single pattern - in such case the day of week is used as a sanity check. + - Q: Quarter of year. Rarely useful in parsing as it is not sufficient to + construct a date without a month, but it can be used for formatting. Time pattern characters: - H: 24h hour of day (0-23). @@ -94,7 +96,7 @@ type Date_Time_Format_Pattern - In case the year is parsed in two digit mode (`YY`), the default range is 1950-2049, but this can be changed by including the end year in braces e.g. `yy{2099}` - - W: Week of year. + - w: Week of year. - d: Day of week. - d: Numeric day of week (1-7). 1 is Monday. - dd: Numeric day of week with zero padding (01-07). From c8cefa311fe4bb17c455ea77406e19b9dfd73efa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Wed, 13 Sep 2023 18:18:22 +0200 Subject: [PATCH 09/76] updates 2 --- .../0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso index f28c5cd8bb4a..cc32e1112973 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso @@ -38,8 +38,9 @@ type Date_Time_Format_Pattern The weekday names depend on the selected locale. Both day of week and day of month may be included in a single pattern - in such case the day of week is used as a sanity check. - - Q: Quarter of year. Rarely useful in parsing as it is not sufficient to - construct a date without a month, but it can be used for formatting. + - Q: Quarter of year. + If only year and quarter are provided in the pattern, when parsing a + date, the result will be the first day of that quarter. Time pattern characters: - H: 24h hour of day (0-23). From 5f3ac78c9f1c56decbe0fed3b9b5c6dd8e456a30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Thu, 14 Sep 2023 14:05:59 +0200 Subject: [PATCH 10/76] checkpoint --- .../Data/Time/Date_Time_Format_Pattern.enso | 2 +- .../src/Internal/Time/Format/Parser.enso | 143 ++++++++++++++++++ .../Tokenizer.enso} | 0 3 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso rename distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/{Date_Time_Format_Parser.enso => Format/Tokenizer.enso} (100%) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso index cc32e1112973..b6c96e6ef7f5 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso @@ -96,7 +96,7 @@ type Date_Time_Format_Pattern - Y: The week based year. - In case the year is parsed in two digit mode (`YY`), the default range is 1950-2049, but this can be changed by including the end year - in braces e.g. `yy{2099}` + in braces e.g. `YY{2099}` - w: Week of year. - d: Day of week. - d: Numeric day of week (1-7). 1 is Monday. diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso new file mode 100644 index 000000000000..15becebff519 --- /dev/null +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso @@ -0,0 +1,143 @@ +import project.Data.Numbers.Integer +import project.Data.Numbers.Number_Parse_Error +import project.Data.Text.Text +import project.Data.Time.Date_Time_Format_Pattern.Date_Time_Format_Parse_Error +import project.Data.Vector.Builder as Vector_Builder +import project.Data.Vector.Vector +import project.Error.Error +import project.Nothing.Nothing +import project.Panic.Panic +import project.Runtime.Ref.Ref +from project.Data.Boolean import Boolean, False, True +from project.Data.Text.Extensions import all + +import project.Internal.Time.Format.Tokenizer.Format_Token + +type Text_Representation + Short_Form + Long_Form + +type Numeric_Representation + Value digits:Integer + +type Two_Digit_Year_Representation + Value max_year:Integer + +type Common_Nodes + Literal text:Text + Optional_Section inner_nodes:Vector + +type Standard_Date_Patterns + Year (representation : Numeric_Representation | Two_Digit_Year_Representation) + Quarter (representation : Numeric_Representation) + Month (representation : Numeric_Representation | Text_Representation) + Day_Of_Month (representation : Numeric_Representation) + Day_Of_Week (representation : Text_Representation) + +type ISO_Week_Year_Patterns + Week_Based_Year (representation : Numeric_Representation | Two_Digit_Year_Representation) + Week_Of_Year (representation : Numeric_Representation) + Day_Of_Week (representation : Numeric_Representation | Text_Representation) + +type Time_Patterns + Hour (representation : Numeric_Representation) is_24h:Boolean + AmPm + Minute (representation : Numeric_Representation) + Second (representation : Numeric_Representation) + Fraction_Of_Second (representation : Numeric_Representation) + +type Time_Zone_Patterns + Time_Zone_Name + Time_Zone_ID + Time_Zone_Offset + +type Parser_Mode + Simple + ISO_Week_Year + +type Parser + Instance (tokens : Vector (Format_Token | Nothing)) (position : Ref Integer) + + parse self = + case self.consume_token of + Format_Token.Literal text -> + self.result_builder.append (Common_Nodes.Literal text) + Format_Token.Curly_Section inner_text -> + Panic.throw (Date_Time_Format_Parse_Error.Error "Unexpected section in curly braces: {"+inner_text+"}. Currently, the curly braces are only used to parametrize the two-digit year with the max year, e.g. `yy{2049}`. If you want to include a curly brace as literal, escape it with single quotes like '{"+inner_text+"}'") + Format_Token.Pattern character count -> + case mode of + Parser_Mode.Simple -> self.parse_simple_date_pattern character count + Parser_Mode.ISO_Week_Year -> self.parse_iso_week_year_pattern character count + + consume_token self = + current_position = self.position.get position + self.position.put position+1 + tokens.at current_position + + consume_curly_parameter_if_exists self = + current_position = self.position.get position + case tokens.at current_position of + Format_Token.Curly_Section inner_text -> + self.position.put current_position+1 + inner_text + _ -> + # If no Curly_Section is set, do not advance the pointer and return Nothing. + Nothing + + resolve_year_representation self count = case count of + 2 -> + max_year = case self.consume_curly_parameter_if_exists of + Nothing -> default_max_year + parameter_text -> + Integer.parse parameter_text . catch Number_Parse_Error _-> + Panic.throw (Date_Time_Format_Parse_Error.Error "The curly braces setting the maximum year for `yy` must be an integer, but got: "+parameter_text) + Two_Digit_Year_Representation.Value max_year=max_year + _ -> + Numeric_Representation.Value count + + parse_simple_date_pattern self character count = + lowercase = character.to_case Case.Lower + case lowercase of + "y" -> Standard_Date_Patterns.Year (self.resolve_year_representation count) + "q" -> Standard_Date_Patterns.Quarter (Numeric_Representation.Value count) + "m" -> case character of + "M" -> Standard_Date_Patterns.Month (Numeric_Representation.Value count) + # Lowercase form is reserved for minutes - handled elsewhere. + "m" -> Nothing + "d" -> case count of + 1 -> Standard_Date_Patterns.Day_Of_Month (Numeric_Representation.Value 1) + 2 -> Standard_Date_Patterns.Day_Of_Month (Numeric_Representation.Value 2) + 3 -> Standard_Date_Patterns.Day_Of_Week (Text_Representation.Short_Form) + _ -> Standard_Date_Patterns.Day_Of_Week (Text_Representation.Long_Form) + _ -> Nothing + + parse_iso_week_year_pattern self character count = + lowercase = character.to_case Case.Lower + case lowercase of + "y" -> ISO_Week_Year_Patterns.Week_Based_Year (self.resolve_year_representation count) + "w" -> ISO_Week_Year_Patterns.Week_Of_Year (Numeric_Representation.Value count) + "d" -> case count of + 1 -> ISO_Week_Year_Patterns.Day_Of_Week (Numeric_Representation.Value 1) + 2 -> ISO_Week_Year_Patterns.Day_Of_Week (Numeric_Representation.Value 2) + 3 -> ISO_Week_Year_Patterns.Day_Of_Week (Text_Representation.Short_Form) + _ -> ISO_Week_Year_Patterns.Day_Of_Week (Text_Representation.Long_Form) + _ -> Nothing + + parse_time_or_timezone_pattern character count = + lowercase = character.to_case Case.Lower + case lowercase of + "h" -> case character of + "H" -> Time_Patterns.Hour (Numeric_Representation.Value count) is_24h=True + "h" -> Time_Patterns.Hour (Numeric_Representation.Value count) is_24h=False + "m" -> case character of + "m" -> Time_Patterns.Minute (Numeric_Representation.Value count) + # Lowercase form is reserved for months - handled elsewhere. + "M" -> Nothing + "s" -> Time_Patterns.Second (Numeric_Representation.Value count) + "f" -> Time_Patterns.Fraction_Of_Second (Numeric_Representation.Value count) + "a" -> Time_Patterns.AmPm + "t" -> if count <= 3 then Time_Zone_Patterns.Time_Zone_ID else Time_Zone_Patterns.Time_Zone_Name + "z" -> Time_Zone_Patterns.Time_Zone_Offset + _ -> Nothing + +default_max_year = 2049 diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Date_Time_Format_Parser.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Tokenizer.enso similarity index 100% rename from distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Date_Time_Format_Parser.enso rename to distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Tokenizer.enso From 5ec972ea637bd8a34aa2d5518f73f5095b0c61ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Thu, 14 Sep 2023 20:38:02 +0200 Subject: [PATCH 11/76] Implemented the parser --- .../Data/Time/Date_Time_Format_Pattern.enso | 5 + .../src/Internal/Time/Format/Parser.enso | 93 +++++++++++++++---- 2 files changed, 78 insertions(+), 20 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso index b6c96e6ef7f5..facf34934de2 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso @@ -143,3 +143,8 @@ type Date_Time_Format_Parse_Error ## PRIVATE Indicates an error during parsing of a `Date_Time_Format_Pattern`. Error message + + ## PRIVATE + to_display_text : Text + to_display_text self = + "Error parsing date/time format pattern: " + self.message diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso index 15becebff519..d76490aa0826 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso @@ -1,10 +1,12 @@ import project.Data.Numbers.Integer import project.Data.Numbers.Number_Parse_Error +import project.Data.Text.Case.Case import project.Data.Text.Text import project.Data.Time.Date_Time_Format_Pattern.Date_Time_Format_Parse_Error import project.Data.Vector.Builder as Vector_Builder import project.Data.Vector.Vector import project.Error.Error +import project.Errors.Illegal_State.Illegal_State import project.Nothing.Nothing import project.Panic.Panic import project.Runtime.Ref.Ref @@ -55,28 +57,67 @@ type Parser_Mode Simple ISO_Week_Year -type Parser - Instance (tokens : Vector (Format_Token | Nothing)) (position : Ref Integer) + pattern_format_name self = case self of + Parser_Mode.Simple -> "Simple" + Parser_Mode.ISO_Week_Year -> "ISO_Week_Date" - parse self = - case self.consume_token of - Format_Token.Literal text -> - self.result_builder.append (Common_Nodes.Literal text) - Format_Token.Curly_Section inner_text -> - Panic.throw (Date_Time_Format_Parse_Error.Error "Unexpected section in curly braces: {"+inner_text+"}. Currently, the curly braces are only used to parametrize the two-digit year with the max year, e.g. `yy{2049}`. If you want to include a curly brace as literal, escape it with single quotes like '{"+inner_text+"}'") - Format_Token.Pattern character count -> - case mode of - Parser_Mode.Simple -> self.parse_simple_date_pattern character count - Parser_Mode.ISO_Week_Year -> self.parse_iso_week_year_pattern character count +type Parser + Instance (tokens : Vector (Format_Token | Nothing)) (position : Ref Integer) (mode : Parser_Mode) + + new tokens mode = + Parser.Instance tokens=tokens+[Nothing] position=(Ref.new 0) mode=mode + + run self = Panic.recover Date_Time_Format_Parse_Error <| + result_builder = Vector.new_builder + go _ = case self.consume_token of + Nothing -> Nothing + Format_Token.Optional_Section_Start -> + inner_nodes = self.run_optional + result_builder.append (Common_Nodes.Optional_Section inner_nodes) + @Tail_Call go Nothing + other_token -> + parsed_node = self.parse_common_token other_token + result_builder.append parsed_node + @Tail_Call go Nothing + go Nothing + result_builder.to_vector + + run_optional self = + result_builder = Vector.new_builder + go _ = case self.consume_token of + Nothing -> Panic.throw (Illegal_State.Error "Unterminated optional section. This should have been caught by the tokenizer.") + Format_Token.Optional_Section_End -> Nothing + other_token -> + parsed_node = self.parse_common_token other_token + result_builder.append parsed_node + @Tail_Call go Nothing + go Nothing + result_builder.to_vector + + parse_common_token self token = case token of + Format_Token.Literal text -> + Common_Nodes.Literal text + Format_Token.Curly_Section inner_text -> + Panic.throw (Date_Time_Format_Parse_Error.Error "Unexpected section in curly braces: {"+inner_text+"}. Currently, the curly braces are only used to parametrize the two-digit year with the max year, e.g. `yy{2049}`. If you want to include a curly brace as literal, escape it with single quotes like '{"+inner_text+"}'") + Format_Token.Pattern character count -> + date_pattern = case self.mode of + Parser_Mode.Simple -> self.parse_simple_date_pattern character count + Parser_Mode.ISO_Week_Year -> self.parse_iso_week_year_pattern character count + any_pattern = date_pattern.if_nothing <| + self.parse_time_or_timezone_pattern character count + any_pattern.if_nothing <| + Panic.throw (Date_Time_Format_Parse_Error.Error "The pattern "+(character*count)+" is not recognized as any known pattern for the "+self.mode.pattern_format_name+" format.") + any_pattern + _ -> Panic.throw (Illegal_State.Error "Unexpected (here) token type: "+token.to_text) consume_token self = - current_position = self.position.get position - self.position.put position+1 - tokens.at current_position + current_position = self.position.get + self.position.put current_position+1 + self.tokens.at current_position consume_curly_parameter_if_exists self = - current_position = self.position.get position - case tokens.at current_position of + current_position = self.position.get + case self.tokens.at current_position of Format_Token.Curly_Section inner_text -> self.position.put current_position+1 inner_text @@ -90,7 +131,7 @@ type Parser Nothing -> default_max_year parameter_text -> Integer.parse parameter_text . catch Number_Parse_Error _-> - Panic.throw (Date_Time_Format_Parse_Error.Error "The curly braces setting the maximum year for `yy` must be an integer, but got: "+parameter_text) + Panic.throw (Date_Time_Format_Parse_Error.Error "The curly braces setting the maximum year for `yy` must be an integer, but got: {"+parameter_text+"}.") Two_Digit_Year_Representation.Value max_year=max_year _ -> Numeric_Representation.Value count @@ -101,7 +142,13 @@ type Parser "y" -> Standard_Date_Patterns.Year (self.resolve_year_representation count) "q" -> Standard_Date_Patterns.Quarter (Numeric_Representation.Value count) "m" -> case character of - "M" -> Standard_Date_Patterns.Month (Numeric_Representation.Value count) + "M" -> + representation = case count of + 1 -> Numeric_Representation.Value 1 + 2 -> Numeric_Representation.Value 2 + 3 -> Text_Representation.Short_Form + _ -> Text_Representation.Long_Form + Standard_Date_Patterns.Month representation # Lowercase form is reserved for minutes - handled elsewhere. "m" -> Nothing "d" -> case count of @@ -123,7 +170,7 @@ type Parser _ -> ISO_Week_Year_Patterns.Day_Of_Week (Text_Representation.Long_Form) _ -> Nothing - parse_time_or_timezone_pattern character count = + parse_time_or_timezone_pattern self character count = lowercase = character.to_case Case.Lower case lowercase of "h" -> case character of @@ -141,3 +188,9 @@ type Parser _ -> Nothing default_max_year = 2049 + +parse_simple_date_pattern tokens = + Parser.new tokens mode=Parser_Mode.Simple . run + +parse_iso_week_year_pattern tokens = + Parser.new tokens mode=Parser_Mode.ISO_Week_Year . run From f3f206e5c611a3eeb36ebc6b2b78325ec86393a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Fri, 15 Sep 2023 01:44:15 +0200 Subject: [PATCH 12/76] Interpret IR as DateTimeFormatter using a builder (some stuff WIP), integrate --- .../Data/Time/Date_Time_Format_Pattern.enso | 19 +++- .../Format/As_Java_Formatter_Interpreter.enso | 92 +++++++++++++++++++ .../src/Internal/Time/Format/Parser.enso | 5 +- .../main/java/org/enso/base/Time_Utils.java | 11 +++ 4 files changed, 124 insertions(+), 3 deletions(-) create mode 100644 distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/As_Java_Formatter_Interpreter.enso diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso index facf34934de2..0c3780e0aa78 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso @@ -1,5 +1,10 @@ +import project.Data.Locale.Locale import project.Data.Text.Text +import project.Internal.Time.Format.Tokenizer.Tokenizer +import project.Internal.Time.Format.Parser +import project.Internal.Time.Format.As_Java_Formatter_Interpreter + polyglot java import java.time.format.DateTimeFormatter type Date_Time_Format_Pattern @@ -83,7 +88,7 @@ type Date_Time_Format_Pattern Parsing a two-digit year with a custom base year. Date.parse "dd MMMM ''yy{2099}" "1 November '95" == (Date.new 2095 11 01) - Simple pattern:Text + Simple pattern:Text locale:Locale=Locale.default ## A pattern for formats related to the ISO 8601 leap week calendar. @@ -124,7 +129,7 @@ type Date_Time_Format_Pattern Omitting the day of the week will result in the first day of that week. Date.parse (ISO_Week_Date "YYYY-'W'WW" "1978-W01") == (Date.new 1978 01 02) - ISO_Week_Date pattern:Text + ISO_Week_Date pattern:Text locale:Locale=Locale.default ## ADVANCED A pattern built using the Java pattern syntax or a Java DateTimeFormatter @@ -136,6 +141,16 @@ type Date_Time_Format_Pattern https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/format/DateTimeFormatter.html#patterns Java (pattern : Text | DateTimeFormatter) + build_java_formatter : DateTimeFormatter + build_java_formatter self = case self of + Date_Time_Format_Pattern.Simple pattern locale -> + Tokenizer.tokenize pattern |> Parser.parse_simple_date_pattern |> As_Java_Formatter_Interpreter.interpret locale + Date_Time_Format_Pattern.ISO_Week_Date pattern locale -> + Tokenizer.tokenize pattern |> Parser.parse_iso_week_year_pattern |> As_Java_Formatter_Interpreter.interpret locale + Date_Time_Format_Pattern.Java pattern -> case pattern of + _ : Text -> DateTimeFormatter.ofPattern pattern + formatter : DateTimeFormatter -> formatter + ## PRIVATE Date_Time_Format_Pattern.from (that:Text) = Date_Time_Format_Pattern.Simple that diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/As_Java_Formatter_Interpreter.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/As_Java_Formatter_Interpreter.enso new file mode 100644 index 000000000000..922b2f7d6ee3 --- /dev/null +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/As_Java_Formatter_Interpreter.enso @@ -0,0 +1,92 @@ +import project.Data.Locale.Locale +import project.Data.Text.Text +import project.Data.Time.Date.Date +import project.Data.Vector.Vector +import project.Errors.Illegal_Argument.Illegal_Argument +import project.Panic.Panic +from project.Data.Boolean import Boolean, False, True + +from project.Internal.Time.Format.Parser import Common_Nodes, Standard_Date_Patterns, ISO_Week_Year_Patterns, Time_Patterns, Time_Zone_Patterns +from project.Internal.Time.Format.Parser import Text_Representation, Numeric_Representation, Two_Digit_Year_Representation + +polyglot java import java.time.format.DateTimeFormatter +polyglot java import java.time.format.DateTimeFormatterBuilder +polyglot java import java.time.format.SignStyle +polyglot java import java.time.format.TextStyle +polyglot java import java.time.temporal.ChronoField +polyglot java import java.time.temporal.IsoFields +polyglot java import org.enso.base.Time_Utils + +interpret : Locale -> Vector (Common_Nodes | Standard_Date_Patterns | ISO_Week_Year_Patterns | Time_Patterns | Time_Zone_Patterns) -> DateTimeFormatter +interpret locale nodes = + builder = DateTimeFormatterBuilder.new + interpret_node node = case node of + Common_Nodes.Literal text -> + builder.appendLiteral text + Common_Nodes.Optional_Section nested_nodes -> + builder.optionalStart + nested_nodes.each interpret_node + builder.optionalEnd + + Time_Zone_Patterns.Time_Zone_ID -> + builder.appendZoneId + Time_Zone_Patterns.Time_Zone_Name -> + # TODO maybe ability to SHORT if less than 4 chars + builder.appendZoneText TextStyle.FULL + Time_Zone_Patterns.Time_Zone_Offset -> + ## TODO + appendOffset("+HHMM", "+0000"); + count == 4 + appendLocalizedOffset(TextStyle.FULL); + count == 5 + appendOffset("+HH:MM:ss","Z"); + builder.appendLocalizedOffset TextStyle.FULL + + Time_Patterns.AmPm -> + builder.appendValue ChronoField.AMPM_OF_DAY 2 + Time_Patterns.Fraction_Of_Second representation -> + min_digits = 1 + max_digits = case representation.digits of + # If just one digit provided, we allow as many as possible. + # TODO + 1 -> 9 + digits -> digits + includes_decimal_point = False + builder.appendFraction ChronoField.NANO_OF_SECOND min_digits max_digits includes_decimal_point + + _ -> + field = get_field_for node + append_field builder field node.representation + + nodes.each interpret_node + builder.toFormatter locale.java_locale + +get_field_for node = case node of + Standard_Date_Patterns.Year _ -> ChronoField.YEAR + Standard_Date_Patterns.Quarter _ -> IsoFields.QUARTER_OF_YEAR + Standard_Date_Patterns.Month _ -> ChronoField.MONTH_OF_YEAR + Standard_Date_Patterns.Day_Of_Month _ -> ChronoField.DAY_OF_MONTH + Standard_Date_Patterns.Day_Of_Week _ -> ChronoField.DAY_OF_WEEK + + ISO_Week_Year_Patterns.Week_Based_Year _ -> IsoFields.WEEK_BASED_YEAR + ISO_Week_Year_Patterns.Week_Of_Year _ -> IsoFields.WEEK_OF_WEEK_BASED_YEAR + ISO_Week_Year_Patterns.Day_Of_Week _ -> ChronoField.DAY_OF_WEEK + + Time_Patterns.Hour _ is24h -> case is24h of + True -> ChronoField.HOUR_OF_DAY + False -> ChronoField.CLOCK_HOUR_OF_AMPM + Time_Patterns.Minute _ -> ChronoField.MINUTE_OF_HOUR + Time_Patterns.Second _ -> ChronoField.SECOND_OF_MINUTE + + _ -> Panic.throw (Illegal_Argument.Error "Cannot extract a TemporalField from "+node.to_text) + +append_field builder field representation = case representation of + Numeric_Representation.Value digits -> case digits of + 2 -> builder.appendValue field 2 + _ -> builder.appendValue field digits 19 SignStyle.NORMAL + Text_Representation.Short_Form -> + builder.appendText field TextStyle.SHORT + Text_Representation.Long_Form -> + builder.appendText field TextStyle.FULL + Two_Digit_Year_Representation.Value max_year -> + Time_Utils.appendTwoDigitYear builder field max_year diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso index d76490aa0826..7a81be3b0546 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso @@ -181,7 +181,10 @@ type Parser # Lowercase form is reserved for months - handled elsewhere. "M" -> Nothing "s" -> Time_Patterns.Second (Numeric_Representation.Value count) - "f" -> Time_Patterns.Fraction_Of_Second (Numeric_Representation.Value count) + "f" -> + if count > 9 then + Panic.throw (Date_Time_Format_Parse_Error.Error "It is meaningless to have more than 9 digits in the fractional-of-second pattern, because at most nanosecond precision of seconds is currently supported.") + Time_Patterns.Fraction_Of_Second (Numeric_Representation.Value count) "a" -> Time_Patterns.AmPm "t" -> if count <= 3 then Time_Zone_Patterns.Time_Zone_ID else Time_Zone_Patterns.Time_Zone_Name "z" -> Time_Zone_Patterns.Time_Zone_Offset diff --git a/std-bits/base/src/main/java/org/enso/base/Time_Utils.java b/std-bits/base/src/main/java/org/enso/base/Time_Utils.java index be2e7b9a066a..7c3bff288f54 100644 --- a/std-bits/base/src/main/java/org/enso/base/Time_Utils.java +++ b/std-bits/base/src/main/java/org/enso/base/Time_Utils.java @@ -15,6 +15,7 @@ import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; +import java.time.temporal.ChronoField; import java.time.temporal.ChronoUnit; import java.time.temporal.TemporalField; import java.time.temporal.TemporalUnit; @@ -315,4 +316,14 @@ public static LocalTime unit_time_add(TemporalUnit unit, LocalTime time, long am public static ZonedDateTime unit_datetime_add(TemporalUnit unit, ZonedDateTime datetime, long amount) { return datetime.plus(amount, unit); } + + /** + * This helper method is needed, because calling `appendValueReduced` directly from Enso fails to convert an EnsoDate + * to a LocalDate due to polyglot unable to handle the polymorphism of the method. + */ + public static void appendTwoDigitYear(DateTimeFormatterBuilder builder, ChronoField yearField, int maxYear) { + int minYear = maxYear - 99; + LocalDate baseDate = LocalDate.of(minYear, 1, 1); + builder.appendValueReduced(yearField, 2, 2, baseDate); + } } From cc406634444179be51b2ea62fdf329afb6b66310 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Fri, 15 Sep 2023 12:01:24 +0200 Subject: [PATCH 13/76] export --- .../Standard/Base/0.0.0-dev/src/Data/Time/Conversions.enso | 5 +++++ .../0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso | 3 --- distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso | 4 ++++ 3 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Conversions.enso diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Conversions.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Conversions.enso new file mode 100644 index 000000000000..a7fb971dcb71 --- /dev/null +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Conversions.enso @@ -0,0 +1,5 @@ +import project.Data.Text.Text +import project.Data.Time.Date_Time_Format_Pattern.Date_Time_Format_Pattern + +## PRIVATE +Date_Time_Format_Pattern.from (that:Text) = Date_Time_Format_Pattern.Simple that diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso index 0c3780e0aa78..163ae6eeb7ca 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso @@ -151,9 +151,6 @@ type Date_Time_Format_Pattern _ : Text -> DateTimeFormatter.ofPattern pattern formatter : DateTimeFormatter -> formatter -## PRIVATE -Date_Time_Format_Pattern.from (that:Text) = Date_Time_Format_Pattern.Simple that - type Date_Time_Format_Parse_Error ## PRIVATE Indicates an error during parsing of a `Date_Time_Format_Pattern`. diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso index 627f627c2ce1..434ee73f4daf 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso @@ -40,6 +40,7 @@ import project.Data.Time.Date.Date import project.Data.Time.Date_Period.Date_Period import project.Data.Time.Date_Range.Date_Range import project.Data.Time.Date_Time.Date_Time +import project.Data.Time.Date_Time_Format_Pattern.Date_Time_Format_Pattern import project.Data.Time.Day_Of_Week.Day_Of_Week import project.Data.Time.Day_Of_Week_From import project.Data.Time.Duration.Duration @@ -85,6 +86,7 @@ from project.Data.Index_Sub_Range.Index_Sub_Range import First, Last from project.Data.Json.Extensions import all from project.Data.Range.Extensions import all from project.Data.Text.Extensions import all +from project.Data.Time.Conversions import all from project.Errors.Problem_Behavior.Problem_Behavior import all from project.Meta.Enso_Project import enso_project from project.Network.Extensions import all @@ -128,6 +130,7 @@ export project.Data.Time.Date.Date export project.Data.Time.Date_Period.Date_Period export project.Data.Time.Date_Range.Date_Range export project.Data.Time.Date_Time.Date_Time +export project.Data.Time.Date_Time_Format_Pattern.Date_Time_Format_Pattern export project.Data.Time.Day_Of_Week.Day_Of_Week export project.Data.Time.Day_Of_Week_From export project.Data.Time.Duration.Duration @@ -175,6 +178,7 @@ from project.Data.Numbers export Float, Integer, Number from project.Data.Range.Extensions export all from project.Data.Statistics export all hiding to_moment_statistic, wrap_java_call, calculate_correlation_statistics, calculate_spearman_rank, calculate_correlation_statistics_matrix, compute_fold, empty_value, is_valid from project.Data.Text.Extensions export all +from project.Data.Time.Conversions export all from project.Errors.Problem_Behavior.Problem_Behavior export all from project.Function export all from project.Meta.Enso_Project export enso_project From f6663ae7e5711785ca0c4529994a805d55b9e279 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Fri, 15 Sep 2023 12:03:28 +0200 Subject: [PATCH 14/76] mark stuff as PRIVATE --- .../Data/Time/Date_Time_Format_Pattern.enso | 1 + .../Format/As_Java_Formatter_Interpreter.enso | 3 + .../src/Internal/Time/Format/Parser.enso | 64 +++++++++++++++++++ 3 files changed, 68 insertions(+) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso index 163ae6eeb7ca..71758e074572 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso @@ -141,6 +141,7 @@ type Date_Time_Format_Pattern https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/format/DateTimeFormatter.html#patterns Java (pattern : Text | DateTimeFormatter) + ## PRIVATE build_java_formatter : DateTimeFormatter build_java_formatter self = case self of Date_Time_Format_Pattern.Simple pattern locale -> diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/As_Java_Formatter_Interpreter.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/As_Java_Formatter_Interpreter.enso index 922b2f7d6ee3..4fc0057325f2 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/As_Java_Formatter_Interpreter.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/As_Java_Formatter_Interpreter.enso @@ -17,6 +17,7 @@ polyglot java import java.time.temporal.ChronoField polyglot java import java.time.temporal.IsoFields polyglot java import org.enso.base.Time_Utils +## PRIVATE interpret : Locale -> Vector (Common_Nodes | Standard_Date_Patterns | ISO_Week_Year_Patterns | Time_Patterns | Time_Zone_Patterns) -> DateTimeFormatter interpret locale nodes = builder = DateTimeFormatterBuilder.new @@ -61,6 +62,7 @@ interpret locale nodes = nodes.each interpret_node builder.toFormatter locale.java_locale +## PRIVATE get_field_for node = case node of Standard_Date_Patterns.Year _ -> ChronoField.YEAR Standard_Date_Patterns.Quarter _ -> IsoFields.QUARTER_OF_YEAR @@ -80,6 +82,7 @@ get_field_for node = case node of _ -> Panic.throw (Illegal_Argument.Error "Cannot extract a TemporalField from "+node.to_text) +## PRIVATE append_field builder field representation = case representation of Numeric_Representation.Value digits -> case digits of 2 -> builder.appendValue field 2 diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso index 7a81be3b0546..129f9c589ff1 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso @@ -15,58 +15,111 @@ from project.Data.Text.Extensions import all import project.Internal.Time.Format.Tokenizer.Format_Token +## PRIVATE type Text_Representation + ## PRIVATE Short_Form + + ## PRIVATE Long_Form +## PRIVATE type Numeric_Representation + ## PRIVATE Value digits:Integer +## PRIVATE type Two_Digit_Year_Representation + ## PRIVATE Value max_year:Integer +## PRIVATE type Common_Nodes + ## PRIVATE Literal text:Text + + ## PRIVATE Optional_Section inner_nodes:Vector +## PRIVATE type Standard_Date_Patterns + ## PRIVATE Year (representation : Numeric_Representation | Two_Digit_Year_Representation) + + ## PRIVATE Quarter (representation : Numeric_Representation) + + ## PRIVATE Month (representation : Numeric_Representation | Text_Representation) + + ## PRIVATE Day_Of_Month (representation : Numeric_Representation) + + ## PRIVATE Day_Of_Week (representation : Text_Representation) +## PRIVATE type ISO_Week_Year_Patterns + ## PRIVATE Week_Based_Year (representation : Numeric_Representation | Two_Digit_Year_Representation) + + ## PRIVATE Week_Of_Year (representation : Numeric_Representation) + + ## PRIVATE Day_Of_Week (representation : Numeric_Representation | Text_Representation) +## PRIVATE type Time_Patterns + ## PRIVATE Hour (representation : Numeric_Representation) is_24h:Boolean + + ## PRIVATE AmPm + + ## PRIVATE Minute (representation : Numeric_Representation) + + ## PRIVATE Second (representation : Numeric_Representation) + + ## PRIVATE Fraction_Of_Second (representation : Numeric_Representation) +## PRIVATE type Time_Zone_Patterns + ## PRIVATE Time_Zone_Name + + ## PRIVATE Time_Zone_ID + + ## PRIVATE Time_Zone_Offset +## PRIVATE type Parser_Mode + ## PRIVATE Simple + + ## PRIVATE ISO_Week_Year + ## PRIVATE pattern_format_name self = case self of Parser_Mode.Simple -> "Simple" Parser_Mode.ISO_Week_Year -> "ISO_Week_Date" +## PRIVATE type Parser + ## PRIVATE Instance (tokens : Vector (Format_Token | Nothing)) (position : Ref Integer) (mode : Parser_Mode) + ## PRIVATE new tokens mode = Parser.Instance tokens=tokens+[Nothing] position=(Ref.new 0) mode=mode + ## PRIVATE run self = Panic.recover Date_Time_Format_Parse_Error <| result_builder = Vector.new_builder go _ = case self.consume_token of @@ -82,6 +135,7 @@ type Parser go Nothing result_builder.to_vector + ## PRIVATE run_optional self = result_builder = Vector.new_builder go _ = case self.consume_token of @@ -94,6 +148,7 @@ type Parser go Nothing result_builder.to_vector + ## PRIVATE parse_common_token self token = case token of Format_Token.Literal text -> Common_Nodes.Literal text @@ -110,11 +165,13 @@ type Parser any_pattern _ -> Panic.throw (Illegal_State.Error "Unexpected (here) token type: "+token.to_text) + ## PRIVATE consume_token self = current_position = self.position.get self.position.put current_position+1 self.tokens.at current_position + ## PRIVATE consume_curly_parameter_if_exists self = current_position = self.position.get case self.tokens.at current_position of @@ -125,6 +182,7 @@ type Parser # If no Curly_Section is set, do not advance the pointer and return Nothing. Nothing + ## PRIVATE resolve_year_representation self count = case count of 2 -> max_year = case self.consume_curly_parameter_if_exists of @@ -136,6 +194,7 @@ type Parser _ -> Numeric_Representation.Value count + ## PRIVATE parse_simple_date_pattern self character count = lowercase = character.to_case Case.Lower case lowercase of @@ -158,6 +217,7 @@ type Parser _ -> Standard_Date_Patterns.Day_Of_Week (Text_Representation.Long_Form) _ -> Nothing + ## PRIVATE parse_iso_week_year_pattern self character count = lowercase = character.to_case Case.Lower case lowercase of @@ -170,6 +230,7 @@ type Parser _ -> ISO_Week_Year_Patterns.Day_Of_Week (Text_Representation.Long_Form) _ -> Nothing + ## PRIVATE parse_time_or_timezone_pattern self character count = lowercase = character.to_case Case.Lower case lowercase of @@ -190,10 +251,13 @@ type Parser "z" -> Time_Zone_Patterns.Time_Zone_Offset _ -> Nothing +## PRIVATE default_max_year = 2049 +## PRIVATE parse_simple_date_pattern tokens = Parser.new tokens mode=Parser_Mode.Simple . run +## PRIVATE parse_iso_week_year_pattern tokens = Parser.new tokens mode=Parser_Mode.ISO_Week_Year . run From 2ef283d7f9010d95bbf94f7eaf45798723d0e344 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Fri, 15 Sep 2023 12:08:30 +0200 Subject: [PATCH 15/76] make_formatter does not have to be in Core --- .../common_utils/Core_Date_Utils.java | 86 +++++++++---------- .../main/java/org/enso/base/Time_Utils.java | 13 ++- .../enso/table/formatting/DateFormatter.java | 10 +-- .../enso/table/formatting/TimeFormatter.java | 4 +- .../enso/table/parsing/BaseTimeParser.java | 3 +- 5 files changed, 62 insertions(+), 54 deletions(-) diff --git a/lib/scala/common-polyglot-core-utils/src/main/java/org/enso/polyglot/common_utils/Core_Date_Utils.java b/lib/scala/common-polyglot-core-utils/src/main/java/org/enso/polyglot/common_utils/Core_Date_Utils.java index 95cb3813a6ee..8006be7eb120 100644 --- a/lib/scala/common-polyglot-core-utils/src/main/java/org/enso/polyglot/common_utils/Core_Date_Utils.java +++ b/lib/scala/common-polyglot-core-utils/src/main/java/org/enso/polyglot/common_utils/Core_Date_Utils.java @@ -1,19 +1,24 @@ package org.enso.polyglot.common_utils; -import java.time.*; +import static java.time.temporal.ChronoField.INSTANT_SECONDS; +import static java.time.temporal.ChronoField.NANO_OF_SECOND; + +import java.time.DateTimeException; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; import java.time.format.DateTimeParseException; import java.time.temporal.ChronoField; import java.time.temporal.TemporalQueries; -import java.util.Locale; - -import static java.time.temporal.ChronoField.INSTANT_SECONDS; -import static java.time.temporal.ChronoField.NANO_OF_SECOND; public class Core_Date_Utils { /** * Replace space with T in ISO date time string to make it compatible with ISO format. + * * @param dateString Raw date time string with either space or T as separator * @return ISO format date time string */ @@ -29,9 +34,17 @@ public static String normaliseISODateTime(String dateString) { /** @return default Date Time formatter for parsing a Date_Time. */ public static DateTimeFormatter defaultZonedDateTimeFormatter() { - return new DateTimeFormatterBuilder().append(DateTimeFormatter.ISO_LOCAL_DATE_TIME) - .optionalStart().parseLenient().appendOffsetId().optionalEnd() - .optionalStart().appendLiteral('[').parseCaseSensitive().appendZoneRegionId().appendLiteral(']') + return new DateTimeFormatterBuilder() + .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME) + .optionalStart() + .parseLenient() + .appendOffsetId() + .optionalEnd() + .optionalStart() + .appendLiteral('[') + .parseCaseSensitive() + .appendZoneRegionId() + .appendLiteral(']') .toFormatter(); } @@ -46,8 +59,8 @@ public static DateTimeFormatter defaultLocalTimeFormatter() { } /** - * Parse a date string into a LocalDate. - * Allows missing day (assumes first day of month) or missing year (assumes current year). + * Parse a date string into a LocalDate. Allows missing day (assumes first day of month) or + * missing year (assumes current year). * * @param dateString the date time string * @param formatter the formatter to use @@ -62,19 +75,22 @@ public static LocalDate parseLocalDate(String dateString, DateTimeFormatter form // Allow Year and Month to be parsed without a day (use first day of month). if (parsed.isSupported(ChronoField.YEAR) && parsed.isSupported(ChronoField.MONTH_OF_YEAR)) { - var dayOfMonth = parsed.isSupported(ChronoField.DAY_OF_MONTH) - ? parsed.get(ChronoField.DAY_OF_MONTH) - : 1; - return LocalDate.of(parsed.get(ChronoField.YEAR), parsed.get(ChronoField.MONTH_OF_YEAR), dayOfMonth); + var dayOfMonth = + parsed.isSupported(ChronoField.DAY_OF_MONTH) ? parsed.get(ChronoField.DAY_OF_MONTH) : 1; + return LocalDate.of( + parsed.get(ChronoField.YEAR), parsed.get(ChronoField.MONTH_OF_YEAR), dayOfMonth); } // Allow Month and Day to be parsed without a year (use current year). - if (parsed.isSupported(ChronoField.DAY_OF_MONTH) && parsed.isSupported(ChronoField.MONTH_OF_YEAR)) { - return LocalDate.of(LocalDate.now().getYear(), parsed.get(ChronoField.MONTH_OF_YEAR), parsed.get(ChronoField.DAY_OF_MONTH)); + if (parsed.isSupported(ChronoField.DAY_OF_MONTH) + && parsed.isSupported(ChronoField.MONTH_OF_YEAR)) { + return LocalDate.of( + LocalDate.now().getYear(), + parsed.get(ChronoField.MONTH_OF_YEAR), + parsed.get(ChronoField.DAY_OF_MONTH)); } throw new DateTimeParseException("Unable to parse date.", dateString, 0); - } /** @@ -90,16 +106,19 @@ public static ZonedDateTime parseZonedDateTime(String dateString, DateTimeFormat try { // Resolve Zone var zone = resolved.query(TemporalQueries.zoneId()); - zone = zone != null ? zone : - (resolved.isSupported(ChronoField.OFFSET_SECONDS) - ? ZoneOffset.ofTotalSeconds(resolved.get(ChronoField.OFFSET_SECONDS)) - : ZoneId.systemDefault()); + zone = + zone != null + ? zone + : (resolved.isSupported(ChronoField.OFFSET_SECONDS) + ? ZoneOffset.ofTotalSeconds(resolved.get(ChronoField.OFFSET_SECONDS)) + : ZoneId.systemDefault()); // Instant Based if (resolved.isSupported(INSTANT_SECONDS)) { long epochSecond = resolved.getLong(INSTANT_SECONDS); int nanoOfSecond = resolved.get(NANO_OF_SECOND); - return ZonedDateTime.ofInstant(java.time.Instant.ofEpochSecond(epochSecond, nanoOfSecond), zone); + return ZonedDateTime.ofInstant( + java.time.Instant.ofEpochSecond(epochSecond, nanoOfSecond), zone); } // Local Based @@ -109,27 +128,8 @@ public static ZonedDateTime parseZonedDateTime(String dateString, DateTimeFormat } catch (DateTimeException e) { throw new DateTimeException("Unable to parse Text '" + dateString + "' to Date_Time.", e); } catch (ArithmeticException e) { - throw new DateTimeException("Unable to parse Text '" + dateString + "' to Date_Time due to arithmetic error.", e); + throw new DateTimeException( + "Unable to parse Text '" + dateString + "' to Date_Time due to arithmetic error.", e); } } - - /** - * Creates a DateTimeFormatter from a format string, supporting building standard formats. - * - * @param format format string - * @param locale locale needed for custom formats - * @return DateTimeFormatter - */ - public static DateTimeFormatter make_formatter(String format, Locale locale) { - var usedLocale = locale == Locale.ROOT ? Locale.US : locale; - return switch (format) { - case "ENSO_ZONED_DATE_TIME" -> defaultZonedDateTimeFormatter(); - case "ISO_ZONED_DATE_TIME" -> DateTimeFormatter.ISO_ZONED_DATE_TIME; - case "ISO_OFFSET_DATE_TIME" -> DateTimeFormatter.ISO_OFFSET_DATE_TIME; - case "ISO_LOCAL_DATE_TIME" -> DateTimeFormatter.ISO_LOCAL_DATE_TIME; - case "ISO_LOCAL_DATE" -> DateTimeFormatter.ISO_LOCAL_DATE; - case "ISO_LOCAL_TIME" -> DateTimeFormatter.ISO_LOCAL_TIME; - default -> DateTimeFormatter.ofPattern(format, usedLocale); - }; - } } diff --git a/std-bits/base/src/main/java/org/enso/base/Time_Utils.java b/std-bits/base/src/main/java/org/enso/base/Time_Utils.java index 7c3bff288f54..7ac440927ddc 100644 --- a/std-bits/base/src/main/java/org/enso/base/Time_Utils.java +++ b/std-bits/base/src/main/java/org/enso/base/Time_Utils.java @@ -39,7 +39,16 @@ public enum AdjustOp { * @return DateTimeFormatter */ public static DateTimeFormatter make_formatter(String format, Locale locale) { - return Core_Date_Utils.make_formatter(format, locale); + var usedLocale = locale == Locale.ROOT ? Locale.US : locale; + return switch (format) { + case "ENSO_ZONED_DATE_TIME" -> Core_Date_Utils.defaultZonedDateTimeFormatter(); + case "ISO_ZONED_DATE_TIME" -> DateTimeFormatter.ISO_ZONED_DATE_TIME; + case "ISO_OFFSET_DATE_TIME" -> DateTimeFormatter.ISO_OFFSET_DATE_TIME; + case "ISO_LOCAL_DATE_TIME" -> DateTimeFormatter.ISO_LOCAL_DATE_TIME; + case "ISO_LOCAL_DATE" -> DateTimeFormatter.ISO_LOCAL_DATE; + case "ISO_LOCAL_TIME" -> DateTimeFormatter.ISO_LOCAL_TIME; + default -> DateTimeFormatter.ofPattern(format, usedLocale); + }; } /** @@ -53,7 +62,7 @@ public static DateTimeFormatter make_formatter(String format, Locale locale) { public static DateTimeFormatter make_output_formatter(String format, Locale locale) { return format.equals("ENSO_ZONED_DATE_TIME") ? Time_Utils.default_output_date_time_formatter() - : Core_Date_Utils.make_formatter(format, locale); + : make_formatter(format, locale); } /** diff --git a/std-bits/table/src/main/java/org/enso/table/formatting/DateFormatter.java b/std-bits/table/src/main/java/org/enso/table/formatting/DateFormatter.java index 91df54669b75..35e9cf645b0c 100644 --- a/std-bits/table/src/main/java/org/enso/table/formatting/DateFormatter.java +++ b/std-bits/table/src/main/java/org/enso/table/formatting/DateFormatter.java @@ -1,17 +1,17 @@ package org.enso.table.formatting; +import org.enso.base.Time_Utils; +import org.graalvm.polyglot.Value; + import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.Locale; -import org.enso.polyglot.common_utils.Core_Date_Utils; -import org.graalvm.polyglot.Value; - public class DateFormatter implements DataFormatter { private final DateTimeFormatter formatter; public DateFormatter(String formatString, Locale locale) { - formatter = Core_Date_Utils.make_formatter(formatString, locale); + formatter = Time_Utils.make_formatter(formatString, locale); } @Override @@ -21,7 +21,7 @@ public String format(Object value) { } if (value instanceof Value v && v.isDate()) { - value = v.asDate(); + value = v.asDate(); } if (value instanceof LocalDate date) { diff --git a/std-bits/table/src/main/java/org/enso/table/formatting/TimeFormatter.java b/std-bits/table/src/main/java/org/enso/table/formatting/TimeFormatter.java index 985f615b931e..6c3d1545427a 100644 --- a/std-bits/table/src/main/java/org/enso/table/formatting/TimeFormatter.java +++ b/std-bits/table/src/main/java/org/enso/table/formatting/TimeFormatter.java @@ -1,6 +1,6 @@ package org.enso.table.formatting; -import org.enso.polyglot.common_utils.Core_Date_Utils; +import org.enso.base.Time_Utils; import org.graalvm.polyglot.Value; import java.time.LocalTime; @@ -11,7 +11,7 @@ public class TimeFormatter implements DataFormatter { private final DateTimeFormatter formatter; public TimeFormatter(String formatString, Locale locale) { - formatter = Core_Date_Utils.make_formatter(formatString, locale); + formatter = Time_Utils.make_formatter(formatString, locale); } @Override diff --git a/std-bits/table/src/main/java/org/enso/table/parsing/BaseTimeParser.java b/std-bits/table/src/main/java/org/enso/table/parsing/BaseTimeParser.java index 2150dcbdf93d..e0b6e1ec2359 100644 --- a/std-bits/table/src/main/java/org/enso/table/parsing/BaseTimeParser.java +++ b/std-bits/table/src/main/java/org/enso/table/parsing/BaseTimeParser.java @@ -4,7 +4,6 @@ import java.time.format.DateTimeParseException; import java.util.Locale; import org.enso.base.Time_Utils; -import org.enso.polyglot.common_utils.Core_Date_Utils; import org.enso.table.parsing.problems.ProblemAggregator; public abstract class BaseTimeParser extends IncrementalDatatypeParser { @@ -22,7 +21,7 @@ protected BaseTimeParser(String[] formats, Locale locale, ParseStrategy parseStr formatters = new DateTimeFormatter[formats.length]; replaceSpaces = new boolean[formats.length]; for (int i = 0; i < formats.length; i++) { - formatters[i] = Core_Date_Utils.make_formatter(formats[i], locale); + formatters[i] = Time_Utils.make_formatter(formats[i], locale); replaceSpaces[i] = Time_Utils.is_iso_datetime_based(formats[i]); } } From 2b767b88df0cfe8807264ca3695dc96746b56c6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Fri, 15 Sep 2023 12:33:21 +0200 Subject: [PATCH 16/76] mark make_formatter as deprecated --- std-bits/base/src/main/java/org/enso/base/Time_Utils.java | 1 + 1 file changed, 1 insertion(+) diff --git a/std-bits/base/src/main/java/org/enso/base/Time_Utils.java b/std-bits/base/src/main/java/org/enso/base/Time_Utils.java index 7ac440927ddc..40ba36458f07 100644 --- a/std-bits/base/src/main/java/org/enso/base/Time_Utils.java +++ b/std-bits/base/src/main/java/org/enso/base/Time_Utils.java @@ -38,6 +38,7 @@ public enum AdjustOp { * @param locale locale needed for custom formats * @return DateTimeFormatter */ + @Deprecated public static DateTimeFormatter make_formatter(String format, Locale locale) { var usedLocale = locale == Locale.ROOT ? Locale.US : locale; return switch (format) { From db5044460e020d8f6125fe155b6819d79bf58c06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Fri, 15 Sep 2023 17:09:32 +0200 Subject: [PATCH 17/76] checkpoint - adding builtin formats --- .../Data/Time/Date_Time_Format_Pattern.enso | 40 +++++++++++++++++++ .../main/java/org/enso/base/Time_Utils.java | 4 -- .../enso/base/time/EnsoDateTimeFormatter.java | 13 ++++++ 3 files changed, 53 insertions(+), 4 deletions(-) create mode 100644 std-bits/base/src/main/java/org/enso/base/time/EnsoDateTimeFormatter.java diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso index 71758e074572..157923b597c2 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso @@ -131,6 +131,42 @@ type Date_Time_Format_Pattern Date.parse (ISO_Week_Date "YYYY-'W'WW" "1978-W01") == (Date.new 1978 01 02) ISO_Week_Date pattern:Text locale:Locale=Locale.default + ## The default format for date-time used in Enso. + It acts as `ISO_Zoned_Date_Time` but both offset and timezone are optional. + + For example, it may parse date of the form `2011-12-03 10:15:30+01:00[Europe/Paris]`, + as well as `2011-12-03T10:15:30` assuming the default timezone. + Default_Enso_Zoned_Date_Time + + ## The ISO 8601 format for date-time with offset and timezone. + The date and time parts may be separated by a single space or a `T`. + + For example, it may parse date of the form `2011-12-03 10:15:30+01:00[Europe/Paris]`. + ISO_Zoned_Date_Time + + ## The ISO 8601 format for date-time with offset. + The date and time parts may be separated by a single space or a `T`. + + For example, it may parse date of the form `2011-12-03 10:15:30+01:00`. + ISO_Offset_Date_Time + + ## The ISO 8601 format for date-time without a timezone. + The date and time parts may be separated by a single space or a `T`. + + For example, it may parse date of the form `2011-12-03 10:15:30`. The + timezone will be set to `Time_Zone.system`. + ISO_Local_Date_Time + + ## The ISO 8601 format for date. + + For example, it may parse date of the form `2011-12-03`. + ISO_Local_Date + + ## The ISO 8601 format for time. + + For example, it may parse time of the form `10:15:30`. + ISO_Local_Time + ## ADVANCED A pattern built using the Java pattern syntax or a Java DateTimeFormatter instance. @@ -148,6 +184,10 @@ type Date_Time_Format_Pattern Tokenizer.tokenize pattern |> Parser.parse_simple_date_pattern |> As_Java_Formatter_Interpreter.interpret locale Date_Time_Format_Pattern.ISO_Week_Date pattern locale -> Tokenizer.tokenize pattern |> Parser.parse_iso_week_year_pattern |> As_Java_Formatter_Interpreter.interpret locale + Date_Time_Format_Pattern.Default_Enso_Zoned_Date_Time -> + Time_Utils.default_date_time_formatter + Date_Time_Format_Pattern.ISO_Zoned_Date_Time -> + DateTimeFormatter.ISO_ZONED_DATE_TIME Date_Time_Format_Pattern.Java pattern -> case pattern of _ : Text -> DateTimeFormatter.ofPattern pattern formatter : DateTimeFormatter -> formatter diff --git a/std-bits/base/src/main/java/org/enso/base/Time_Utils.java b/std-bits/base/src/main/java/org/enso/base/Time_Utils.java index 40ba36458f07..1c774bfe18fc 100644 --- a/std-bits/base/src/main/java/org/enso/base/Time_Utils.java +++ b/std-bits/base/src/main/java/org/enso/base/Time_Utils.java @@ -153,10 +153,6 @@ public static String time_of_day_format(LocalTime localTime, DateTimeFormatter f return formatter.format(localTime); } - public static String time_of_day_format_with_locale(LocalTime localTime, Object format, Locale locale) { - return make_output_formatter(format.toString(), locale).format(localTime); - } - public static LocalDate date_adjust(LocalDate date, AdjustOp op, Period period) { return switch (op) { case PLUS -> date.plus(period); diff --git a/std-bits/base/src/main/java/org/enso/base/time/EnsoDateTimeFormatter.java b/std-bits/base/src/main/java/org/enso/base/time/EnsoDateTimeFormatter.java new file mode 100644 index 000000000000..1fcc7447cec7 --- /dev/null +++ b/std-bits/base/src/main/java/org/enso/base/time/EnsoDateTimeFormatter.java @@ -0,0 +1,13 @@ +package org.enso.base.time; + +import java.time.format.DateTimeFormatter; + +public class EnsoDateTimeFormatter { + private final DateTimeFormatter formatter; + private final boolean needsISOTreplaceWorkaround; + + private EnsoDateTimeFormatter(DateTimeFormatter formatter, boolean needsISOreplaceTWorkaround) { + this.formatter = formatter; + this.needsISOTreplaceWorkaround = needsISOreplaceTWorkaround; + } +} From 73a4447d2472e04366965e7a0b14ae3ccda6aaff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Sat, 16 Sep 2023 22:55:02 +0200 Subject: [PATCH 18/76] checkpoint - reworked Formatter structure, got Date parsing using new infra, TODO: rest --- .../0.0.0-dev/src/Data/Time/Conversions.enso | 6 +- .../Base/0.0.0-dev/src/Data/Time/Date.enso | 95 ++++-- .../Data/Time/Date_Time_Format_Pattern.enso | 10 - .../src/Data/Time/Date_Time_Formatter.enso | 273 ++++++++++++++++++ .../Base/0.0.0-dev/src/Errors/Time_Error.enso | 3 +- .../src/Internal/Time/Format/Parser.enso | 2 +- .../src/Internal/Time/Format/Tokenizer.enso | 2 +- .../lib/Standard/Base/0.0.0-dev/src/Main.enso | 4 +- .../main/java/org/enso/base/Time_Utils.java | 21 +- .../enso/base/time/EnsoDateTimeFormatter.java | 139 ++++++++- .../org/enso/base/time/FormatterKind.java | 8 + .../expressions/ExpressionVisitorImpl.java | 5 +- 12 files changed, 511 insertions(+), 57 deletions(-) create mode 100644 distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso create mode 100644 std-bits/base/src/main/java/org/enso/base/time/FormatterKind.java diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Conversions.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Conversions.enso index a7fb971dcb71..6b00bebd1ddb 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Conversions.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Conversions.enso @@ -1,5 +1,7 @@ +import project.Data.Locale.Locale import project.Data.Text.Text -import project.Data.Time.Date_Time_Format_Pattern.Date_Time_Format_Pattern +import project.Data.Time.Date_Time_Formatter.Date_Time_Formatter ## PRIVATE -Date_Time_Format_Pattern.from (that:Text) = Date_Time_Format_Pattern.Simple that +Date_Time_Formatter.from (that:Text) (locale:Locale = Locale.default) = + Date_Time_Formatter.from_simple_pattern that locale diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date.enso index 61c6bcaeec34..61d163db560a 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date.enso @@ -8,6 +8,7 @@ import project.Data.Text.Text import project.Data.Time.Date_Period.Date_Period import project.Data.Time.Date_Range.Date_Range import project.Data.Time.Date_Time.Date_Time +import project.Data.Time.Date_Time_Formatter.Date_Time_Formatter import project.Data.Time.Day_Of_Week.Day_Of_Week import project.Data.Time.Day_Of_Week_From import project.Data.Time.Duration.Duration @@ -25,6 +26,7 @@ import project.Nothing.Nothing import project.Panic.Panic from project.Data.Boolean import Boolean, False, True from project.Data.Text.Extensions import all +from project.Data.Time.Conversions import all from project.Data.Time.Date_Time import ensure_in_epoch from project.Widget_Helpers import make_date_format_selector @@ -105,17 +107,38 @@ type Date Arguments: - text: The text to try and parse as a date. - - pattern: An optional pattern describing how to parse the text. - - locale: The locale in which the pattern should be interpreted. + - format: A pattern describing how to parse the text, or a `Date_Time_Formatter`. Returns a `Time_Error` if the provided `text` cannot be parsed using the - provided `pattern`. + provided `format`. ? Pattern Syntax - A custom pattern string consists of one or more custom date and time - format specifiers. For example, "d MMM yyyy" will format "2011-12-03" - as "3 Dec 2011". See https://docs.oracle.com/en/java/javase/18/docs/api/java.base/java/time/format/DateTimeFormatter.html - for a complete format specification. + If the pattern is provided as `Text`, it is parsed using the format + described below. See `Date_Time_Formatter` for more options. + - y: Year. The number of pattern letters determines the minimum number of + digits. + - y: The year using any number of digits. + - yy: The year, using at most two digits. The default range is + 1950-2049, but this can be changed by including the end year in + braces e.g. `yy{2099}`. + - yyyy: The year, using exactly four digits. + - M: Month of year. The number of pattern letters determines the format: + - M: Any number (1-12). + - MM: Month number with zero padding required (01-12). + - MMM: Short name of the month (Jan-Dec). + - MMMM: Full name of the month (January-December). + The month names depend on the selected locale. + - d: Day. The number of pattern letters determines the format: + - d: Any number (1-31). + - dd: Day number with zero padding required (01-31). + - ddd: Short name of the day of week (Mon-Sun). + - dddd: Full name of the day of week (Monday-Sunday). + The weekday names depend on the selected locale. + Both day of week and day of month may be included in a single pattern - + in such case the day of week is used as a sanity check. + - Q: Quarter of year. + If only year and quarter are provided in the pattern, when parsing a + date, the result will be the first day of that quarter. ? Default Date Formatting Unless you provide a custom format, the text must represent a valid date @@ -165,16 +188,9 @@ type Date date = Date.parse "1999-1-1" "yyyy-MM-dd" date.catch Time_Error (_->Date.new 2000 1 1) @pattern make_date_format_selector - @locale Locale.default_widget - parse : Text -> Text -> Locale -> Date ! Time_Error - parse text:Text pattern:Text="" locale:Locale=Locale.default = - result = Panic.recover Any <| - formatter = if pattern.is_empty then Time_Utils.default_date_formatter else - Time_Utils.make_formatter pattern locale.java_locale - Time_Utils.parse_date text.trim formatter - result . map_error <| case _ of - err : JException -> Time_Error.Error err.getMessage - ex -> ex + parse : Text -> Date_Time_Formatter -> Date ! Time_Error + parse text:Text format:Date_Time_Formatter=Date_Time_Formatter.iso_date = + format.parse_date text ## GROUP Metadata Get the year field. @@ -709,15 +725,35 @@ type Date Format this date using the provided format specifier. Arguments: - - pattern: The text specifying the format for formatting the date. - - locale: The locale in which the format should be interpreted. - (Defaults to Locale.default.) + - format: The text specifying the format, or a `Date_Time_Formatter`. ? Pattern Syntax - A custom pattern string consists of one or more custom date and time - format specifiers. For example, "d MMM yyyy" will format "2011-12-03" - as "3 Dec 2011". See https://docs.oracle.com/en/java/javase/18/docs/api/java.base/java/time/format/DateTimeFormatter.html - for a complete format specification. + If the pattern is provided as `Text`, it is parsed using the format + described below. See `Date_Time_Formatter` for more options. + - y: Year. The number of pattern letters determines the minimum number of + digits. + - y: The year using any number of digits. + - yy: The year, using at most two digits. The default range is + 1950-2049, but this can be changed by including the end year in + braces e.g. `yy{2099}`. + - yyyy: The year, using exactly four digits. + - M: Month of year. The number of pattern letters determines the format: + - M: Any number (1-12). + - MM: Month number with zero padding required (01-12). + - MMM: Short name of the month (Jan-Dec). + - MMMM: Full name of the month (January-December). + The month names depend on the selected locale. + - d: Day. The number of pattern letters determines the format: + - d: Any number (1-31). + - dd: Day number with zero padding required (01-31). + - ddd: Short name of the day of week (Mon-Sun). + - dddd: Full name of the day of week (Monday-Sunday). + The weekday names depend on the selected locale. + Both day of week and day of month may be included in a single pattern - + in such case the day of week is used as a sanity check. + - Q: Quarter of year. + If only year and quarter are provided in the pattern, when parsing a + date, the result will be the first day of that quarter. > Example Format "2020-06-02" as "2 Jun 2020" @@ -749,14 +785,11 @@ type Date > Example Format "2020-06-21" with French locale as "21. juin 2020" - example_format = Date.new 2020 6 21 . format "d. MMMM yyyy" (Locale.new "fr") + example_format = Date.new 2020 6 21 . format (Date_Time_Formatter.from "d. MMMM yyyy" (Locale.new "fr")) @pattern (value-> make_date_format_selector value) - @locale Locale.default_widget - format : Text -> Locale -> Text - format self pattern:Text locale=Locale.default = - formatter = if pattern.is_empty then Time_Utils.default_date_formatter else - Time_Utils.make_formatter pattern locale.java_locale - Time_Utils.date_format self formatter + format : Date_Time_Formatter -> Text + format self format:Date_Time_Formatter=Date_Time_Formatter.iso_date = + format.format_date self ## PRIVATE week_days_between start end = diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso index 157923b597c2..0439b15a785c 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso @@ -191,13 +191,3 @@ type Date_Time_Format_Pattern Date_Time_Format_Pattern.Java pattern -> case pattern of _ : Text -> DateTimeFormatter.ofPattern pattern formatter : DateTimeFormatter -> formatter - -type Date_Time_Format_Parse_Error - ## PRIVATE - Indicates an error during parsing of a `Date_Time_Format_Pattern`. - Error message - - ## PRIVATE - to_display_text : Text - to_display_text self = - "Error parsing date/time format pattern: " + self.message diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso new file mode 100644 index 000000000000..95a73038a24d --- /dev/null +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso @@ -0,0 +1,273 @@ +import project.Data.Locale.Locale +import project.Data.Text.Text +import project.Error.Error +import project.Errors.Time_Error.Time_Error +import project.Panic.Panic +from project.Data.Boolean import Boolean, False, True + +import project.Internal.Time.Format.Tokenizer.Tokenizer +import project.Internal.Time.Format.Parser +import project.Internal.Time.Format.As_Java_Formatter_Interpreter + +polyglot java import java.lang.Exception as JException +polyglot java import java.time.format.DateTimeFormatter +polyglot java import org.enso.base.time.EnsoDateTimeFormatter +polyglot java import org.enso.base.time.FormatterKind + +type Date_Time_Formatter + ## PRIVATE + Value (underlying : EnsoDateTimeFormatter) + + ## TODO + 1. move to this, check stuff at construction + 2. add conversion + 3. move Date.parse/format et al to this, add Java sibling for Data_Formatter + 4. set up new dropdowns + 5. add compatibility check (can do Date, can do Time) + + ## Creates a formatter from a simple date-time format pattern. + + Every letter in the pattern is interpreted as a pattern character as + described in the table below. Any character that is not a letter in the + pattern is treated as a literal character. If a sequence of letters needs + to be put in as a literal, it can be escaped using single quotes. Use two + single quotes in a row to represent a single quote in the result. As + explained below, curly braces can have special meaning (see 'yy'); to + enter a literal curly brace, put it inside a quoted literal. + + Pattern characters are interpreted case insensitively, with the exception + of `M/m' and 'H/h'. + + Date pattern characters: + - y: Year. The number of pattern letters determines the minimum number of + digits. + - y: The year using any number of digits. + - yy: The year, using at most two digits. The default range is + 1950-2049, but this can be changed by including the end year in + braces e.g. `yy{2099}`. + - yyyy: The year, using exactly four digits. + - M: Month of year. The number of pattern letters determines the format: + - M: Any number (1-12). + - MM: Month number with zero padding required (01-12). + - MMM: Short name of the month (Jan-Dec). + - MMMM: Full name of the month (January-December). + The month names depend on the selected locale. + - d: Day. The number of pattern letters determines the format: + - d: Any number (1-31). + - dd: Day number with zero padding required (01-31). + - ddd: Short name of the day of week (Mon-Sun). + - dddd: Full name of the day of week (Monday-Sunday). + The weekday names depend on the selected locale. + Both day of week and day of month may be included in a single pattern - + in such case the day of week is used as a sanity check. + - Q: Quarter of year. + If only year and quarter are provided in the pattern, when parsing a + date, the result will be the first day of that quarter. + + Time pattern characters: + - H: 24h hour of day (0-23). + - h: 12h hour of day (0-12). The `a` pattern is needed to disambiguate + between AM and PM. + - m: Minute of hour. + - s: Second of minute. + - f: Fractional part of the second. The number of pattern letters + determines the number of digits. If one letter is used, any number of + digits will be accepted. + - a: AM/PM marker. + + Time zone pattern characters: + - T: If repeated 3 or less times - Time zone ID (e.g. Europe/Warsaw, Z, + -08:30), otherwise - Time zone name (e.g. Central European Time, CET). + - Z: Zone offset (e.g. +0000, -0830, +08:30:15). + + Some parts, like fractions of a second may not be required. The square + brackets `[]` can be used to surround such optional sections. + + > Example + Parsing date/time values + + Date_Time.parse "yyyy-MM-dd'T'HH:mm:ss.fZ" "2021-10-12T12:34:56.789+0200" == (Date_Time.new 2021 10 12 12 34 56 milliseconds=789 (Time_Zone.new hours=2)) + Date.parse "ddd, d MMM yyyy" "Tue, 12 Oct 2021" == (Date.new 2021 10 12) + Date.parse "dddd, dd MMMM ''yy" "Thursday, 1 October '98" == (Date.new 1998 10 01) + Date_Time.parse "d/M/Y h:mm a" "12/10/2021 5:34 PM" == (Date_Time.new 2021 10 12 17 34 00) + + > Example + Omitting the day will yield the first day of the month. + + Date.parse "yyyy-MM" "2021-10" == (Date.new 2021 10 01) + + > Example + Omitting the year will yield the current year. + + Date.parse "MM-dd" "10-12" == (Date.new (Date.today.year) 10 12) + + > Example + Parsing a two-digit year with a custom base year. + + Date.parse "dd MMMM ''yy{2099}" "1 November '95" == (Date.new 2095 11 01) + @locale Locale.default_widget + from_simple_pattern pattern:Text locale:Locale=Locale.default = + java_formatter = Tokenizer.tokenize pattern |> Parser.parse_simple_date_pattern |> + As_Java_Formatter_Interpreter.interpret locale + Date_Time_Formatter.Value (EnsoDateTimeFormatter.new java_formatter False pattern FormatterKind.SIMPLE) + + ## Creates a formatter from a pattern for the ISO 8601 leap week calendar. + + The ISO 8601 leap week calendar is a variation of the ISO 8601 calendar + that defines a leap week as the week that contains the 29th of February. + This calendar is used by some European and Middle Eastern countries. + + The pattern is a sequence of letters and symbols that are interpreted as + follows: + - Y: The week based year. + - In case the year is parsed in two digit mode (`YY`), the default + range is 1950-2049, but this can be changed by including the end year + in braces e.g. `YY{2099}` + - w: Week of year. + - d: Day of week. + - d: Numeric day of week (1-7). 1 is Monday. + - dd: Numeric day of week with zero padding (01-07). + - ddd: Short name of the day of week (Mon-Sun). + - dddd: Full name of the day of week (Monday-Sunday). + The weekday names depend on the selected locale. + + Moreover, all time and timezone pattern characters like in `Simple` case + are supported too - in case you need to parse a date time value with the + date part in ISO week date format. + + The same as in the `Simple` pattern, the single quotes can be used to + escape letter literals and square brackets can be used to indicate + optional sections. + + > Example + Parsing a date in the ISO week date format + + Date.parse (ISO_Week_Date "YYYY-'W'WW-d" "1976-W53-6") == (Date.new 1977 01 01) + Date.parse (ISO_Week_Date "YYYY-'W'WW, eee" "1978-W01, Mon") == (Date.new 1978 01 02) + Date_Time.parse (ISO_Week_Date "YYYY-'W'WW-d HH:mm:ss" "1978-W01-4 12:34:56") == (Date_Time.new 1978 01 05 12 34 56) + + > Example + Omitting the day of the week will result in the first day of that week. + + Date.parse (ISO_Week_Date "YYYY-'W'WW" "1978-W01") == (Date.new 1978 01 02) + @locale Locale.default_widget + from_iso_week_date_pattern pattern:Text locale:Locale=Locale.default = + java_formatter = Tokenizer.tokenize pattern |> Parser.parse_iso_week_year_pattern |> + As_Java_Formatter_Interpreter.interpret locale + Date_Time_Formatter.Value (EnsoDateTimeFormatter.new java_formatter False pattern FormatterKind.ISO_WEEK_DATE) + + ## ADVANCED + Creates a formatter from a Java `DateTimeFormatter` instance or a text + pattern parsed using the Java parser: `DateTimeFormatter.ofPattern`. + + See the Java documentation for explanation of the pattern format: + https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/format/DateTimeFormatter.html#patterns + from_java (pattern:Text|DateTimeFormatter) = + java_formatter = case pattern of + java_formatter : DateTimeFormatter -> + Date_Time_Formatter.Value (EnsoDateTimeFormatter.new java_formatter False Nothing FormatterKind.RAW_JAVA) + _ : Text -> + # TODO setting Locale too! + java_formatter = DateTimeFormatter.ofPattern pattern + Date_Time_Formatter.Value (EnsoDateTimeFormatter.new java_formatter False pattern FormatterKind.RAW_JAVA) + + # TODO Locale for constants?? + + ## The default format for date-time used in Enso. + It acts as `ISO_Zoned_Date_Time` but both offset and timezone are optional. + + For example, it may parse date of the form `2011-12-03 10:15:30+01:00[Europe/Paris]`, + as well as `2011-12-03T10:15:30` assuming the default timezone. + default_enso_zoned_date_time = + Date_Time_Formatter.Value Time_Utils.default_date_time_formatter + + ## The ISO 8601 format for date-time with offset and timezone. + The date and time parts may be separated by a single space or a `T`. + + For example, it may parse date of the form `2011-12-03 10:15:30+01:00[Europe/Paris]`. + iso_zoned_date_time = + Date_Time_Formatter.Value (EnsoDateTimeFormatter.new DateTimeFormatter.ISO_ZONED_DATE_TIME True "iso_zoned_date_time" FormatterKind.CONSTANT) + + ## The ISO 8601 format for date-time with offset. + The date and time parts may be separated by a single space or a `T`. + + For example, it may parse date of the form `2011-12-03 10:15:30+01:00`. + iso_offset_date_time = + Date_Time_Formatter.Value (EnsoDateTimeFormatter.new DateTimeFormatter.ISO_OFFSET_DATE_TIME True "iso_offset_date_time" FormatterKind.CONSTANT) + + ## The ISO 8601 format for date-time without a timezone. + The date and time parts may be separated by a single space or a `T`. + + For example, it may parse date of the form `2011-12-03 10:15:30`. The + timezone will be set to `Time_Zone.system`. + iso_local_date_time = + Date_Time_Formatter.Value (EnsoDateTimeFormatter.new DateTimeFormatter.ISO_LOCAL_DATE_TIME True "iso_local_date_time" FormatterKind.CONSTANT) + + ## The ISO 8601 format for date. + + For example, it may parse date of the form `2011-12-03`. + iso_date = + Date_Time_Formatter.Value (EnsoDateTimeFormatter.new DateTimeFormatter.ISO_DATE True "iso_date" FormatterKind.CONSTANT) + + ## The ISO 8601 format for time. + + For example, it may parse time of the form `10:15:30`. + iso_time = + Date_Time_Formatter.Value (EnsoDateTimeFormatter.new DateTimeFormatter.ISO_TIME True "iso_time" FormatterKind.CONSTANT) + + ## Returns a text representation of this formatter. + to_text : Text + to_text self = case self.underlying.getFormatterKind of + FormatterKind.CONSTANT -> + "Date_Time_Formatter." + self.underlying.getOriginalPattern + FormatterKind.SIMPLE -> + # TODO locale too? + "Date_Time_Formatter.from_simple_pattern " + self.underlying.getOriginalPattern.pretty + FormatterKind.ISO_WEEK_DATE -> + "Date_Time_Formatter.from_iso_week_date_pattern " + self.underlying.getOriginalPattern.pretty + FormatterKind.RAW_JAVA -> case self.underlying.getOriginalPattern of + original_pattern : Text -> "Date_Time_Formatter.from_java " + original_pattern.pretty + Nothing -> "Date_Time_Formatter.from_java " + self.underlying.getFormatter.to_text + + ## Parses a human-readable representation of this formatter. + to_display_text self = self.to_text + + ## PRIVATE + handle_java_errors self ~action = + Panic.catch JException action caught_panic-> + Error.throw (Time_Error.Error caught_panic.payload.getMessage caught_panic.payload) + + ## PRIVATE + parse_date self (text:Text) = self.handle_java_errors <| + self.underlying.parseLocalDate text + + ## PRIVATE + parse_date_time self (text:Text) = self.handle_java_errors <| + self.underlying.parseZonedDateTime text + + ## PRIVATE + parse_time self (text:Text) = self.handle_java_errors <| + self.underlying.parseLocalTime text + + ## PRIVATE + format_date self (date:Date) = self.handle_java_errors <| + self.underlying.formatLocalDate date + + ## PRIVATE + format_date_time self (date_time:Date_Time) = self.handle_java_errors <| + self.underlying.formatZonedDateTime date_time + + ## PRIVATE + format_time self (time:Time) = self.handle_java_errors <| + self.underlying.formatLocalTime time + +## PRIVATE +type Date_Time_Format_Parse_Error + ## PRIVATE + Indicates an error during parsing of a `Date_Time_Format_Pattern`. + Error message + + ## PRIVATE + to_display_text : Text + to_display_text self = + "Error parsing date/time format pattern: " + self.message diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Errors/Time_Error.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Errors/Time_Error.enso index 587761dbf38f..79b0dec9526f 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Errors/Time_Error.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Errors/Time_Error.enso @@ -8,7 +8,8 @@ type Time_Error Arguments: - error_message: The message for the error. - Error error_message + - cause: An optional exception that caused this error (usually a Java Exception). + Error error_message cause=Nothing ## PRIVATE epoch_start : Time_Error diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso index 129f9c589ff1..9cf6a0f28a4c 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso @@ -2,7 +2,7 @@ import project.Data.Numbers.Integer import project.Data.Numbers.Number_Parse_Error import project.Data.Text.Case.Case import project.Data.Text.Text -import project.Data.Time.Date_Time_Format_Pattern.Date_Time_Format_Parse_Error +import project.Data.Time.Date_Time_Formatter.Date_Time_Format_Parse_Error import project.Data.Vector.Builder as Vector_Builder import project.Data.Vector.Vector import project.Error.Error diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Tokenizer.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Tokenizer.enso index 3cd73c03aa19..e7b7b5be3945 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Tokenizer.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Tokenizer.enso @@ -1,6 +1,6 @@ import project.Data.Numbers.Integer import project.Data.Text.Text -import project.Data.Time.Date_Time_Format_Pattern.Date_Time_Format_Parse_Error +import project.Data.Time.Date_Time_Formatter.Date_Time_Format_Parse_Error import project.Data.Vector.Builder as Vector_Builder import project.Data.Vector.Vector import project.Error.Error diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso index 434ee73f4daf..7fe5a7602790 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso @@ -40,7 +40,7 @@ import project.Data.Time.Date.Date import project.Data.Time.Date_Period.Date_Period import project.Data.Time.Date_Range.Date_Range import project.Data.Time.Date_Time.Date_Time -import project.Data.Time.Date_Time_Format_Pattern.Date_Time_Format_Pattern +import project.Data.Time.Date_Time_Formatter.Date_Time_Formatter import project.Data.Time.Day_Of_Week.Day_Of_Week import project.Data.Time.Day_Of_Week_From import project.Data.Time.Duration.Duration @@ -130,7 +130,7 @@ export project.Data.Time.Date.Date export project.Data.Time.Date_Period.Date_Period export project.Data.Time.Date_Range.Date_Range export project.Data.Time.Date_Time.Date_Time -export project.Data.Time.Date_Time_Format_Pattern.Date_Time_Format_Pattern +export project.Data.Time.Date_Time_Formatter.Date_Time_Formatter export project.Data.Time.Day_Of_Week.Day_Of_Week export project.Data.Time.Day_Of_Week_From export project.Data.Time.Duration.Duration diff --git a/std-bits/base/src/main/java/org/enso/base/Time_Utils.java b/std-bits/base/src/main/java/org/enso/base/Time_Utils.java index 1c774bfe18fc..2fa010cbd1ff 100644 --- a/std-bits/base/src/main/java/org/enso/base/Time_Utils.java +++ b/std-bits/base/src/main/java/org/enso/base/Time_Utils.java @@ -2,6 +2,8 @@ import org.enso.base.time.Date_Time_Utils; import org.enso.base.time.Date_Utils; +import org.enso.base.time.EnsoDateTimeFormatter; +import org.enso.base.time.FormatterKind; import org.enso.base.time.TimeUtilsBase; import org.enso.base.time.Time_Of_Day_Utils; import org.enso.polyglot.common_utils.Core_Date_Utils; @@ -53,8 +55,8 @@ public static DateTimeFormatter make_formatter(String format, Locale locale) { } /** - * Creates a DateTimeFormatter from a format string, supporting building standard formats. - * For Enso format, return the default output formatter. + * Creates a DateTimeFormatter from a format string, supporting building standard formats. For Enso format, return the + * default output formatter. * * @param format format string * @param locale locale needed for custom formats @@ -82,8 +84,13 @@ public static boolean is_iso_datetime_based(String format) { /** * @return default DateTimeFormatter for parsing a Date_Time. */ - public static DateTimeFormatter default_date_time_formatter() { - return Core_Date_Utils.defaultZonedDateTimeFormatter(); + public static EnsoDateTimeFormatter default_date_time_formatter() { + return new EnsoDateTimeFormatter( + Core_Date_Utils.defaultZonedDateTimeFormatter(), + true, + "default_enso_zoned_date_time", + FormatterKind.CONSTANT + ); } /** @@ -223,13 +230,13 @@ public static LocalDate parse_date(String text, DateTimeFormatter formatter) { * @param formatter the formatter to use. * @return parsed LocalTime instance. */ - public static LocalTime parse_time_of_day(String text,DateTimeFormatter formatter) { + public static LocalTime parse_time_of_day(String text, DateTimeFormatter formatter) { return LocalTime.parse(text, formatter); } /** - * Normally this method could be done in Enso by pattern matching, but currently matching on Time - * types is not supported, so this is a workaround. + * Normally this method could be done in Enso by pattern matching, but currently matching on Time types is not + * supported, so this is a workaround. * *

TODO once the related issue is fixed, this workaround may be replaced with pattern matching * in Enso; Pivotal issue. diff --git a/std-bits/base/src/main/java/org/enso/base/time/EnsoDateTimeFormatter.java b/std-bits/base/src/main/java/org/enso/base/time/EnsoDateTimeFormatter.java index 1fcc7447cec7..ca820792bf2c 100644 --- a/std-bits/base/src/main/java/org/enso/base/time/EnsoDateTimeFormatter.java +++ b/std-bits/base/src/main/java/org/enso/base/time/EnsoDateTimeFormatter.java @@ -1,13 +1,150 @@ package org.enso.base.time; +import java.time.DateTimeException; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.time.temporal.ChronoField; +import java.time.temporal.TemporalQueries; + +import static java.time.temporal.ChronoField.INSTANT_SECONDS; +import static java.time.temporal.ChronoField.NANO_OF_SECOND; public class EnsoDateTimeFormatter { private final DateTimeFormatter formatter; private final boolean needsISOTreplaceWorkaround; + private final String originalPattern; + private final FormatterKind formatterKind; - private EnsoDateTimeFormatter(DateTimeFormatter formatter, boolean needsISOreplaceTWorkaround) { + public EnsoDateTimeFormatter(DateTimeFormatter formatter, boolean needsISOreplaceTWorkaround, String originalPattern, FormatterKind formatterKind) { this.formatter = formatter; this.needsISOTreplaceWorkaround = needsISOreplaceTWorkaround; + this.originalPattern = originalPattern; + this.formatterKind = formatterKind; + } + + public DateTimeFormatter getRawJavaFormatter() { + return formatter; + } + + public String getOriginalPattern() { + return originalPattern; + } + + public FormatterKind getFormatterKind() { + return formatterKind; + } + + private String normaliseISODateTime(String dateString) { + if (dateString != null && dateString.length() > 10 && dateString.charAt(10) == ' ') { + var builder = new StringBuilder(dateString); + builder.replace(10, 11, "T"); + return builder.toString(); + } + + return dateString; + } + + @Override + public String toString() { + return switch (formatterKind) { + case SIMPLE -> "(Simple) " + originalPattern; + case ISO_WEEK_DATE -> "(ISO Week Date Format) " + originalPattern; + case RAW_JAVA -> "(Java Format Pattern) " + originalPattern; + case CONSTANT -> originalPattern; + }; + } + + public LocalDate parseLocalDate(String dateString) { + if (needsISOTreplaceWorkaround) { + dateString = normaliseISODateTime(dateString); + } + + var parsed = formatter.parse(dateString); + + if (parsed.isSupported(ChronoField.EPOCH_DAY)) { + return LocalDate.ofEpochDay(parsed.getLong(ChronoField.EPOCH_DAY)); + } + + // Allow Year and Month to be parsed without a day (use first day of month). + if (parsed.isSupported(ChronoField.YEAR) && parsed.isSupported(ChronoField.MONTH_OF_YEAR)) { + var dayOfMonth = + parsed.isSupported(ChronoField.DAY_OF_MONTH) ? parsed.get(ChronoField.DAY_OF_MONTH) : 1; + return LocalDate.of( + parsed.get(ChronoField.YEAR), parsed.get(ChronoField.MONTH_OF_YEAR), dayOfMonth); + } + + // Allow Month and Day to be parsed without a year (use current year). + if (parsed.isSupported(ChronoField.DAY_OF_MONTH) + && parsed.isSupported(ChronoField.MONTH_OF_YEAR)) { + return LocalDate.of( + LocalDate.now().getYear(), + parsed.get(ChronoField.MONTH_OF_YEAR), + parsed.get(ChronoField.DAY_OF_MONTH)); + } + + // This will usually throw at this point, but it will construct a more informative exception than we could. + return LocalDate.from(parsed); + } + + public ZonedDateTime parseZonedDateTime(String dateString) { + if (needsISOTreplaceWorkaround) { + dateString = normaliseISODateTime(dateString); + } + + var resolved = formatter.parse(dateString); + + try { + // Resolve Zone + var zone = resolved.query(TemporalQueries.zoneId()); + zone = + zone != null + ? zone + : (resolved.isSupported(ChronoField.OFFSET_SECONDS) + ? ZoneOffset.ofTotalSeconds(resolved.get(ChronoField.OFFSET_SECONDS)) + : ZoneId.systemDefault()); + + // Instant Based + if (resolved.isSupported(INSTANT_SECONDS)) { + long epochSecond = resolved.getLong(INSTANT_SECONDS); + int nanoOfSecond = resolved.get(NANO_OF_SECOND); + return ZonedDateTime.ofInstant( + java.time.Instant.ofEpochSecond(epochSecond, nanoOfSecond), zone); + } + + // Local Based + var localDate = LocalDate.from(resolved); + var localTime = LocalTime.from(resolved); + return ZonedDateTime.of(localDate, localTime, zone); + } catch (DateTimeException e) { + throw new DateTimeException("Unable to parse Text '" + dateString + "' to Date_Time: "+e.getMessage(), e); + } catch (ArithmeticException e) { + throw new DateTimeException( + "Unable to parse Text '" + dateString + "' to Date_Time due to arithmetic error.", e); + } + } + + public LocalTime parseLocalTime(String text) { + if (needsISOTreplaceWorkaround) { + text = normaliseISODateTime(text); + } + + return LocalTime.parse(text, formatter); + } + + public String formatLocalDate(LocalDate date) { + return formatter.format(date); + } + + public String formatZonedDateTime(ZonedDateTime dateTime) { + return formatter.format(dateTime); + } + + public String formatLocalTime(LocalTime time) { + return formatter.format(time); } } diff --git a/std-bits/base/src/main/java/org/enso/base/time/FormatterKind.java b/std-bits/base/src/main/java/org/enso/base/time/FormatterKind.java new file mode 100644 index 000000000000..a6493e4dcf55 --- /dev/null +++ b/std-bits/base/src/main/java/org/enso/base/time/FormatterKind.java @@ -0,0 +1,8 @@ +package org.enso.base.time; + +public enum FormatterKind { + SIMPLE, + ISO_WEEK_DATE, + RAW_JAVA, + CONSTANT +} diff --git a/std-bits/table/src/main/java/org/enso/table/expressions/ExpressionVisitorImpl.java b/std-bits/table/src/main/java/org/enso/table/expressions/ExpressionVisitorImpl.java index ba4478d22eb7..f18d44a7ccf8 100644 --- a/std-bits/table/src/main/java/org/enso/table/expressions/ExpressionVisitorImpl.java +++ b/std-bits/table/src/main/java/org/enso/table/expressions/ExpressionVisitorImpl.java @@ -7,6 +7,7 @@ import org.antlr.v4.runtime.Recognizer; import org.enso.base.Time_Utils; +import org.enso.base.time.EnsoDateTimeFormatter; import org.enso.polyglot.common_utils.Core_Date_Utils; import org.graalvm.polyglot.Context; import org.graalvm.polyglot.PolyglotException; @@ -315,12 +316,14 @@ public Value visitTime(ExpressionParser.TimeContext ctx) { } } + private static final EnsoDateTimeFormatter dateTimeFormatter = Time_Utils.default_date_time_formatter(); + @Override public Value visitDatetime(ExpressionParser.DatetimeContext ctx) { var text = Time_Utils.normalise_iso_datetime(ctx.text.getText()); try { - var dateTime = Core_Date_Utils.parseZonedDateTime(text, Time_Utils.default_date_time_formatter()); + var dateTime = dateTimeFormatter.parseZonedDateTime(text); return Value.asValue(dateTime); } catch (DateTimeParseException ignored) { } From 66cfa8c992a66a2834d19adaa41e7611ec92d8ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Sat, 16 Sep 2023 23:32:36 +0200 Subject: [PATCH 19/76] fixing imports, compile errors, updating widgets WIP --- .../Base/0.0.0-dev/src/Data/Time/Date.enso | 4 +-- .../Data/Time/Date_Time_Format_Pattern.enso | 2 +- .../src/Data/Time/Date_Time_Formatter.enso | 26 +++++++++++-------- .../Base/0.0.0-dev/src/Errors/Time_Error.enso | 1 + .../Base/0.0.0-dev/src/Widget_Helpers.enso | 24 ++++++++++++----- 5 files changed, 36 insertions(+), 21 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date.enso index 61d163db560a..570aa29b9b8a 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date.enso @@ -187,7 +187,7 @@ type Date example_parse_err = date = Date.parse "1999-1-1" "yyyy-MM-dd" date.catch Time_Error (_->Date.new 2000 1 1) - @pattern make_date_format_selector + @format make_date_format_selector parse : Text -> Date_Time_Formatter -> Date ! Time_Error parse text:Text format:Date_Time_Formatter=Date_Time_Formatter.iso_date = format.parse_date text @@ -786,7 +786,7 @@ type Date Format "2020-06-21" with French locale as "21. juin 2020" example_format = Date.new 2020 6 21 . format (Date_Time_Formatter.from "d. MMMM yyyy" (Locale.new "fr")) - @pattern (value-> make_date_format_selector value) + @format (value-> make_date_format_selector value) format : Date_Time_Formatter -> Text format self format:Date_Time_Formatter=Date_Time_Formatter.iso_date = format.format_date self diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso index 0439b15a785c..47d8458d0a4d 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso @@ -185,7 +185,7 @@ type Date_Time_Format_Pattern Date_Time_Format_Pattern.ISO_Week_Date pattern locale -> Tokenizer.tokenize pattern |> Parser.parse_iso_week_year_pattern |> As_Java_Formatter_Interpreter.interpret locale Date_Time_Format_Pattern.Default_Enso_Zoned_Date_Time -> - Time_Utils.default_date_time_formatter + 0 #Time_Utils.default_date_time_formatter Date_Time_Format_Pattern.ISO_Zoned_Date_Time -> DateTimeFormatter.ISO_ZONED_DATE_TIME Date_Time_Format_Pattern.Java pattern -> case pattern of diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso index 95a73038a24d..f533fe63318b 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso @@ -1,7 +1,11 @@ import project.Data.Locale.Locale import project.Data.Text.Text +import project.Data.Time.Date.Date +import project.Data.Time.Date_Time.Date_Time +import project.Data.Time.Time_Of_Day.Time_Of_Day import project.Error.Error import project.Errors.Time_Error.Time_Error +import project.Nothing.Nothing import project.Panic.Panic from project.Data.Boolean import Boolean, False, True @@ -11,6 +15,7 @@ import project.Internal.Time.Format.As_Java_Formatter_Interpreter polyglot java import java.lang.Exception as JException polyglot java import java.time.format.DateTimeFormatter +polyglot java import org.enso.base.Time_Utils polyglot java import org.enso.base.time.EnsoDateTimeFormatter polyglot java import org.enso.base.time.FormatterKind @@ -162,14 +167,13 @@ type Date_Time_Formatter See the Java documentation for explanation of the pattern format: https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/format/DateTimeFormatter.html#patterns - from_java (pattern:Text|DateTimeFormatter) = - java_formatter = case pattern of - java_formatter : DateTimeFormatter -> - Date_Time_Formatter.Value (EnsoDateTimeFormatter.new java_formatter False Nothing FormatterKind.RAW_JAVA) - _ : Text -> - # TODO setting Locale too! - java_formatter = DateTimeFormatter.ofPattern pattern - Date_Time_Formatter.Value (EnsoDateTimeFormatter.new java_formatter False pattern FormatterKind.RAW_JAVA) + from_java (pattern:Text|DateTimeFormatter) = case pattern of + java_formatter : DateTimeFormatter -> + Date_Time_Formatter.Value (EnsoDateTimeFormatter.new java_formatter False Nothing FormatterKind.RAW_JAVA) + _ : Text -> + # TODO setting Locale too! + java_formatter = DateTimeFormatter.ofPattern pattern + Date_Time_Formatter.Value (EnsoDateTimeFormatter.new java_formatter False pattern FormatterKind.RAW_JAVA) # TODO Locale for constants?? @@ -207,13 +211,13 @@ type Date_Time_Formatter For example, it may parse date of the form `2011-12-03`. iso_date = - Date_Time_Formatter.Value (EnsoDateTimeFormatter.new DateTimeFormatter.ISO_DATE True "iso_date" FormatterKind.CONSTANT) + Date_Time_Formatter.Value (EnsoDateTimeFormatter.new DateTimeFormatter.ISO_DATE False "iso_date" FormatterKind.CONSTANT) ## The ISO 8601 format for time. For example, it may parse time of the form `10:15:30`. iso_time = - Date_Time_Formatter.Value (EnsoDateTimeFormatter.new DateTimeFormatter.ISO_TIME True "iso_time" FormatterKind.CONSTANT) + Date_Time_Formatter.Value (EnsoDateTimeFormatter.new DateTimeFormatter.ISO_TIME False "iso_time" FormatterKind.CONSTANT) ## Returns a text representation of this formatter. to_text : Text @@ -258,7 +262,7 @@ type Date_Time_Formatter self.underlying.formatZonedDateTime date_time ## PRIVATE - format_time self (time:Time) = self.handle_java_errors <| + format_time self (time:Time_Of_Day) = self.handle_java_errors <| self.underlying.formatLocalTime time ## PRIVATE diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Errors/Time_Error.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Errors/Time_Error.enso index 79b0dec9526f..c37620bd2876 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Errors/Time_Error.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Errors/Time_Error.enso @@ -1,4 +1,5 @@ import project.Data.Text.Text +import project.Nothing.Nothing type Time_Error ## PRIVATE diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Widget_Helpers.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Widget_Helpers.enso index 8c4cfb284871..55438a27d8cd 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Widget_Helpers.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Widget_Helpers.enso @@ -1,6 +1,7 @@ import project.Data.Time.Date.Date import project.Data.Time.Date_Time.Date_Time import project.Data.Time.Time_Of_Day.Time_Of_Day +import project.Data.Time.Date_Time_Formatter.Date_Time_Formatter import project.Metadata.Widget from project.Metadata import make_single_choice @@ -21,19 +22,28 @@ make_delimiter_selector = Creates a Single_Choice Widget for parsing dates. make_date_format_selector : Date -> Widget make_date_format_selector (date:Date=(Date.new 2012 3 14)) = - iso_format = ['ISO-Format (e.g. ' + date.to_text + ')', '""'] - formats = ['d/M/yyyy', 'dd/MM/yyyy', 'd-MMM-yy', 'd MMMM yyyy', 'M/d/yyyy', 'MM/dd/yyyy', 'MMMM d, yyyy'].map f-> [f + " (e.g. " + date.format f + ")", f.pretty] + iso_format = ['ISO-Format (e.g. ' + date.to_text + ')', 'Date_Time_Formatter.iso_date'] + formats = ['d/M/yyyy', 'dd/MM/yyyy', 'd-MMM-yy', 'd MMMM yyyy', 'M/d/yyyy', 'MM/dd/yyyy', 'MMMM d, yyyy', 'yyyy-MM'].map f-> + [f + " (e.g. " + date.format f + ")", f.pretty] + week_date_formats = ['YYYY-ww-d', 'YYYY-ww', 'ddd, YYYY-ww'].map f-> + ["ISO Week-based Date: " + f + " (e.g. " + date.format f + ")", "Date_Time_Formatter.from_iso_week_date_pattern " + f.pretty] - make_single_choice ([iso_format] + formats) + make_single_choice ([iso_format] + formats + week_date_formats) ## PRIVATE Creates a Single_Choice Widget for parsing date times. make_date_time_format_selector : Date_Time -> Widget make_date_time_format_selector (date_time:Date_Time=(Date_Time.new 2012 3 14 15 9 26 123)) = - enso_format = ['Default (e.g. ' + date_time.to_text + ')', '""'] - iso_format = ['ISO-Format (e.g. ' + (date_time.format "ISO_ZONED_DATE_TIME") + ')', '"ISO_ZONED_DATE_TIME"'] - iso_local = ['ISO-Local (e.g. ' + (date_time.format "ISO_LOCAL_DATE_TIME") + ')', '"ISO_LOCAL_DATE_TIME"'] - formats = ['yyyy-MM-dd HH:mm:ss.S', 'yyyy-MM-dd HH:mm:ss.S VV', 'd/M/yyyy h:mm a', 'dd/MM/yyyy HH:mm:ss', 'd-MMM-yy HH:mm:ss', 'd-MMM-yy h:mm:ss a', 'd MMMM yyyy h:mm a', 'M/d/yyyy h:mm:ss a', 'MM/dd/yyyy HH:mm:ss'] + enso_format = + format = Date_Time_Formatter.default_enso_zoned_date_time + ['Default (e.g. ' + date_time.format format + ')', 'Date_Time_Formatter.default_enso_zoned_date_time'] + iso_format = + format = Date_Time_Formatter.iso_zoned_date_time + ['ISO-Format (e.g. ' + (date_time.format format) + ')', 'Date_Time_Formatter.iso_zoned_date_time'] + iso_local = + format = Date_Time_Formatter.iso_local_date_time + ['ISO-Local (e.g. ' + (date_time.format format) + ')', 'Date_Time_Formatter.iso_local_date_time'] + formats = ['yyyy-MM-dd HH:mm:ss.f', 'yyyy-MM-dd HH:mm:ss.f TT', 'd/M/yyyy h:mm a', 'dd/MM/yyyy HH:mm:ss', 'd-MMM-yy HH:mm:ss', 'd-MMM-yy h:mm:ss a', 'd MMMM yyyy h:mm a', 'M/d/yyyy h:mm:ss a', 'MM/dd/yyyy HH:mm:ss'] mapped_formats = formats.map f-> [f + " (e.g. " + date_time.format f + ")", f.pretty] make_single_choice ([enso_format, iso_format, iso_local] + mapped_formats) From d78624cebcbd5694dfb95eabcd4150cbe481322d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Sat, 16 Sep 2023 23:56:36 +0200 Subject: [PATCH 20/76] checkpoint: fixes, widgets --- .../0.0.0-dev/src/Data/Time/Date_Time.enso | 39 ++++++------------- .../Base/0.0.0-dev/src/Widget_Helpers.enso | 1 + 2 files changed, 13 insertions(+), 27 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso index 57a0cc7a1abc..0ef8f0cf47ef 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso @@ -7,6 +7,7 @@ import project.Data.Ordering.Ordering import project.Data.Text.Text import project.Data.Time.Date.Date import project.Data.Time.Date_Period.Date_Period +import project.Data.Time.Date_Time_Formatter.Date_Time_Formatter import project.Data.Time.Day_Of_Week.Day_Of_Week import project.Data.Time.Day_Of_Week_From import project.Data.Time.Duration.Duration @@ -24,6 +25,7 @@ import project.Panic.Panic import project.Warning.Warning from project.Data.Boolean import Boolean, False, True from project.Data.Text.Extensions import all +from project.Data.Time.Conversions import all from project.Widget_Helpers import make_date_time_format_selector polyglot java import java.lang.ArithmeticException @@ -158,14 +160,10 @@ type Date_Time 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: TODO ? Pattern Syntax - A custom pattern string consists of one or more custom date and time - format specifiers. For example, "d MMM yyyy" will format "2011-12-03" - as "3 Dec 2011". See https://docs.oracle.com/en/java/javase/18/docs/api/java.base/java/time/format/DateTimeFormatter.html - for a complete format specification. + TODO ? Default Date_Time Format The text must represent a valid date-time as defined by the ISO-8601 @@ -234,20 +232,10 @@ type Date_Time example_parse = Date_Time.parse "06 of May 2020 at 04:30AM" "dd 'of' MMMM yyyy 'at' hh:mma" - @pattern make_date_time_format_selector - @locale Locale.default_widget - parse : Text -> Text -> Locale -> Date_Time ! Time_Error - parse text:Text pattern:Text="" locale:Locale=Locale.default = - result = Panic.recover Any <| - formatter = if pattern.is_empty then Time_Utils.default_date_time_formatter else - Time_Utils.make_formatter pattern locale.java_locale - - needs_normalised = if pattern.is_empty then True else Time_Utils.is_iso_datetime_based pattern - normalised = if needs_normalised then Time_Utils.normalise_iso_datetime text.trim else text.trim - Time_Utils.parse_date_time normalised formatter - result . map_error <| case _ of - err : JException -> Time_Error.Error err.getMessage - ex -> ex + @format make_date_time_format_selector + parse : Text -> Date_Time_Formatter -> Date_Time ! Time_Error + parse text:Text format:Date_Time_Formatter=Date_Time_Formatter.default_enso_zoned_date_time = + format.parse_date_time text ## GROUP Metadata Get the year portion of the time. @@ -781,10 +769,7 @@ type Date_Time example_format = Date_Time.parse "2020-06-21T16:41:13+03:00" . format "d. MMMM yyyy" (Locale.new "fr") - @pattern (value-> make_date_time_format_selector value) - @locale Locale.default_widget - format : Text -> Locale -> Text - format self pattern:Text locale:Locale=Locale.default = - formatter = if pattern.is_empty then Time_Utils.default_output_date_time_formatter else - Time_Utils.make_formatter pattern locale.java_locale - Time_Utils.date_time_format self formatter + @format (value-> make_date_time_format_selector value) + format : Date_Time_Formatter -> Text + format self format:Date_Time_Formatter = + format.format_date_time self diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Widget_Helpers.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Widget_Helpers.enso index 55438a27d8cd..e1fa0987602a 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Widget_Helpers.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Widget_Helpers.enso @@ -2,6 +2,7 @@ import project.Data.Time.Date.Date import project.Data.Time.Date_Time.Date_Time import project.Data.Time.Time_Of_Day.Time_Of_Day import project.Data.Time.Date_Time_Formatter.Date_Time_Formatter +from project.Data.Time.Conversions import all import project.Metadata.Widget from project.Metadata import make_single_choice From 85ac640b974187c98818bf1ddf819b3192670278 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Mon, 18 Sep 2023 11:04:26 +0200 Subject: [PATCH 21/76] checkpoint - widgets show up, but some weird issue with truncating methods --- .../0.0.0-dev/src/Data/Time/Conversions.enso | 5 +++++ .../Data/Time/Date_Time_Format_Pattern.enso | 2 +- .../src/Data/Time/Date_Time_Formatter.enso | 8 +++++--- .../lib/Standard/Base/0.0.0-dev/src/Main.enso | 2 ++ .../Base/0.0.0-dev/src/Widget_Helpers.enso | 19 +++++++++++++------ 5 files changed, 26 insertions(+), 10 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Conversions.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Conversions.enso index 6b00bebd1ddb..eb02d39ad18a 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Conversions.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Conversions.enso @@ -1,7 +1,12 @@ import project.Data.Locale.Locale import project.Data.Text.Text import project.Data.Time.Date_Time_Formatter.Date_Time_Formatter +import project.Data.Time.Date_Time_Format_Pattern.Date_Time_Format_Pattern ## PRIVATE Date_Time_Formatter.from (that:Text) (locale:Locale = Locale.default) = Date_Time_Formatter.from_simple_pattern that locale + +Date_Time_Formatter.from (that:Date_Time_Format_Pattern) = case that of + Date_Time_Format_Pattern.ISO_Date -> Date_Time_Formatter.iso_date + _ -> Date_Time_Formatter.from_simple_pattern (Text.from "TODO") diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso index 47d8458d0a4d..90ba46334fd3 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso @@ -160,7 +160,7 @@ type Date_Time_Format_Pattern ## The ISO 8601 format for date. For example, it may parse date of the form `2011-12-03`. - ISO_Local_Date + ISO_Date ## The ISO 8601 format for time. diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso index f533fe63318b..36f1d41b853b 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso @@ -19,9 +19,6 @@ polyglot java import org.enso.base.Time_Utils polyglot java import org.enso.base.time.EnsoDateTimeFormatter polyglot java import org.enso.base.time.FormatterKind -type Date_Time_Formatter - ## PRIVATE - Value (underlying : EnsoDateTimeFormatter) ## TODO 1. move to this, check stuff at construction @@ -30,6 +27,11 @@ type Date_Time_Formatter 4. set up new dropdowns 5. add compatibility check (can do Date, can do Time) + +type Date_Time_Formatter + ## PRIVATE + Value (underlying : EnsoDateTimeFormatter) + ## Creates a formatter from a simple date-time format pattern. Every letter in the pattern is interpreted as a pattern character as diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso index 7fe5a7602790..7d2007b3bd8b 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso @@ -41,6 +41,7 @@ import project.Data.Time.Date_Period.Date_Period import project.Data.Time.Date_Range.Date_Range import project.Data.Time.Date_Time.Date_Time import project.Data.Time.Date_Time_Formatter.Date_Time_Formatter +import project.Data.Time.Date_Time_Format_Pattern.Date_Time_Format_Pattern import project.Data.Time.Day_Of_Week.Day_Of_Week import project.Data.Time.Day_Of_Week_From import project.Data.Time.Duration.Duration @@ -131,6 +132,7 @@ export project.Data.Time.Date_Period.Date_Period export project.Data.Time.Date_Range.Date_Range export project.Data.Time.Date_Time.Date_Time export project.Data.Time.Date_Time_Formatter.Date_Time_Formatter +export project.Data.Time.Date_Time_Format_Pattern.Date_Time_Format_Pattern export project.Data.Time.Day_Of_Week.Day_Of_Week export project.Data.Time.Day_Of_Week_From export project.Data.Time.Duration.Duration diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Widget_Helpers.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Widget_Helpers.enso index e1fa0987602a..4cefc06e0713 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Widget_Helpers.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Widget_Helpers.enso @@ -2,6 +2,7 @@ import project.Data.Time.Date.Date import project.Data.Time.Date_Time.Date_Time import project.Data.Time.Time_Of_Day.Time_Of_Day import project.Data.Time.Date_Time_Formatter.Date_Time_Formatter +import project.Meta from project.Data.Time.Conversions import all import project.Metadata.Widget @@ -23,11 +24,13 @@ make_delimiter_selector = Creates a Single_Choice Widget for parsing dates. make_date_format_selector : Date -> Widget make_date_format_selector (date:Date=(Date.new 2012 3 14)) = - iso_format = ['ISO-Format (e.g. ' + date.to_text + ')', 'Date_Time_Formatter.iso_date'] + fqn = "Date_Time_Formatter" + iso_format = ['ISO-Format (e.g. ' + date.to_text + ')', "Date_Time_Format_Pattern.ISO_Date"] formats = ['d/M/yyyy', 'dd/MM/yyyy', 'd-MMM-yy', 'd MMMM yyyy', 'M/d/yyyy', 'MM/dd/yyyy', 'MMMM d, yyyy', 'yyyy-MM'].map f-> [f + " (e.g. " + date.format f + ")", f.pretty] week_date_formats = ['YYYY-ww-d', 'YYYY-ww', 'ddd, YYYY-ww'].map f-> - ["ISO Week-based Date: " + f + " (e.g. " + date.format f + ")", "Date_Time_Formatter.from_iso_week_date_pattern " + f.pretty] + format = Date_Time_Formatter.from_iso_week_date_pattern f + ["ISO Week-based Date: " + f + " (e.g. " + date.format format + ")", "("+fqn+".from_iso_week_date_pattern " + f.pretty + ")"] make_single_choice ([iso_format] + formats + week_date_formats) @@ -35,19 +38,23 @@ make_date_format_selector (date:Date=(Date.new 2012 3 14)) = Creates a Single_Choice Widget for parsing date times. make_date_time_format_selector : Date_Time -> Widget make_date_time_format_selector (date_time:Date_Time=(Date_Time.new 2012 3 14 15 9 26 123)) = + fqn = "Date_Time_Formatter" enso_format = format = Date_Time_Formatter.default_enso_zoned_date_time - ['Default (e.g. ' + date_time.format format + ')', 'Date_Time_Formatter.default_enso_zoned_date_time'] + ['Default (e.g. ' + date_time.format format + ')', "("+fqn+".default_enso_zoned_date_time)"] iso_format = format = Date_Time_Formatter.iso_zoned_date_time - ['ISO-Format (e.g. ' + (date_time.format format) + ')', 'Date_Time_Formatter.iso_zoned_date_time'] + ['ISO-Format (e.g. ' + (date_time.format format) + ')', "("+fqn+".iso_zoned_date_time)"] iso_local = format = Date_Time_Formatter.iso_local_date_time - ['ISO-Local (e.g. ' + (date_time.format format) + ')', 'Date_Time_Formatter.iso_local_date_time'] + ['ISO-Local (e.g. ' + (date_time.format format) + ')', "("+fqn+".iso_local_date_time)"] formats = ['yyyy-MM-dd HH:mm:ss.f', 'yyyy-MM-dd HH:mm:ss.f TT', 'd/M/yyyy h:mm a', 'dd/MM/yyyy HH:mm:ss', 'd-MMM-yy HH:mm:ss', 'd-MMM-yy h:mm:ss a', 'd MMMM yyyy h:mm a', 'M/d/yyyy h:mm:ss a', 'MM/dd/yyyy HH:mm:ss'] mapped_formats = formats.map f-> [f + " (e.g. " + date_time.format f + ")", f.pretty] + week_date_formats = ['YYYY-ww-d HH:mm:ss.f'].map f-> + format = Date_Time_Formatter.from_iso_week_date_pattern f + ["ISO Week-based Date-Time: " + f + " (e.g. " + date_time.format format + ")", "("+fqn+".from_iso_week_date_pattern " + f.pretty + ")"] - make_single_choice ([enso_format, iso_format, iso_local] + mapped_formats) + make_single_choice ([enso_format, iso_format, iso_local] + mapped_formats + week_date_formats) ## PRIVATE Creates a Single_Choice Widget for parsing times. From a0737d7b107b65570f823e202fbfcd13a0b80a2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Mon, 18 Sep 2023 13:33:09 +0200 Subject: [PATCH 22/76] Simple temporary workaround for https://github.com/enso-org/enso/issues/7824 (to remove just update Widget_Helpers.enso and remove Date_Time_Format_Constants.enso) --- .../0.0.0-dev/src/Data/Time/Conversions.enso | 5 - .../Data/Time/Date_Time_Format_Constants.enso | 55 +++++ .../Data/Time/Date_Time_Format_Pattern.enso | 193 ------------------ .../src/Data/Time/Date_Time_Formatter.enso | 2 +- .../lib/Standard/Base/0.0.0-dev/src/Main.enso | 4 +- .../Base/0.0.0-dev/src/Widget_Helpers.enso | 13 +- 6 files changed, 65 insertions(+), 207 deletions(-) create mode 100644 distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Constants.enso delete mode 100644 distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Conversions.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Conversions.enso index eb02d39ad18a..6b00bebd1ddb 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Conversions.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Conversions.enso @@ -1,12 +1,7 @@ import project.Data.Locale.Locale import project.Data.Text.Text import project.Data.Time.Date_Time_Formatter.Date_Time_Formatter -import project.Data.Time.Date_Time_Format_Pattern.Date_Time_Format_Pattern ## PRIVATE Date_Time_Formatter.from (that:Text) (locale:Locale = Locale.default) = Date_Time_Formatter.from_simple_pattern that locale - -Date_Time_Formatter.from (that:Date_Time_Format_Pattern) = case that of - Date_Time_Format_Pattern.ISO_Date -> Date_Time_Formatter.iso_date - _ -> Date_Time_Formatter.from_simple_pattern (Text.from "TODO") diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Constants.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Constants.enso new file mode 100644 index 000000000000..d6eb538e2412 --- /dev/null +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Constants.enso @@ -0,0 +1,55 @@ +import project.Data.Locale.Locale +import project.Data.Text.Text + +import project.Data.Time.Date_Time_Formatter.Date_Time_Formatter + +## PRIVATE + Workaround for https://github.com/enso-org/enso/issues/7824 + Once that issue is fixed, this should be removed. +type Date_Time_Format_Constants + ## The default format for date-time used in Enso. + It acts as `ISO_Zoned_Date_Time` but both offset and timezone are optional. + + For example, it may parse date of the form `2011-12-03 10:15:30+01:00[Europe/Paris]`, + as well as `2011-12-03T10:15:30` assuming the default timezone. + Default_Enso_Zoned_Date_Time + + ## The ISO 8601 format for date-time with offset and timezone. + The date and time parts may be separated by a single space or a `T`. + + For example, it may parse date of the form `2011-12-03 10:15:30+01:00[Europe/Paris]`. + ISO_Zoned_Date_Time + + ## The ISO 8601 format for date-time with offset. + The date and time parts may be separated by a single space or a `T`. + + For example, it may parse date of the form `2011-12-03 10:15:30+01:00`. + ISO_Offset_Date_Time + + ## The ISO 8601 format for date-time without a timezone. + The date and time parts may be separated by a single space or a `T`. + + For example, it may parse date of the form `2011-12-03 10:15:30`. The + timezone will be set to `Time_Zone.system`. + ISO_Local_Date_Time + + ## The ISO 8601 format for date. + + For example, it may parse date of the form `2011-12-03`. + ISO_Date + + ## The ISO 8601 format for time. + + For example, it may parse time of the form `10:15:30`. + ISO_Time + +## PRIVATE + Workaround for https://github.com/enso-org/enso/issues/7824 + Once that issue is fixed, this should be removed. +Date_Time_Formatter.from (that:Date_Time_Format_Constants) = case that of + Date_Time_Format_Constants.ISO_Date -> Date_Time_Formatter.iso_date + Date_Time_Format_Constants.ISO_Time -> Date_Time_Formatter.iso_time + Date_Time_Format_Constants.ISO_Local_Date_Time -> Date_Time_Formatter.iso_local_date_time + Date_Time_Format_Constants.ISO_Offset_Date_Time -> Date_Time_Formatter.iso_offset_date_time + Date_Time_Format_Constants.ISO_Zoned_Date_Time -> Date_Time_Formatter.iso_zoned_date_time + Date_Time_Format_Constants.Default_Enso_Zoned_Date_Time -> Date_Time_Formatter.default_enso_zoned_date_time diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso deleted file mode 100644 index 90ba46334fd3..000000000000 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Pattern.enso +++ /dev/null @@ -1,193 +0,0 @@ -import project.Data.Locale.Locale -import project.Data.Text.Text - -import project.Internal.Time.Format.Tokenizer.Tokenizer -import project.Internal.Time.Format.Parser -import project.Internal.Time.Format.As_Java_Formatter_Interpreter - -polyglot java import java.time.format.DateTimeFormatter - -type Date_Time_Format_Pattern - ## A simple date-time format pattern. - - Every letter in the pattern is interpreted as a pattern character as - described in the table below. Any character that is not a letter in the - pattern is treated as a literal character. If a sequence of letters needs - to be put in as a literal, it can be escaped using single quotes. Use two - single quotes in a row to represent a single quote in the result. As - explained below, curly braces can have special meaning (see 'yy'); to - enter a literal curly brace, put it inside a quoted literal. - - Pattern characters are interpreted case insensitively, with the exception - of `M/m' and 'H/h'. - - Date pattern characters: - - y: Year. The number of pattern letters determines the minimum number of - digits. - - y: The year using any number of digits. - - yy: The year, using at most two digits. The default range is - 1950-2049, but this can be changed by including the end year in - braces e.g. `yy{2099}`. - - yyyy: The year, using exactly four digits. - - M: Month of year. The number of pattern letters determines the format: - - M: Any number (1-12). - - MM: Month number with zero padding required (01-12). - - MMM: Short name of the month (Jan-Dec). - - MMMM: Full name of the month (January-December). - The month names depend on the selected locale. - - d: Day. The number of pattern letters determines the format: - - d: Any number (1-31). - - dd: Day number with zero padding required (01-31). - - ddd: Short name of the day of week (Mon-Sun). - - dddd: Full name of the day of week (Monday-Sunday). - The weekday names depend on the selected locale. - Both day of week and day of month may be included in a single pattern - - in such case the day of week is used as a sanity check. - - Q: Quarter of year. - If only year and quarter are provided in the pattern, when parsing a - date, the result will be the first day of that quarter. - - Time pattern characters: - - H: 24h hour of day (0-23). - - h: 12h hour of day (0-12). The `a` pattern is needed to disambiguate - between AM and PM. - - m: Minute of hour. - - s: Second of minute. - - f: Fractional part of the second. The number of pattern letters - determines the number of digits. If one letter is used, any number of - digits will be accepted. - - a: AM/PM marker. - - Time zone pattern characters: - - T: If repeated 3 or less times - Time zone ID (e.g. Europe/Warsaw, Z, - -08:30), otherwise - Time zone name (e.g. Central European Time, CET). - - Z: Zone offset (e.g. +0000, -0830, +08:30:15). - - Some parts, like fractions of a second may not be required. The square - brackets `[]` can be used to surround such optional sections. - - > Example - Parsing date/time values - - Date_Time.parse "yyyy-MM-dd'T'HH:mm:ss.fZ" "2021-10-12T12:34:56.789+0200" == (Date_Time.new 2021 10 12 12 34 56 milliseconds=789 (Time_Zone.new hours=2)) - Date.parse "ddd, d MMM yyyy" "Tue, 12 Oct 2021" == (Date.new 2021 10 12) - Date.parse "dddd, dd MMMM ''yy" "Thursday, 1 October '98" == (Date.new 1998 10 01) - Date_Time.parse "d/M/Y h:mm a" "12/10/2021 5:34 PM" == (Date_Time.new 2021 10 12 17 34 00) - - > Example - Omitting the day will yield the first day of the month. - - Date.parse "yyyy-MM" "2021-10" == (Date.new 2021 10 01) - - > Example - Omitting the year will yield the current year. - - Date.parse "MM-dd" "10-12" == (Date.new (Date.today.year) 10 12) - - > Example - Parsing a two-digit year with a custom base year. - - Date.parse "dd MMMM ''yy{2099}" "1 November '95" == (Date.new 2095 11 01) - Simple pattern:Text locale:Locale=Locale.default - - ## A pattern for formats related to the ISO 8601 leap week calendar. - - The ISO 8601 leap week calendar is a variation of the ISO 8601 calendar - that defines a leap week as the week that contains the 29th of February. - This calendar is used by some European and Middle Eastern countries. - - The pattern is a sequence of letters and symbols that are interpreted as - follows: - - Y: The week based year. - - In case the year is parsed in two digit mode (`YY`), the default - range is 1950-2049, but this can be changed by including the end year - in braces e.g. `YY{2099}` - - w: Week of year. - - d: Day of week. - - d: Numeric day of week (1-7). 1 is Monday. - - dd: Numeric day of week with zero padding (01-07). - - ddd: Short name of the day of week (Mon-Sun). - - dddd: Full name of the day of week (Monday-Sunday). - The weekday names depend on the selected locale. - - Moreover, all time and timezone pattern characters like in `Simple` case - are supported too - in case you need to parse a date time value with the - date part in ISO week date format. - - The same as in the `Simple` pattern, the single quotes can be used to - escape letter literals and square brackets can be used to indicate - optional sections. - - > Example - Parsing a date in the ISO week date format - - Date.parse (ISO_Week_Date "YYYY-'W'WW-d" "1976-W53-6") == (Date.new 1977 01 01) - Date.parse (ISO_Week_Date "YYYY-'W'WW, eee" "1978-W01, Mon") == (Date.new 1978 01 02) - Date_Time.parse (ISO_Week_Date "YYYY-'W'WW-d HH:mm:ss" "1978-W01-4 12:34:56") == (Date_Time.new 1978 01 05 12 34 56) - - > Example - Omitting the day of the week will result in the first day of that week. - - Date.parse (ISO_Week_Date "YYYY-'W'WW" "1978-W01") == (Date.new 1978 01 02) - ISO_Week_Date pattern:Text locale:Locale=Locale.default - - ## The default format for date-time used in Enso. - It acts as `ISO_Zoned_Date_Time` but both offset and timezone are optional. - - For example, it may parse date of the form `2011-12-03 10:15:30+01:00[Europe/Paris]`, - as well as `2011-12-03T10:15:30` assuming the default timezone. - Default_Enso_Zoned_Date_Time - - ## The ISO 8601 format for date-time with offset and timezone. - The date and time parts may be separated by a single space or a `T`. - - For example, it may parse date of the form `2011-12-03 10:15:30+01:00[Europe/Paris]`. - ISO_Zoned_Date_Time - - ## The ISO 8601 format for date-time with offset. - The date and time parts may be separated by a single space or a `T`. - - For example, it may parse date of the form `2011-12-03 10:15:30+01:00`. - ISO_Offset_Date_Time - - ## The ISO 8601 format for date-time without a timezone. - The date and time parts may be separated by a single space or a `T`. - - For example, it may parse date of the form `2011-12-03 10:15:30`. The - timezone will be set to `Time_Zone.system`. - ISO_Local_Date_Time - - ## The ISO 8601 format for date. - - For example, it may parse date of the form `2011-12-03`. - ISO_Date - - ## The ISO 8601 format for time. - - For example, it may parse time of the form `10:15:30`. - ISO_Local_Time - - ## ADVANCED - A pattern built using the Java pattern syntax or a Java DateTimeFormatter - instance. - - This method parses the pattern using the Java `DateTimeFormatter.ofPattern` - method to build the date time format. See the Java documentation for - explanation of the pattern format: - https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/format/DateTimeFormatter.html#patterns - Java (pattern : Text | DateTimeFormatter) - - ## PRIVATE - build_java_formatter : DateTimeFormatter - build_java_formatter self = case self of - Date_Time_Format_Pattern.Simple pattern locale -> - Tokenizer.tokenize pattern |> Parser.parse_simple_date_pattern |> As_Java_Formatter_Interpreter.interpret locale - Date_Time_Format_Pattern.ISO_Week_Date pattern locale -> - Tokenizer.tokenize pattern |> Parser.parse_iso_week_year_pattern |> As_Java_Formatter_Interpreter.interpret locale - Date_Time_Format_Pattern.Default_Enso_Zoned_Date_Time -> - 0 #Time_Utils.default_date_time_formatter - Date_Time_Format_Pattern.ISO_Zoned_Date_Time -> - DateTimeFormatter.ISO_ZONED_DATE_TIME - Date_Time_Format_Pattern.Java pattern -> case pattern of - _ : Text -> DateTimeFormatter.ofPattern pattern - formatter : DateTimeFormatter -> formatter diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso index 36f1d41b853b..853ed2f7ea1f 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso @@ -270,7 +270,7 @@ type Date_Time_Formatter ## PRIVATE type Date_Time_Format_Parse_Error ## PRIVATE - Indicates an error during parsing of a `Date_Time_Format_Pattern`. + Indicates an error during parsing of a `Date_Time_Format_Constants`. Error message ## PRIVATE diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso index 7d2007b3bd8b..4599ca5cdaff 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso @@ -41,7 +41,6 @@ import project.Data.Time.Date_Period.Date_Period import project.Data.Time.Date_Range.Date_Range import project.Data.Time.Date_Time.Date_Time import project.Data.Time.Date_Time_Formatter.Date_Time_Formatter -import project.Data.Time.Date_Time_Format_Pattern.Date_Time_Format_Pattern import project.Data.Time.Day_Of_Week.Day_Of_Week import project.Data.Time.Day_Of_Week_From import project.Data.Time.Duration.Duration @@ -88,6 +87,7 @@ from project.Data.Json.Extensions import all from project.Data.Range.Extensions import all from project.Data.Text.Extensions import all from project.Data.Time.Conversions import all +from project.Data.Time.Date_Time_Format_Constants import all from project.Errors.Problem_Behavior.Problem_Behavior import all from project.Meta.Enso_Project import enso_project from project.Network.Extensions import all @@ -132,7 +132,6 @@ export project.Data.Time.Date_Period.Date_Period export project.Data.Time.Date_Range.Date_Range export project.Data.Time.Date_Time.Date_Time export project.Data.Time.Date_Time_Formatter.Date_Time_Formatter -export project.Data.Time.Date_Time_Format_Pattern.Date_Time_Format_Pattern export project.Data.Time.Day_Of_Week.Day_Of_Week export project.Data.Time.Day_Of_Week_From export project.Data.Time.Duration.Duration @@ -181,6 +180,7 @@ from project.Data.Range.Extensions export all from project.Data.Statistics export all hiding to_moment_statistic, wrap_java_call, calculate_correlation_statistics, calculate_spearman_rank, calculate_correlation_statistics_matrix, compute_fold, empty_value, is_valid from project.Data.Text.Extensions export all from project.Data.Time.Conversions export all +from project.Data.Time.Date_Time_Format_Constants export all from project.Errors.Problem_Behavior.Problem_Behavior export all from project.Function export all from project.Meta.Enso_Project export enso_project diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Widget_Helpers.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Widget_Helpers.enso index 4cefc06e0713..501c6fe97106 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Widget_Helpers.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Widget_Helpers.enso @@ -25,7 +25,7 @@ make_delimiter_selector = make_date_format_selector : Date -> Widget make_date_format_selector (date:Date=(Date.new 2012 3 14)) = fqn = "Date_Time_Formatter" - iso_format = ['ISO-Format (e.g. ' + date.to_text + ')', "Date_Time_Format_Pattern.ISO_Date"] + iso_format = ['ISO-Format (e.g. ' + date.to_text + ')', "Date_Time_Format_Constants.ISO_Date"] formats = ['d/M/yyyy', 'dd/MM/yyyy', 'd-MMM-yy', 'd MMMM yyyy', 'M/d/yyyy', 'MM/dd/yyyy', 'MMMM d, yyyy', 'yyyy-MM'].map f-> [f + " (e.g. " + date.format f + ")", f.pretty] week_date_formats = ['YYYY-ww-d', 'YYYY-ww', 'ddd, YYYY-ww'].map f-> @@ -41,13 +41,13 @@ make_date_time_format_selector (date_time:Date_Time=(Date_Time.new 2012 3 14 15 fqn = "Date_Time_Formatter" enso_format = format = Date_Time_Formatter.default_enso_zoned_date_time - ['Default (e.g. ' + date_time.format format + ')', "("+fqn+".default_enso_zoned_date_time)"] + ['Default (e.g. ' + date_time.format format + ')', "Date_Time_Format_Constants.Default_Enso_Zoned_Date_Time"] iso_format = format = Date_Time_Formatter.iso_zoned_date_time - ['ISO-Format (e.g. ' + (date_time.format format) + ')', "("+fqn+".iso_zoned_date_time)"] + ['ISO-Format (e.g. ' + (date_time.format format) + ')', "Date_Time_Format_Constants.ISO_Zoned_Date_Time"] iso_local = format = Date_Time_Formatter.iso_local_date_time - ['ISO-Local (e.g. ' + (date_time.format format) + ')', "("+fqn+".iso_local_date_time)"] + ['ISO-Local (e.g. ' + (date_time.format format) + ')', "Date_Time_Format_Constants.ISO_Local_Date_Time"] formats = ['yyyy-MM-dd HH:mm:ss.f', 'yyyy-MM-dd HH:mm:ss.f TT', 'd/M/yyyy h:mm a', 'dd/MM/yyyy HH:mm:ss', 'd-MMM-yy HH:mm:ss', 'd-MMM-yy h:mm:ss a', 'd MMMM yyyy h:mm a', 'M/d/yyyy h:mm:ss a', 'MM/dd/yyyy HH:mm:ss'] mapped_formats = formats.map f-> [f + " (e.g. " + date_time.format f + ")", f.pretty] week_date_formats = ['YYYY-ww-d HH:mm:ss.f'].map f-> @@ -60,7 +60,8 @@ make_date_time_format_selector (date_time:Date_Time=(Date_Time.new 2012 3 14 15 Creates a Single_Choice Widget for parsing times. make_time_format_selector : Time_Of_Day -> Widget make_time_format_selector (time:Time_Of_Day=(Time_Of_Day.new 13 30 55 123)) = - iso_format = ['ISO-Format (e.g. ' + time.to_text + ')', '""'] - formats = ['HH:mm[:ss]', 'HH:mm:ss', 'h:mm[:ss] a', 'hh:mm:ss a', 'HH:mm:ss.S'].map f-> [f + " (e.g. " + time.format f + ")", f.pretty] + iso_format = ['ISO-Format (e.g. ' + time.to_text + ')', "Date_Time_Format_Constants.ISO_Time"] + formats = ['HH:mm[:ss]', 'HH:mm:ss', 'h:mm[:ss] a', 'hh:mm:ss a', 'HH:mm:ss.S'].map f-> + [f + " (e.g. " + time.format f + ")", f.pretty] make_single_choice ([iso_format] + formats) From 47c96c28112daa87b30129c5133565d2464631df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Mon, 18 Sep 2023 13:36:59 +0200 Subject: [PATCH 23/76] new format for Time_Of_Day --- .../0.0.0-dev/src/Data/Time/Time_Of_Day.enso | 26 ++++++------------- 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Time_Of_Day.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Time_Of_Day.enso index e92fe9875a65..13b294c8a48e 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Time_Of_Day.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Time_Of_Day.enso @@ -160,17 +160,10 @@ type Time_Of_Day from Standard.Base import Time_Of_Day example_parse = Time_Of_Day.parse "4:30AM" "h:mma" - @pattern make_time_format_selector - @locale Locale.default_widget - parse : Text -> Text -> Locale -> Time_Of_Day ! Time_Error - parse text:Text pattern:Text="" locale:Locale=Locale.default = - result = Panic.recover Any <| - formatter = if pattern.is_empty then Time_Utils.default_time_of_day_formatter else - Time_Utils.make_formatter pattern locale.java_locale - Time_Utils.parse_time_of_day text.trim formatter - result . map_error <| case _ of - err : JException -> Time_Error.Error err.getMessage - ex -> ex + @format make_date_time_format_selector + parse : Text -> Date_Time_Formatter -> Time_Of_Day ! Time_Error + parse text:Text format:Date_Time_Formatter=Date_Time_Formatter.iso_time = + format.parse_time text ## GROUP Metadata Get the hour portion of the time of day. @@ -459,10 +452,7 @@ type Time_Of_Day from Standard.Base import Time_Of_Day example_format = Time_Of_Day.new 16 21 10 . format "'hour:'h" - @pattern (value-> make_time_format_selector value) - @locale Locale.default_widget - format : Text -> Locale -> Text - format self pattern:Text locale:Locale=Locale.default = - formatter = if pattern.is_empty then Time_Utils.default_time_of_day_formatter else - Time_Utils.make_formatter pattern locale.java_locale - Time_Utils.time_of_day_format self formatter + @format (value-> make_date_time_format_selector value) + format : Date_Time_Formatter -> Text + format self format:Date_Time_Formatter = + format.format_time self From aa10183da7b9b9e30bc36c69a781e0914a8b0db4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Mon, 18 Sep 2023 15:19:43 +0200 Subject: [PATCH 24/76] fixing imports --- .../src/Data/Time/Date_Time_Format_Constants.enso | 3 --- .../0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso | 10 +--------- .../Base/0.0.0-dev/src/Data/Time/Time_Of_Day.enso | 6 ++++-- 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Constants.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Constants.enso index d6eb538e2412..6269c1492b87 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Constants.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Constants.enso @@ -1,6 +1,3 @@ -import project.Data.Locale.Locale -import project.Data.Text.Text - import project.Data.Time.Date_Time_Formatter.Date_Time_Formatter ## PRIVATE diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso index 853ed2f7ea1f..219aa69ed0d3 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso @@ -19,15 +19,7 @@ polyglot java import org.enso.base.Time_Utils polyglot java import org.enso.base.time.EnsoDateTimeFormatter polyglot java import org.enso.base.time.FormatterKind - - ## TODO - 1. move to this, check stuff at construction - 2. add conversion - 3. move Date.parse/format et al to this, add Java sibling for Data_Formatter - 4. set up new dropdowns - 5. add compatibility check (can do Date, can do Time) - - +## TODO compatibility check? can do Date can do Time? type Date_Time_Formatter ## PRIVATE Value (underlying : EnsoDateTimeFormatter) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Time_Of_Day.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Time_Of_Day.enso index 13b294c8a48e..f952fd360ca2 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Time_Of_Day.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Time_Of_Day.enso @@ -6,6 +6,7 @@ import project.Data.Ordering.Comparable import project.Data.Text.Text import project.Data.Time.Date.Date import project.Data.Time.Date_Time.Date_Time +import project.Data.Time.Date_Time_Formatter.Date_Time_Formatter import project.Data.Time.Duration.Duration import project.Data.Time.Period.Period import project.Data.Time.Time_Period.Time_Period @@ -19,6 +20,7 @@ import project.Nothing.Nothing import project.Panic.Panic from project.Data.Boolean import Boolean, False, True from project.Data.Text.Extensions import all +from project.Data.Time.Conversions import all from project.Widget_Helpers import make_time_format_selector polyglot java import java.lang.Exception as JException @@ -160,7 +162,7 @@ type Time_Of_Day from Standard.Base import Time_Of_Day example_parse = Time_Of_Day.parse "4:30AM" "h:mma" - @format make_date_time_format_selector + @format make_time_format_selector parse : Text -> Date_Time_Formatter -> Time_Of_Day ! Time_Error parse text:Text format:Date_Time_Formatter=Date_Time_Formatter.iso_time = format.parse_time text @@ -452,7 +454,7 @@ type Time_Of_Day from Standard.Base import Time_Of_Day example_format = Time_Of_Day.new 16 21 10 . format "'hour:'h" - @format (value-> make_date_time_format_selector value) + @format (value-> make_time_format_selector value) format : Date_Time_Formatter -> Text format self format:Date_Time_Formatter = format.format_time self From 1e0677d724b11b8ed214315673051d318435983c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Mon, 18 Sep 2023 15:19:59 +0200 Subject: [PATCH 25/76] make AM/PM actually textual --- .../src/Internal/Time/Format/As_Java_Formatter_Interpreter.enso | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/As_Java_Formatter_Interpreter.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/As_Java_Formatter_Interpreter.enso index 4fc0057325f2..5d97ade57d06 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/As_Java_Formatter_Interpreter.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/As_Java_Formatter_Interpreter.enso @@ -44,7 +44,7 @@ interpret locale nodes = builder.appendLocalizedOffset TextStyle.FULL Time_Patterns.AmPm -> - builder.appendValue ChronoField.AMPM_OF_DAY 2 + builder.appendText ChronoField.AMPM_OF_DAY Time_Patterns.Fraction_Of_Second representation -> min_digits = 1 max_digits = case representation.digits of From 221d9eaf974742e52702536273419ac3b07fa96e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Mon, 18 Sep 2023 16:21:37 +0200 Subject: [PATCH 26/76] add `locale` to `from_java`, add examples with locale on `from Text` --- .../src/Data/Time/Date_Time_Formatter.enso | 19 +++++++++++++++---- .../Base/0.0.0-dev/src/Widget_Helpers.enso | 11 +++++++++-- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso index 219aa69ed0d3..ebb84aeb26c7 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso @@ -161,12 +161,23 @@ type Date_Time_Formatter See the Java documentation for explanation of the pattern format: https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/format/DateTimeFormatter.html#patterns - from_java (pattern:Text|DateTimeFormatter) = case pattern of + + Arguments: + - pattern: The pattern string to parse using the Java pattern rules, or + an existing `DateTimeFormatter` instance. + - locale: A locale to use when constructing the formatter from a text + pattern. If not specified, defaults to `Locale.default`. If passing a + `DateTimeFormatter` instance and this argument is set, it will + overwrite the original locale of that formatter. + from_java (pattern:Text|DateTimeFormatter) (locale:Locale|Nothing=Nothing) = case pattern of java_formatter : DateTimeFormatter -> - Date_Time_Formatter.Value (EnsoDateTimeFormatter.new java_formatter False Nothing FormatterKind.RAW_JAVA) + amended_formatter = case locale of + Nothing -> java_formatter + _ : Locale -> java_formatter.withLocale locale.java_locale + Date_Time_Formatter.Value (EnsoDateTimeFormatter.new amended_formatter False Nothing FormatterKind.RAW_JAVA) _ : Text -> - # TODO setting Locale too! - java_formatter = DateTimeFormatter.ofPattern pattern + java_locale = (locale.if_nothing Locale.default).java_locale + java_formatter = DateTimeFormatter.ofPattern pattern java_locale Date_Time_Formatter.Value (EnsoDateTimeFormatter.new java_formatter False pattern FormatterKind.RAW_JAVA) # TODO Locale for constants?? diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Widget_Helpers.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Widget_Helpers.enso index 501c6fe97106..ba4c943fbc52 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Widget_Helpers.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Widget_Helpers.enso @@ -1,3 +1,4 @@ +import project.Data.Locale.Locale import project.Data.Time.Date.Date import project.Data.Time.Date_Time.Date_Time import project.Data.Time.Time_Of_Day.Time_Of_Day @@ -28,11 +29,14 @@ make_date_format_selector (date:Date=(Date.new 2012 3 14)) = iso_format = ['ISO-Format (e.g. ' + date.to_text + ')', "Date_Time_Format_Constants.ISO_Date"] formats = ['d/M/yyyy', 'dd/MM/yyyy', 'd-MMM-yy', 'd MMMM yyyy', 'M/d/yyyy', 'MM/dd/yyyy', 'MMMM d, yyyy', 'yyyy-MM'].map f-> [f + " (e.g. " + date.format f + ")", f.pretty] + custom_locale_format = + format = Date_Time_Formatter.from 'd MMMM yyyy' locale=Locale.france + ['d MMMM yyyy with custom Locale (e.g. ' + date.format format + ')', "(Date_Time_Formatter.from 'd MMMM yyyy' locale=Locale.france)"] week_date_formats = ['YYYY-ww-d', 'YYYY-ww', 'ddd, YYYY-ww'].map f-> format = Date_Time_Formatter.from_iso_week_date_pattern f ["ISO Week-based Date: " + f + " (e.g. " + date.format format + ")", "("+fqn+".from_iso_week_date_pattern " + f.pretty + ")"] - make_single_choice ([iso_format] + formats + week_date_formats) + make_single_choice ([iso_format] + formats + [custom_locale_format] + week_date_formats) ## PRIVATE Creates a Single_Choice Widget for parsing date times. @@ -50,11 +54,14 @@ make_date_time_format_selector (date_time:Date_Time=(Date_Time.new 2012 3 14 15 ['ISO-Local (e.g. ' + (date_time.format format) + ')', "Date_Time_Format_Constants.ISO_Local_Date_Time"] formats = ['yyyy-MM-dd HH:mm:ss.f', 'yyyy-MM-dd HH:mm:ss.f TT', 'd/M/yyyy h:mm a', 'dd/MM/yyyy HH:mm:ss', 'd-MMM-yy HH:mm:ss', 'd-MMM-yy h:mm:ss a', 'd MMMM yyyy h:mm a', 'M/d/yyyy h:mm:ss a', 'MM/dd/yyyy HH:mm:ss'] mapped_formats = formats.map f-> [f + " (e.g. " + date_time.format f + ")", f.pretty] + custom_locale_format = + format = Date_Time_Formatter.from 'd MMMM yyyy h:mm a' locale=Locale.france + ['d MMMM yyyy h:mm a with custom Locale (e.g. ' + date_time.format format + ')', "(Date_Time_Formatter.from 'd MMMM yyyy h:mm a' locale=Locale.france)"] week_date_formats = ['YYYY-ww-d HH:mm:ss.f'].map f-> format = Date_Time_Formatter.from_iso_week_date_pattern f ["ISO Week-based Date-Time: " + f + " (e.g. " + date_time.format format + ")", "("+fqn+".from_iso_week_date_pattern " + f.pretty + ")"] - make_single_choice ([enso_format, iso_format, iso_local] + mapped_formats + week_date_formats) + make_single_choice ([enso_format, iso_format, iso_local] + mapped_formats + [custom_locale_format] + week_date_formats) ## PRIVATE Creates a Single_Choice Widget for parsing times. From 31e27b0b53b12fce3f5a02da21ccda1ba8cd4b99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Mon, 18 Sep 2023 16:36:03 +0200 Subject: [PATCH 27/76] update example after discussion: avoiding confusing `a` --- .../lib/Standard/Base/0.0.0-dev/src/Widget_Helpers.enso | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Widget_Helpers.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Widget_Helpers.enso index ba4c943fbc52..eef18edb3be9 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Widget_Helpers.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Widget_Helpers.enso @@ -31,7 +31,7 @@ make_date_format_selector (date:Date=(Date.new 2012 3 14)) = [f + " (e.g. " + date.format f + ")", f.pretty] custom_locale_format = format = Date_Time_Formatter.from 'd MMMM yyyy' locale=Locale.france - ['d MMMM yyyy with custom Locale (e.g. ' + date.format format + ')', "(Date_Time_Formatter.from 'd MMMM yyyy' locale=Locale.france)"] + ['d MMMM yyyy - with custom Locale (e.g. ' + date.format format + ')', "(Date_Time_Formatter.from 'd MMMM yyyy' locale=Locale.france)"] week_date_formats = ['YYYY-ww-d', 'YYYY-ww', 'ddd, YYYY-ww'].map f-> format = Date_Time_Formatter.from_iso_week_date_pattern f ["ISO Week-based Date: " + f + " (e.g. " + date.format format + ")", "("+fqn+".from_iso_week_date_pattern " + f.pretty + ")"] @@ -55,8 +55,8 @@ make_date_time_format_selector (date_time:Date_Time=(Date_Time.new 2012 3 14 15 formats = ['yyyy-MM-dd HH:mm:ss.f', 'yyyy-MM-dd HH:mm:ss.f TT', 'd/M/yyyy h:mm a', 'dd/MM/yyyy HH:mm:ss', 'd-MMM-yy HH:mm:ss', 'd-MMM-yy h:mm:ss a', 'd MMMM yyyy h:mm a', 'M/d/yyyy h:mm:ss a', 'MM/dd/yyyy HH:mm:ss'] mapped_formats = formats.map f-> [f + " (e.g. " + date_time.format f + ")", f.pretty] custom_locale_format = - format = Date_Time_Formatter.from 'd MMMM yyyy h:mm a' locale=Locale.france - ['d MMMM yyyy h:mm a with custom Locale (e.g. ' + date_time.format format + ')', "(Date_Time_Formatter.from 'd MMMM yyyy h:mm a' locale=Locale.france)"] + format = Date_Time_Formatter.from 'd MMMM yyyy HH:mm' locale=Locale.france + ['d MMMM yyyy HH:mm - with custom Locale (e.g. ' + date_time.format format + ')', "(Date_Time_Formatter.from 'd MMMM yyyy HH:mm' locale=Locale.france)"] week_date_formats = ['YYYY-ww-d HH:mm:ss.f'].map f-> format = Date_Time_Formatter.from_iso_week_date_pattern f ["ISO Week-based Date-Time: " + f + " (e.g. " + date_time.format format + ")", "("+fqn+".from_iso_week_date_pattern " + f.pretty + ")"] From 42233ba4bde42e35b231450140269f6f401e432c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Tue, 19 Sep 2023 14:14:29 +0200 Subject: [PATCH 28/76] New types, widgets for Data_Formatter.enso --- .../0.0.0-dev/src/Data/Data_Formatter.enso | 75 +++++++++++-------- 1 file changed, 45 insertions(+), 30 deletions(-) diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Data_Formatter.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Data_Formatter.enso index b0997fd5adf9..8a0e15ecc2c7 100644 --- a/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Data_Formatter.enso +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Data_Formatter.enso @@ -1,6 +1,10 @@ from Standard.Base import all import Standard.Base.Errors.Illegal_Argument.Illegal_Argument +import Standard.Base.Metadata.Display +from Standard.Base.Metadata.Widget import Vector_Editor +from Standard.Base.Widget_Helpers import make_date_format_selector, make_time_format_selector, make_date_time_format_selector + import project.Data.Type.Storage import project.Internal.Java_Problems import project.Internal.Parse_Values_Helper @@ -28,10 +32,7 @@ type Data_Formatter ## Specifies options for reading text data in a table to more specific types and serializing them back. - For date and time formats, a Java format string can be used or one of - `ENSO_ZONED_DATE_TIME`, `ISO_ZONED_DATE_TIME`, `ISO_LOCAL_DATE_TIME`, - `ISO_OFFSET_DATE_TIME`, `ISO_LOCAL_DATE`, `ISO_LOCAL_TIME` to use a - predefined format. + For date and time formats, see `Date_Time_Formatter`. Arguments: - trim_values: Trim whitespace before parsing. @@ -51,10 +52,12 @@ type Data_Formatter - datetime_formats: Expected datetime formats. - date_formats: Expected date formats. - time_formats: Expected time formats. - - datetime_locale: The locale to use when parsing dates and times. - true_values: Values representing True. - false_values: Values representing False. - Value trim_values:Boolean=True allow_leading_zeros:Boolean=False decimal_point:Text|Auto=Auto thousand_separator:Text='' allow_exponential_notation:Boolean=False datetime_formats:(Vector Text)=["ENSO_ZONED_DATE_TIME"] date_formats:(Vector Text)=["ISO_LOCAL_DATE"] time_formats:(Vector Text)=["ISO_LOCAL_TIME"] datetime_locale:Locale=Locale.default true_values:(Vector Text)=["True","true","TRUE"] false_values:(Vector Text)=["False","false","FALSE"] + @datetime_formats (make_vector_widget make_date_time_format_selector) + @date_formats (make_vector_widget make_date_format_selector) + @time_formats (make_vector_widget make_time_format_selector) + Value trim_values:Boolean=True allow_leading_zeros:Boolean=False decimal_point:Text|Auto=Auto thousand_separator:Text='' allow_exponential_notation:Boolean=False datetime_formats:(Vector Date_Time_Formatter)=[Date_Time_Formatter.default_enso_zoned_date_time] date_formats:(Vector Date_Time_Formatter)=[Date_Time_Formatter.iso_date] time_formats:(Vector Text)=[Date_Time_Formatter.iso_time] true_values:(Vector Text)=["True","true","TRUE"] false_values:(Vector Text)=["False","false","FALSE"] ## PRIVATE ADVANCED @@ -70,6 +73,7 @@ type Data_Formatter If set to `Ignore`, the operation proceeds without errors or warnings. 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 = + # TODO [RW] move to value_type parser = self.make_datatype_parser datatype Java_Problems.unpack_value_with_aggregated_problems on_problems problem_mapping=(Parse_Values_Helper.translate_parsing_problem datatype) <| parser.parseIndependentValue text @@ -98,19 +102,34 @@ type Data_Formatter ## Specify values for Date/Time parsing. - A Java format string can be used or one of `ENSO_ZONED_DATE_TIME`, - `ISO_ZONED_DATE_TIME`, `ISO_LOCAL_DATE_TIME`, `ISO_OFFSET_DATE_TIME`, - `ISO_LOCAL_DATE`, `ISO_LOCAL_TIME` to use a predefined format. + A plain text pattern can be provided and it will be automatically + converted into a `Date_Time_Formatter` using simple pattern parsing + rules. See `Date_Time_Formatter` for available options. Arguments: - datetime_formats: Expected datetime formats. - date_formats: Expected date formats. - time_formats: Expected time formats. - with_datetime_formats : Text|(Vector Text) -> Text|(Vector Text) -> Text|(Vector Text) -> Data_Formatter - with_datetime_formats self datetime_formats=self.datetime_formats date_formats=self.date_formats time_formats=self.time_formats = - datetime_vector = wrap_text_in_vector datetime_formats - date_vector = wrap_text_in_vector date_formats - time_vector = wrap_text_in_vector time_formats + @datetime_formats (make_vector_widget make_date_time_format_selector) + @date_formats (make_vector_widget make_date_format_selector) + @time_formats (make_vector_widget make_time_format_selector) + with_datetime_formats : ((Vector Date_Time_Formatter) | Date_Time_Formatter) -> ((Vector Date_Time_Formatter) | Date_Time_Formatter) -> ((Vector Date_Time_Formatter) | Date_Time_Formatter) -> Data_Formatter + with_datetime_formats self (datetime_formats:Vector|Date_Time_Formatter = self.datetime_formats) (date_formats:Vector|Date_Time_Formatter = self.date_formats) (time_formats:Vector|Date_Time_Formatter = self.time_formats) = + convert_formats formats = + vector = case formats of + v : Vector -> v + singleton -> [singleton] + converted = vector.map elem-> + ## Ensure the element is a `Date_Time_Formatter` or is converted to it. + We need to convert _each_ element - we cannot perform a 'bulk' conversion like `vector : Vector Date_Time_Formatter` because of erasure. + checked = elem : Date_Time_Formatter + # Temporary variable is a workaround for https://github.com/enso-org/enso/issues/7841 + checked + converted + + datetime_vector = convert_formats datetime_formats + date_vector = convert_formats date_formats + time_vector = convert_formats time_formats self.clone datetime_formats=datetime_vector date_formats=date_vector time_formats=time_vector ## Specify values for Boolean parsing. @@ -124,14 +143,6 @@ type Data_Formatter false_vector = wrap_text_in_vector false_values self.clone true_values=true_vector false_values=false_vector - ## Create a clone of self with a specified Locale. - - Arguments: - - locale: The locale to use when parsing dates and times. - @datetime_locale Locale.default_widget - with_locale : Locale -> Data_Formatter - with_locale self datetime_locale = self.clone datetime_locale=datetime_locale - ## Create a clone of self with a changed format string for a particular datatype. @@ -143,14 +154,14 @@ type Data_Formatter - format: The new format string to set. For dates, it is the usual date format notation, and for booleans it should be two values that represent true and false, separated by a `|`. - with_format : Value_Type | Auto -> Text -> Data_Formatter + with_format : Value_Type | Auto -> (Text | Date_Time_Formatter) -> Data_Formatter with_format self type format = case type of Value_Type.Date -> self.with_datetime_formats date_formats=[format] Value_Type.Time -> self.with_datetime_formats time_formats=[format] Value_Type.Date_Time _ -> self.with_datetime_formats datetime_formats=[format] Value_Type.Boolean -> - formats = format.split "|" + formats = (format : Text).split "|" if formats.length != 2 then Error.throw (Illegal_Argument.Error "The `format` for Booleans must be a string with two values separated by `|`, for example: 'Yes|No'.") else self.with_boolean_values true_values=[formats.at 0] false_values=[formats.at 1] Auto -> @@ -161,8 +172,8 @@ type Data_Formatter ## PRIVATE Clone the instance with some properties overridden. clone : Boolean -> Boolean -> Text -> Text -> Boolean -> Vector Text -> Vector Text -> Vector Text -> Locale -> Vector Text -> Vector Text -> Data_Formatter - clone self (trim_values=self.trim_values) (allow_leading_zeros=self.allow_leading_zeros) (decimal_point=self.decimal_point) (thousand_separator=self.thousand_separator) (allow_exponential_notation=self.allow_exponential_notation) (datetime_formats=self.datetime_formats) (date_formats=self.date_formats) (time_formats=self.time_formats) (datetime_locale=self.datetime_locale) (true_values=self.true_values) (false_values=self.false_values) = - Data_Formatter.Value trim_values=trim_values allow_leading_zeros=allow_leading_zeros decimal_point=decimal_point thousand_separator=thousand_separator allow_exponential_notation=allow_exponential_notation datetime_formats=datetime_formats date_formats=date_formats time_formats=time_formats datetime_locale=datetime_locale true_values=true_values false_values=false_values + clone self (trim_values=self.trim_values) (allow_leading_zeros=self.allow_leading_zeros) (decimal_point=self.decimal_point) (thousand_separator=self.thousand_separator) (allow_exponential_notation=self.allow_exponential_notation) (datetime_formats=self.datetime_formats) (date_formats=self.date_formats) (time_formats=self.time_formats) (true_values=self.true_values) (false_values=self.false_values) = + Data_Formatter.Value trim_values=trim_values allow_leading_zeros=allow_leading_zeros decimal_point=decimal_point thousand_separator=thousand_separator allow_exponential_notation=allow_exponential_notation datetime_formats=datetime_formats date_formats=date_formats time_formats=time_formats true_values=true_values false_values=false_values ## PRIVATE get_thousand_separator self = @@ -191,17 +202,17 @@ type Data_Formatter ## PRIVATE make_date_parser self = self.wrap_base_parser <| Panic.catch Any handler=(caught_panic-> Error.throw (Illegal_Argument.Error caught_panic.payload.getMessage)) <| - DateParser.new self.date_formats self.datetime_locale.java_locale + DateParser.new (self.date_formats.map .underlying) ## PRIVATE make_date_time_parser self = self.wrap_base_parser <| Panic.catch Any handler=(caught_panic-> Error.throw (Illegal_Argument.Error caught_panic.payload.getMessage)) <| - DateTimeParser.new self.datetime_formats self.datetime_locale.java_locale + DateTimeParser.new (self.datetime_formats.map .underlying) ## PRIVATE make_time_of_day_parser self = self.wrap_base_parser <| Panic.catch Any handler=(caught_panic-> Error.throw (Illegal_Argument.Error caught_panic.payload.getMessage)) <| - TimeOfDayParser.new self.time_formats self.datetime_locale.java_locale + TimeOfDayParser.new (self.time_formats.map .underlying) ## PRIVATE make_identity_parser self = self.wrap_base_parser IdentityParser.new @@ -209,7 +220,7 @@ type Data_Formatter ## PRIVATE make_datatype_parser self datatype = case datatype of Integer -> self.make_integer_parser - Float -> self.make_decimal_parser + Float -> self.make_decimal_parser Boolean -> self.make_boolean_parser Date -> self.make_date_parser Date_Time -> self.make_date_time_parser @@ -307,3 +318,7 @@ type Data_Formatter wrap_text_in_vector v = case v of _ : Text -> [v] _ -> v + +## PRIVATE +make_vector_widget single_choice_widget display=Display.Always = + Vector_Editor item_editor=single_choice_widget item_default=single_choice_widget.values.first.value display=display From 5340b391352fbd9b627acbd00e9682cedcdfa056 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Tue, 19 Sep 2023 14:28:59 +0200 Subject: [PATCH 29/76] fix: we were catching Any panic, but calling `getMessage` on it - that can only be expected from Java Exceptions - so it was causing a panic inside of a panic hanlder obfuscating the error - instead now we only intercept Java Exceptions there --- .../Table/0.0.0-dev/src/Data/Data_Formatter.enso | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Data_Formatter.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Data_Formatter.enso index 8a0e15ecc2c7..6d509bac631e 100644 --- a/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Data_Formatter.enso +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Data_Formatter.enso @@ -10,6 +10,7 @@ import project.Internal.Java_Problems import project.Internal.Parse_Values_Helper from project.Data.Type.Value_Type import Auto, Bits, Value_Type +polyglot java import java.lang.Exception as Java_Exception polyglot java import java.lang.IllegalArgumentException polyglot java import org.enso.table.formatting.AnyObjectFormatter polyglot java import org.enso.table.formatting.BooleanFormatter @@ -201,17 +202,17 @@ type Data_Formatter ## PRIVATE make_date_parser self = self.wrap_base_parser <| - Panic.catch Any handler=(caught_panic-> Error.throw (Illegal_Argument.Error caught_panic.payload.getMessage)) <| + Panic.catch Java_Exception handler=(caught_panic-> Error.throw (Illegal_Argument.Error caught_panic.payload.getMessage)) <| DateParser.new (self.date_formats.map .underlying) ## PRIVATE make_date_time_parser self = self.wrap_base_parser <| - Panic.catch Any handler=(caught_panic-> Error.throw (Illegal_Argument.Error caught_panic.payload.getMessage)) <| + Panic.catch Java_Exception handler=(caught_panic-> Error.throw (Illegal_Argument.Error caught_panic.payload.getMessage)) <| DateTimeParser.new (self.datetime_formats.map .underlying) ## PRIVATE make_time_of_day_parser self = self.wrap_base_parser <| - Panic.catch Any handler=(caught_panic-> Error.throw (Illegal_Argument.Error caught_panic.payload.getMessage)) <| + Panic.catch Java_Exception handler=(caught_panic-> Error.throw (Illegal_Argument.Error caught_panic.payload.getMessage)) <| TimeOfDayParser.new (self.time_formats.map .underlying) ## PRIVATE @@ -267,19 +268,19 @@ type Data_Formatter ## PRIVATE make_date_formatter self = if self.date_formats.is_empty then Error.throw (Illegal_Argument.Error "Formatting dates requires at least one entry in the `date_formats` parameter") else - Panic.catch Any handler=(caught_panic-> Error.throw (Illegal_Argument.Error caught_panic.payload.getMessage)) <| + Panic.catch Java_Exception handler=(caught_panic-> Error.throw (Illegal_Argument.Error caught_panic.payload.getMessage)) <| DateFormatter.new self.date_formats.first self.datetime_locale.java_locale ## PRIVATE make_time_of_day_formatter self = if self.time_formats.is_empty then Error.throw (Illegal_Argument.Error "Formatting times requires at least one entry in the `time_formats` parameter") else - Panic.catch Any handler=(caught_panic-> Error.throw (Illegal_Argument.Error caught_panic.payload.getMessage)) <| + Panic.catch Java_Exception handler=(caught_panic-> Error.throw (Illegal_Argument.Error caught_panic.payload.getMessage)) <| TimeFormatter.new self.time_formats.first self.datetime_locale.java_locale ## PRIVATE make_date_time_formatter self = if self.datetime_formats.is_empty then Error.throw (Illegal_Argument.Error "Formatting date-times requires at least one entry in the `datetime_formats` parameter") else - Panic.catch Any handler=(caught_panic-> Error.throw (Illegal_Argument.Error caught_panic.payload.getMessage)) <| + Panic.catch Java_Exception handler=(caught_panic-> Error.throw (Illegal_Argument.Error caught_panic.payload.getMessage)) <| DateTimeFormatter.new self.datetime_formats.first self.datetime_locale.java_locale ## PRIVATE From 66781934af8abc16c0c62f854d419a71f9b3b29f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Tue, 19 Sep 2023 16:18:34 +0200 Subject: [PATCH 30/76] migrate underlying formatters/parsers to Date_Time_Formatter (EnsoDateTimeFormatter) --- .../0.0.0-dev/src/Data/Data_Formatter.enso | 6 ++--- .../enso/table/formatting/DateFormatter.java | 7 +++--- .../table/formatting/DateTimeFormatter.java | 7 +++--- .../enso/table/formatting/TimeFormatter.java | 7 +++--- .../enso/table/parsing/BaseTimeParser.java | 23 +++++++------------ .../org/enso/table/parsing/DateParser.java | 7 +++--- .../enso/table/parsing/DateTimeParser.java | 7 +++--- .../enso/table/parsing/TimeOfDayParser.java | 7 +++--- 8 files changed, 29 insertions(+), 42 deletions(-) diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Data_Formatter.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Data_Formatter.enso index 6d509bac631e..0f559967285f 100644 --- a/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Data_Formatter.enso +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Data_Formatter.enso @@ -269,19 +269,19 @@ type Data_Formatter make_date_formatter self = if self.date_formats.is_empty then Error.throw (Illegal_Argument.Error "Formatting dates requires at least one entry in the `date_formats` parameter") else Panic.catch Java_Exception handler=(caught_panic-> Error.throw (Illegal_Argument.Error caught_panic.payload.getMessage)) <| - DateFormatter.new self.date_formats.first self.datetime_locale.java_locale + DateFormatter.new self.date_formats.first.underlying ## PRIVATE make_time_of_day_formatter self = if self.time_formats.is_empty then Error.throw (Illegal_Argument.Error "Formatting times requires at least one entry in the `time_formats` parameter") else Panic.catch Java_Exception handler=(caught_panic-> Error.throw (Illegal_Argument.Error caught_panic.payload.getMessage)) <| - TimeFormatter.new self.time_formats.first self.datetime_locale.java_locale + TimeFormatter.new self.time_formats.first.underlying ## PRIVATE make_date_time_formatter self = if self.datetime_formats.is_empty then Error.throw (Illegal_Argument.Error "Formatting date-times requires at least one entry in the `datetime_formats` parameter") else Panic.catch Java_Exception handler=(caught_panic-> Error.throw (Illegal_Argument.Error caught_panic.payload.getMessage)) <| - DateTimeFormatter.new self.datetime_formats.first self.datetime_locale.java_locale + DateTimeFormatter.new self.datetime_formats.first.underlying ## PRIVATE make_boolean_formatter self = diff --git a/std-bits/table/src/main/java/org/enso/table/formatting/DateFormatter.java b/std-bits/table/src/main/java/org/enso/table/formatting/DateFormatter.java index 35e9cf645b0c..671f1f61a004 100644 --- a/std-bits/table/src/main/java/org/enso/table/formatting/DateFormatter.java +++ b/std-bits/table/src/main/java/org/enso/table/formatting/DateFormatter.java @@ -1,17 +1,16 @@ package org.enso.table.formatting; -import org.enso.base.Time_Utils; +import org.enso.base.time.EnsoDateTimeFormatter; import org.graalvm.polyglot.Value; import java.time.LocalDate; import java.time.format.DateTimeFormatter; -import java.util.Locale; public class DateFormatter implements DataFormatter { private final DateTimeFormatter formatter; - public DateFormatter(String formatString, Locale locale) { - formatter = Time_Utils.make_formatter(formatString, locale); + public DateFormatter(EnsoDateTimeFormatter ensoFormatter) { + formatter = ensoFormatter.getRawJavaFormatter(); } @Override diff --git a/std-bits/table/src/main/java/org/enso/table/formatting/DateTimeFormatter.java b/std-bits/table/src/main/java/org/enso/table/formatting/DateTimeFormatter.java index 7e47908df895..0e91bba2aef9 100644 --- a/std-bits/table/src/main/java/org/enso/table/formatting/DateTimeFormatter.java +++ b/std-bits/table/src/main/java/org/enso/table/formatting/DateTimeFormatter.java @@ -1,17 +1,16 @@ package org.enso.table.formatting; -import org.enso.base.Time_Utils; +import org.enso.base.time.EnsoDateTimeFormatter; import org.graalvm.polyglot.Value; import java.time.LocalDateTime; import java.time.ZonedDateTime; -import java.util.Locale; public class DateTimeFormatter implements DataFormatter { private final java.time.format.DateTimeFormatter formatter; - public DateTimeFormatter(String formatString, Locale locale) { - formatter = Time_Utils.make_output_formatter(formatString, locale); + public DateTimeFormatter(EnsoDateTimeFormatter ensoFormatter) { + formatter = ensoFormatter.getRawJavaFormatter(); } @Override diff --git a/std-bits/table/src/main/java/org/enso/table/formatting/TimeFormatter.java b/std-bits/table/src/main/java/org/enso/table/formatting/TimeFormatter.java index 6c3d1545427a..bd289b5e2107 100644 --- a/std-bits/table/src/main/java/org/enso/table/formatting/TimeFormatter.java +++ b/std-bits/table/src/main/java/org/enso/table/formatting/TimeFormatter.java @@ -1,17 +1,16 @@ package org.enso.table.formatting; -import org.enso.base.Time_Utils; +import org.enso.base.time.EnsoDateTimeFormatter; import org.graalvm.polyglot.Value; import java.time.LocalTime; import java.time.format.DateTimeFormatter; -import java.util.Locale; public class TimeFormatter implements DataFormatter { private final DateTimeFormatter formatter; - public TimeFormatter(String formatString, Locale locale) { - formatter = Time_Utils.make_formatter(formatString, locale); + public TimeFormatter(EnsoDateTimeFormatter ensoFormatter) { + formatter = ensoFormatter.getRawJavaFormatter(); } @Override diff --git a/std-bits/table/src/main/java/org/enso/table/parsing/BaseTimeParser.java b/std-bits/table/src/main/java/org/enso/table/parsing/BaseTimeParser.java index e0b6e1ec2359..c0168c59458d 100644 --- a/std-bits/table/src/main/java/org/enso/table/parsing/BaseTimeParser.java +++ b/std-bits/table/src/main/java/org/enso/table/parsing/BaseTimeParser.java @@ -2,37 +2,30 @@ import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; -import java.util.Locale; import org.enso.base.Time_Utils; +import org.enso.base.time.EnsoDateTimeFormatter; import org.enso.table.parsing.problems.ProblemAggregator; public abstract class BaseTimeParser extends IncrementalDatatypeParser { protected interface ParseStrategy { - Object parse(String text, DateTimeFormatter formatter) throws DateTimeParseException; + Object parse(String text, EnsoDateTimeFormatter formatter) throws DateTimeParseException; } - protected final DateTimeFormatter[] formatters; - protected final boolean[] replaceSpaces; + protected final EnsoDateTimeFormatter[] formatters; protected final ParseStrategy parseStrategy; - protected BaseTimeParser(String[] formats, Locale locale, ParseStrategy parseStrategy) { + protected BaseTimeParser(EnsoDateTimeFormatter[] formatters, ParseStrategy parseStrategy) { this.parseStrategy = parseStrategy; - - formatters = new DateTimeFormatter[formats.length]; - replaceSpaces = new boolean[formats.length]; - for (int i = 0; i < formats.length; i++) { - formatters[i] = Time_Utils.make_formatter(formats[i], locale); - replaceSpaces[i] = Time_Utils.is_iso_datetime_based(formats[i]); - } + this.formatters = formatters; } @Override protected Object parseSingleValue(String text, ProblemAggregator problemAggregator) { - for (int i = 0; i < formatters.length; i++) { + for (EnsoDateTimeFormatter formatter : formatters) { try { - var replaced = replaceSpaces[i] ? Time_Utils.normalise_iso_datetime(text) : text; - return parseStrategy.parse(replaced, formatters[i]); + return parseStrategy.parse(text, formatter); } catch (DateTimeParseException ignored) { + // TODO I think ideally we should try to return Option instead of throwing, as throwing is inefficient } } diff --git a/std-bits/table/src/main/java/org/enso/table/parsing/DateParser.java b/std-bits/table/src/main/java/org/enso/table/parsing/DateParser.java index 029ed256da7b..b39f5fb20d0f 100644 --- a/std-bits/table/src/main/java/org/enso/table/parsing/DateParser.java +++ b/std-bits/table/src/main/java/org/enso/table/parsing/DateParser.java @@ -1,13 +1,12 @@ package org.enso.table.parsing; -import java.util.Locale; -import org.enso.polyglot.common_utils.Core_Date_Utils; +import org.enso.base.time.EnsoDateTimeFormatter; import org.enso.table.data.column.builder.Builder; import org.enso.table.data.column.builder.DateBuilder; public class DateParser extends BaseTimeParser { - public DateParser(String[] formats, Locale locale) { - super(formats, locale, Core_Date_Utils::parseLocalDate); + public DateParser(EnsoDateTimeFormatter[] formatters) { + super(formatters, (String text, EnsoDateTimeFormatter formatter) -> formatter.parseLocalDate(text)); } @Override diff --git a/std-bits/table/src/main/java/org/enso/table/parsing/DateTimeParser.java b/std-bits/table/src/main/java/org/enso/table/parsing/DateTimeParser.java index 24f4c95868d1..46fae441fc23 100644 --- a/std-bits/table/src/main/java/org/enso/table/parsing/DateTimeParser.java +++ b/std-bits/table/src/main/java/org/enso/table/parsing/DateTimeParser.java @@ -1,13 +1,12 @@ package org.enso.table.parsing; -import java.util.Locale; -import org.enso.polyglot.common_utils.Core_Date_Utils; +import org.enso.base.time.EnsoDateTimeFormatter; import org.enso.table.data.column.builder.Builder; import org.enso.table.data.column.builder.DateTimeBuilder; public class DateTimeParser extends BaseTimeParser { - public DateTimeParser(String[] formats, Locale locale) { - super(formats, locale, Core_Date_Utils::parseZonedDateTime); + public DateTimeParser(EnsoDateTimeFormatter[] formatters) { + super(formatters, (String text, EnsoDateTimeFormatter formatter) -> formatter.parseZonedDateTime(text)); } @Override diff --git a/std-bits/table/src/main/java/org/enso/table/parsing/TimeOfDayParser.java b/std-bits/table/src/main/java/org/enso/table/parsing/TimeOfDayParser.java index 659e1e74ec3a..4f561e32d7cc 100644 --- a/std-bits/table/src/main/java/org/enso/table/parsing/TimeOfDayParser.java +++ b/std-bits/table/src/main/java/org/enso/table/parsing/TimeOfDayParser.java @@ -1,13 +1,12 @@ package org.enso.table.parsing; -import java.time.LocalTime; -import java.util.Locale; +import org.enso.base.time.EnsoDateTimeFormatter; import org.enso.table.data.column.builder.Builder; import org.enso.table.data.column.builder.TimeOfDayBuilder; public class TimeOfDayParser extends BaseTimeParser { - public TimeOfDayParser(String[] formats, Locale locale) { - super(formats, locale, LocalTime::parse); + public TimeOfDayParser(EnsoDateTimeFormatter[] formatters) { + super(formatters, (String text, EnsoDateTimeFormatter formatter) -> formatter.parseLocalTime(text)); } @Override From 8ed22c06e80371625b0c02031e0d09286719e1a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Tue, 19 Sep 2023 17:21:33 +0200 Subject: [PATCH 31/76] update docs of parse --- .../Base/0.0.0-dev/src/Data/Time/Date.enso | 33 ++++++----- .../0.0.0-dev/src/Data/Time/Date_Time.enso | 58 +++++++++++++++---- .../0.0.0-dev/src/Data/Time/Time_Of_Day.enso | 28 +++++---- 3 files changed, 83 insertions(+), 36 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date.enso index 570aa29b9b8a..1cc2fc3c0b23 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date.enso @@ -107,11 +107,27 @@ type Date Arguments: - text: The text to try and parse as a date. - - format: A pattern describing how to parse the text, or a `Date_Time_Formatter`. + - format: A pattern describing how to parse the text, + or a `Date_Time_Formatter`. Returns a `Time_Error` if the provided `text` cannot be parsed using the provided `format`. + ? Default Date Formatting + Unless you provide a custom format, the text must represent a valid date + that can be parsed using the ISO-8601 extended local date format. The + format consists of: + + - Four digits or more for the year. Years in the range 0000 to 9999 + will be pre-padded by zero to ensure four digits. Years outside + that range will have a prefixed positive or negative symbol. + - A dash + - Two digits for the month-of-year. This is pre-padded by zero to ensure + two digits. + - A dash + - Two digits for the day-of-month. This is pre-padded by zero to ensure two + digits. + ? Pattern Syntax If the pattern is provided as `Text`, it is parsed using the format described below. See `Date_Time_Formatter` for more options. @@ -140,21 +156,6 @@ type Date If only year and quarter are provided in the pattern, when parsing a date, the result will be the first day of that quarter. - ? Default Date Formatting - Unless you provide a custom format, the text must represent a valid date - that can be parsed using the ISO-8601 extended local date format. The - format consists of: - - - Four digits or more for the year. Years in the range 0000 to 9999 - will be pre-padded by zero to ensure four digits. Years outside - that range will have a prefixed positive or negative symbol. - - A dash - - Two digits for the month-of-year. This is pre-padded by zero to ensure - two digits. - - A dash - - Two digits for the day-of-month. This is pre-padded by zero to ensure two - digits. - > Example Parse the date of 23rd December 2020. diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso index 0ef8f0cf47ef..47c6afe228d0 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso @@ -158,19 +158,60 @@ type Date_Time Obtains an instance of `Time` from a text such as "2007-12-03T10:15:30+01:00 Europe/Paris". + This method will return a `Time_Error` if the provided time cannot be + parsed. + Arguments: - text: The text representing the time to be parsed. - - pattern: TODO + - format: A pattern describing how to parse the text, + or a `Date_Time_Formatter`. ? Pattern Syntax - TODO + If the pattern is provided as `Text`, it is parsed using the format + described below. See `Date_Time_Formatter` for more options. + - y: Year. The number of pattern letters determines the minimum number of + digits. + - y: The year using any number of digits. + - yy: The year, using at most two digits. The default range is + 1950-2049, but this can be changed by including the end year in + braces e.g. `yy{2099}`. + - yyyy: The year, using exactly four digits. + - M: Month of year. The number of pattern letters determines the format: + - M: Any number (1-12). + - MM: Month number with zero padding required (01-12). + - MMM: Short name of the month (Jan-Dec). + - MMMM: Full name of the month (January-December). + The month names depend on the selected locale. + - d: Day. The number of pattern letters determines the format: + - d: Any number (1-31). + - dd: Day number with zero padding required (01-31). + - ddd: Short name of the day of week (Mon-Sun). + - dddd: Full name of the day of week (Monday-Sunday). + The weekday names depend on the selected locale. + Both day of week and day of month may be included in a single pattern - + in such case the day of week is used as a sanity check. + - Q: Quarter of year. + If only year and quarter are provided in the pattern, when parsing a + date, the result will be the first day of that quarter. + - H: 24h hour of day (0-23). + - h: 12h hour of day (0-12). The `a` pattern is needed to disambiguate + between AM and PM. + - m: Minute of hour. + - s: Second of minute. + - f: Fractional part of the second. The number of pattern letters + determines the number of digits. If one letter is used, any number of + digits will be accepted. + - a: AM/PM marker. + - T: If repeated 3 or less times - Time zone ID (e.g. Europe/Warsaw, Z, + -08:30), otherwise - Time zone name (e.g. Central European Time, CET). + - Z: Zone offset (e.g. +0000, -0830, +08:30:15). ? Default Date_Time Format - The text must represent a valid date-time as defined by the ISO-8601 - format. (See https://en.wikipedia.org/wiki/ISO_8601.) If a time zone is - present, it must be in the ISO-8601 Extended Date/Time Format (EDTF). - (See https://en.wikipedia.org/wiki/ISO_8601#EDTF.) The time zone format - consists of: + Unless you provide a custom format, the text must represent a valid + date-time as defined by the ISO-8601 format (see https://en.wikipedia.org/wiki/ISO_8601).make_date_time_format_selector + If a time zone is present, it must be in the ISO-8601 Extended + Date/Time Format (EDTF) (see https://en.wikipedia.org/wiki/ISO_8601#EDTF). + The time zone format consists of: - The ISO offset date time. - If the zone ID is not available or is a zone offset then the format is @@ -180,9 +221,6 @@ type Date_Time 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. diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Time_Of_Day.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Time_Of_Day.enso index f952fd360ca2..83c8d93bb8b8 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Time_Of_Day.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Time_Of_Day.enso @@ -102,21 +102,16 @@ type Time_Of_Day Arguments: - text: The text to parse as a time of day. - - pattern: The pattern to use for parsing the input text. - - locale: The locale in which the pattern should be interpreted. + - format: A pattern describing how to parse the text, + or a `Date_Time_Formatter`. Returns a `Time_Error` if the provided text cannot be parsed using the default format. - ? Pattern Syntax - A custom pattern string consists of one or more custom date and time - format specifiers. For example, "d MMM yyyy" will format "2011-12-03" - as "3 Dec 2011". See https://docs.oracle.com/en/java/javase/18/docs/api/java.base/java/time/format/DateTimeFormatter.html - for a complete format specification. - ? Default Time Format - The text must represent a valid time and is parsed using the ISO-8601 - extended local time format. The format consists of: + Unless you provide a custom format, the text must represent a valid + time and is parsed using the ISO-8601 extended local time format. + The format consists of: - Two digits for the hour-of-day. This is pre-padded by zero to ensure two digits. @@ -133,6 +128,19 @@ type Time_Of_Day - One to nine digits for the nano-of-second. As many digits will be output as required. + ? Pattern Syntax + If the pattern is provided as `Text`, it is parsed using the format + described below. See `Date_Time_Formatter` for more options. + - H: 24h hour of day (0-23). + - h: 12h hour of day (0-12). The `a` pattern is needed to disambiguate + between AM and PM. + - m: Minute of hour. + - s: Second of minute. + - f: Fractional part of the second. The number of pattern letters + determines the number of digits. If one letter is used, any number of + digits will be accepted. + - a: AM/PM marker. + > Example Get the time 15:05:30. From c66a6da5866a42923b6329c6305f28db9c1cb478 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Tue, 19 Sep 2023 17:23:22 +0200 Subject: [PATCH 32/76] update docs of format --- .../Base/0.0.0-dev/src/Data/Time/Date.enso | 3 +- .../0.0.0-dev/src/Data/Time/Date_Time.enso | 47 ++++++++++++++++--- .../0.0.0-dev/src/Data/Time/Time_Of_Day.enso | 20 +++++--- 3 files changed, 55 insertions(+), 15 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date.enso index 1cc2fc3c0b23..179ca5b04378 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date.enso @@ -726,7 +726,8 @@ type Date Format this date using the provided format specifier. Arguments: - - format: The text specifying the format, or a `Date_Time_Formatter`. + - format: A pattern describing how to format the text, + or a `Date_Time_Formatter`. ? Pattern Syntax If the pattern is provided as `Text`, it is parsed using the format diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso index 47c6afe228d0..045c0353e572 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso @@ -763,15 +763,48 @@ type Date_Time Format this time as text using the specified format specifier. Arguments: - - pattern: The pattern that specifies how to format the time. - - locale: The locale in which the format should be interpreted. - (Defaults to Locale.default.) + - format: A pattern describing how to format the text, + or a `Date_Time_Formatter`. ? Pattern Syntax - A custom pattern string consists of one or more custom date and time - format specifiers. For example, "d MMM yyyy" will format "2011-12-03" - as "3 Dec 2011". See https://docs.oracle.com/en/java/javase/18/docs/api/java.base/java/time/format/DateTimeFormatter.html - for a complete format specification. + If the pattern is provided as `Text`, it is parsed using the format + described below. See `Date_Time_Formatter` for more options. + - y: Year. The number of pattern letters determines the minimum number of + digits. + - y: The year using any number of digits. + - yy: The year, using at most two digits. The default range is + 1950-2049, but this can be changed by including the end year in + braces e.g. `yy{2099}`. + - yyyy: The year, using exactly four digits. + - M: Month of year. The number of pattern letters determines the format: + - M: Any number (1-12). + - MM: Month number with zero padding required (01-12). + - MMM: Short name of the month (Jan-Dec). + - MMMM: Full name of the month (January-December). + The month names depend on the selected locale. + - d: Day. The number of pattern letters determines the format: + - d: Any number (1-31). + - dd: Day number with zero padding required (01-31). + - ddd: Short name of the day of week (Mon-Sun). + - dddd: Full name of the day of week (Monday-Sunday). + The weekday names depend on the selected locale. + Both day of week and day of month may be included in a single pattern - + in such case the day of week is used as a sanity check. + - Q: Quarter of year. + If only year and quarter are provided in the pattern, when parsing a + date, the result will be the first day of that quarter. + - H: 24h hour of day (0-23). + - h: 12h hour of day (0-12). The `a` pattern is needed to disambiguate + between AM and PM. + - m: Minute of hour. + - s: Second of minute. + - f: Fractional part of the second. The number of pattern letters + determines the number of digits. If one letter is used, any number of + digits will be accepted. + - a: AM/PM marker. + - T: If repeated 3 or less times - Time zone ID (e.g. Europe/Warsaw, Z, + -08:30), otherwise - Time zone name (e.g. Central European Time, CET). + - Z: Zone offset (e.g. +0000, -0830, +08:30:15). > Example Format "2020-10-08T16:41:13+03:00[Europe/Moscow]" as diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Time_Of_Day.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Time_Of_Day.enso index 83c8d93bb8b8..183f9e739325 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Time_Of_Day.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Time_Of_Day.enso @@ -418,15 +418,21 @@ type Time_Of_Day Format this time of day using the provided formatter pattern. Arguments: - - pattern: The pattern specifying how to format the time of day. - - locale: The locale in which the format should be interpreted. - (Defaults to Locale.default.) + - format: A pattern describing how to format the text, + or a `Date_Time_Formatter`. ? Pattern Syntax - A custom pattern string consists of one or more custom date and time - format specifiers. For example, "d MMM yyyy" will format "2011-12-03" - as "3 Dec 2011". See https://docs.oracle.com/en/java/javase/18/docs/api/java.base/java/time/format/DateTimeFormatter.html - for a complete format specification. + If the pattern is provided as `Text`, it is parsed using the format + described below. See `Date_Time_Formatter` for more options. + - H: 24h hour of day (0-23). + - h: 12h hour of day (0-12). The `a` pattern is needed to disambiguate + between AM and PM. + - m: Minute of hour. + - s: Second of minute. + - f: Fractional part of the second. The number of pattern letters + determines the number of digits. If one letter is used, any number of + digits will be accepted. + - a: AM/PM marker. > Example Format "16:21:10" as "16:21:00.1234" From e9d6c0323184d740db764a68faccbc107bcb7f66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Tue, 19 Sep 2023 17:27:15 +0200 Subject: [PATCH 33/76] update Text extensions for date parsing --- .../0.0.0-dev/src/Data/Text/Extensions.enso | 150 ++++++++++++------ 1 file changed, 105 insertions(+), 45 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Text/Extensions.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Text/Extensions.enso index eaa9ff077383..bf07a4e15c76 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Text/Extensions.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Text/Extensions.enso @@ -18,6 +18,7 @@ import project.Data.Text.Text_Sub_Range.Codepoint_Ranges import project.Data.Text.Text_Sub_Range.Text_Sub_Range import project.Data.Time.Date.Date import project.Data.Time.Date_Time.Date_Time +import project.Data.Time.Date_Time_Formatter.Date_Time_Formatter import project.Data.Time.Time_Of_Day.Time_Of_Day import project.Data.Time.Time_Zone.Time_Zone import project.Data.Vector.Vector @@ -1473,17 +1474,10 @@ Text.parse_json self = Json.parse self Converts text containing a date into a Date object. - Arguments: - - format: An optional format describing how to parse the text. - - Returns a `Time_Error` if `self`` cannot be parsed using the provided - `format`. + This method will return a `Time_Error` if the provided time cannot be parsed. - ? Format Syntax - A custom format string consists of one or more custom date and time format - specifiers. For example, "d MMM yyyy" will format "2011-12-03" as - "3 Dec 2011". See https://docs.oracle.com/en/java/javase/18/docs/api/java.base/java/time/format/DateTimeFormatter.html - for a complete format specification. + Arguments: + - format: The format to use for parsing the input text. ? Default Date Formatting Unless you provide a custom format, the text must represent a valid date @@ -1500,6 +1494,34 @@ Text.parse_json self = Json.parse self - Two digits for the day-of-month. This is pre-padded by zero to ensure two digits. + ? Pattern Syntax + If the pattern is provided as `Text`, it is parsed using the format + described below. See `Date_Time_Formatter` for more options. + - y: Year. The number of pattern letters determines the minimum number of + digits. + - y: The year using any number of digits. + - yy: The year, using at most two digits. The default range is + 1950-2049, but this can be changed by including the end year in + braces e.g. `yy{2099}`. + - yyyy: The year, using exactly four digits. + - M: Month of year. The number of pattern letters determines the format: + - M: Any number (1-12). + - MM: Month number with zero padding required (01-12). + - MMM: Short name of the month (Jan-Dec). + - MMMM: Full name of the month (January-December). + The month names depend on the selected locale. + - d: Day. The number of pattern letters determines the format: + - d: Any number (1-31). + - dd: Day number with zero padding required (01-31). + - ddd: Short name of the day of week (Mon-Sun). + - dddd: Full name of the day of week (Monday-Sunday). + The weekday names depend on the selected locale. + Both day of week and day of month may be included in a single pattern - + in such case the day of week is used as a sanity check. + - Q: Quarter of year. + If only year and quarter are provided in the pattern, when parsing a + date, the result will be the first day of that quarter. + > Example Parse the date of 23rd December 2020. @@ -1533,9 +1555,9 @@ Text.parse_json self = Json.parse self date = "1999-1-1".parse_date "yyyy-MM-dd" date.catch Time_Error (_->Date.new 2000 1 1) @format make_date_format_selector -@locale Locale.default_widget -Text.parse_date : Text -> Locale -> Date ! Time_Error -Text.parse_date self format:Text="" locale:Locale=Locale.default = Date.parse self format locale +Text.parse_date : Date_Time_Formatter -> Date ! Time_Error +Text.parse_date self format:Date_Time_Formatter=Date_Time_Formatter.iso_date = + Date.parse self format ## ALIAS date_time from text GROUP Conversions @@ -1543,22 +1565,17 @@ Text.parse_date self format:Text="" locale:Locale=Locale.default = Date.parse se Obtains an instance of `Date_Time` from a text such as "2007-12-03T10:15:30+01:00 Europe/Paris". + This method will return a `Time_Error` if the provided time cannot be parsed. + Arguments: - format: The format to use for parsing the input text. - - locale: The locale in which the format should be interpreted. - - ? Format Syntax - A custom format string consists of one or more custom date and time format - specifiers. For example, "d MMM yyyy" will format "2011-12-03" as - "3 Dec 2011". See https://docs.oracle.com/en/java/javase/18/docs/api/java.base/java/time/format/DateTimeFormatter.html - for a complete format specification. ? Default Date_Time Format - The text must represent a valid date-time as defined by the ISO-8601 - format. (See https://en.wikipedia.org/wiki/ISO_8601.) If a time zone is - present, it must be in the ISO-8601 Extended Date/Time Format (EDTF). - (See https://en.wikipedia.org/wiki/ISO_8601#EDTF.) The time zone format - consists of: + Unless you provide a custom format, the text must represent a valid + date-time as defined by the ISO-8601 format (see https://en.wikipedia.org/wiki/ISO_8601). + If a time zone is present, it must be in the ISO-8601 Extended Date/Time + Format (EDTF) (see https://en.wikipedia.org/wiki/ISO_8601#EDTF). The time + zone format consists of: - The ISO offset date time. - If the zone ID is not available or is a zone offset then the format is @@ -1568,8 +1585,45 @@ Text.parse_date self format:Text="" locale:Locale=Locale.default = Date.parse se sensitive. - A close square bracket ']'. - This method will return a `Time_Error` if the provided time cannot be parsed - using the above format. + ? Pattern Syntax + If the pattern is provided as `Text`, it is parsed using the format + described below. See `Date_Time_Formatter` for more options. + - y: Year. The number of pattern letters determines the minimum number of + digits. + - y: The year using any number of digits. + - yy: The year, using at most two digits. The default range is + 1950-2049, but this can be changed by including the end year in + braces e.g. `yy{2099}`. + - yyyy: The year, using exactly four digits. + - M: Month of year. The number of pattern letters determines the format: + - M: Any number (1-12). + - MM: Month number with zero padding required (01-12). + - MMM: Short name of the month (Jan-Dec). + - MMMM: Full name of the month (January-December). + The month names depend on the selected locale. + - d: Day. The number of pattern letters determines the format: + - d: Any number (1-31). + - dd: Day number with zero padding required (01-31). + - ddd: Short name of the day of week (Mon-Sun). + - dddd: Full name of the day of week (Monday-Sunday). + The weekday names depend on the selected locale. + Both day of week and day of month may be included in a single pattern - + in such case the day of week is used as a sanity check. + - Q: Quarter of year. + If only year and quarter are provided in the pattern, when parsing a + date, the result will be the first day of that quarter. + - H: 24h hour of day (0-23). + - h: 12h hour of day (0-12). The `a` pattern is needed to disambiguate + between AM and PM. + - m: Minute of hour. + - s: Second of minute. + - f: Fractional part of the second. The number of pattern letters + determines the number of digits. If one letter is used, any number of + digits will be accepted. + - a: AM/PM marker. + - T: If repeated 3 or less times - Time zone ID (e.g. Europe/Warsaw, Z, + -08:30), otherwise - Time zone name (e.g. Central European Time, CET). + - Z: Zone offset (e.g. +0000, -0830, +08:30:15). > Example Parse UTC time. @@ -1621,31 +1675,24 @@ Text.parse_date self format:Text="" locale:Locale=Locale.default = Date.parse se example_parse = "06 of May 2020 at 04:30AM".parse_date_time "dd 'of' MMMM yyyy 'at' hh:mma" @format make_date_time_format_selector -@locale Locale.default_widget -Text.parse_date_time : Text -> Locale -> Date_Time ! Time_Error -Text.parse_date_time self format:Text="" locale:Locale=Locale.default = Date_Time.parse self format locale +Text.parse_date_time : Date_Time_Formatter -> Date_Time ! Time_Error +Text.parse_date_time self format:Date_Time_Formatter=Date_Time_Formatter.default_enso_zoned_date_time = + Date_Time.parse self format ## ALIAS time_of_day from text, to_time_of_day GROUP Conversions Obtains an instance of `Time_Of_Day` from a text such as "10:15". + This method will return a `Time_Error` if the provided time cannot be parsed. + Arguments: - format: The format to use for parsing the input text. - - locale: The locale in which the format should be interpreted. - - Returns a `Time_Error` if the provided text cannot be parsed using the - default format. - - ? Format Syntax - A custom format string consists of one or more custom date and time format - specifiers. For example, "d MMM yyyy" will format "2011-12-03" as - "3 Dec 2011". See https://docs.oracle.com/en/java/javase/18/docs/api/java.base/java/time/format/DateTimeFormatter.html - for a complete format specification. ? Default Time Format - The text must represent a valid time and is parsed using the ISO-8601 - extended local time format. The format consists of: + Unless you provide a custom format, the text must represent a valid time + and is parsed using the ISO-8601 extended local time format. + The format consists of: - Two digits for the hour-of-day. This is pre-padded by zero to ensure two digits. @@ -1662,6 +1709,19 @@ Text.parse_date_time self format:Text="" locale:Locale=Locale.default = Date_Tim - One to nine digits for the nano-of-second. As many digits will be output as required. + ? Pattern Syntax + If the pattern is provided as `Text`, it is parsed using the format + described below. See `Date_Time_Formatter` for more options. + - H: 24h hour of day (0-23). + - h: 12h hour of day (0-12). The `a` pattern is needed to disambiguate + between AM and PM. + - m: Minute of hour. + - s: Second of minute. + - f: Fractional part of the second. The number of pattern letters + determines the number of digits. If one letter is used, any number of + digits will be accepted. + - a: AM/PM marker. + > Example Get the time 15:05:30. @@ -1692,9 +1752,9 @@ Text.parse_date_time self format:Text="" locale:Locale=Locale.default = Date_Tim example_parse = "4:30AM".parse_time_of_day "h:mma" @format make_time_format_selector -@locale Locale.default_widget -Text.parse_time_of_day : Text -> Locale -> Time_Of_Day ! Time_Error -Text.parse_time_of_day self format:Text="" locale:Locale=Locale.default = Time_Of_Day.parse self format locale +Text.parse_time_of_day : Date_Time_Formatter -> Time_Of_Day ! Time_Error +Text.parse_time_of_day self format:Date_Time_Formatter=Date_Time_Formatter.iso_time = + Time_Of_Day.parse self format ## ALIAS time_zone from text, to_time_zone GROUP Conversions From ce3de1717a8bce8ae9eabd74b96c320f69f99ad8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Wed, 20 Sep 2023 01:26:48 +0200 Subject: [PATCH 34/76] fixing tests --- test/Tests/src/Data/Time/Date_Spec.enso | 44 +++---------------- .../Data/Time/Date_Time_Formatter_Spec.enso | 20 +++++++++ test/Tests/src/Data/Time/Date_Time_Spec.enso | 6 +-- test/Tests/src/Data/Time/Spec.enso | 2 + .../Tests/src/Data/Time/Time_Of_Day_Spec.enso | 6 +-- test/Tests/src/Data/Time/Time_Zone_Spec.enso | 2 +- 6 files changed, 36 insertions(+), 44 deletions(-) create mode 100644 test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso diff --git a/test/Tests/src/Data/Time/Date_Spec.enso b/test/Tests/src/Data/Time/Date_Spec.enso index 46d58cafe856..4e8da39738c7 100644 --- a/test/Tests/src/Data/Time/Date_Spec.enso +++ b/test/Tests/src/Data/Time/Date_Spec.enso @@ -30,7 +30,7 @@ spec_with name create_new_date parse_date = Test.specify "should handle errors when creating local date" <| case create_new_date 2020 30 30 . catch of - Time_Error.Error msg -> + Time_Error.Error msg _ -> msg . should_equal "Invalid value for MonthOfYear (valid values 1 - 12): 30" result -> Test.fail ("Unexpected result: " + result.to_text) @@ -39,15 +39,11 @@ spec_with name create_new_date parse_date = text = create_new_date 2020 12 21 . format "yyyyMMdd" text . should_equal "20201221" - Test.specify "should format local date using provided pattern and US locale" <| - d = create_new_date 2020 6 21 - d.format "d. MMM yyyy" . should_equal "21. Jun 2020" - d.format "d. MMMM yyyy" . should_equal "21. June 2020" - Test.specify "should format local date using provided pattern and locale" <| d = create_new_date 2020 6 21 - d.format "d. MMMM yyyy" (Locale.new "gb") . should_equal "21. Jun 2020" - d.format "d. MMMM yyyy" (Locale.new "fr") . should_equal "21. juin 2020" + d.format "d. MMMM yyyy" . should_equal "21. Jun 2020" + d.format (Date_Time_Formatter.from "d. MMMM yyyy" locale=(Locale.uk)) . should_equal "21. June 2020" + d.format (Date_Time_Formatter.from "d. MMMM yyyy" locale=(Locale.france)) . should_equal "21. juin 2020" Test.specify "should format local date using default pattern" <| text = create_new_date 2020 12 21 . to_text @@ -62,8 +58,8 @@ spec_with name create_new_date parse_date = Test.specify "should throw error when parsing invalid date" <| case parse_date "birthday" . catch of - Time_Error.Error msg -> - msg . should_equal "Text 'birthday' could not be parsed at index 0" + Time_Error.Error msg _ -> + msg . should_contain "Text 'birthday' could not be parsed" result -> Test.fail ("Unexpected result: " + result.to_text) @@ -73,32 +69,6 @@ spec_with name create_new_date parse_date = date . month . should_equal 1 date . day . should_equal 1 - Test.specify "should parse custom format" <| - date = parse_date "1999 1 1" "yyyy M d" - date . year . should_equal 1999 - date . month . should_equal 1 - date . day . should_equal 1 - - Test.specify "should parse text month formats" <| - date = parse_date "1999 Jan 1" "yyyy MMM d" - date . year . should_equal 1999 - date . month . should_equal 1 - date . day . should_equal 1 - - Test.specify "should parse text long month formats" <| - date = parse_date "1999 January 1" "yyyy MMMM d" - date . year . should_equal 1999 - date . month . should_equal 1 - date . day . should_equal 1 - - Test.specify "should throw error when parsing custom format" <| - date = parse_date "1999-01-01" "yyyy M d" - case date.catch of - Time_Error.Error msg -> - msg . should_equal "Text '1999-01-01' could not be parsed at index 4" - result -> - Test.fail ("Unexpected result: " + result.to_text) - Test.specify "should convert to time" <| time = create_new_date 2000 12 21 . to_date_time (Time_Of_Day.new 12 30 45) Time_Zone.utc time . year . should_equal 2000 @@ -555,7 +525,7 @@ main = Test_Suite.run_main spec parseNormally x y = (Date.parse x y) . to_text -js_parse text format="" = +js_parse text format=Date_Time_Formatter.iso_date = d = Date.parse text format js_date d.year d.month d.day diff --git a/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso b/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso new file mode 100644 index 000000000000..3bcbfe6b9ad0 --- /dev/null +++ b/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso @@ -0,0 +1,20 @@ +from Standard.Base import all +import Standard.Base.Errors.Time_Error.Time_Error + +from Standard.Test import Test, Test_Suite +import Standard.Test.Extensions + +spec = + Test.group "Formatting date/time values" <| + Test.specify "should allow printing month names" <| + d = Date.new 2020 6 30 + d.format "d. MMM yyyy" . should_equal "30. Jun 2020" + d.format (Date_Time_Formatter.from "d. MMM yyyy" Locale.us) . should_equal "30. June 2020" + # Note that the default (ROOT) locale returns a short name even for MMMM (full name) month format. + d.format (Date_Time_Formatter.from "d. MMM yyyy" Locale.default) . should_equal "30. Jun 2020" + Test.group "Parsing date/time values" <| + Test.specify "should allow short month names" <| + Date.parse "30. Jun 2020" "d. MMM yyyy" . should_equal (Date.new 2020 6 30) + + Test.specify "should allow long month names" <| + Date.parse "30. June 2020" (Date_Time_Formatter.from "d. MMMM yyyy" Locale.uk) . should_equal (Date.new 2020 6 30) diff --git a/test/Tests/src/Data/Time/Date_Time_Spec.enso b/test/Tests/src/Data/Time/Date_Time_Spec.enso index b16292e6db02..bd8e7f22e94a 100644 --- a/test/Tests/src/Data/Time/Date_Time_Spec.enso +++ b/test/Tests/src/Data/Time/Date_Time_Spec.enso @@ -67,7 +67,7 @@ spec_with name create_new_datetime parse_datetime nanoseconds_loss_in_precision= Test.specify "should handle errors when creating time" <| case create_new_datetime 1970 0 0 . catch of - Time_Error.Error msg -> + Time_Error.Error msg _ -> msg.to_text . contains "0" . should_be_true msg.to_text . contains "1" . should_be_true msg.to_text . contains "12" . should_be_true @@ -180,7 +180,7 @@ spec_with name create_new_datetime parse_datetime nanoseconds_loss_in_precision= Test.specify "should throw error when parsing invalid time" <| case parse_datetime "2008-1-1" . catch of - Time_Error.Error msg -> + Time_Error.Error msg _ -> msg . should_equal "Text '2008-1-1' could not be parsed at index 5" result -> Test.fail ("Unexpected result: " + result.to_text) @@ -213,7 +213,7 @@ spec_with name create_new_datetime parse_datetime nanoseconds_loss_in_precision= Test.specify "should throw error when parsing custom format" <| time = parse_datetime "2008-01-01" "yyyy-MM-dd'T'HH:mm:ss'['z']'" case time.catch of - Time_Error.Error msg -> + Time_Error.Error msg _ -> msg . should_equal "Text '2008-01-01' could not be parsed at index 10" result -> Test.fail ("Unexpected result: " + result.to_text) diff --git a/test/Tests/src/Data/Time/Spec.enso b/test/Tests/src/Data/Time/Spec.enso index 1322d2dd627c..7cf4ce3d4c35 100644 --- a/test/Tests/src/Data/Time/Spec.enso +++ b/test/Tests/src/Data/Time/Spec.enso @@ -9,6 +9,7 @@ import project.Data.Time.Time_Of_Day_Spec import project.Data.Time.Date_Spec import project.Data.Time.Date_Range_Spec import project.Data.Time.Date_Time_Spec +import project.Data.Time.Date_Time_Formatter_Spec import project.Data.Time.Time_Zone_Spec import project.Data.Time.Day_Of_Week_Spec @@ -19,6 +20,7 @@ spec = Period_Spec.spec Time_Of_Day_Spec.spec Date_Time_Spec.spec + Date_Time_Formatter_Spec.spec Time_Zone_Spec.spec Day_Of_Week_Spec.spec diff --git a/test/Tests/src/Data/Time/Time_Of_Day_Spec.enso b/test/Tests/src/Data/Time/Time_Of_Day_Spec.enso index a26675b5d738..88da4f891f53 100644 --- a/test/Tests/src/Data/Time/Time_Of_Day_Spec.enso +++ b/test/Tests/src/Data/Time/Time_Of_Day_Spec.enso @@ -28,7 +28,7 @@ specWith name create_new_time parse_time nanoseconds_loss_in_precision=False = Test.specify "should handle errors when creating a time" <| case create_new_time 24 0 0 . catch of - Time_Error.Error msg -> + Time_Error.Error msg _ -> msg.to_text . contains "24" . should_not_equal -1 result -> Test.fail ("Unexpected result: " + result.to_text) @@ -65,7 +65,7 @@ specWith name create_new_time parse_time nanoseconds_loss_in_precision=False = Test.specify "should throw error when parsing invalid time" <| case parse_time "1200" . catch of - Time_Error.Error msg -> + Time_Error.Error msg _ -> msg . should_equal "Text '1200' could not be parsed at index 2" result -> Test.fail ("Unexpected result: " + result.to_text) @@ -77,7 +77,7 @@ specWith name create_new_time parse_time nanoseconds_loss_in_precision=False = Test.specify "should throw error when parsing custom format" <| time = parse_time "12:30" "HH:mm:ss" case time.catch of - Time_Error.Error msg -> + Time_Error.Error msg _ -> msg . should_equal "Text '12:30' could not be parsed at index 5" result -> Test.fail ("Unexpected result: " + result.to_text) diff --git a/test/Tests/src/Data/Time/Time_Zone_Spec.enso b/test/Tests/src/Data/Time/Time_Zone_Spec.enso index b05fa31c235c..46db0e86a179 100644 --- a/test/Tests/src/Data/Time/Time_Zone_Spec.enso +++ b/test/Tests/src/Data/Time/Time_Zone_Spec.enso @@ -37,7 +37,7 @@ spec = JS_Object.from_pairs [["type", "Time_Zone"], ["constructor", "new"], ["id", "UTC"]] . to_text Test.specify "should throw error when parsing invalid zone id" <| case Time_Zone.parse "foo" . catch of - Time_Error.Error msg -> + Time_Error.Error msg _ -> msg . should_equal "Unknown time-zone ID: foo" result -> Test.fail ("Unexpected result: " + result.to_text) From 79aa3708d14a1bae19d55294fb7302ba1c20be11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Wed, 20 Sep 2023 01:37:56 +0200 Subject: [PATCH 35/76] more fixes --- .../Base/0.0.0-dev/src/Data/Time/Date_Time.enso | 4 ++-- test/Tests/src/Data/Time/Date_Spec.enso | 2 +- .../Tests/src/Data/Time/Date_Time_Formatter_Spec.enso | 4 ++-- test/Tests/src/Data/Time/Date_Time_Spec.enso | 8 ++++---- test/Tests/src/Data/Time/Time_Of_Day_Spec.enso | 11 ++++------- 5 files changed, 13 insertions(+), 16 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso index 045c0353e572..7e34d3b110b3 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso @@ -744,7 +744,7 @@ type Date_Time to_display_text : Text to_display_text self = time_format = if self.nanosecond include_milliseconds=True == 0 then "HH:mm:ss" else "HH:mm:ss.n" - self.format "yyyy-MM-dd "+time_format+" VV" + self.format "yyyy-MM-dd "+time_format+" tt" ## PRIVATE Convert to a JavaScript Object representing a Date_Time. @@ -813,7 +813,7 @@ type Date_Time from Standard.Base import Date_Time example_format = - Date_Time.parse "2020-10-08T16:41:13+03:00[Europe/Moscow]" . format "yyyy-MM-dd'T'HH:mm:ssZZZZ'['VV']'" + Date_Time.parse "2020-10-08T16:41:13+03:00[Europe/Moscow]" . format "yyyy-MM-dd'T'HH:mm:ssZZZZ'['tt']'" > Example Format "2020-10-08T16:41:13+03:00[Europe/Moscow]" as diff --git a/test/Tests/src/Data/Time/Date_Spec.enso b/test/Tests/src/Data/Time/Date_Spec.enso index 4e8da39738c7..e31409feb868 100644 --- a/test/Tests/src/Data/Time/Date_Spec.enso +++ b/test/Tests/src/Data/Time/Date_Spec.enso @@ -560,7 +560,7 @@ python_date year month=1 day=1 = "Invalid value for MonthOfYear (valid values 1 - 12): " + month.to_text Error.throw <| Time_Error.Error msg -python_parse text format="" = +python_parse text format=Date_Time_Formatter.iso_date = d = Date.parse text format python_date d.year d.month d.day diff --git a/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso b/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso index 3bcbfe6b9ad0..6d253632eb6c 100644 --- a/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso +++ b/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso @@ -9,9 +9,9 @@ spec = Test.specify "should allow printing month names" <| d = Date.new 2020 6 30 d.format "d. MMM yyyy" . should_equal "30. Jun 2020" - d.format (Date_Time_Formatter.from "d. MMM yyyy" Locale.us) . should_equal "30. June 2020" + d.format (Date_Time_Formatter.from "d. MMMM yyyy" Locale.us) . should_equal "30. June 2020" # Note that the default (ROOT) locale returns a short name even for MMMM (full name) month format. - d.format (Date_Time_Formatter.from "d. MMM yyyy" Locale.default) . should_equal "30. Jun 2020" + d.format (Date_Time_Formatter.from "d. MMMM yyyy" Locale.default) . should_equal "30. Jun 2020" Test.group "Parsing date/time values" <| Test.specify "should allow short month names" <| Date.parse "30. Jun 2020" "d. MMM yyyy" . should_equal (Date.new 2020 6 30) diff --git a/test/Tests/src/Data/Time/Date_Time_Spec.enso b/test/Tests/src/Data/Time/Date_Time_Spec.enso index bd8e7f22e94a..1b766a1ad0d7 100644 --- a/test/Tests/src/Data/Time/Date_Time_Spec.enso +++ b/test/Tests/src/Data/Time/Date_Time_Spec.enso @@ -80,8 +80,8 @@ spec_with name create_new_datetime parse_datetime nanoseconds_loss_in_precision= Test.specify "should format using provided pattern and locale" <| d = create_new_datetime 2020 6 21 - d.format "d. MMMM yyyy" (Locale.new "gb") . should_equal "21. Jun 2020" - d.format "d. MMMM yyyy" (Locale.new "fr") . should_equal "21. juin 2020" + d.format (Date_Time_Formatter.from "d. MMMM yyyy" (Locale.new "gb")) . should_equal "21. Jun 2020" + d.format (Date_Time_Formatter.from "d. MMMM yyyy" (Locale.new "fr")) . should_equal "21. juin 2020" Test.specify "should format using default pattern" <| text = create_new_datetime 1970 (zone = Time_Zone.utc) . to_text @@ -877,7 +877,7 @@ python_datetime year month=1 day=1 hour=0 minute=0 second=0 nanosecond=0 zone=No Panic.catch Any (python_datetime_impl year month day hour minute second nanosecond z) err-> Error.throw (Time_Error.Error err.payload) -python_parse text format="" = +python_parse text format=Date_Time_Formatter.default_enso_zoned_date_time = d = Date_Time.parse text format python_datetime d.year d.month d.day d.hour d.minute d.second (d.nanosecond include_milliseconds=True) d.zone @@ -887,7 +887,7 @@ foreign js js_local_datetime_impl year month day hour minute second nanosecond = } return new Date(year, month - 1, day, hour, minute, second, nanosecond / 1000000); -js_parse text format="" = +js_parse text format=Date_Time_Formatter.default_enso_zoned_date_time = d = Date_Time.parse text format js_datetime d.year d.month d.day d.hour d.minute d.second (d.nanosecond include_milliseconds=True) d.zone diff --git a/test/Tests/src/Data/Time/Time_Of_Day_Spec.enso b/test/Tests/src/Data/Time/Time_Of_Day_Spec.enso index 88da4f891f53..96a309ea1174 100644 --- a/test/Tests/src/Data/Time/Time_Of_Day_Spec.enso +++ b/test/Tests/src/Data/Time/Time_Of_Day_Spec.enso @@ -41,8 +41,8 @@ specWith name create_new_time parse_time nanoseconds_loss_in_precision=False = d = create_new_time 12 20 44 # Note that the results are all the same. d.format "HH:mm" . should_equal "12:20" - d.format "HH:mm" (Locale.new "gb") . should_equal "12:20" - d.format "HH:mm" (Locale.new "fr") . should_equal "12:20" + d.format (Date_Time_Formatter.from "HH:mm" (Locale.new "gb")) . should_equal "12:20" + d.format (Date_Time_Formatter.from "HH:mm" (Locale.new "fr")) . should_equal "12:20" Test.specify "should format local time using default pattern" <| text = create_new_time 12 20 44 . to_text @@ -286,11 +286,8 @@ java_parse time_text pattern=Nothing = python_time hour minute=0 second=0 nanoOfSecond=0 = Panic.catch Any (python_time_impl hour minute second nanoOfSecond) (err -> Error.throw (Time_Error.Error <| err.payload)) -python_parse time_text pattern=Nothing = - t = Panic.catch Any handler=(err -> Error.throw (Time_Error.Error err.payload.getMessage)) <| - if pattern.is_nothing then LocalTime.parse time_text else - formatter = DateTimeFormatter.ofPattern pattern - LocalTime.parse time_text (formatter.withLocale Locale.default.java_locale) +python_parse time_text pattern=Date_Time_Formatter.iso_time = + t = Time_Of_Day.parse time_text pattern python_time t.hour t.minute t.second t.nanosecond main = Test_Suite.run_main spec From a2e74a4618332478a7a2ab42be0cda9cc3254276 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Wed, 20 Sep 2023 01:56:10 +0200 Subject: [PATCH 36/76] more fixes 2 --- .../0.0.0-dev/src/Data/Time/Date_Time.enso | 1 + .../Data/Time/Date_Time_Formatter_Spec.enso | 116 +++++++++++++++ test/Tests/src/Data/Time/Date_Time_Spec.enso | 135 +----------------- 3 files changed, 119 insertions(+), 133 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso index 7e34d3b110b3..5aea383b3280 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso @@ -743,6 +743,7 @@ type Date_Time Convert to a display representation of this Date_Time. to_display_text : Text to_display_text self = + # TODO note that we are using a format that will not be parsed by our default formatters and needs custom formatter to be parsed. Is that OK? time_format = if self.nanosecond include_milliseconds=True == 0 then "HH:mm:ss" else "HH:mm:ss.n" self.format "yyyy-MM-dd "+time_format+" tt" diff --git a/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso b/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso index 6d253632eb6c..a73c8f9b81f1 100644 --- a/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso +++ b/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso @@ -18,3 +18,119 @@ spec = Test.specify "should allow long month names" <| Date.parse "30. June 2020" (Date_Time_Formatter.from "d. MMMM yyyy" Locale.uk) . should_equal (Date.new 2020 6 30) + + Test.specify "should parse default time format" <| + text = Date_Time.new 1970 (zone = Time_Zone.utc) . to_text + time = Date_Time.parse text + time . year . should_equal 1970 + time . month . should_equal 1 + time . day . should_equal 1 + time . hour . should_equal 0 + time . minute . should_equal 0 + time . second . should_equal 0 + time . nanosecond . should_equal 0 + time . zone . zone_id . should_equal Time_Zone.utc.zone_id + + Test.specify "should parse local time adding system zone" <| + time = Date_Time.parse "1970-01-01T00:00:01" + time . year . should_equal 1970 + time . month . should_equal 1 + time . day . should_equal 1 + time . hour . should_equal 0 + time . minute . should_equal 0 + time . second . should_equal 1 + time . nanosecond . should_equal 0 + (time.zone.offset time) . should_equal (Time_Zone.system.offset time) + + Test.specify "should parse time Z" <| + time = Date_Time.parse "1582-10-15T00:00:01Z" + time . to_enso_epoch_seconds . should_equal 1 + time.zone.zone_id . should_equal "Z" + + Test.specify "should parse time UTC" <| + time = Date_Time.parse "1582-10-15T00:00:01Z[UTC]" + time . to_enso_epoch_seconds . should_equal 1 + time . zone . zone_id . should_equal "UTC" + + Test.specify "should parse time with nanoseconds" <| + time = Date_Time.parse "1970-01-01T00:00:01.123456789Z" + time . year . should_equal 1970 + time . month . should_equal 1 + time . day . should_equal 1 + time . hour . should_equal 0 + time . minute . should_equal 0 + time . second . should_equal 1 + time . nanosecond include_milliseconds=True . should_equal 123456789 + time . millisecond . should_equal 123 + time . microsecond . should_equal 456 + time . nanosecond . should_equal 789 + time.zone.zone_id . should_equal "Z" + + Test.specify "should parse time with offset-based zone" <| + time = Date_Time.parse "1970-01-01T00:00:01+01:00" + time . year . should_equal 1970 + time . month . should_equal 1 + time . day . should_equal 1 + time . hour . should_equal 0 + time . minute . should_equal 0 + time . second . should_equal 1 + time . millisecond . should_equal 0 + time . microsecond . should_equal 0 + time . nanosecond . should_equal 0 + time.zone.zone_id . take (Last 6) . should_equal "+01:00" + + Test.specify "should parse time with id-based zone" <| + time = Date_Time.parse "1970-01-01T00:00:01+01:00[Europe/Paris]" + time . year . should_equal 1970 + time . month . should_equal 1 + time . day . should_equal 1 + time . hour . should_equal 0 + time . minute . should_equal 0 + time . second . should_equal 1 + time . millisecond . should_equal 0 + time . microsecond . should_equal 0 + time . nanosecond . should_equal 0 + zone = time.zone + zone.offset time . should_equal 3600 + zone.zone_id . should_equal "Europe/Paris" + time.to_display_text . should_equal "1970-01-01 00:00:01 Europe/Paris" + + Test.specify "should throw error when parsing invalid time" <| + case Date_Time.parse "2008-1-1" . catch of + Time_Error.Error msg _ -> + msg . should_equal "Text '2008-1-1' could not be parsed at index 5" + result -> + Test.fail ("Unexpected result: " + result.to_text) + + Test.specify "should parse custom format of zoned time" <| + time = Date_Time.parse "2020-05-06 04:30:20 UTC" "yyyy-MM-dd HH:mm:ss tt" + time . year . should_equal 2020 + time . month . should_equal 5 + time . day . should_equal 6 + time . hour . should_equal 4 + time . minute . should_equal 30 + time . second . should_equal 20 + time . millisecond . should_equal 0 + time . microsecond . should_equal 0 + time . nanosecond . should_equal 0 + (time.zone.zone_id . take (Last 3) . to_case Case.Upper) . should_equal "UTC" + + Test.specify "should parse custom format of local time" <| + time = Date_Time.parse "06 of May 2020 at 04:30AM" "dd 'of' MMMM yyyy 'at' hh:mma" + time . year . should_equal 2020 + time . month . should_equal 5 + time . day . should_equal 6 + time . hour . should_equal 4 + time . minute . should_equal 30 + time . second . should_equal 0 + time . millisecond . should_equal 0 + time . microsecond . should_equal 0 + time . nanosecond . should_equal 0 + + Test.specify "should throw error when parsing custom format" <| + time = Date_Time.parse "2008-01-01" "yyyy-MM-dd'T'HH:mm:ss'['tt']'" + case time.catch of + Time_Error.Error msg _ -> + msg . should_equal "Text '2008-01-01' could not be parsed at index 10" + result -> + Test.fail ("Unexpected result: " + result.to_text) diff --git a/test/Tests/src/Data/Time/Date_Time_Spec.enso b/test/Tests/src/Data/Time/Date_Time_Spec.enso index 1b766a1ad0d7..70d0868220f3 100644 --- a/test/Tests/src/Data/Time/Date_Time_Spec.enso +++ b/test/Tests/src/Data/Time/Date_Time_Spec.enso @@ -21,10 +21,7 @@ spec = spec_with "Date_Time" enso_datetime Date_Time.parse spec_with "JavascriptDate" js_datetime js_parse nanoseconds_loss_in_precision=True if Polyglot.is_language_installed "python" then - ignore_z_check a b = - if a == 'Z' || b == 'Z' then True else - a.should_equal frames_to_skip=2 b - spec_with "PythonDate" python_datetime python_parse nanoseconds_loss_in_precision=True loose_zone_equal=ignore_z_check + spec_with "PythonDate" python_datetime python_parse nanoseconds_loss_in_precision=True spec_with "JavaZonedDateTime" java_datetime java_parse spec_with "JavascriptDataInArray" js_array_datetime js_parse nanoseconds_loss_in_precision=True @@ -47,11 +44,7 @@ spec = (Date_Time.new 2022 12 12).should_equal (Date_Time.new 2022 12 12) (Date_Time.new 2022 12 12).should_not_equal (Date_Time.new 1996) -default_zone_equal z1 z2 = - z1 . should_equal frames_to_skip=1 z2 - False - -spec_with name create_new_datetime parse_datetime nanoseconds_loss_in_precision=False loose_zone_equal=default_zone_equal = +spec_with name create_new_datetime parse_datetime nanoseconds_loss_in_precision=False = Test.group name <| Test.specify "should create time" <| @@ -94,130 +87,6 @@ spec_with name create_new_datetime parse_datetime nanoseconds_loss_in_precision= time_pairs = [["year", 1970], ["month", 12], ["day", 21], ["hour", 0], ["minute", 0], ["second", 0], ["nanosecond", 0]] JS_Object.from_pairs ([["type", "Date_Time"], ["constructor", "new"]] + time_pairs + zone_pairs) . to_text - Test.specify "should parse default time format" <| - text = create_new_datetime 1970 (zone = Time_Zone.utc) . to_text - time = parse_datetime text - time . year . should_equal 1970 - time . month . should_equal 1 - time . day . should_equal 1 - time . hour . should_equal 0 - time . minute . should_equal 0 - time . second . should_equal 0 - time . nanosecond . should_equal 0 - time . zone . zone_id . should_equal Time_Zone.utc.zone_id - - Test.specify "should parse local time adding system zone" <| - time = parse_datetime "1970-01-01T00:00:01" - time . year . should_equal 1970 - time . month . should_equal 1 - time . day . should_equal 1 - time . hour . should_equal 0 - time . minute . should_equal 0 - time . second . should_equal 1 - time . nanosecond . should_equal 0 - (time.zone.offset time) . should_equal (Time_Zone.system.offset time) - - Test.specify "should parse time Z" <| - time = parse_datetime "1582-10-15T00:00:01Z" - time . to_enso_epoch_seconds . should_equal 1 - loose_zone_equal time.zone.zone_id "Z" - - Test.specify "should parse time UTC" <| - time = parse_datetime "1582-10-15T00:00:01Z[UTC]" - time . to_enso_epoch_seconds . should_equal 1 - time . zone . zone_id . should_equal "UTC" - - Test.specify "should parse time with nanoseconds" <| - time = parse_datetime "1970-01-01T00:00:01.123456789Z" - time . year . should_equal 1970 - time . month . should_equal 1 - time . day . should_equal 1 - time . hour . should_equal 0 - time . minute . should_equal 0 - time . second . should_equal 1 - case nanoseconds_loss_in_precision of - True -> - time . nanosecond include_milliseconds=True . should_equal 123000000 - time . millisecond . should_equal 123 - time . microsecond . should_equal 0 - time . nanosecond . should_equal 0 - False -> - time . nanosecond include_milliseconds=True . should_equal 123456789 - time . millisecond . should_equal 123 - time . microsecond . should_equal 456 - time . nanosecond . should_equal 789 - loose_zone_equal time.zone.zone_id "Z" - - Test.specify "should parse time with offset-based zone" <| - time = parse_datetime "1970-01-01T00:00:01+01:00" - time . year . should_equal 1970 - time . month . should_equal 1 - time . day . should_equal 1 - time . hour . should_equal 0 - time . minute . should_equal 0 - time . second . should_equal 1 - time . millisecond . should_equal 0 - time . microsecond . should_equal 0 - time . nanosecond . should_equal 0 - time.zone.zone_id . take (Last 6) . should_equal "+01:00" - - Test.specify "should parse time with id-based zone" <| - time = parse_datetime "1970-01-01T00:00:01+01:00[Europe/Paris]" - time . year . should_equal 1970 - time . month . should_equal 1 - time . day . should_equal 1 - time . hour . should_equal 0 - time . minute . should_equal 0 - time . second . should_equal 1 - time . millisecond . should_equal 0 - time . microsecond . should_equal 0 - time . nanosecond . should_equal 0 - zone = time.zone - zone.offset time . should_equal 3600 - if name.contains "Python" then time.zone.zone_id . should_equal "UTC+01:00" else - zone.zone_id . should_equal "Europe/Paris" - time.to_display_text . should_equal "1970-01-01 00:00:01 Europe/Paris" - - Test.specify "should throw error when parsing invalid time" <| - case parse_datetime "2008-1-1" . catch of - Time_Error.Error msg _ -> - msg . should_equal "Text '2008-1-1' could not be parsed at index 5" - result -> - Test.fail ("Unexpected result: " + result.to_text) - - Test.specify "should parse custom format of zoned time" <| - time = parse_datetime "2020-05-06 04:30:20 UTC" "yyyy-MM-dd HH:mm:ss z" - time . year . should_equal 2020 - time . month . should_equal 5 - time . day . should_equal 6 - time . hour . should_equal 4 - time . minute . should_equal 30 - time . second . should_equal 20 - time . millisecond . should_equal 0 - time . microsecond . should_equal 0 - time . nanosecond . should_equal 0 - (time.zone.zone_id . take (Last 3) . to_case Case.Upper) . should_equal "UTC" - - Test.specify "should parse custom format of local time" <| - time = parse_datetime "06 of May 2020 at 04:30AM" "dd 'of' MMMM yyyy 'at' hh:mma" - time . year . should_equal 2020 - time . month . should_equal 5 - time . day . should_equal 6 - time . hour . should_equal 4 - time . minute . should_equal 30 - time . second . should_equal 0 - time . millisecond . should_equal 0 - time . microsecond . should_equal 0 - time . nanosecond . should_equal 0 - - Test.specify "should throw error when parsing custom format" <| - time = parse_datetime "2008-01-01" "yyyy-MM-dd'T'HH:mm:ss'['z']'" - case time.catch of - Time_Error.Error msg _ -> - msg . should_equal "Text '2008-01-01' could not be parsed at index 10" - result -> - Test.fail ("Unexpected result: " + result.to_text) - Test.specify "should get Enso epoch seconds" <| (create_new_datetime 1582 10 15 0 0 8 (zone = Time_Zone.utc)).to_enso_epoch_seconds . should_equal 8 (Date_Time.enso_epoch_start + (Duration.new minutes=2)).to_enso_epoch_seconds . should_equal (2 * 60) From 7be177075b1df66dc33f97fe1192d43aff34b868 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Wed, 20 Sep 2023 02:08:12 +0200 Subject: [PATCH 37/76] resolve Date_Time_Formatter conversion at Text Extensions definition site (missing import) --- .../lib/Standard/Base/0.0.0-dev/src/Data/Text/Extensions.enso | 1 + 1 file changed, 1 insertion(+) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Text/Extensions.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Text/Extensions.enso index bf07a4e15c76..28c71fea6532 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Text/Extensions.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Text/Extensions.enso @@ -36,6 +36,7 @@ from project.Data.Boolean import Boolean, False, True from project.Data.Json import Invalid_JSON, JS_Object, Json from project.Data.Numbers import Float, Integer, Number, Number_Parse_Error from project.Data.Range.Extensions import all +from project.Data.Time.Conversions import all from project.Widget_Helpers import make_date_format_selector, make_date_time_format_selector, make_delimiter_selector, make_regex_text_widget, make_time_format_selector polyglot java import com.ibm.icu.lang.UCharacter From c917b2ea806b53aa60b9674d51f163c085aba0da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Wed, 20 Sep 2023 02:41:26 +0200 Subject: [PATCH 38/76] Make default format without 'T' but keep it flexible on parsing --- .../src/Data/Time/Date_Time_Formatter.enso | 21 ++++---- .../common_utils/Core_Date_Utils.java | 7 ++- .../main/java/org/enso/base/Time_Utils.java | 12 ----- .../enso/base/time/EnsoDateTimeFormatter.java | 54 +++++++++++++------ .../expressions/ExpressionVisitorImpl.java | 2 +- 5 files changed, 54 insertions(+), 42 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso index ebb84aeb26c7..7fc7b47c8935 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso @@ -15,7 +15,6 @@ import project.Internal.Time.Format.As_Java_Formatter_Interpreter polyglot java import java.lang.Exception as JException polyglot java import java.time.format.DateTimeFormatter -polyglot java import org.enso.base.Time_Utils polyglot java import org.enso.base.time.EnsoDateTimeFormatter polyglot java import org.enso.base.time.FormatterKind @@ -108,7 +107,7 @@ type Date_Time_Formatter from_simple_pattern pattern:Text locale:Locale=Locale.default = java_formatter = Tokenizer.tokenize pattern |> Parser.parse_simple_date_pattern |> As_Java_Formatter_Interpreter.interpret locale - Date_Time_Formatter.Value (EnsoDateTimeFormatter.new java_formatter False pattern FormatterKind.SIMPLE) + Date_Time_Formatter.Value (EnsoDateTimeFormatter.new java_formatter pattern FormatterKind.SIMPLE) ## Creates a formatter from a pattern for the ISO 8601 leap week calendar. @@ -153,7 +152,7 @@ type Date_Time_Formatter from_iso_week_date_pattern pattern:Text locale:Locale=Locale.default = java_formatter = Tokenizer.tokenize pattern |> Parser.parse_iso_week_year_pattern |> As_Java_Formatter_Interpreter.interpret locale - Date_Time_Formatter.Value (EnsoDateTimeFormatter.new java_formatter False pattern FormatterKind.ISO_WEEK_DATE) + Date_Time_Formatter.Value (EnsoDateTimeFormatter.new java_formatter pattern FormatterKind.ISO_WEEK_DATE) ## ADVANCED Creates a formatter from a Java `DateTimeFormatter` instance or a text @@ -174,11 +173,11 @@ type Date_Time_Formatter amended_formatter = case locale of Nothing -> java_formatter _ : Locale -> java_formatter.withLocale locale.java_locale - Date_Time_Formatter.Value (EnsoDateTimeFormatter.new amended_formatter False Nothing FormatterKind.RAW_JAVA) + Date_Time_Formatter.Value (EnsoDateTimeFormatter.new amended_formatter Nothing FormatterKind.RAW_JAVA) _ : Text -> java_locale = (locale.if_nothing Locale.default).java_locale java_formatter = DateTimeFormatter.ofPattern pattern java_locale - Date_Time_Formatter.Value (EnsoDateTimeFormatter.new java_formatter False pattern FormatterKind.RAW_JAVA) + Date_Time_Formatter.Value (EnsoDateTimeFormatter.new java_formatter pattern FormatterKind.RAW_JAVA) # TODO Locale for constants?? @@ -188,21 +187,21 @@ type Date_Time_Formatter For example, it may parse date of the form `2011-12-03 10:15:30+01:00[Europe/Paris]`, as well as `2011-12-03T10:15:30` assuming the default timezone. default_enso_zoned_date_time = - Date_Time_Formatter.Value Time_Utils.default_date_time_formatter + Date_Time_Formatter.Value EnsoDateTimeFormatter.default_enso_zoned_date_time_formatter ## The ISO 8601 format for date-time with offset and timezone. The date and time parts may be separated by a single space or a `T`. For example, it may parse date of the form `2011-12-03 10:15:30+01:00[Europe/Paris]`. iso_zoned_date_time = - Date_Time_Formatter.Value (EnsoDateTimeFormatter.new DateTimeFormatter.ISO_ZONED_DATE_TIME True "iso_zoned_date_time" FormatterKind.CONSTANT) + Date_Time_Formatter.Value (EnsoDateTimeFormatter.makeISOConstant DateTimeFormatter.ISO_ZONED_DATE_TIME "iso_zoned_date_time") ## The ISO 8601 format for date-time with offset. The date and time parts may be separated by a single space or a `T`. For example, it may parse date of the form `2011-12-03 10:15:30+01:00`. iso_offset_date_time = - Date_Time_Formatter.Value (EnsoDateTimeFormatter.new DateTimeFormatter.ISO_OFFSET_DATE_TIME True "iso_offset_date_time" FormatterKind.CONSTANT) + Date_Time_Formatter.Value (EnsoDateTimeFormatter.makeISOConstant DateTimeFormatter.ISO_OFFSET_DATE_TIME "iso_offset_date_time") ## The ISO 8601 format for date-time without a timezone. The date and time parts may be separated by a single space or a `T`. @@ -210,19 +209,19 @@ type Date_Time_Formatter For example, it may parse date of the form `2011-12-03 10:15:30`. The timezone will be set to `Time_Zone.system`. iso_local_date_time = - Date_Time_Formatter.Value (EnsoDateTimeFormatter.new DateTimeFormatter.ISO_LOCAL_DATE_TIME True "iso_local_date_time" FormatterKind.CONSTANT) + Date_Time_Formatter.Value (EnsoDateTimeFormatter.makeISOConstant DateTimeFormatter.ISO_LOCAL_DATE_TIME "iso_local_date_time") ## The ISO 8601 format for date. For example, it may parse date of the form `2011-12-03`. iso_date = - Date_Time_Formatter.Value (EnsoDateTimeFormatter.new DateTimeFormatter.ISO_DATE False "iso_date" FormatterKind.CONSTANT) + Date_Time_Formatter.Value (EnsoDateTimeFormatter.makeISOConstant DateTimeFormatter.ISO_DATE "iso_date") ## The ISO 8601 format for time. For example, it may parse time of the form `10:15:30`. iso_time = - Date_Time_Formatter.Value (EnsoDateTimeFormatter.new DateTimeFormatter.ISO_TIME False "iso_time" FormatterKind.CONSTANT) + Date_Time_Formatter.Value (EnsoDateTimeFormatter.makeISOConstant DateTimeFormatter.ISO_TIME "iso_time") ## Returns a text representation of this formatter. to_text : Text diff --git a/lib/scala/common-polyglot-core-utils/src/main/java/org/enso/polyglot/common_utils/Core_Date_Utils.java b/lib/scala/common-polyglot-core-utils/src/main/java/org/enso/polyglot/common_utils/Core_Date_Utils.java index 8006be7eb120..5657f651ef8c 100644 --- a/lib/scala/common-polyglot-core-utils/src/main/java/org/enso/polyglot/common_utils/Core_Date_Utils.java +++ b/lib/scala/common-polyglot-core-utils/src/main/java/org/enso/polyglot/common_utils/Core_Date_Utils.java @@ -9,9 +9,11 @@ import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; +import java.time.chrono.IsoChronology; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; import java.time.format.DateTimeParseException; +import java.time.format.ResolverStyle; import java.time.temporal.ChronoField; import java.time.temporal.TemporalQueries; @@ -35,7 +37,10 @@ public static String normaliseISODateTime(String dateString) { /** @return default Date Time formatter for parsing a Date_Time. */ public static DateTimeFormatter defaultZonedDateTimeFormatter() { return new DateTimeFormatterBuilder() - .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME) + .parseLenient() + .append(DateTimeFormatter.ISO_LOCAL_DATE) + .appendLiteral(' ') + .append(DateTimeFormatter.ISO_LOCAL_TIME) .optionalStart() .parseLenient() .appendOffsetId() diff --git a/std-bits/base/src/main/java/org/enso/base/Time_Utils.java b/std-bits/base/src/main/java/org/enso/base/Time_Utils.java index 2fa010cbd1ff..3c55f358c42d 100644 --- a/std-bits/base/src/main/java/org/enso/base/Time_Utils.java +++ b/std-bits/base/src/main/java/org/enso/base/Time_Utils.java @@ -81,18 +81,6 @@ public static boolean is_iso_datetime_based(String format) { }; } - /** - * @return default DateTimeFormatter for parsing a Date_Time. - */ - public static EnsoDateTimeFormatter default_date_time_formatter() { - return new EnsoDateTimeFormatter( - Core_Date_Utils.defaultZonedDateTimeFormatter(), - true, - "default_enso_zoned_date_time", - FormatterKind.CONSTANT - ); - } - /** * @return default DateTimeFormatter for parsing a Date. */ diff --git a/std-bits/base/src/main/java/org/enso/base/time/EnsoDateTimeFormatter.java b/std-bits/base/src/main/java/org/enso/base/time/EnsoDateTimeFormatter.java index ca820792bf2c..0a1bd6522e3e 100644 --- a/std-bits/base/src/main/java/org/enso/base/time/EnsoDateTimeFormatter.java +++ b/std-bits/base/src/main/java/org/enso/base/time/EnsoDateTimeFormatter.java @@ -1,5 +1,8 @@ package org.enso.base.time; +import org.enso.polyglot.common_utils.Core_Date_Utils; +import org.graalvm.collections.Pair; + import java.time.DateTimeException; import java.time.LocalDate; import java.time.LocalTime; @@ -7,7 +10,6 @@ import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeParseException; import java.time.temporal.ChronoField; import java.time.temporal.TemporalQueries; @@ -16,17 +18,34 @@ public class EnsoDateTimeFormatter { private final DateTimeFormatter formatter; - private final boolean needsISOTreplaceWorkaround; + private final Pair isoReplacementPair; private final String originalPattern; private final FormatterKind formatterKind; - public EnsoDateTimeFormatter(DateTimeFormatter formatter, boolean needsISOreplaceTWorkaround, String originalPattern, FormatterKind formatterKind) { + private EnsoDateTimeFormatter(DateTimeFormatter formatter, Pair isoReplacementPair, String originalPattern, FormatterKind formatterKind) { this.formatter = formatter; - this.needsISOTreplaceWorkaround = needsISOreplaceTWorkaround; + this.isoReplacementPair = isoReplacementPair; this.originalPattern = originalPattern; this.formatterKind = formatterKind; } + public EnsoDateTimeFormatter(DateTimeFormatter formatter, String originalPattern, FormatterKind formatterKind) { + this(formatter, null, originalPattern, formatterKind); + } + + public static EnsoDateTimeFormatter makeISOConstant(DateTimeFormatter formatter, String name) { + return new EnsoDateTimeFormatter(formatter, Pair.create(' ', "T"), name, FormatterKind.CONSTANT); + } + + public static EnsoDateTimeFormatter default_enso_zoned_date_time_formatter() { + return new EnsoDateTimeFormatter( + Core_Date_Utils.defaultZonedDateTimeFormatter(), + Pair.create('T', " "), + "default_enso_zoned_date_time", + FormatterKind.CONSTANT + ); + } + public DateTimeFormatter getRawJavaFormatter() { return formatter; } @@ -39,10 +58,18 @@ public FormatterKind getFormatterKind() { return formatterKind; } - private String normaliseISODateTime(String dateString) { - if (dateString != null && dateString.length() > 10 && dateString.charAt(10) == ' ') { + private String normaliseInput(String dateString) { + if (isoReplacementPair == null) { + // Nothing to do + return dateString; + } + + char from = isoReplacementPair.getLeft(); + String to = isoReplacementPair.getRight(); + + if (dateString != null && dateString.length() > 10 && dateString.charAt(10) == from) { var builder = new StringBuilder(dateString); - builder.replace(10, 11, "T"); + builder.replace(10, 11, to); return builder.toString(); } @@ -60,9 +87,7 @@ public String toString() { } public LocalDate parseLocalDate(String dateString) { - if (needsISOTreplaceWorkaround) { - dateString = normaliseISODateTime(dateString); - } + dateString = normaliseInput(dateString); var parsed = formatter.parse(dateString); @@ -92,9 +117,7 @@ public LocalDate parseLocalDate(String dateString) { } public ZonedDateTime parseZonedDateTime(String dateString) { - if (needsISOTreplaceWorkaround) { - dateString = normaliseISODateTime(dateString); - } + dateString = normaliseInput(dateString); var resolved = formatter.parse(dateString); @@ -129,10 +152,7 @@ public ZonedDateTime parseZonedDateTime(String dateString) { } public LocalTime parseLocalTime(String text) { - if (needsISOTreplaceWorkaround) { - text = normaliseISODateTime(text); - } - + text = normaliseInput(text); return LocalTime.parse(text, formatter); } diff --git a/std-bits/table/src/main/java/org/enso/table/expressions/ExpressionVisitorImpl.java b/std-bits/table/src/main/java/org/enso/table/expressions/ExpressionVisitorImpl.java index f18d44a7ccf8..759d12f1bfff 100644 --- a/std-bits/table/src/main/java/org/enso/table/expressions/ExpressionVisitorImpl.java +++ b/std-bits/table/src/main/java/org/enso/table/expressions/ExpressionVisitorImpl.java @@ -316,7 +316,7 @@ public Value visitTime(ExpressionParser.TimeContext ctx) { } } - private static final EnsoDateTimeFormatter dateTimeFormatter = Time_Utils.default_date_time_formatter(); + private static final EnsoDateTimeFormatter dateTimeFormatter = EnsoDateTimeFormatter.default_enso_zoned_date_time_formatter(); @Override public Value visitDatetime(ExpressionParser.DatetimeContext ctx) { From 0ee8472eb617bf4bbebc6aa3fde91e115262c031 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Wed, 20 Sep 2023 02:56:19 +0200 Subject: [PATCH 39/76] fixing Data_Formatter tests and some others too --- .../src/Formatting/Data_Formatter_Spec.enso | 46 ++++--------------- .../src/Formatting/Parse_Values_Spec.enso | 11 +++-- .../Data/Time/Date_Time_Formatter_Spec.enso | 4 +- 3 files changed, 18 insertions(+), 43 deletions(-) diff --git a/test/Table_Tests/src/Formatting/Data_Formatter_Spec.enso b/test/Table_Tests/src/Formatting/Data_Formatter_Spec.enso index d2e88706262c..08448253f263 100644 --- a/test/Table_Tests/src/Formatting/Data_Formatter_Spec.enso +++ b/test/Table_Tests/src/Formatting/Data_Formatter_Spec.enso @@ -1,4 +1,5 @@ from Standard.Base import all +import Standard.Base.Data.Time.Date_Time_Formatter.Date_Time_Format_Parse_Error import Standard.Base.Errors.Illegal_Argument.Illegal_Argument import Standard.Base.Errors.Illegal_State.Illegal_State @@ -195,17 +196,17 @@ spec = Test.specify "should format dates" <| formatter = Data_Formatter.Value formatter.format (Date.new 2022) . should_equal "2022-01-01" - formatter.format (Date_Time.new 1999) . should_equal "1999-01-01 00:00:00" - formatter.format (Date_Time.new 1999 zone=Time_Zone.utc) . should_equal "1999-01-01 00:00:00" - formatter.format (Date_Time.new 1999 zone=(Time_Zone.parse "America/Los_Angeles")) . should_equal "1999-01-01 00:00:00" + formatter.format (Date_Time.new 1999) . should_contain "1999-01-01 00:00:00" + formatter.format (Date_Time.new 1999 zone=Time_Zone.utc) . should_equal '1999-01-01 00:00:00Z[UTC]' + formatter.format (Date_Time.new 1999 zone=(Time_Zone.parse "America/Los_Angeles")) . should_equal "1999-01-01 00:00:00-08:00[America/Los_Angeles]" formatter.format (Time_Of_Day.new) . should_equal "00:00:00" Test.specify "should allow custom date formats" <| - formatter = Data_Formatter.Value 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 = Data_Formatter.Value.with_datetime_formats date_formats=["ddd, d MMM y", Date_Time_Formatter.from_java "d MMM y[ G]"] datetime_formats=["dd/MM/yyyy HH:mm [zz]"] time_formats=["h:mma"] formatter.format (Date.new 2022 06 21) . should_equal "Tue, 21 Jun 2022" - formatter.format (Date_Time.new 1999 02 03 04 56 11 zone=Time_Zone.utc) . should_equal "03/02/1999 04:56 UTC" + formatter.format (Date_Time.new 1999 02 03 04 56 11 zone=Time_Zone.utc) . should_equal "03/02/1999 04:56 GMT" formatter.format (Date_Time.new 1999 02 03 04 56 11 zone=(Time_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" + formatter.format (Time_Of_Day.new 13 55) . should_equal "1:55PM" Test.specify "should act as identity on Text" <| formatter = Data_Formatter.Value @@ -237,7 +238,7 @@ spec = Test.group "DataFormatter builders" <| # We create a formatter with all non-default values to ensure that the builders keep the existing values of other properties instead of switching to the constructor's defaults. - formatter_1 = Data_Formatter.Value trim_values=False allow_leading_zeros=True decimal_point=',' thousand_separator='_' allow_exponential_notation=True datetime_formats=["yyyy/MM/dd HH:mm:ss"] date_formats=["dd/MM/yyyy"] time_formats=["HH/mm/ss"] datetime_locale=Locale.uk true_values=["YES"] false_values=["NO"] + formatter_1 = Data_Formatter.Value trim_values=False allow_leading_zeros=True decimal_point=',' thousand_separator='_' allow_exponential_notation=True datetime_formats=[Date_Time_Formatter.from "yyyy/MM/dd HH:mm:ss"] date_formats=[Date_Time_Formatter.from "dd/MM/yyyy"] time_formats=[Date_Time_Formatter.from "HH/mm/ss"] true_values=["YES"] false_values=["NO"] Test.specify "should allow changing number formatting settings" <| formatter_2 = formatter_1.with_number_formatting decimal_point="*" formatter_2.decimal_point . should_equal "*" @@ -249,7 +250,6 @@ spec = formatter_2.date_formats . should_equal formatter_1.date_formats formatter_2.datetime_formats . should_equal formatter_1.datetime_formats formatter_2.time_formats . should_equal formatter_1.time_formats - formatter_2.datetime_locale . should_equal formatter_1.datetime_locale formatter_2.trim_values . should_equal formatter_1.trim_values formatter_3 = formatter_1.with_number_formatting thousand_separator="" allow_exponential_notation=False allow_leading_zeros=False @@ -262,7 +262,7 @@ spec = formatter_1.with_datetime_formats . should_equal formatter_1 formatter_2 = formatter_1.with_datetime_formats date_formats="dd.MM.yyyy" - formatter_2.date_formats . should_equal ["dd.MM.yyyy"] + formatter_2.date_formats.to_text . should_equal [Date_Time_Formatter.from "dd.MM.yyyy"].to_text formatter_2.datetime_formats . should_equal formatter_1.datetime_formats formatter_2.time_formats . should_equal formatter_1.time_formats formatter_2.decimal_point . should_equal formatter_1.decimal_point @@ -271,35 +271,10 @@ spec = formatter_2.allow_exponential_notation . should_equal formatter_1.allow_exponential_notation formatter_2.true_values . should_equal formatter_1.true_values formatter_2.false_values . should_equal formatter_1.false_values - formatter_2.datetime_locale . should_equal formatter_1.datetime_locale formatter_2.trim_values . should_equal formatter_1.trim_values formatter_3 = formatter_1.with_datetime_formats date_formats=[] datetime_formats=["foobar"] time_formats="baz" - formatter_3.date_formats . should_equal [] - formatter_3.datetime_formats . should_equal ["foobar"] - formatter_3.time_formats . should_equal ["baz"] - formatter_3.decimal_point . should_equal formatter_1.decimal_point - formatter_3.thousand_separator . should_equal formatter_1.thousand_separator - formatter_3.allow_leading_zeros . should_equal formatter_1.allow_leading_zeros - formatter_3.allow_exponential_notation . should_equal formatter_1.allow_exponential_notation - formatter_3.true_values . should_equal formatter_1.true_values - formatter_3.false_values . should_equal formatter_1.false_values - formatter_3.datetime_locale . should_equal formatter_1.datetime_locale - formatter_3.trim_values . should_equal formatter_1.trim_values - - Test.specify "should allow changing the datetime locale" <| - formatter_2 = formatter_1.with_locale Locale.france - formatter_2.datetime_locale . should_equal Locale.france - formatter_2.date_formats . should_equal formatter_1.date_formats - formatter_2.datetime_formats . should_equal formatter_1.datetime_formats - formatter_2.time_formats . should_equal formatter_1.time_formats - formatter_2.decimal_point . should_equal formatter_1.decimal_point - formatter_2.thousand_separator . should_equal formatter_1.thousand_separator - formatter_2.allow_leading_zeros . should_equal formatter_1.allow_leading_zeros - formatter_2.allow_exponential_notation . should_equal formatter_1.allow_exponential_notation - formatter_2.true_values . should_equal formatter_1.true_values - formatter_2.false_values . should_equal formatter_1.false_values - formatter_2.trim_values . should_equal formatter_1.trim_values + formatter_3.should_fail_with Date_Time_Format_Parse_Error Test.specify "should allow changing booleans' representations" <| formatter_2 = formatter_1.with_boolean_values "1" "0" @@ -312,7 +287,6 @@ spec = formatter_2.allow_exponential_notation . should_equal formatter_1.allow_exponential_notation formatter_2.true_values . should_equal ["1"] formatter_2.false_values . should_equal ["0"] - formatter_2.datetime_locale . should_equal formatter_1.datetime_locale formatter_2.trim_values . should_equal formatter_1.trim_values formatter_3 = formatter_1.with_boolean_values false_values=[] true_values=[] diff --git a/test/Table_Tests/src/Formatting/Parse_Values_Spec.enso b/test/Table_Tests/src/Formatting/Parse_Values_Spec.enso index a3c7922902ae..78970f5c3a95 100644 --- a/test/Table_Tests/src/Formatting/Parse_Values_Spec.enso +++ b/test/Table_Tests/src/Formatting/Parse_Values_Spec.enso @@ -1,5 +1,6 @@ from Standard.Base import all import Standard.Base.Errors.Illegal_Argument.Illegal_Argument +import Standard.Base.Data.Time.Date_Time_Formatter.Date_Time_Format_Parse_Error from Standard.Table import Table, Data_Formatter, Column from Standard.Table.Data.Type.Value_Type import Value_Type, Auto @@ -91,13 +92,13 @@ spec = t8.at "dates" . to_vector . should_equal [Date.new 2022 5 7, Date.new 2001 1 1, Date.new 2010 12 31] Test.specify "should parse date and time in various formats" <| - opts = Data_Formatter.Value date_formats=["d.M.y", "d MMM y[ G]", "E, d MMM y"] datetime_formats=["yyyy-MM-dd'T'HH:mm:ss", "dd/MM/yyyy HH:mm"] time_formats=["H:mm:ss.n", "h:mma"] + opts = Data_Formatter.Value.with_datetime_formats date_formats=["d.M.y", (Date_Time_Formatter.from_java "d MMM y[ G]"), "ddd, d MMM y"] datetime_formats=["yyyy-MM-dd HH:mm:ss", "dd/MM/yyyy HH:mm"] time_formats=["H:mm:ss.f", "h:mma"] t1 = Table.new [["dates", ["1.2.476", "10 Jan 1900 AD", "Tue, 3 Jun 2008"]]] t2 = t1.parse format=opts type=Value_Type.Date t2.at "dates" . to_vector . should_equal [Date.new 476 2 1, Date.new 1900 1 10, Date.new 2008 6 3] - t3 = Table.new [["datetimes", ["2011-12-03T10:15:30", "31/12/2012 22:33"]]] + t3 = Table.new [["datetimes", ["2011-12-03 10:15:30", "31/12/2012 22:33"]]] t4 = t3.parse format=opts type=Value_Type.Date_Time t4.at "datetimes" . to_vector . should_equal [Date_Time.new 2011 12 3 10 15 30, Date_Time.new 2012 12 31 22 33] @@ -566,9 +567,9 @@ spec = Test.specify "should handle invalid format strings gracefully" <| c1 = Column.from_vector "date" ["5/7/2022", "1/1/2000", "12/31/2010"] - c1.parse type=Value_Type.Date "M/d/fqsrf" . should_fail_with Illegal_Argument - c1.parse type=Value_Type.Time "HH:mm:ss.fff" . should_fail_with Illegal_Argument - c1.parse type=Value_Type.Date_Time "M/d/fqsrf HH:mm:ss.fff" . should_fail_with Illegal_Argument + c1.parse type=Value_Type.Date "M/d/fqsrf" . should_fail_with Date_Time_Format_Parse_Error + c1.parse type=Value_Type.Time "ęęę" . should_fail_with Date_Time_Format_Parse_Error + c1.parse type=Value_Type.Date_Time "M/d/fqsrf HH:mm:ss.fff" . should_fail_with Date_Time_Format_Parse_Error Test.specify "should correctly work in Auto mode" <| c1 = Column.from_vector "A" ["1", "2", "3"] diff --git a/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso b/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso index a73c8f9b81f1..6eb6cd3c062b 100644 --- a/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso +++ b/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso @@ -98,7 +98,7 @@ spec = Test.specify "should throw error when parsing invalid time" <| case Date_Time.parse "2008-1-1" . catch of Time_Error.Error msg _ -> - msg . should_equal "Text '2008-1-1' could not be parsed at index 5" + msg . should_contain "Text '2008-1-1' could not be parsed" result -> Test.fail ("Unexpected result: " + result.to_text) @@ -131,6 +131,6 @@ spec = time = Date_Time.parse "2008-01-01" "yyyy-MM-dd'T'HH:mm:ss'['tt']'" case time.catch of Time_Error.Error msg _ -> - msg . should_equal "Text '2008-01-01' could not be parsed at index 10" + msg . should_contain "Text '2008-01-01' could not be parsed" result -> Test.fail ("Unexpected result: " + result.to_text) From 2a73ff6dfc8f91610a2844a983c8a5ea24bdda98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Wed, 20 Sep 2023 14:21:00 +0200 Subject: [PATCH 40/76] updating Column.format --- .../src/Data/Time/Date_Time_Formatter.enso | 5 +++ .../Database/0.0.0-dev/src/Data/Column.enso | 4 +- .../Table/0.0.0-dev/src/Data/Column.enso | 37 +++++----------- .../0.0.0-dev/src/Internal/Column_Format.enso | 19 +++++--- .../enso/base/time/EnsoDateTimeFormatter.java | 5 +++ .../src/In_Memory/Column_Format_Spec.enso | 44 ++++++++++++------- 6 files changed, 65 insertions(+), 49 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso index 7fc7b47c8935..b4133be07867 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso @@ -240,6 +240,11 @@ type Date_Time_Formatter ## Parses a human-readable representation of this formatter. to_display_text self = self.to_text + ## Returns a copy of this formatter with a changed locale. + with_locale : Locale -> Date_Time_Formatter + with_locale self (locale : Locale) = + Date_Time_Formatter.Value (self.underlying.withLocale locale.java_locale) + ## PRIVATE handle_java_errors self ~action = Panic.catch JException action caught_panic-> diff --git a/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Column.enso b/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Column.enso index 66feea10b6a9..bd4b51cc806a 100644 --- a/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Column.enso +++ b/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Column.enso @@ -1522,8 +1522,8 @@ type Column ## GROUP Standard.Base.Conversions Formatting values is not supported in database columns. @locale Locale.default_widget - format : Text | Column -> Locale -> Column ! Illegal_Argument - format self format=Nothing locale=Locale.default = + format : Text | Date_Time_Formatter | Column -> Locale -> Column ! Illegal_Argument + format self (format : Text | Date_Time_Formatter | Column | Nothing)=Nothing locale=Locale.default = _ = [format, locale] Error.throw <| Unsupported_Database_Operation.Error "`Column.format` is not implemented yet for the Database backends." diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Column.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Column.enso index bb7bf7bc3f25..4865a0865c76 100644 --- a/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Column.enso +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Column.enso @@ -1619,7 +1619,12 @@ type Column Arguments: - format: The type-dependent format string to use to format the values. If `format` is `""` or `Nothing`, .to_text is used to format the value. + In case of date/time columns, the format can also be a + `Date_Time_Formatter`. - locale: The locale in which the format should be interpreted. + If a `Date_Time_Formatter` is provided for `format` and the `locale` is + set to anything else than `Locale.default`, then that locale will + override the formatters locale. ! Error Conditions @@ -1637,15 +1642,7 @@ type Column ? `Value_Type.Date`, `Value_Type.Date_Time`, `Value_Type.Time` format strings - A custom pattern string consists of one or more custom date and time - format specifiers. For example, "d MMM yyyy" will format "2011-12-03" - as "3 Dec 2011". See https://docs.oracle.com/en/java/javase/18/docs/api/java.base/java/time/format/DateTimeFormatter.html - for a complete format specification. - - Note that the format string can specify decimal point and digit - separators, but these characters are interpreted in the context of the - Locale used. The format string specifies their location, but the Locale - has the final decision about which characters are used. + See `Date_Time_Formatter` for more details. ? `Value_Type.Integer`, `Value_Type.Float` format strings @@ -1695,26 +1692,16 @@ type Column input.format "#,##0.00" locale=(Locale.new "fr") # ==> ["100 000 000,00", "2 222,00", "3,00"] @locale Locale.default_widget - format : Text | Column -> Locale -> Column ! Illegal_Argument - format self format=Nothing locale=Locale.default = - create_formatter = make_value_formatter_for_value_type self.value_type locale - + format : Text | Date_Time_Formatter | Column -> Locale -> Column ! Illegal_Argument + format self (format : Text | Date_Time_Formatter | Column | Nothing)=Nothing locale=Locale.default = new_column = case format of - "" -> - formatter = .to_text - Column_Ops.map_over_storage self formatter make_string_builder on_problems=Problem_Behavior.Report_Error - Nothing -> - formatter = .to_text - Column_Ops.map_over_storage self formatter make_string_builder on_problems=Problem_Behavior.Report_Error - _ : Text -> - formatter = create_formatter - formatter.if_not_error <| - Column_Ops.map_over_storage self (formatter format=format) make_string_builder on_problems=Problem_Behavior.Report_Error format_column : Column -> Value_Type.expect_text format_column <| - formatter = create_formatter + formatter = make_value_formatter_for_value_type self.value_type locale formatter.if_not_error <| Column_Ops.map_2_over_storage self format_column formatter make_string_builder - _ -> Error.throw <| Illegal_Argument.Error <| "Unsupported format type: " + format.to_text + _ -> + formatter = make_value_formatter_for_value_type self.value_type locale format=format + Column_Ops.map_over_storage self formatter make_string_builder on_problems=Problem_Behavior.Report_Error new_column ## GROUP Standard.Base.Conversions diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Column_Format.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Column_Format.enso index 08a5be12231f..f43e7a4b643d 100644 --- a/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Column_Format.enso +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Column_Format.enso @@ -19,9 +19,9 @@ polyglot java import org.enso.table.operations.OrderBuilder Create a formatter for the specified `Value_Type`. make_value_formatter_for_value_type : Value_Type -> Locale -> (Any -> Text) make_value_formatter_for_value_type value_type locale = case value_type of - Value_Type.Date -> make_value_formatter locale - Value_Type.Date_Time _ -> make_value_formatter locale - Value_Type.Time -> make_value_formatter locale + Value_Type.Date -> make_datetime_formatter locale + Value_Type.Date_Time _ -> make_datetime_formatter locale + Value_Type.Time -> make_datetime_formatter locale Value_Type.Boolean -> make_boolean_formatter Value_Type.Integer _ -> make_value_formatter locale Value_Type.Float _ -> make_value_formatter locale @@ -33,8 +33,8 @@ make_value_formatter_for_value_type value_type locale = case value_type of Create a formatter for the given format string. The `value` parameter has to have a `format` method that takes a format and locale. -make_value_formatter : Locale -> (Any -> Text) -make_value_formatter locale = value-> format-> +make_value_formatter : Locale -> (Any -> Text -> Text) +make_value_formatter locale value (format : Text | Nothing) = handle_illegal_argument_exception format <| if format.is_nothing || format.is_empty then value.to_text else value.format format locale @@ -42,11 +42,18 @@ make_value_formatter locale = value-> format-> Create a `Boolean` formatter that takes the format string as the second parameter. make_boolean_formatter : (Boolean -> Text -> Text) -make_boolean_formatter = bool-> format-> +make_boolean_formatter (bool : Boolean) (format : Text | Nothing) = if format.is_nothing || format.is_empty then bool.to_text else data_formatter = Data_Formatter.Value.with_format Value_Type.Boolean format data_formatter.format bool +make_datetime_formatter : Locale -> (Any -> Date_Time_Formatter -> Text) +make_datetime_formatter (locale_override : Locale) value (format : Date_Time_Formatter | Nothing) = + if format.is_nothing then value.to_text else + # If locale is set to default, keep the locale of the formatter, otherwise override it. + effective_formatter = if locale_override == Locale.default then format else format.with_locale locale_override + value.format effective_formatter + ## PRIVATE Rethrow a Java IllegalArgumentException as an Illegal_Argument. handle_illegal_argument_exception : Text -> Any -> Any diff --git a/std-bits/base/src/main/java/org/enso/base/time/EnsoDateTimeFormatter.java b/std-bits/base/src/main/java/org/enso/base/time/EnsoDateTimeFormatter.java index 0a1bd6522e3e..a7c790de2ecf 100644 --- a/std-bits/base/src/main/java/org/enso/base/time/EnsoDateTimeFormatter.java +++ b/std-bits/base/src/main/java/org/enso/base/time/EnsoDateTimeFormatter.java @@ -12,6 +12,7 @@ import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoField; import java.time.temporal.TemporalQueries; +import java.util.Locale; import static java.time.temporal.ChronoField.INSTANT_SECONDS; import static java.time.temporal.ChronoField.NANO_OF_SECOND; @@ -46,6 +47,10 @@ public static EnsoDateTimeFormatter default_enso_zoned_date_time_formatter() { ); } + public EnsoDateTimeFormatter withLocale(Locale locale) { + return new EnsoDateTimeFormatter(formatter.withLocale(locale), isoReplacementPair, originalPattern, formatterKind); + } + public DateTimeFormatter getRawJavaFormatter() { return formatter; } diff --git a/test/Table_Tests/src/In_Memory/Column_Format_Spec.enso b/test/Table_Tests/src/In_Memory/Column_Format_Spec.enso index 30abd594bb05..812545f76eca 100644 --- a/test/Table_Tests/src/In_Memory/Column_Format_Spec.enso +++ b/test/Table_Tests/src/In_Memory/Column_Format_Spec.enso @@ -1,5 +1,7 @@ from Standard.Base import all +import Standard.Base.Errors.Common.Type_Error import Standard.Base.Errors.Illegal_Argument.Illegal_Argument +import Standard.Base.Data.Time.Date_Time_Formatter.Date_Time_Format_Parse_Error import Standard.Table.Data.Type.Value_Type.Bits @@ -26,9 +28,9 @@ spec = expected_gb = Column.from_vector "values" ["21. Jun 2020", "25. Apr 2023"] expected_fr = Column.from_vector "values" ["21. juin 2020", "25. avril 2023"] input.format "d. MMMM yyyy" . should_equal expected_default - input.format "d. MMMM yyyy" (Locale.default) . should_equal expected_default - input.format "d. MMMM yyyy" (Locale.new "gb") . should_equal expected_gb - input.format "d. MMMM yyyy" (Locale.new "fr") . should_equal expected_fr + input.format (Date_Time_Formatter.from "d. MMMM yyyy" (Locale.default)) . should_equal expected_default + input.format (Date_Time_Formatter.from "d. MMMM yyyy" (Locale.new "gb")) . should_equal expected_gb + input.format (Date_Time_Formatter.from "d. MMMM yyyy" (Locale.new "fr")) . should_equal expected_fr Test.specify "Empty/Nothing format" <| input = Column.from_vector "values" [Date.new 2020 12 21, Date.new 2023 4 25] @@ -90,9 +92,19 @@ spec = expected_gb = Column.from_vector "values" ["21. Jun 2020 08.10.20", "25. Apr 2023 14.25.02"] expected_fr = Column.from_vector "values" ["21. juin 2020 08.10.20", "25. avril 2023 14.25.02"] input.format "d. MMMM yyyy HH.mm.ss" . should_equal expected_default - input.format "d. MMMM yyyy HH.mm.ss" (Locale.default) . should_equal expected_default - input.format "d. MMMM yyyy HH.mm.ss" (Locale.new "gb") . should_equal expected_gb - input.format "d. MMMM yyyy HH.mm.ss" (Locale.new "fr") . should_equal expected_fr + input.format (Date_Time_Formatter.from "d. MMMM yyyy HH.mm.ss" Locale.default) . should_equal expected_default + input.format (Date_Time_Formatter.from "d. MMMM yyyy HH.mm.ss" Locale.uk) . should_equal expected_gb + input.format (Date_Time_Formatter.from "d. MMMM yyyy HH.mm.ss" Locale.france) . should_equal expected_fr + + Test.specify "overriding the Locale with `format` argument" <| + formatter = Date_Time_Formatter.from "d. MMMM yyyy HH.mm.ss" Locale.france + input = Column.from_vector "values" [Date_Time.new 2020 6 21 8 10 20, Date_Time.new 2023 4 25 14 25 2] + expected_fr = Column.from_vector "values" ["21. juin 2020 08.10.20", "25. avril 2023 14.25.02"] + expected_pl = Column.from_vector "values" ["21. czerwca 2020 08.10.20", "25. kwietnia 2023 14.25.02"] + + input.format formatter . should_equal expected_fr + # If I provide a locale argument, it overrides what is already in the formatter: + input.format formatter Locale.poland . should_equal expected_pl Test.specify "Empty/Nothing format" <| zone = Time_Zone.parse "US/Hawaii" @@ -104,7 +116,7 @@ spec = Test.specify "Bad format" <| input = Column.from_vector "values" [Date_Time.new 2020 6 21 8 10 20, Date_Time.new 2023 4 25 14 25 2] - input.format "DDDDD" . should_fail_with Illegal_Argument + input.format "jjjjjjjj" . should_fail_with Date_Time_Format_Parse_Error Test.group "Date_Time Column.format, with format Column" <| Test.specify "Date_Time column" <| @@ -130,8 +142,8 @@ spec = Test.specify "Bad format" <| input = Column.from_vector "values" [Date_Time.new 2020 6 21 8 10 20, Date_Time.new 2023 4 25 14 25 2, Date_Time.new 2023 4 26 3 4 5] - formats = Column.from_vector "formats" ["yyyyMMdd HH.mm.ss", "DDDDD", "FFF"] - input.format formats . should_fail_with Illegal_Argument + formats = Column.from_vector "formats" ["yyyyMMdd HH.mm.ss", "jjjjj", "FFF"] + input.format formats . should_fail_with Date_Time_Format_Parse_Error Test.specify "Bad format column type" <| input = Column.from_vector "values" [Date_Time.new 2020 6 21 8 10 20, Date_Time.new 2023 4 25 14 25 2] @@ -140,7 +152,7 @@ spec = Test.specify "column length mismatch" <| input = Column.from_vector "values" [Date_Time.new 2020 6 21 8 10 20, Date_Time.new 2023 4 25 14 25 2] - formats = Column.from_vector "formats" ["yyyyMMdd", "DDDDD", "w"] + formats = Column.from_vector "formats" ["yyyyMMdd", "jjjj", "w"] input.format formats . should_fail_with Illegal_Argument Test.group "Time_Of_Day Column.format, with format string" <| @@ -167,7 +179,7 @@ spec = Test.specify "Bad format" <| input = Column.from_vector "values" [Time_Of_Day.new 8 10 20, Time_Of_Day.new 14 25 2] - input.format "DDDDD" . should_fail_with Illegal_Argument + input.format "jjjj" . should_fail_with Date_Time_Format_Parse_Error Test.specify "Format for wrong date type" <| input = Column.from_vector "values" [Time_Of_Day.new 8 10 20, Time_Of_Day.new 14 25 2] @@ -196,8 +208,8 @@ spec = Test.specify "Bad format" <| input = Column.from_vector "values" [Time_Of_Day.new 8 10 20, Time_Of_Day.new 14 25 2, Time_Of_Day.new 3 4 5] - formats = Column.from_vector "formats" ["HH.mm.ss", "DDDDD", "FFF"] - input.format formats . should_fail_with Illegal_Argument + formats = Column.from_vector "formats" ["HH.mm.ss", "jjjjj", "FFF"] + input.format formats . should_fail_with Date_Time_Format_Parse_Error Test.specify "Bad format column type" <| input = Column.from_vector "values" [Time_Of_Day.new 8 10 20, Time_Of_Day.new 14 25 2] @@ -206,8 +218,8 @@ spec = Test.specify "column length mismatch" <| input = Column.from_vector "values" [Time_Of_Day.new 8 10 20, Time_Of_Day.new 14 25 2] - formats = Column.from_vector "formats" ["yyyyMMdd", "DDDDD", "w"] - input.format formats . should_fail_with Illegal_Argument + formats = Column.from_vector "formats" ["yyyyMMdd", "jjjj", "w"] + input.format formats . should_fail_with Date_Time_Format_Parse_Error Test.group "Boolean Column.format, with format string" <| Test.specify "Boolean column" <| @@ -289,7 +301,7 @@ spec = Test.specify "Format is not text" <| input = Column.from_vector "values" [Date.new 2020 12 21, Date.new 2023 4 25] - input.format 73 . should_fail_with Illegal_Argument + Test.expect_panic_with (input.format 73) Type_Error Test.group "Edge cases" <| Test.specify "empty table is ok" <| From c1a46a828ba4da97946b6be9d6c17739142d5941 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Wed, 20 Sep 2023 14:24:09 +0200 Subject: [PATCH 41/76] fix frame offset in Test lib --- test/Table_Tests/src/Util.enso | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Table_Tests/src/Util.enso b/test/Table_Tests/src/Util.enso index acc35795a8ec..a8e1195067f8 100644 --- a/test/Table_Tests/src/Util.enso +++ b/test/Table_Tests/src/Util.enso @@ -35,7 +35,7 @@ In_Memory_Column.should_equal self expected frames_to_skip=0 = Test.fail "Expected column name "+expected.name+", but got "+self.name+" (at "+loc+")." if self.length != expected.length then Test.fail "Expected column length "+expected.length.to_text+", but got "+self.length.to_text+" (at "+loc+")." - self.to_vector.should_equal expected.to_vector + self.to_vector.should_equal expected.to_vector 2+frames_to_skip _ -> Test.fail "Got a Column, but expected a "+expected.to_display_text+' (at '+loc+').' Database_Table.should_equal : Database_Table -> Integer -> Test_Result From 2ff78abb492c3e3ddeba094379605b83b4d3ff79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Wed, 20 Sep 2023 14:56:39 +0200 Subject: [PATCH 42/76] fixing Column.format --- .../Table/0.0.0-dev/src/Data/Column.enso | 5 +-- .../0.0.0-dev/src/Internal/Column_Format.enso | 35 +++++++++++-------- .../src/In_Memory/Column_Format_Spec.enso | 12 +++---- 3 files changed, 29 insertions(+), 23 deletions(-) diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Column.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Column.enso index 4865a0865c76..d9c38acdf63d 100644 --- a/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Column.enso +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Column.enso @@ -1698,9 +1698,10 @@ type Column format_column : Column -> Value_Type.expect_text format_column <| formatter = make_value_formatter_for_value_type self.value_type locale formatter.if_not_error <| - Column_Ops.map_2_over_storage self format_column formatter make_string_builder + formatter_flipped value format = formatter format value + Column_Ops.map_2_over_storage self format_column formatter_flipped make_string_builder _ -> - formatter = make_value_formatter_for_value_type self.value_type locale format=format + formatter = make_value_formatter_for_value_type self.value_type locale format Column_Ops.map_over_storage self formatter make_string_builder on_problems=Problem_Behavior.Report_Error new_column diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Column_Format.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Column_Format.enso index f43e7a4b643d..4e5fb86aff68 100644 --- a/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Column_Format.enso +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Column_Format.enso @@ -17,7 +17,7 @@ polyglot java import org.enso.table.operations.OrderBuilder ## PRIVATE Create a formatter for the specified `Value_Type`. -make_value_formatter_for_value_type : Value_Type -> Locale -> (Any -> Text) +make_value_formatter_for_value_type : Value_Type -> Locale -> (Text | Date_Time_Formatter | Nothing -> Any -> Text) make_value_formatter_for_value_type value_type locale = case value_type of Value_Type.Date -> make_datetime_formatter locale Value_Type.Date_Time _ -> make_datetime_formatter locale @@ -33,26 +33,31 @@ make_value_formatter_for_value_type value_type locale = case value_type of Create a formatter for the given format string. The `value` parameter has to have a `format` method that takes a format and locale. -make_value_formatter : Locale -> (Any -> Text -> Text) -make_value_formatter locale value (format : Text | Nothing) = - handle_illegal_argument_exception format <| - if format.is_nothing || format.is_empty then value.to_text else - value.format format locale +make_value_formatter : Locale -> (Text -> Any -> Text) +make_value_formatter locale (format : Text | Nothing) = + if format.is_nothing || format.is_empty then .to_text else + value-> + handle_illegal_argument_exception format <| + value.format format locale ## PRIVATE Create a `Boolean` formatter that takes the format string as the second parameter. -make_boolean_formatter : (Boolean -> Text -> Text) -make_boolean_formatter (bool : Boolean) (format : Text | Nothing) = - if format.is_nothing || format.is_empty then bool.to_text else +make_boolean_formatter : (Text -> Boolean -> Text) +make_boolean_formatter (format : Text | Nothing) = + if format.is_nothing || format.is_empty then .to_text else data_formatter = Data_Formatter.Value.with_format Value_Type.Boolean format - data_formatter.format bool + bool -> data_formatter.format bool -make_datetime_formatter : Locale -> (Any -> Date_Time_Formatter -> Text) -make_datetime_formatter (locale_override : Locale) value (format : Date_Time_Formatter | Nothing) = - if format.is_nothing then value.to_text else +## PRIVATE +make_datetime_formatter : Locale -> Date_Time_Formatter | Text | Nothing -> (Any -> Text) +make_datetime_formatter (locale_override : Locale) (format : Text | Date_Time_Formatter | Nothing) = + use_default = format.is_nothing || (format == "") + if use_default then .to_text else + # If the format was Text, we now ensure it gets converted. + effective_formatter = format : Date_Time_Formatter # If locale is set to default, keep the locale of the formatter, otherwise override it. - effective_formatter = if locale_override == Locale.default then format else format.with_locale locale_override - value.format effective_formatter + formatter_with_updated_locale = if locale_override == Locale.default then effective_formatter else effective_formatter.with_locale locale_override + date_time_value -> date_time_value.format formatter_with_updated_locale ## PRIVATE Rethrow a Java IllegalArgumentException as an Illegal_Argument. diff --git a/test/Table_Tests/src/In_Memory/Column_Format_Spec.enso b/test/Table_Tests/src/In_Memory/Column_Format_Spec.enso index 812545f76eca..82893763c9d1 100644 --- a/test/Table_Tests/src/In_Memory/Column_Format_Spec.enso +++ b/test/Table_Tests/src/In_Memory/Column_Format_Spec.enso @@ -24,13 +24,13 @@ spec = Test.specify "Date with locale" <| input = Column.from_vector "values" [Date.new 2020 6 21, Date.new 2023 4 25] - expected_default = Column.from_vector "values" ["21. June 2020", "25. April 2023"] - expected_gb = Column.from_vector "values" ["21. Jun 2020", "25. Apr 2023"] + expected_default = Column.from_vector "values" ["21. Jun 2020", "25. Apr 2023"] + expected_gb = Column.from_vector "values" ["21. June 2020", "25. April 2023"] expected_fr = Column.from_vector "values" ["21. juin 2020", "25. avril 2023"] input.format "d. MMMM yyyy" . should_equal expected_default - input.format (Date_Time_Formatter.from "d. MMMM yyyy" (Locale.default)) . should_equal expected_default - input.format (Date_Time_Formatter.from "d. MMMM yyyy" (Locale.new "gb")) . should_equal expected_gb - input.format (Date_Time_Formatter.from "d. MMMM yyyy" (Locale.new "fr")) . should_equal expected_fr + input.format (Date_Time_Formatter.from "d. MMMM yyyy" Locale.default) . should_equal expected_default + input.format (Date_Time_Formatter.from "d. MMMM yyyy" Locale.uk) . should_equal expected_gb + input.format (Date_Time_Formatter.from "d. MMMM yyyy" Locale.france) . should_equal expected_fr Test.specify "Empty/Nothing format" <| input = Column.from_vector "values" [Date.new 2020 12 21, Date.new 2023 4 25] @@ -41,7 +41,7 @@ spec = Test.specify "Bad format" <| input = Column.from_vector "values" [Date.new 2020 6 21, Date.new 2023 4 25] - input.format "DDDDD" . should_fail_with Illegal_Argument + input.format "jjjjjj" . should_fail_with Illegal_Argument Test.group "Date Column.format, with format Column" <| Test.specify "Date column" <| From 13cfd3cc93a8a9b6f16d30e8c89d9190efc909fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Wed, 20 Sep 2023 14:58:22 +0200 Subject: [PATCH 43/76] fix some tests --- .../src/In_Memory/Column_Format_Spec.enso | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/test/Table_Tests/src/In_Memory/Column_Format_Spec.enso b/test/Table_Tests/src/In_Memory/Column_Format_Spec.enso index 82893763c9d1..e0e7b4757b8f 100644 --- a/test/Table_Tests/src/In_Memory/Column_Format_Spec.enso +++ b/test/Table_Tests/src/In_Memory/Column_Format_Spec.enso @@ -1,5 +1,6 @@ from Standard.Base import all import Standard.Base.Errors.Common.Type_Error +import Standard.Base.Errors.Time_Error.Time_Error import Standard.Base.Errors.Illegal_Argument.Illegal_Argument import Standard.Base.Data.Time.Date_Time_Formatter.Date_Time_Format_Parse_Error @@ -41,7 +42,7 @@ spec = Test.specify "Bad format" <| input = Column.from_vector "values" [Date.new 2020 6 21, Date.new 2023 4 25] - input.format "jjjjjj" . should_fail_with Illegal_Argument + input.format "jjjjjj" . should_fail_with Date_Time_Format_Parse_Error Test.group "Date Column.format, with format Column" <| Test.specify "Date column" <| @@ -66,8 +67,8 @@ spec = Test.specify "Bad format" <| input = Column.from_vector "values" [Date.new 2020 6 21, Date.new 2023 4 25, Date.new 2023 4 26] - formats = Column.from_vector "formats" ["yyyyMMdd", "DDDDD", "FFF"] - input.format formats . should_fail_with Illegal_Argument + formats = Column.from_vector "formats" ["yyyyMMdd", "jjjjj", "FFF"] + input.format formats . should_fail_with Date_Time_Format_Parse_Error Test.specify "Bad format column type" <| input = Column.from_vector "values" [Date.new 2020 6 21, Date.new 2023 4 25, Date.new 2023 4 26] @@ -88,8 +89,8 @@ spec = Test.specify "Date_Time with locale" <| input = Column.from_vector "values" [Date_Time.new 2020 6 21 8 10 20, Date_Time.new 2023 4 25 14 25 2] - expected_default = Column.from_vector "values" ["21. June 2020 08.10.20", "25. April 2023 14.25.02"] - expected_gb = Column.from_vector "values" ["21. Jun 2020 08.10.20", "25. Apr 2023 14.25.02"] + expected_default = Column.from_vector "values" ["21. Jun 2020 08.10.20", "25. Apr 2023 14.25.02"] + expected_gb = Column.from_vector "values" ["21. June 2020 08.10.20", "25. April 2023 14.25.02"] expected_fr = Column.from_vector "values" ["21. juin 2020 08.10.20", "25. avril 2023 14.25.02"] input.format "d. MMMM yyyy HH.mm.ss" . should_equal expected_default input.format (Date_Time_Formatter.from "d. MMMM yyyy HH.mm.ss" Locale.default) . should_equal expected_default @@ -183,7 +184,7 @@ spec = Test.specify "Format for wrong date type" <| input = Column.from_vector "values" [Time_Of_Day.new 8 10 20, Time_Of_Day.new 14 25 2] - input.format "yyyyMMdd HH.mm.ss" . should_fail_with Illegal_Argument + input.format "yyyyMMdd HH.mm.ss" . should_fail_with Time_Error Test.group "Time_Of_Day Column.format, with format Column" <| Test.specify "Time_Of_Day column" <| @@ -218,8 +219,8 @@ spec = Test.specify "column length mismatch" <| input = Column.from_vector "values" [Time_Of_Day.new 8 10 20, Time_Of_Day.new 14 25 2] - formats = Column.from_vector "formats" ["yyyyMMdd", "jjjj", "w"] - input.format formats . should_fail_with Date_Time_Format_Parse_Error + formats = Column.from_vector "formats" ["yyyyMMdd", "jjjjj", "w"] + input.format formats . should_fail_with Illegal_Argument Test.group "Boolean Column.format, with format string" <| Test.specify "Boolean column" <| From b31968c3f13554455d2c441833d29ea3b4a8952f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Wed, 20 Sep 2023 16:12:26 +0200 Subject: [PATCH 44/76] change default formatter --- .../java/org/enso/interpreter/runtime/data/EnsoDateTime.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoDateTime.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoDateTime.java index b5644bda019f..de3a3a4c644f 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoDateTime.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoDateTime.java @@ -17,6 +17,7 @@ import org.enso.interpreter.runtime.EnsoContext; import org.enso.interpreter.runtime.data.text.Text; import org.enso.interpreter.runtime.library.dispatch.TypesLibrary; +import org.enso.polyglot.common_utils.Core_Date_Utils; @ExportLibrary(InteropLibrary.class) @ExportLibrary(TypesLibrary.class) @@ -227,7 +228,7 @@ Type getType(@CachedLibrary("this") TypesLibrary thisLib, @Cached("1") int ignor @ExportMessage @CompilerDirectives.TruffleBoundary public Object toDisplayString(boolean allowSideEffects) { - return DateTimeFormatter.ISO_ZONED_DATE_TIME.format(dateTime); + return Core_Date_Utils.defaultZonedDateTimeFormatter().format(dateTime); } // 15. October 1582 From 2e0fa48f63cdedae3f51f26fd47e7ab195ef091f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Wed, 20 Sep 2023 16:31:20 +0200 Subject: [PATCH 45/76] change default formatter pt. 2 (to_text) --- .../java/org/enso/interpreter/runtime/data/EnsoDateTime.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoDateTime.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoDateTime.java index de3a3a4c644f..50940d4156d2 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoDateTime.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoDateTime.java @@ -169,10 +169,10 @@ public EnsoDate toLocalDate() { return new EnsoDate(dateTime.toLocalDate()); } - @Builtin.Method(description = "Return this datetime to the datetime in the provided time zone.") + @Builtin.Method(description = "Return a text representation of this date-time.") @CompilerDirectives.TruffleBoundary public Text toText() { - return Text.create(DateTimeFormatter.ISO_ZONED_DATE_TIME.format(dateTime)); + return Text.create(Core_Date_Utils.defaultZonedDateTimeFormatter.format(dateTime)); } @ExportMessage From a837e1c667a7a2a1579d35d5cb3d43beeef22c58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Wed, 20 Sep 2023 16:34:16 +0200 Subject: [PATCH 46/76] update test --- test/Tests/src/Data/Time/Date_Time_Spec.enso | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Tests/src/Data/Time/Date_Time_Spec.enso b/test/Tests/src/Data/Time/Date_Time_Spec.enso index 70d0868220f3..613ff1b1d827 100644 --- a/test/Tests/src/Data/Time/Date_Time_Spec.enso +++ b/test/Tests/src/Data/Time/Date_Time_Spec.enso @@ -78,7 +78,7 @@ spec_with name create_new_datetime parse_datetime nanoseconds_loss_in_precision= Test.specify "should format using default pattern" <| text = create_new_datetime 1970 (zone = Time_Zone.utc) . to_text - text . should_equal "1970-01-01T00:00:00Z[UTC]" + text . should_equal "1970-01-01 00:00:00Z[UTC]" Test.specify "should convert to Json" <| time = create_new_datetime 1970 12 21 (zone = Time_Zone.utc) From 9e69c8c529112c3dcbc1d597037c0adfd6dc6c6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Wed, 20 Sep 2023 16:34:22 +0200 Subject: [PATCH 47/76] cleanup --- .../interpreter/runtime/data/EnsoDate.java | 5 +- .../runtime/data/EnsoDateTime.java | 2 +- .../common_utils/Core_Date_Utils.java | 35 +++-------- .../main/java/org/enso/base/Time_Utils.java | 63 +------------------ .../enso/base/time/EnsoDateTimeFormatter.java | 2 +- .../cast/ToTextStorageConverter.java | 6 +- .../expressions/ExpressionVisitorImpl.java | 2 +- 7 files changed, 15 insertions(+), 100 deletions(-) diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoDate.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoDate.java index 8bc82110a6ab..84811811bf58 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoDate.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoDate.java @@ -101,9 +101,6 @@ Type getType(@CachedLibrary("this") TypesLibrary thisLib, @Cached("1") int ignor @CompilerDirectives.TruffleBoundary @ExportMessage public Object toDisplayString(boolean allowSideEffects) { - return DATE_FORMATTER.format(date); + return Core_Date_Utils.defaultLocalDateFormatter.format(date); } - - private static final DateTimeFormatter DATE_FORMATTER = - Core_Date_Utils.defaultLocalDateFormatter(); } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoDateTime.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoDateTime.java index 50940d4156d2..f3cecf1a1877 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoDateTime.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoDateTime.java @@ -228,7 +228,7 @@ Type getType(@CachedLibrary("this") TypesLibrary thisLib, @Cached("1") int ignor @ExportMessage @CompilerDirectives.TruffleBoundary public Object toDisplayString(boolean allowSideEffects) { - return Core_Date_Utils.defaultZonedDateTimeFormatter().format(dateTime); + return Core_Date_Utils.defaultZonedDateTimeFormatter.format(dateTime); } // 15. October 1582 diff --git a/lib/scala/common-polyglot-core-utils/src/main/java/org/enso/polyglot/common_utils/Core_Date_Utils.java b/lib/scala/common-polyglot-core-utils/src/main/java/org/enso/polyglot/common_utils/Core_Date_Utils.java index 5657f651ef8c..59e6d955c5c3 100644 --- a/lib/scala/common-polyglot-core-utils/src/main/java/org/enso/polyglot/common_utils/Core_Date_Utils.java +++ b/lib/scala/common-polyglot-core-utils/src/main/java/org/enso/polyglot/common_utils/Core_Date_Utils.java @@ -18,25 +18,9 @@ import java.time.temporal.TemporalQueries; public class Core_Date_Utils { - /** - * Replace space with T in ISO date time string to make it compatible with ISO format. - * - * @param dateString Raw date time string with either space or T as separator - * @return ISO format date time string - */ - public static String normaliseISODateTime(String dateString) { - if (dateString != null && dateString.length() > 10 && dateString.charAt(10) == ' ') { - var builder = new StringBuilder(dateString); - builder.replace(10, 11, "T"); - return builder.toString(); - } - - return dateString; - } - - /** @return default Date Time formatter for parsing a Date_Time. */ - public static DateTimeFormatter defaultZonedDateTimeFormatter() { - return new DateTimeFormatterBuilder() + /** default Date Time formatter for parsing a Date_Time. */ + public static final DateTimeFormatter defaultZonedDateTimeFormatter = + new DateTimeFormatterBuilder() .parseLenient() .append(DateTimeFormatter.ISO_LOCAL_DATE) .appendLiteral(' ') @@ -51,17 +35,12 @@ public static DateTimeFormatter defaultZonedDateTimeFormatter() { .appendZoneRegionId() .appendLiteral(']') .toFormatter(); - } - /** @return default Date formatter for parsing a Date. */ - public static DateTimeFormatter defaultLocalDateFormatter() { - return DateTimeFormatter.ISO_LOCAL_DATE; - } + /** default Date formatter for parsing a Date. */ + public static final DateTimeFormatter defaultLocalDateFormatter = DateTimeFormatter.ISO_LOCAL_DATE; - /** @return default Time formatter for parsing a Time_Of_Day. */ - public static DateTimeFormatter defaultLocalTimeFormatter() { - return DateTimeFormatter.ISO_LOCAL_TIME; - } + /** default Time formatter for parsing a Time_Of_Day. */ + public static final DateTimeFormatter defaultLocalTimeFormatter = DateTimeFormatter.ISO_LOCAL_TIME; /** * Parse a date string into a LocalDate. Allows missing day (assumes first day of month) or diff --git a/std-bits/base/src/main/java/org/enso/base/Time_Utils.java b/std-bits/base/src/main/java/org/enso/base/Time_Utils.java index 3c55f358c42d..7fb7b4d438fa 100644 --- a/std-bits/base/src/main/java/org/enso/base/Time_Utils.java +++ b/std-bits/base/src/main/java/org/enso/base/Time_Utils.java @@ -44,7 +44,7 @@ public enum AdjustOp { public static DateTimeFormatter make_formatter(String format, Locale locale) { var usedLocale = locale == Locale.ROOT ? Locale.US : locale; return switch (format) { - case "ENSO_ZONED_DATE_TIME" -> Core_Date_Utils.defaultZonedDateTimeFormatter(); + case "ENSO_ZONED_DATE_TIME" -> Core_Date_Utils.defaultZonedDateTimeFormatter; case "ISO_ZONED_DATE_TIME" -> DateTimeFormatter.ISO_ZONED_DATE_TIME; case "ISO_OFFSET_DATE_TIME" -> DateTimeFormatter.ISO_OFFSET_DATE_TIME; case "ISO_LOCAL_DATE_TIME" -> DateTimeFormatter.ISO_LOCAL_DATE_TIME; @@ -54,67 +54,6 @@ public static DateTimeFormatter make_formatter(String format, Locale locale) { }; } - /** - * Creates a DateTimeFormatter from a format string, supporting building standard formats. For Enso format, return the - * default output formatter. - * - * @param format format string - * @param locale locale needed for custom formats - * @return DateTimeFormatter - */ - public static DateTimeFormatter make_output_formatter(String format, Locale locale) { - return format.equals("ENSO_ZONED_DATE_TIME") - ? Time_Utils.default_output_date_time_formatter() - : make_formatter(format, locale); - } - - /** - * Given a format string, returns true if it is a format that is based on ISO date time. - * - * @param format format string - * @return True if format is based on ISO date time - */ - public static boolean is_iso_datetime_based(String format) { - return switch (format) { - case "ENSO_ZONED_DATE_TIME", "ISO_ZONED_DATE_TIME", "ISO_OFFSET_DATE_TIME", "ISO_LOCAL_DATE_TIME" -> true; - default -> false; - }; - } - - /** - * @return default DateTimeFormatter for parsing a Date. - */ - public static DateTimeFormatter default_date_formatter() { - return Core_Date_Utils.defaultLocalDateFormatter(); - } - - /** - * @return default DateTimeFormatter for parsing a Time_Of_Day. - */ - public static DateTimeFormatter default_time_of_day_formatter() { - return Core_Date_Utils.defaultLocalTimeFormatter(); - } - - /** - * @return default Date Time formatter for writing a Date_Time. - */ - public static DateTimeFormatter default_output_date_time_formatter() { - return new DateTimeFormatterBuilder().append(DateTimeFormatter.ISO_LOCAL_DATE) - .appendLiteral(' ') - .append(DateTimeFormatter.ISO_LOCAL_TIME) - .toFormatter(); - } - - /** - * Replace space with T in ISO date time string to make it compatible with ISO format. - * - * @param dateString Raw date time string with either space or T as separator - * @return ISO format date time string - */ - public static String normalise_iso_datetime(String dateString) { - return Core_Date_Utils.normaliseISODateTime(dateString); - } - /** * Format a LocalDate instance using a formatter. * diff --git a/std-bits/base/src/main/java/org/enso/base/time/EnsoDateTimeFormatter.java b/std-bits/base/src/main/java/org/enso/base/time/EnsoDateTimeFormatter.java index a7c790de2ecf..c8f807a528f8 100644 --- a/std-bits/base/src/main/java/org/enso/base/time/EnsoDateTimeFormatter.java +++ b/std-bits/base/src/main/java/org/enso/base/time/EnsoDateTimeFormatter.java @@ -40,7 +40,7 @@ public static EnsoDateTimeFormatter makeISOConstant(DateTimeFormatter formatter, public static EnsoDateTimeFormatter default_enso_zoned_date_time_formatter() { return new EnsoDateTimeFormatter( - Core_Date_Utils.defaultZonedDateTimeFormatter(), + Core_Date_Utils.defaultZonedDateTimeFormatter, Pair.create('T', " "), "default_enso_zoned_date_time", FormatterKind.CONSTANT diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/operation/cast/ToTextStorageConverter.java b/std-bits/table/src/main/java/org/enso/table/data/column/operation/cast/ToTextStorageConverter.java index 7323932bfd85..b76839d32eb4 100644 --- a/std-bits/table/src/main/java/org/enso/table/data/column/operation/cast/ToTextStorageConverter.java +++ b/std-bits/table/src/main/java/org/enso/table/data/column/operation/cast/ToTextStorageConverter.java @@ -75,9 +75,9 @@ public Storage castFromMixed(Storage mixedStorage, CastProblemBuilder return builder.seal(); } - private final DateTimeFormatter dateFormatter = Core_Date_Utils.defaultLocalDateFormatter(); - private final DateTimeFormatter timeFormatter = Core_Date_Utils.defaultLocalTimeFormatter(); - private final DateTimeFormatter dateTimeFormatter = Core_Date_Utils.defaultZonedDateTimeFormatter(); + private final DateTimeFormatter dateFormatter = Core_Date_Utils.defaultLocalDateFormatter; + private final DateTimeFormatter timeFormatter = Core_Date_Utils.defaultLocalTimeFormatter; + private final DateTimeFormatter dateTimeFormatter = Core_Date_Utils.defaultZonedDateTimeFormatter; private String convertDate(LocalDate date) { diff --git a/std-bits/table/src/main/java/org/enso/table/expressions/ExpressionVisitorImpl.java b/std-bits/table/src/main/java/org/enso/table/expressions/ExpressionVisitorImpl.java index 759d12f1bfff..559d6c2b7b08 100644 --- a/std-bits/table/src/main/java/org/enso/table/expressions/ExpressionVisitorImpl.java +++ b/std-bits/table/src/main/java/org/enso/table/expressions/ExpressionVisitorImpl.java @@ -320,7 +320,7 @@ public Value visitTime(ExpressionParser.TimeContext ctx) { @Override public Value visitDatetime(ExpressionParser.DatetimeContext ctx) { - var text = Time_Utils.normalise_iso_datetime(ctx.text.getText()); + var text = ctx.text.getText(); try { var dateTime = dateTimeFormatter.parseZonedDateTime(text); From 5404c53a6eacedc2fb142ace4c8e3df103d1fdd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Wed, 20 Sep 2023 16:34:50 +0200 Subject: [PATCH 48/76] cleanup 2 --- .../main/java/org/enso/base/Time_Utils.java | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/std-bits/base/src/main/java/org/enso/base/Time_Utils.java b/std-bits/base/src/main/java/org/enso/base/Time_Utils.java index 7fb7b4d438fa..2be3bcef3235 100644 --- a/std-bits/base/src/main/java/org/enso/base/Time_Utils.java +++ b/std-bits/base/src/main/java/org/enso/base/Time_Utils.java @@ -33,27 +33,6 @@ public enum AdjustOp { MINUS } - /** - * Creates a DateTimeFormatter from a format string, supporting building standard formats. - * - * @param format format string - * @param locale locale needed for custom formats - * @return DateTimeFormatter - */ - @Deprecated - public static DateTimeFormatter make_formatter(String format, Locale locale) { - var usedLocale = locale == Locale.ROOT ? Locale.US : locale; - return switch (format) { - case "ENSO_ZONED_DATE_TIME" -> Core_Date_Utils.defaultZonedDateTimeFormatter; - case "ISO_ZONED_DATE_TIME" -> DateTimeFormatter.ISO_ZONED_DATE_TIME; - case "ISO_OFFSET_DATE_TIME" -> DateTimeFormatter.ISO_OFFSET_DATE_TIME; - case "ISO_LOCAL_DATE_TIME" -> DateTimeFormatter.ISO_LOCAL_DATE_TIME; - case "ISO_LOCAL_DATE" -> DateTimeFormatter.ISO_LOCAL_DATE; - case "ISO_LOCAL_TIME" -> DateTimeFormatter.ISO_LOCAL_TIME; - default -> DateTimeFormatter.ofPattern(format, usedLocale); - }; - } - /** * Format a LocalDate instance using a formatter. * From e9f665a74d5cd87cd51469a6b16daddd9315ad0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Wed, 20 Sep 2023 17:15:37 +0200 Subject: [PATCH 49/76] fixes - edge cases --- .../enso/base/time/EnsoDateTimeFormatter.java | 26 +++++++++++++++++-- .../src/IO/Delimited_Write_Spec.enso | 2 +- .../src/In_Memory/Column_Format_Spec.enso | 4 +-- .../src/In_Memory/Table_Date_Time_Spec.enso | 3 ++- 4 files changed, 29 insertions(+), 6 deletions(-) diff --git a/std-bits/base/src/main/java/org/enso/base/time/EnsoDateTimeFormatter.java b/std-bits/base/src/main/java/org/enso/base/time/EnsoDateTimeFormatter.java index c8f807a528f8..5ea3eba30055 100644 --- a/std-bits/base/src/main/java/org/enso/base/time/EnsoDateTimeFormatter.java +++ b/std-bits/base/src/main/java/org/enso/base/time/EnsoDateTimeFormatter.java @@ -12,6 +12,7 @@ import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoField; import java.time.temporal.TemporalQueries; +import java.util.Arrays; import java.util.Locale; import static java.time.temporal.ChronoField.INSTANT_SECONDS; @@ -23,7 +24,8 @@ public class EnsoDateTimeFormatter { private final String originalPattern; private final FormatterKind formatterKind; - private EnsoDateTimeFormatter(DateTimeFormatter formatter, Pair isoReplacementPair, String originalPattern, FormatterKind formatterKind) { + private EnsoDateTimeFormatter(DateTimeFormatter formatter, Pair isoReplacementPair, + String originalPattern, FormatterKind formatterKind) { this.formatter = formatter; this.isoReplacementPair = isoReplacementPair; this.originalPattern = originalPattern; @@ -149,7 +151,7 @@ public ZonedDateTime parseZonedDateTime(String dateString) { var localTime = LocalTime.from(resolved); return ZonedDateTime.of(localDate, localTime, zone); } catch (DateTimeException e) { - throw new DateTimeException("Unable to parse Text '" + dateString + "' to Date_Time: "+e.getMessage(), e); + throw new DateTimeException("Unable to parse Text '" + dateString + "' to Date_Time: " + e.getMessage(), e); } catch (ArithmeticException e) { throw new DateTimeException( "Unable to parse Text '" + dateString + "' to Date_Time due to arithmetic error.", e); @@ -172,4 +174,24 @@ public String formatZonedDateTime(ZonedDateTime dateTime) { public String formatLocalTime(LocalTime time) { return formatter.format(time); } + + @Override + public int hashCode() { + // We ignore formatter here because it has identity semantics. + return Arrays.hashCode(new Object[]{isoReplacementPair, originalPattern, formatterKind}); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof EnsoDateTimeFormatter other) { + // The DateTimeFormatter has identity semantics, so instead we try to check the pattern instead, if available. + if (originalPattern != null) { + return formatterKind == other.formatterKind && originalPattern.equals(other.originalPattern) && isoReplacementPair.equals(other.isoReplacementPair) && formatter.getLocale().equals(other.formatter.getLocale()); + } else { + return formatterKind == other.formatterKind && formatter.equals(other.formatter); + } + } else { + return false; + } + } } diff --git a/test/Table_Tests/src/IO/Delimited_Write_Spec.enso b/test/Table_Tests/src/IO/Delimited_Write_Spec.enso index a732b73da59e..12b50c884aa6 100644 --- a/test/Table_Tests/src/IO/Delimited_Write_Spec.enso +++ b/test/Table_Tests/src/IO/Delimited_Write_Spec.enso @@ -169,7 +169,7 @@ spec = file.delete Test.specify 'should allow to always quote text and custom values, but for non-text primitives only if absolutely necessary' <| - format = Delimited "," value_formatter=(Data_Formatter.Value thousand_separator='"' date_formats=["E, d MMM y"]) . with_quotes always_quote=True quote_escape='\\' + format = Delimited "," value_formatter=(Data_Formatter.Value thousand_separator='"' . with_datetime_formats date_formats=["dddd, 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.Value 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.delete_if_exists diff --git a/test/Table_Tests/src/In_Memory/Column_Format_Spec.enso b/test/Table_Tests/src/In_Memory/Column_Format_Spec.enso index e0e7b4757b8f..08f051dc8602 100644 --- a/test/Table_Tests/src/In_Memory/Column_Format_Spec.enso +++ b/test/Table_Tests/src/In_Memory/Column_Format_Spec.enso @@ -110,7 +110,7 @@ spec = Test.specify "Empty/Nothing format" <| zone = Time_Zone.parse "US/Hawaii" input = Column.from_vector "values" [Date_Time.new 2020 12 21 8 10 20 zone=zone, Date_Time.new 2023 4 25 14 25 2 zone=zone] - expected = Column.from_vector "values" ['2020-12-21T08:10:20-10:00[US/Hawaii]', '2023-04-25T14:25:02-10:00[US/Hawaii]'] + expected = Column.from_vector "values" ['2020-12-21 08:10:20-10:00[US/Hawaii]', '2023-04-25 14:25:02-10:00[US/Hawaii]'] input.format . should_equal expected input.format "" . should_equal expected input.format Nothing . should_equal expected @@ -137,7 +137,7 @@ spec = zone = Time_Zone.parse "US/Hawaii" input = Column.from_vector "values" [Date_Time.new 2020 6 21 8 10 20 zone=zone, Date_Time.new 2023 4 25 14 25 2 zone=zone] formats = Column.from_vector "formats" ["", Nothing] - expected = Column.from_vector "values" ['2020-06-21T08:10:20-10:00[US/Hawaii]', '2023-04-25T14:25:02-10:00[US/Hawaii]'] + expected = Column.from_vector "values" ['2020-06-21 08:10:20-10:00[US/Hawaii]', '2023-04-25 14:25:02-10:00[US/Hawaii]'] actual = input.format formats actual . should_equal expected diff --git a/test/Table_Tests/src/In_Memory/Table_Date_Time_Spec.enso b/test/Table_Tests/src/In_Memory/Table_Date_Time_Spec.enso index a723f22c01b9..2f69ef48b991 100644 --- a/test/Table_Tests/src/In_Memory/Table_Date_Time_Spec.enso +++ b/test/Table_Tests/src/In_Memory/Table_Date_Time_Spec.enso @@ -38,7 +38,8 @@ spec = Test.specify "should serialise back to input" <| expected_text = normalize_lines <| (enso_project.data / "datetime_sample_normalized_hours.csv").read_text - delimited = Text.from expected format=(Delimited "," line_endings=Line_Ending_Style.Unix) + data_formatter = Data_Formatter.Value . with_datetime_formats datetime_formats=["yyyy-MM-dd HH:mm:ss"] + delimited = Text.from expected format=(Delimited "," line_endings=Line_Ending_Style.Unix value_formatter=data_formatter) delimited.should_equal expected_text Test.specify "should serialise dates with format" <| From 2dad8663e34ba755c831ec31f870f3c79934e78b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Wed, 20 Sep 2023 17:16:42 +0200 Subject: [PATCH 50/76] javafmtAll --- .../interpreter/runtime/data/EnsoDate.java | 1 - .../runtime/data/EnsoDateTime.java | 1 - .../common_utils/Core_Date_Utils.java | 38 +++++++++---------- .../enso/table/parsing/BaseTimeParser.java | 5 +-- .../org/enso/table/parsing/DateParser.java | 4 +- .../enso/table/parsing/DateTimeParser.java | 4 +- .../enso/table/parsing/TimeOfDayParser.java | 4 +- 7 files changed, 30 insertions(+), 27 deletions(-) diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoDate.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoDate.java index 84811811bf58..6c4679d96744 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoDate.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoDate.java @@ -10,7 +10,6 @@ import java.time.DateTimeException; import java.time.LocalDate; import java.time.LocalTime; -import java.time.format.DateTimeFormatter; import org.enso.interpreter.dsl.Builtin; import org.enso.interpreter.runtime.EnsoContext; import org.enso.interpreter.runtime.library.dispatch.TypesLibrary; diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoDateTime.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoDateTime.java index f3cecf1a1877..0cae721d84f0 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoDateTime.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoDateTime.java @@ -12,7 +12,6 @@ import java.time.LocalTime; import java.time.ZoneId; import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; import org.enso.interpreter.dsl.Builtin; import org.enso.interpreter.runtime.EnsoContext; import org.enso.interpreter.runtime.data.text.Text; diff --git a/lib/scala/common-polyglot-core-utils/src/main/java/org/enso/polyglot/common_utils/Core_Date_Utils.java b/lib/scala/common-polyglot-core-utils/src/main/java/org/enso/polyglot/common_utils/Core_Date_Utils.java index 59e6d955c5c3..02d8105f6ba9 100644 --- a/lib/scala/common-polyglot-core-utils/src/main/java/org/enso/polyglot/common_utils/Core_Date_Utils.java +++ b/lib/scala/common-polyglot-core-utils/src/main/java/org/enso/polyglot/common_utils/Core_Date_Utils.java @@ -9,38 +9,38 @@ import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; -import java.time.chrono.IsoChronology; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; import java.time.format.DateTimeParseException; -import java.time.format.ResolverStyle; import java.time.temporal.ChronoField; import java.time.temporal.TemporalQueries; public class Core_Date_Utils { /** default Date Time formatter for parsing a Date_Time. */ public static final DateTimeFormatter defaultZonedDateTimeFormatter = - new DateTimeFormatterBuilder() - .parseLenient() - .append(DateTimeFormatter.ISO_LOCAL_DATE) - .appendLiteral(' ') - .append(DateTimeFormatter.ISO_LOCAL_TIME) - .optionalStart() - .parseLenient() - .appendOffsetId() - .optionalEnd() - .optionalStart() - .appendLiteral('[') - .parseCaseSensitive() - .appendZoneRegionId() - .appendLiteral(']') - .toFormatter(); + new DateTimeFormatterBuilder() + .parseLenient() + .append(DateTimeFormatter.ISO_LOCAL_DATE) + .appendLiteral(' ') + .append(DateTimeFormatter.ISO_LOCAL_TIME) + .optionalStart() + .parseLenient() + .appendOffsetId() + .optionalEnd() + .optionalStart() + .appendLiteral('[') + .parseCaseSensitive() + .appendZoneRegionId() + .appendLiteral(']') + .toFormatter(); /** default Date formatter for parsing a Date. */ - public static final DateTimeFormatter defaultLocalDateFormatter = DateTimeFormatter.ISO_LOCAL_DATE; + public static final DateTimeFormatter defaultLocalDateFormatter = + DateTimeFormatter.ISO_LOCAL_DATE; /** default Time formatter for parsing a Time_Of_Day. */ - public static final DateTimeFormatter defaultLocalTimeFormatter = DateTimeFormatter.ISO_LOCAL_TIME; + public static final DateTimeFormatter defaultLocalTimeFormatter = + DateTimeFormatter.ISO_LOCAL_TIME; /** * Parse a date string into a LocalDate. Allows missing day (assumes first day of month) or diff --git a/std-bits/table/src/main/java/org/enso/table/parsing/BaseTimeParser.java b/std-bits/table/src/main/java/org/enso/table/parsing/BaseTimeParser.java index c0168c59458d..3a217bd178d3 100644 --- a/std-bits/table/src/main/java/org/enso/table/parsing/BaseTimeParser.java +++ b/std-bits/table/src/main/java/org/enso/table/parsing/BaseTimeParser.java @@ -1,8 +1,6 @@ package org.enso.table.parsing; -import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; -import org.enso.base.Time_Utils; import org.enso.base.time.EnsoDateTimeFormatter; import org.enso.table.parsing.problems.ProblemAggregator; @@ -25,7 +23,8 @@ protected Object parseSingleValue(String text, ProblemAggregator problemAggregat try { return parseStrategy.parse(text, formatter); } catch (DateTimeParseException ignored) { - // TODO I think ideally we should try to return Option instead of throwing, as throwing is inefficient + // TODO I think ideally we should try to return Option instead of throwing, as throwing is + // inefficient } } diff --git a/std-bits/table/src/main/java/org/enso/table/parsing/DateParser.java b/std-bits/table/src/main/java/org/enso/table/parsing/DateParser.java index b39f5fb20d0f..bdcf9165320b 100644 --- a/std-bits/table/src/main/java/org/enso/table/parsing/DateParser.java +++ b/std-bits/table/src/main/java/org/enso/table/parsing/DateParser.java @@ -6,7 +6,9 @@ public class DateParser extends BaseTimeParser { public DateParser(EnsoDateTimeFormatter[] formatters) { - super(formatters, (String text, EnsoDateTimeFormatter formatter) -> formatter.parseLocalDate(text)); + super( + formatters, + (String text, EnsoDateTimeFormatter formatter) -> formatter.parseLocalDate(text)); } @Override diff --git a/std-bits/table/src/main/java/org/enso/table/parsing/DateTimeParser.java b/std-bits/table/src/main/java/org/enso/table/parsing/DateTimeParser.java index 46fae441fc23..1151f4588eb3 100644 --- a/std-bits/table/src/main/java/org/enso/table/parsing/DateTimeParser.java +++ b/std-bits/table/src/main/java/org/enso/table/parsing/DateTimeParser.java @@ -6,7 +6,9 @@ public class DateTimeParser extends BaseTimeParser { public DateTimeParser(EnsoDateTimeFormatter[] formatters) { - super(formatters, (String text, EnsoDateTimeFormatter formatter) -> formatter.parseZonedDateTime(text)); + super( + formatters, + (String text, EnsoDateTimeFormatter formatter) -> formatter.parseZonedDateTime(text)); } @Override diff --git a/std-bits/table/src/main/java/org/enso/table/parsing/TimeOfDayParser.java b/std-bits/table/src/main/java/org/enso/table/parsing/TimeOfDayParser.java index 4f561e32d7cc..116c4bf0cdc1 100644 --- a/std-bits/table/src/main/java/org/enso/table/parsing/TimeOfDayParser.java +++ b/std-bits/table/src/main/java/org/enso/table/parsing/TimeOfDayParser.java @@ -6,7 +6,9 @@ public class TimeOfDayParser extends BaseTimeParser { public TimeOfDayParser(EnsoDateTimeFormatter[] formatters) { - super(formatters, (String text, EnsoDateTimeFormatter formatter) -> formatter.parseLocalTime(text)); + super( + formatters, + (String text, EnsoDateTimeFormatter formatter) -> formatter.parseLocalTime(text)); } @Override From a4c9fcd3cf9c5914f340d50e1aab2d8b39bb56b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Wed, 20 Sep 2023 17:59:37 +0200 Subject: [PATCH 51/76] remove separate conversions file - obsolete since --- .../Standard/Base/0.0.0-dev/src/Data/Text/Extensions.enso | 1 - .../Standard/Base/0.0.0-dev/src/Data/Time/Conversions.enso | 7 ------- .../lib/Standard/Base/0.0.0-dev/src/Data/Time/Date.enso | 1 - .../Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso | 1 - .../Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso | 4 ++++ .../Standard/Base/0.0.0-dev/src/Data/Time/Time_Of_Day.enso | 1 - .../Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso | 1 + distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso | 1 - .../lib/Standard/Base/0.0.0-dev/src/Widget_Helpers.enso | 1 - 9 files changed, 5 insertions(+), 13 deletions(-) delete mode 100644 distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Conversions.enso diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Text/Extensions.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Text/Extensions.enso index 28c71fea6532..bf07a4e15c76 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Text/Extensions.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Text/Extensions.enso @@ -36,7 +36,6 @@ from project.Data.Boolean import Boolean, False, True from project.Data.Json import Invalid_JSON, JS_Object, Json from project.Data.Numbers import Float, Integer, Number, Number_Parse_Error from project.Data.Range.Extensions import all -from project.Data.Time.Conversions import all from project.Widget_Helpers import make_date_format_selector, make_date_time_format_selector, make_delimiter_selector, make_regex_text_widget, make_time_format_selector polyglot java import com.ibm.icu.lang.UCharacter diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Conversions.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Conversions.enso deleted file mode 100644 index 6b00bebd1ddb..000000000000 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Conversions.enso +++ /dev/null @@ -1,7 +0,0 @@ -import project.Data.Locale.Locale -import project.Data.Text.Text -import project.Data.Time.Date_Time_Formatter.Date_Time_Formatter - -## PRIVATE -Date_Time_Formatter.from (that:Text) (locale:Locale = Locale.default) = - Date_Time_Formatter.from_simple_pattern that locale diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date.enso index 179ca5b04378..b0e29ea286d2 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date.enso @@ -26,7 +26,6 @@ import project.Nothing.Nothing import project.Panic.Panic from project.Data.Boolean import Boolean, False, True from project.Data.Text.Extensions import all -from project.Data.Time.Conversions import all from project.Data.Time.Date_Time import ensure_in_epoch from project.Widget_Helpers import make_date_format_selector diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso index 5aea383b3280..793365c840aa 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso @@ -25,7 +25,6 @@ import project.Panic.Panic import project.Warning.Warning from project.Data.Boolean import Boolean, False, True from project.Data.Text.Extensions import all -from project.Data.Time.Conversions import all from project.Widget_Helpers import make_date_time_format_selector polyglot java import java.lang.ArithmeticException diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso index b4133be07867..2534cc4f8717 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso @@ -274,6 +274,10 @@ type Date_Time_Formatter format_time self (time:Time_Of_Day) = self.handle_java_errors <| self.underlying.formatLocalTime time +## PRIVATE +Date_Time_Formatter.from (that:Text) (locale:Locale = Locale.default) = + Date_Time_Formatter.from_simple_pattern that locale + ## PRIVATE type Date_Time_Format_Parse_Error ## PRIVATE diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Time_Of_Day.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Time_Of_Day.enso index 183f9e739325..e5068a23a3eb 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Time_Of_Day.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Time_Of_Day.enso @@ -20,7 +20,6 @@ import project.Nothing.Nothing import project.Panic.Panic from project.Data.Boolean import Boolean, False, True from project.Data.Text.Extensions import all -from project.Data.Time.Conversions import all from project.Widget_Helpers import make_time_format_selector polyglot java import java.lang.Exception as JException diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso index 9cf6a0f28a4c..f2d37a783ddb 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso @@ -215,6 +215,7 @@ type Parser 2 -> Standard_Date_Patterns.Day_Of_Month (Numeric_Representation.Value 2) 3 -> Standard_Date_Patterns.Day_Of_Week (Text_Representation.Short_Form) _ -> Standard_Date_Patterns.Day_Of_Week (Text_Representation.Long_Form) + "e" -> Panic.throw (Date_Time_Format_Parse_Error.Error "The pattern "+(character*count)+" is not a valid pattern for the "+self.mode.pattern_format_name+" format. If you want to represent the day of week as text use `ddd` for the short form and `dddd` for the long form.") _ -> Nothing ## PRIVATE diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso index 4599ca5cdaff..4b125404b0ab 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso @@ -86,7 +86,6 @@ from project.Data.Index_Sub_Range.Index_Sub_Range import First, Last from project.Data.Json.Extensions import all from project.Data.Range.Extensions import all from project.Data.Text.Extensions import all -from project.Data.Time.Conversions import all from project.Data.Time.Date_Time_Format_Constants import all from project.Errors.Problem_Behavior.Problem_Behavior import all from project.Meta.Enso_Project import enso_project diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Widget_Helpers.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Widget_Helpers.enso index eef18edb3be9..1e1743c2687c 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Widget_Helpers.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Widget_Helpers.enso @@ -4,7 +4,6 @@ import project.Data.Time.Date_Time.Date_Time import project.Data.Time.Time_Of_Day.Time_Of_Day import project.Data.Time.Date_Time_Formatter.Date_Time_Formatter import project.Meta -from project.Data.Time.Conversions import all import project.Metadata.Widget from project.Metadata import make_single_choice From 76db70cd583ab9a59e8ea26aa6e0d9ff3dd005e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Wed, 20 Sep 2023 18:18:41 +0200 Subject: [PATCH 52/76] testing various new edge cases - parsing formats, customizing --- .../src/Data/Time/Date_Time_Formatter.enso | 4 ++ .../src/Internal/Time/Format/Parser.enso | 10 ++- .../Data/Time/Date_Time_Formatter_Spec.enso | 61 +++++++++++++++++++ 3 files changed, 73 insertions(+), 2 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso index 2534cc4f8717..873c740c10f3 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso @@ -278,6 +278,10 @@ type Date_Time_Formatter Date_Time_Formatter.from (that:Text) (locale:Locale = Locale.default) = Date_Time_Formatter.from_simple_pattern that locale +## PRIVATE +Date_Time_Formatter.from (that:DateTimeFormatter) = + Date_Time_Formatter.from_java that + ## PRIVATE type Date_Time_Format_Parse_Error ## PRIVATE diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso index f2d37a783ddb..b6be1a3abf04 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso @@ -161,10 +161,13 @@ type Parser any_pattern = date_pattern.if_nothing <| self.parse_time_or_timezone_pattern character count any_pattern.if_nothing <| - Panic.throw (Date_Time_Format_Parse_Error.Error "The pattern "+(character*count)+" is not recognized as any known pattern for the "+self.mode.pattern_format_name+" format.") + self.fail_invalid_pattern character count any_pattern _ -> Panic.throw (Illegal_State.Error "Unexpected (here) token type: "+token.to_text) + fail_invalid_pattern self character count extra_message="" = + Panic.throw (Date_Time_Format_Parse_Error.Error "The pattern "+(character*count)+" is not a valid pattern for the "+self.mode.pattern_format_name+" format."+extra_message) + ## PRIVATE consume_token self = current_position = self.position.get @@ -215,7 +218,8 @@ type Parser 2 -> Standard_Date_Patterns.Day_Of_Month (Numeric_Representation.Value 2) 3 -> Standard_Date_Patterns.Day_Of_Week (Text_Representation.Short_Form) _ -> Standard_Date_Patterns.Day_Of_Week (Text_Representation.Long_Form) - "e" -> Panic.throw (Date_Time_Format_Parse_Error.Error "The pattern "+(character*count)+" is not a valid pattern for the "+self.mode.pattern_format_name+" format. If you want to represent the day of week as text use `ddd` for the short form and `dddd` for the long form.") + "e" -> self.fail_invalid_pattern character count " If you want to represent the day of week as text use `ddd` for the short form and `dddd` for the long form, or use `from_java` for the Java syntax." + "l" -> self.fail_invalid_pattern character count " If you want to represent the month as text use `MMM` for the short form and `MMMM` for the long form, or use `from_java` for the Java syntax." _ -> Nothing ## PRIVATE @@ -229,6 +233,7 @@ type Parser 2 -> ISO_Week_Year_Patterns.Day_Of_Week (Numeric_Representation.Value 2) 3 -> ISO_Week_Year_Patterns.Day_Of_Week (Text_Representation.Short_Form) _ -> ISO_Week_Year_Patterns.Day_Of_Week (Text_Representation.Long_Form) + "e" -> self.fail_invalid_pattern character count " If you want to represent the day of week as text use `ddd` for the short form and `dddd` for the long form, or use `from_java` for the Java syntax." _ -> Nothing ## PRIVATE @@ -250,6 +255,7 @@ type Parser "a" -> Time_Patterns.AmPm "t" -> if count <= 3 then Time_Zone_Patterns.Time_Zone_ID else Time_Zone_Patterns.Time_Zone_Name "z" -> Time_Zone_Patterns.Time_Zone_Offset + "x" -> self.fail_invalid_pattern character count " If you want to represent the time zone offset use `zz`, or use `from_java` for the Java syntax." _ -> Nothing ## PRIVATE diff --git a/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso b/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso index 6eb6cd3c062b..96a9734c6cb2 100644 --- a/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso +++ b/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso @@ -1,10 +1,36 @@ from Standard.Base import all +import Standard.Base.Data.Time.Date_Time_Formatter.Date_Time_Format_Parse_Error import Standard.Base.Errors.Time_Error.Time_Error from Standard.Test import Test, Test_Suite import Standard.Test.Extensions +polyglot java import org.enso.table.formatting.DateTimeFormatter + spec = + Test.group "Parsing formats" <| + Test.specify "should throw informative error for replacements of Java patterns in Simple format" <| + r1 = Date_Time_Formatter.from "E, d MMM yyyy" + r1.should_fail_with Date_Time_Format_Parse_Error + r1.catch.to_display_text . should_contain "use `ddd`" + + r2 = Date_Time_Formatter.from "d LLL yyyy" + r2.should_fail_with Date_Time_Format_Parse_Error + r2.catch.to_display_text . should_contain "use `MMM`" + + r3 = Date_Time_Formatter.from "dd-MM-yyyy HH:mm:ss '['XX']'" + r3.should_fail_with Date_Time_Format_Parse_Error + r3.catch.to_display_text . should_contain "use `zz`" + + Test.specify "should report format parse failures" <| + Date_Time_Formatter.from "yyyy[" . should_fail_with Date_Time_Format_Parse_Error + Date_Time_Formatter.from "yyyy{12}" . should_fail_with Date_Time_Format_Parse_Error + Date_Time_Formatter.from "yy{baz}" . should_fail_with Date_Time_Format_Parse_Error + Date_Time_Formatter.from "MM{baz}" . should_fail_with Date_Time_Format_Parse_Error + Date_Time_Formatter.from "{baz}" . should_fail_with Date_Time_Format_Parse_Error + Date_Time_Formatter.from "]" . should_fail_with Date_Time_Format_Parse_Error + Date_Time_Formatter.from "'" . should_fail_with Date_Time_Format_Parse_Error + Test.group "Formatting date/time values" <| Test.specify "should allow printing month names" <| d = Date.new 2020 6 30 @@ -12,6 +38,14 @@ spec = d.format (Date_Time_Formatter.from "d. MMMM yyyy" Locale.us) . should_equal "30. June 2020" # Note that the default (ROOT) locale returns a short name even for MMMM (full name) month format. d.format (Date_Time_Formatter.from "d. MMMM yyyy" Locale.default) . should_equal "30. Jun 2020" + + Test.specify "should allow using a Java formatter" <| + jformatter = DateTimeFormatter.ISO_ORDINAL_DATE + Date.new 2020 2 1 . format jformatter . should_equal "2020-032" + + Test.specify "should allow parsing Java patterns" <| + Date.new 2020 2 1 . format (Date_Time_Formatter.from_java "E, d LLL yyyy") . should_equal "Sat, 1 Feb 2020" + Test.group "Parsing date/time values" <| Test.specify "should allow short month names" <| Date.parse "30. Jun 2020" "d. MMM yyyy" . should_equal (Date.new 2020 6 30) @@ -134,3 +168,30 @@ spec = msg . should_contain "Text '2008-01-01' could not be parsed" result -> Test.fail ("Unexpected result: " + result.to_text) + + Test.specify "should be able to parse YYYY as well as yyyy" <| + Date.parse "2020-01-02" "YYYY-MM-dd" . should_equal (Date.new 2020 1 1) + Date.parse "2020-01-02" "yyyy-MM-dd" . should_equal (Date.new 2020 1 1) + + Test.specify "should be able to parse year-month without day" <| + Date.parse "2022-05" "yyyy-MM" . should_equal (Date.new 2022 5 1) + + Test.specify "should be able to parse a quarter without day" <| + Date.parse "Q2 of 2022" "'Q'Q 'of' yyyy" . should_equal (Date.new 2022 4 1) + + Test.specify "should be able to parse a day and month without year - defaulting to current year" <| + current_year = Date.today.year + Date.parse "07/23" "MM/dd" . should_equal (Date.new current_year 7 23) + Date.parse "14. of May" "d. 'of' MMMM" . should_equal (Date.new current_year 5 14) + + Test.specify "should be able to parse 2-digit year" <| + Date.parse "22-05-06" "yy-MM-dd" . should_equal (Date.new 2022 5 6) + Date.parse "99-01-02" "yy-MM-dd" . should_equal (Date.new 1999 1 2) + Date.parse "49-03-04" "yy-MM-dd" . should_equal (Date.new 2049 3 4) + Date.parse "50-03-04" "yy-MM-dd" . should_equal (Date.new 1950 3 4) + + Test.specify "should be able to parse 2-digit year with custom base-year" <| + Date.parse "22-05-06" "yy{1999}-MM-dd" . should_equal (Date.new 1922 5 6) + Date.parse "99-01-02" "yy{1949}-MM-dd" . should_equal (Date.new 1899 1 2) + Date.parse "49-03-04" "yy{3099}-MM-dd" . should_equal (Date.new 3049 3 4) + Date.parse "50-03-04" "yy{2099}-MM-dd" . should_equal (Date.new 2050 3 4) From 5ed449ca85c6f7887d8529ad0fb83d60a1d84c44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Wed, 20 Sep 2023 18:27:37 +0200 Subject: [PATCH 53/76] Conversions from polyglot symbols seem to not be allowed? --- .../Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso | 4 ---- test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso index 873c740c10f3..2534cc4f8717 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso @@ -278,10 +278,6 @@ type Date_Time_Formatter Date_Time_Formatter.from (that:Text) (locale:Locale = Locale.default) = Date_Time_Formatter.from_simple_pattern that locale -## PRIVATE -Date_Time_Formatter.from (that:DateTimeFormatter) = - Date_Time_Formatter.from_java that - ## PRIVATE type Date_Time_Format_Parse_Error ## PRIVATE diff --git a/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso b/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso index 96a9734c6cb2..deec53ac34a2 100644 --- a/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso +++ b/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso @@ -40,7 +40,7 @@ spec = d.format (Date_Time_Formatter.from "d. MMMM yyyy" Locale.default) . should_equal "30. Jun 2020" Test.specify "should allow using a Java formatter" <| - jformatter = DateTimeFormatter.ISO_ORDINAL_DATE + jformatter = Date_Time_Formatter.from_java DateTimeFormatter.ISO_ORDINAL_DATE Date.new 2020 2 1 . format jformatter . should_equal "2020-032" Test.specify "should allow parsing Java patterns" <| From 91ee52a54a79135b0bd032355b49f5832a664791 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Wed, 20 Sep 2023 18:30:56 +0200 Subject: [PATCH 54/76] fixes --- test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso b/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso index deec53ac34a2..5e083909cc75 100644 --- a/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso +++ b/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso @@ -5,7 +5,7 @@ import Standard.Base.Errors.Time_Error.Time_Error from Standard.Test import Test, Test_Suite import Standard.Test.Extensions -polyglot java import org.enso.table.formatting.DateTimeFormatter +polyglot java import java.time.format.DateTimeFormatter spec = Test.group "Parsing formats" <| @@ -195,3 +195,5 @@ spec = Date.parse "99-01-02" "yy{1949}-MM-dd" . should_equal (Date.new 1899 1 2) Date.parse "49-03-04" "yy{3099}-MM-dd" . should_equal (Date.new 3049 3 4) Date.parse "50-03-04" "yy{2099}-MM-dd" . should_equal (Date.new 2050 3 4) + +main = Test_Suite.run_main spec From eb255150b6aabb2e56426fed4fbd533df5421957 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Wed, 20 Sep 2023 18:41:30 +0200 Subject: [PATCH 55/76] fixes 2 --- .../src/Data/Time/Date_Time_Formatter.enso | 6 +++--- .../org/enso/base/time/EnsoDateTimeFormatter.java | 14 +++++++++++++- .../src/Data/Time/Date_Time_Formatter_Spec.enso | 6 +++--- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso index 2534cc4f8717..7e139a897d8c 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso @@ -4,6 +4,7 @@ import project.Data.Time.Date.Date import project.Data.Time.Date_Time.Date_Time import project.Data.Time.Time_Of_Day.Time_Of_Day import project.Error.Error +import project.Errors.Illegal_Argument.Illegal_Argument import project.Errors.Time_Error.Time_Error import project.Nothing.Nothing import project.Panic.Panic @@ -168,7 +169,7 @@ type Date_Time_Formatter pattern. If not specified, defaults to `Locale.default`. If passing a `DateTimeFormatter` instance and this argument is set, it will overwrite the original locale of that formatter. - from_java (pattern:Text|DateTimeFormatter) (locale:Locale|Nothing=Nothing) = case pattern of + from_java pattern (locale:Locale|Nothing=Nothing) = case pattern of java_formatter : DateTimeFormatter -> amended_formatter = case locale of Nothing -> java_formatter @@ -178,8 +179,7 @@ type Date_Time_Formatter java_locale = (locale.if_nothing Locale.default).java_locale java_formatter = DateTimeFormatter.ofPattern pattern java_locale Date_Time_Formatter.Value (EnsoDateTimeFormatter.new java_formatter pattern FormatterKind.RAW_JAVA) - - # TODO Locale for constants?? + _ -> Error.throw (Illegal_Argument.Error "The pattern must either be a string or a Java DateTimeFormatter instance.") ## The default format for date-time used in Enso. It acts as `ISO_Zoned_Date_Time` but both offset and timezone are optional. diff --git a/std-bits/base/src/main/java/org/enso/base/time/EnsoDateTimeFormatter.java b/std-bits/base/src/main/java/org/enso/base/time/EnsoDateTimeFormatter.java index 5ea3eba30055..cd9eebd54913 100644 --- a/std-bits/base/src/main/java/org/enso/base/time/EnsoDateTimeFormatter.java +++ b/std-bits/base/src/main/java/org/enso/base/time/EnsoDateTimeFormatter.java @@ -11,6 +11,7 @@ import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoField; +import java.time.temporal.IsoFields; import java.time.temporal.TemporalQueries; import java.util.Arrays; import java.util.Locale; @@ -104,12 +105,23 @@ public LocalDate parseLocalDate(String dateString) { // Allow Year and Month to be parsed without a day (use first day of month). if (parsed.isSupported(ChronoField.YEAR) && parsed.isSupported(ChronoField.MONTH_OF_YEAR)) { - var dayOfMonth = + int dayOfMonth = parsed.isSupported(ChronoField.DAY_OF_MONTH) ? parsed.get(ChronoField.DAY_OF_MONTH) : 1; return LocalDate.of( parsed.get(ChronoField.YEAR), parsed.get(ChronoField.MONTH_OF_YEAR), dayOfMonth); } + // Allow Year and Quarter to be parsed without a day (use first day of the quarter). + if (parsed.isSupported(ChronoField.YEAR) && parsed.isSupported(IsoFields.QUARTER_OF_YEAR)) { + int dayOfQuarter = + parsed.isSupported(IsoFields.DAY_OF_QUARTER) ? parsed.get(IsoFields.DAY_OF_QUARTER) : 1; + int year = parsed.get(ChronoField.YEAR); + int quarter = parsed.get(IsoFields.QUARTER_OF_YEAR); + int monthsToShift = 3 * (quarter - 1); + LocalDate firstDay = LocalDate.of(year, 1, 1); + return firstDay.plusMonths(monthsToShift).plusDays(dayOfQuarter - 1); + } + // Allow Month and Day to be parsed without a year (use current year). if (parsed.isSupported(ChronoField.DAY_OF_MONTH) && parsed.isSupported(ChronoField.MONTH_OF_YEAR)) { diff --git a/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso b/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso index 5e083909cc75..9b8bb9f276ce 100644 --- a/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso +++ b/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso @@ -170,13 +170,13 @@ spec = Test.fail ("Unexpected result: " + result.to_text) Test.specify "should be able to parse YYYY as well as yyyy" <| - Date.parse "2020-01-02" "YYYY-MM-dd" . should_equal (Date.new 2020 1 1) - Date.parse "2020-01-02" "yyyy-MM-dd" . should_equal (Date.new 2020 1 1) + Date.parse "2020-01-02" "YYYY-MM-dd" . should_equal (Date.new 2020 1 2) + Date.parse "2020-01-02" "yyyy-MM-dd" . should_equal (Date.new 2020 1 2) Test.specify "should be able to parse year-month without day" <| Date.parse "2022-05" "yyyy-MM" . should_equal (Date.new 2022 5 1) - Test.specify "should be able to parse a quarter without day" <| + Test.specify "should be able to parse a quarter without day" pending="TODO" <| Date.parse "Q2 of 2022" "'Q'Q 'of' yyyy" . should_equal (Date.new 2022 4 1) Test.specify "should be able to parse a day and month without year - defaulting to current year" <| From eaf8404fb2c8c075b32dbe63786bbae722349c13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Thu, 21 Sep 2023 13:10:40 +0200 Subject: [PATCH 56/76] Remove workaround for #7824 - once #7845 got merged it is no longer needed --- .../Data/Time/Date_Time_Format_Constants.enso | 52 ------------------- .../src/Data/Time/Date_Time_Formatter.enso | 2 +- .../lib/Standard/Base/0.0.0-dev/src/Main.enso | 2 - .../Base/0.0.0-dev/src/Widget_Helpers.enso | 15 +++--- 4 files changed, 9 insertions(+), 62 deletions(-) delete mode 100644 distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Constants.enso diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Constants.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Constants.enso deleted file mode 100644 index 6269c1492b87..000000000000 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Format_Constants.enso +++ /dev/null @@ -1,52 +0,0 @@ -import project.Data.Time.Date_Time_Formatter.Date_Time_Formatter - -## PRIVATE - Workaround for https://github.com/enso-org/enso/issues/7824 - Once that issue is fixed, this should be removed. -type Date_Time_Format_Constants - ## The default format for date-time used in Enso. - It acts as `ISO_Zoned_Date_Time` but both offset and timezone are optional. - - For example, it may parse date of the form `2011-12-03 10:15:30+01:00[Europe/Paris]`, - as well as `2011-12-03T10:15:30` assuming the default timezone. - Default_Enso_Zoned_Date_Time - - ## The ISO 8601 format for date-time with offset and timezone. - The date and time parts may be separated by a single space or a `T`. - - For example, it may parse date of the form `2011-12-03 10:15:30+01:00[Europe/Paris]`. - ISO_Zoned_Date_Time - - ## The ISO 8601 format for date-time with offset. - The date and time parts may be separated by a single space or a `T`. - - For example, it may parse date of the form `2011-12-03 10:15:30+01:00`. - ISO_Offset_Date_Time - - ## The ISO 8601 format for date-time without a timezone. - The date and time parts may be separated by a single space or a `T`. - - For example, it may parse date of the form `2011-12-03 10:15:30`. The - timezone will be set to `Time_Zone.system`. - ISO_Local_Date_Time - - ## The ISO 8601 format for date. - - For example, it may parse date of the form `2011-12-03`. - ISO_Date - - ## The ISO 8601 format for time. - - For example, it may parse time of the form `10:15:30`. - ISO_Time - -## PRIVATE - Workaround for https://github.com/enso-org/enso/issues/7824 - Once that issue is fixed, this should be removed. -Date_Time_Formatter.from (that:Date_Time_Format_Constants) = case that of - Date_Time_Format_Constants.ISO_Date -> Date_Time_Formatter.iso_date - Date_Time_Format_Constants.ISO_Time -> Date_Time_Formatter.iso_time - Date_Time_Format_Constants.ISO_Local_Date_Time -> Date_Time_Formatter.iso_local_date_time - Date_Time_Format_Constants.ISO_Offset_Date_Time -> Date_Time_Formatter.iso_offset_date_time - Date_Time_Format_Constants.ISO_Zoned_Date_Time -> Date_Time_Formatter.iso_zoned_date_time - Date_Time_Format_Constants.Default_Enso_Zoned_Date_Time -> Date_Time_Formatter.default_enso_zoned_date_time diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso index 7e139a897d8c..64ff315fa080 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso @@ -281,7 +281,7 @@ Date_Time_Formatter.from (that:Text) (locale:Locale = Locale.default) = ## PRIVATE type Date_Time_Format_Parse_Error ## PRIVATE - Indicates an error during parsing of a `Date_Time_Format_Constants`. + Indicates an error during parsing of a date time format pattern. Error message ## PRIVATE diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso index 4b125404b0ab..60f5a8a8ef57 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso @@ -86,7 +86,6 @@ from project.Data.Index_Sub_Range.Index_Sub_Range import First, Last from project.Data.Json.Extensions import all from project.Data.Range.Extensions import all from project.Data.Text.Extensions import all -from project.Data.Time.Date_Time_Format_Constants import all from project.Errors.Problem_Behavior.Problem_Behavior import all from project.Meta.Enso_Project import enso_project from project.Network.Extensions import all @@ -179,7 +178,6 @@ from project.Data.Range.Extensions export all from project.Data.Statistics export all hiding to_moment_statistic, wrap_java_call, calculate_correlation_statistics, calculate_spearman_rank, calculate_correlation_statistics_matrix, compute_fold, empty_value, is_valid from project.Data.Text.Extensions export all from project.Data.Time.Conversions export all -from project.Data.Time.Date_Time_Format_Constants export all from project.Errors.Problem_Behavior.Problem_Behavior export all from project.Function export all from project.Meta.Enso_Project export enso_project diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Widget_Helpers.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Widget_Helpers.enso index 1e1743c2687c..d04bf55fdcec 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Widget_Helpers.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Widget_Helpers.enso @@ -24,8 +24,8 @@ make_delimiter_selector = Creates a Single_Choice Widget for parsing dates. make_date_format_selector : Date -> Widget make_date_format_selector (date:Date=(Date.new 2012 3 14)) = - fqn = "Date_Time_Formatter" - iso_format = ['ISO-Format (e.g. ' + date.to_text + ')', "Date_Time_Format_Constants.ISO_Date"] + fqn = Meta.get_qualified_type_name Date_Time_Formatter + iso_format = ['ISO-Format (e.g. ' + date.format Date_Time_Formatter.iso_date + ')', fqn+".iso_date"] formats = ['d/M/yyyy', 'dd/MM/yyyy', 'd-MMM-yy', 'd MMMM yyyy', 'M/d/yyyy', 'MM/dd/yyyy', 'MMMM d, yyyy', 'yyyy-MM'].map f-> [f + " (e.g. " + date.format f + ")", f.pretty] custom_locale_format = @@ -41,16 +41,16 @@ make_date_format_selector (date:Date=(Date.new 2012 3 14)) = Creates a Single_Choice Widget for parsing date times. make_date_time_format_selector : Date_Time -> Widget make_date_time_format_selector (date_time:Date_Time=(Date_Time.new 2012 3 14 15 9 26 123)) = - fqn = "Date_Time_Formatter" + fqn = Meta.get_qualified_type_name Date_Time_Formatter enso_format = format = Date_Time_Formatter.default_enso_zoned_date_time - ['Default (e.g. ' + date_time.format format + ')', "Date_Time_Format_Constants.Default_Enso_Zoned_Date_Time"] + ['Default (e.g. ' + date_time.format format + ')', fqn+".default_enso_zoned_date_time"] iso_format = format = Date_Time_Formatter.iso_zoned_date_time - ['ISO-Format (e.g. ' + (date_time.format format) + ')', "Date_Time_Format_Constants.ISO_Zoned_Date_Time"] + ['ISO-Format (e.g. ' + (date_time.format format) + ')', fqn+".iso_zoned_date_time"] iso_local = format = Date_Time_Formatter.iso_local_date_time - ['ISO-Local (e.g. ' + (date_time.format format) + ')', "Date_Time_Format_Constants.ISO_Local_Date_Time"] + ['ISO-Local (e.g. ' + (date_time.format format) + ')', fqn+".iso_local_date_time"] formats = ['yyyy-MM-dd HH:mm:ss.f', 'yyyy-MM-dd HH:mm:ss.f TT', 'd/M/yyyy h:mm a', 'dd/MM/yyyy HH:mm:ss', 'd-MMM-yy HH:mm:ss', 'd-MMM-yy h:mm:ss a', 'd MMMM yyyy h:mm a', 'M/d/yyyy h:mm:ss a', 'MM/dd/yyyy HH:mm:ss'] mapped_formats = formats.map f-> [f + " (e.g. " + date_time.format f + ")", f.pretty] custom_locale_format = @@ -66,7 +66,8 @@ make_date_time_format_selector (date_time:Date_Time=(Date_Time.new 2012 3 14 15 Creates a Single_Choice Widget for parsing times. make_time_format_selector : Time_Of_Day -> Widget make_time_format_selector (time:Time_Of_Day=(Time_Of_Day.new 13 30 55 123)) = - iso_format = ['ISO-Format (e.g. ' + time.to_text + ')', "Date_Time_Format_Constants.ISO_Time"] + fqn = Meta.get_qualified_type_name Date_Time_Formatter + iso_format = ['ISO-Format (e.g. ' + time.format Date_Time_Formatter.iso_time + ')', fqn+".iso_time"] formats = ['HH:mm[:ss]', 'HH:mm:ss', 'h:mm[:ss] a', 'hh:mm:ss a', 'HH:mm:ss.S'].map f-> [f + " (e.g. " + time.format f + ")", f.pretty] From 74370c7b41eca201ea00884582d424c8fc137994 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Thu, 21 Sep 2023 13:46:45 +0200 Subject: [PATCH 57/76] fix --- .../lib/Standard/Base/0.0.0-dev/src/Widget_Helpers.enso | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Widget_Helpers.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Widget_Helpers.enso index d04bf55fdcec..8101fa15ecc9 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Widget_Helpers.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Widget_Helpers.enso @@ -58,7 +58,7 @@ make_date_time_format_selector (date_time:Date_Time=(Date_Time.new 2012 3 14 15 ['d MMMM yyyy HH:mm - with custom Locale (e.g. ' + date_time.format format + ')', "(Date_Time_Formatter.from 'd MMMM yyyy HH:mm' locale=Locale.france)"] week_date_formats = ['YYYY-ww-d HH:mm:ss.f'].map f-> format = Date_Time_Formatter.from_iso_week_date_pattern f - ["ISO Week-based Date-Time: " + f + " (e.g. " + date_time.format format + ")", "("+fqn+".from_iso_week_date_pattern " + f.pretty + ")"] + ["ISO Week-based Date-Time: " + f + " (e.g. " + date_time.format format + ")", "(Date_Time_Formatter.from_iso_week_date_pattern " + f.pretty + ")"] make_single_choice ([enso_format, iso_format, iso_local] + mapped_formats + [custom_locale_format] + week_date_formats) From 8d1c125a1035c8d650684922ac4cea997f7825e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Thu, 21 Sep 2023 13:50:31 +0200 Subject: [PATCH 58/76] CR1 --- .../java/org/enso/base/time/EnsoDateTimeFormatter.java | 8 +++++++- .../src/main/java/org/enso/base/time/FormatterKind.java | 8 ++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/std-bits/base/src/main/java/org/enso/base/time/EnsoDateTimeFormatter.java b/std-bits/base/src/main/java/org/enso/base/time/EnsoDateTimeFormatter.java index cd9eebd54913..74ac1a09721c 100644 --- a/std-bits/base/src/main/java/org/enso/base/time/EnsoDateTimeFormatter.java +++ b/std-bits/base/src/main/java/org/enso/base/time/EnsoDateTimeFormatter.java @@ -19,6 +19,12 @@ import static java.time.temporal.ChronoField.INSTANT_SECONDS; import static java.time.temporal.ChronoField.NANO_OF_SECOND; +/** + * An Enso representation of the DateTimeFormatter. + *

+ * It adds some additional functionality to the Java formatter - including a workaround for making the `T` in ISO dates + * optional and tracking how it was constructed. + */ public class EnsoDateTimeFormatter { private final DateTimeFormatter formatter; private final Pair isoReplacementPair; @@ -87,7 +93,7 @@ private String normaliseInput(String dateString) { @Override public String toString() { return switch (formatterKind) { - case SIMPLE -> "(Simple) " + originalPattern; + case SIMPLE -> originalPattern; case ISO_WEEK_DATE -> "(ISO Week Date Format) " + originalPattern; case RAW_JAVA -> "(Java Format Pattern) " + originalPattern; case CONSTANT -> originalPattern; diff --git a/std-bits/base/src/main/java/org/enso/base/time/FormatterKind.java b/std-bits/base/src/main/java/org/enso/base/time/FormatterKind.java index a6493e4dcf55..8560b3d3be0c 100644 --- a/std-bits/base/src/main/java/org/enso/base/time/FormatterKind.java +++ b/std-bits/base/src/main/java/org/enso/base/time/FormatterKind.java @@ -1,8 +1,16 @@ package org.enso.base.time; +/** Specifies how the formatter was constrcuted. */ public enum FormatterKind { + /** Formatters constructed using `from_simple_pattern`. */ SIMPLE, + + /** Formatters constructed using `from_iso_week_date_pattern`. */ ISO_WEEK_DATE, + + /** Formatters constructed from a raw Java formatter or using DateTimeFormatter.ofPattern. */ RAW_JAVA, + + /** Formatters based on a constant, like ISO_ZONED_DATE_TIME, or the default Enso formatter. */ CONSTANT } From fd65d099783939d5052f02c674b6a9a44b064562 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Thu, 21 Sep 2023 14:06:38 +0200 Subject: [PATCH 59/76] CR2 --- .../src/Data/Time/Date_Time_Formatter.enso | 4 ++++ .../Time/Format/As_Java_Formatter_Interpreter.enso | 2 +- .../0.0.0-dev/src/Internal/Time/Format/Parser.enso | 13 +++++++++---- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso index 64ff315fa080..1f2888904e0b 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso @@ -59,6 +59,8 @@ type Date_Time_Formatter The weekday names depend on the selected locale. Both day of week and day of month may be included in a single pattern - in such case the day of week is used as a sanity check. + - e: An alternative notation: single `e` maps to `ddd` and `ee` or more + map to `dddd` meaning name of day of week. - Q: Quarter of year. If only year and quarter are provided in the pattern, when parsing a date, the result will be the first day of that quarter. @@ -129,6 +131,8 @@ type Date_Time_Formatter - ddd: Short name of the day of week (Mon-Sun). - dddd: Full name of the day of week (Monday-Sunday). The weekday names depend on the selected locale. + - e: An alternative notation: single `e` maps to `ddd` and `ee` or more + map to `dddd` meaning name of day of week. Moreover, all time and timezone pattern characters like in `Simple` case are supported too - in case you need to parse a date time value with the diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/As_Java_Formatter_Interpreter.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/As_Java_Formatter_Interpreter.enso index 5d97ade57d06..02cbf79074a1 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/As_Java_Formatter_Interpreter.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/As_Java_Formatter_Interpreter.enso @@ -43,7 +43,7 @@ interpret locale nodes = appendOffset("+HH:MM:ss","Z"); builder.appendLocalizedOffset TextStyle.FULL - Time_Patterns.AmPm -> + Time_Patterns.AM_PM -> builder.appendText ChronoField.AMPM_OF_DAY Time_Patterns.Fraction_Of_Second representation -> min_digits = 1 diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso index b6be1a3abf04..09077be832d1 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso @@ -75,7 +75,7 @@ type Time_Patterns Hour (representation : Numeric_Representation) is_24h:Boolean ## PRIVATE - AmPm + AM_PM ## PRIVATE Minute (representation : Numeric_Representation) @@ -218,8 +218,11 @@ type Parser 2 -> Standard_Date_Patterns.Day_Of_Month (Numeric_Representation.Value 2) 3 -> Standard_Date_Patterns.Day_Of_Week (Text_Representation.Short_Form) _ -> Standard_Date_Patterns.Day_Of_Week (Text_Representation.Long_Form) - "e" -> self.fail_invalid_pattern character count " If you want to represent the day of week as text use `ddd` for the short form and `dddd` for the long form, or use `from_java` for the Java syntax." + "e" -> case count of + 1 -> Standard_Date_Patterns.Day_Of_Week (Text_Representation.Short_Form) + _ -> Standard_Date_Patterns.Day_Of_Week (Text_Representation.Long_Form) "l" -> self.fail_invalid_pattern character count " If you want to represent the month as text use `MMM` for the short form and `MMMM` for the long form, or use `from_java` for the Java syntax." + "w" -> self.fail_invalid_pattern character count " If you want to use the week of year, consider using `from_iso_week_date_pattern` that handles the ISO 8601 leap week calendar." _ -> Nothing ## PRIVATE @@ -233,7 +236,9 @@ type Parser 2 -> ISO_Week_Year_Patterns.Day_Of_Week (Numeric_Representation.Value 2) 3 -> ISO_Week_Year_Patterns.Day_Of_Week (Text_Representation.Short_Form) _ -> ISO_Week_Year_Patterns.Day_Of_Week (Text_Representation.Long_Form) - "e" -> self.fail_invalid_pattern character count " If you want to represent the day of week as text use `ddd` for the short form and `dddd` for the long form, or use `from_java` for the Java syntax." + "e" -> case count of + 1 -> ISO_Week_Year_Patterns.Day_Of_Week (Text_Representation.Short_Form) + _ -> ISO_Week_Year_Patterns.Day_Of_Week (Text_Representation.Long_Form) _ -> Nothing ## PRIVATE @@ -252,7 +257,7 @@ type Parser if count > 9 then Panic.throw (Date_Time_Format_Parse_Error.Error "It is meaningless to have more than 9 digits in the fractional-of-second pattern, because at most nanosecond precision of seconds is currently supported.") Time_Patterns.Fraction_Of_Second (Numeric_Representation.Value count) - "a" -> Time_Patterns.AmPm + "a" -> Time_Patterns.AM_PM "t" -> if count <= 3 then Time_Zone_Patterns.Time_Zone_ID else Time_Zone_Patterns.Time_Zone_Name "z" -> Time_Zone_Patterns.Time_Zone_Offset "x" -> self.fail_invalid_pattern character count " If you want to represent the time zone offset use `zz`, or use `from_java` for the Java syntax." From 55aed40835fdcf32881e874e1c1179bf00ecdd7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Thu, 21 Sep 2023 14:11:03 +0200 Subject: [PATCH 60/76] a few more tests --- .../Data/Time/Date_Time_Formatter_Spec.enso | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso b/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso index 9b8bb9f276ce..20967c33c42c 100644 --- a/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso +++ b/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso @@ -10,17 +10,17 @@ polyglot java import java.time.format.DateTimeFormatter spec = Test.group "Parsing formats" <| Test.specify "should throw informative error for replacements of Java patterns in Simple format" <| - r1 = Date_Time_Formatter.from "E, d MMM yyyy" + r1 = Date_Time_Formatter.from "d LLL yyyy" r1.should_fail_with Date_Time_Format_Parse_Error - r1.catch.to_display_text . should_contain "use `ddd`" + r1.catch.to_display_text . should_contain "use `MMM`" - r2 = Date_Time_Formatter.from "d LLL yyyy" + r2 = Date_Time_Formatter.from "dd-MM-yyyy HH:mm:ss '['XX']'" r2.should_fail_with Date_Time_Format_Parse_Error - r2.catch.to_display_text . should_contain "use `MMM`" + r2.catch.to_display_text . should_contain "use `zz`" - r3 = Date_Time_Formatter.from "dd-MM-yyyy HH:mm:ss '['XX']'" + r3 = Date_Time_Formatter.from "yyyy-ww-dd" r3.should_fail_with Date_Time_Format_Parse_Error - r3.catch.to_display_text . should_contain "use `zz`" + r3.catch.to_display_text . should_contain "consider using `from_iso_week_date_pattern`" Test.specify "should report format parse failures" <| Date_Time_Formatter.from "yyyy[" . should_fail_with Date_Time_Format_Parse_Error @@ -46,6 +46,12 @@ spec = Test.specify "should allow parsing Java patterns" <| Date.new 2020 2 1 . format (Date_Time_Formatter.from_java "E, d LLL yyyy") . should_equal "Sat, 1 Feb 2020" + Test.specify "should handle various formats" <| + Date.new 2023 09 21 . format "E, dd.MM.yy" . should_equal "Thu, 21.09.23" + Date.new 2023 09 21 . format "DDDD" . should_equal "Thursday" + Date.new 2023 09 21 . format (Date_Time_Formatter.from_iso_week_date_pattern "ee, 'W'WW ''yy") . should_equal "Thursday, W38 '23" + Date.new 2023 09 21 . format "'Q'Q ''yy{1999}" . should_equal "Q3 '23" + Test.group "Parsing date/time values" <| Test.specify "should allow short month names" <| Date.parse "30. Jun 2020" "d. MMM yyyy" . should_equal (Date.new 2020 6 30) From fb548a6a76952c14d4b75f63b196876b00c27938 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Thu, 21 Sep 2023 14:18:41 +0200 Subject: [PATCH 61/76] CR3 --- .../lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso index 793365c840aa..57aad90726fe 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso @@ -207,7 +207,7 @@ type Date_Time ? Default Date_Time Format Unless you provide a custom format, the text must represent a valid - date-time as defined by the ISO-8601 format (see https://en.wikipedia.org/wiki/ISO_8601).make_date_time_format_selector + date-time as defined by the ISO-8601 format (see https://en.wikipedia.org/wiki/ISO_8601). If a time zone is present, it must be in the ISO-8601 Extended Date/Time Format (EDTF) (see https://en.wikipedia.org/wiki/ISO_8601#EDTF). The time zone format consists of: From df49e12fe4c21864c19d3b06a2a65f1dadddc9df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Thu, 21 Sep 2023 14:20:31 +0200 Subject: [PATCH 62/76] CR4 --- .../lib/Standard/Table/0.0.0-dev/src/Data/Data_Formatter.enso | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Data_Formatter.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Data_Formatter.enso index 0f559967285f..feddce91dbb9 100644 --- a/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Data_Formatter.enso +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Data_Formatter.enso @@ -74,7 +74,7 @@ type Data_Formatter If set to `Ignore`, the operation proceeds without errors or warnings. 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 = - # TODO [RW] move to value_type + # TODO [RW] move to value_type: https://github.com/enso-org/enso/issues/7866 parser = self.make_datatype_parser datatype Java_Problems.unpack_value_with_aggregated_problems on_problems problem_mapping=(Parse_Values_Helper.translate_parsing_problem datatype) <| parser.parseIndependentValue text From ab74d6a3a816e71e19e045dffd2b603be49536cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Thu, 21 Sep 2023 14:23:30 +0200 Subject: [PATCH 63/76] CR5 --- .../Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso | 9 ++++++--- test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso | 4 ++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso index 09077be832d1..7ffa1951f7d4 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso @@ -209,7 +209,8 @@ type Parser 1 -> Numeric_Representation.Value 1 2 -> Numeric_Representation.Value 2 3 -> Text_Representation.Short_Form - _ -> Text_Representation.Long_Form + 4 -> Text_Representation.Long_Form + _ -> self.fail_invalid_pattern character count " The month pattern takes at most 4 letters." Standard_Date_Patterns.Month representation # Lowercase form is reserved for minutes - handled elsewhere. "m" -> Nothing @@ -217,7 +218,8 @@ type Parser 1 -> Standard_Date_Patterns.Day_Of_Month (Numeric_Representation.Value 1) 2 -> Standard_Date_Patterns.Day_Of_Month (Numeric_Representation.Value 2) 3 -> Standard_Date_Patterns.Day_Of_Week (Text_Representation.Short_Form) - _ -> Standard_Date_Patterns.Day_Of_Week (Text_Representation.Long_Form) + 4 -> Standard_Date_Patterns.Day_Of_Week (Text_Representation.Long_Form) + _ -> self.fail_invalid_pattern character count " The day pattern takes at most 4 letters." "e" -> case count of 1 -> Standard_Date_Patterns.Day_Of_Week (Text_Representation.Short_Form) _ -> Standard_Date_Patterns.Day_Of_Week (Text_Representation.Long_Form) @@ -235,7 +237,8 @@ type Parser 1 -> ISO_Week_Year_Patterns.Day_Of_Week (Numeric_Representation.Value 1) 2 -> ISO_Week_Year_Patterns.Day_Of_Week (Numeric_Representation.Value 2) 3 -> ISO_Week_Year_Patterns.Day_Of_Week (Text_Representation.Short_Form) - _ -> ISO_Week_Year_Patterns.Day_Of_Week (Text_Representation.Long_Form) + 4 -> ISO_Week_Year_Patterns.Day_Of_Week (Text_Representation.Long_Form) + _ -> self.fail_invalid_pattern character count " The day pattern takes at most 4 letters." "e" -> case count of 1 -> ISO_Week_Year_Patterns.Day_Of_Week (Text_Representation.Short_Form) _ -> ISO_Week_Year_Patterns.Day_Of_Week (Text_Representation.Long_Form) diff --git a/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso b/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso index 20967c33c42c..728a3cb49040 100644 --- a/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso +++ b/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso @@ -22,6 +22,10 @@ spec = r3.should_fail_with Date_Time_Format_Parse_Error r3.catch.to_display_text . should_contain "consider using `from_iso_week_date_pattern`" + r4 = Date_Time_Formatter.from "yyyy-MMMMMMM-dd" + r4.should_fail_with Date_Time_Format_Parse_Error + r4.catch.to_display_text . should_contain "at most 4" + Test.specify "should report format parse failures" <| Date_Time_Formatter.from "yyyy[" . should_fail_with Date_Time_Format_Parse_Error Date_Time_Formatter.from "yyyy{12}" . should_fail_with Date_Time_Format_Parse_Error From e837fe2b0643d008ead930b6c910a3f6ab7cab47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Thu, 21 Sep 2023 14:27:17 +0200 Subject: [PATCH 64/76] CR6 --- .../lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso | 2 +- .../Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso | 2 ++ .../Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso | 3 +++ test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso | 5 ++++- 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso index 57aad90726fe..278b63d5b93f 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso @@ -744,7 +744,7 @@ type Date_Time to_display_text self = # TODO note that we are using a format that will not be parsed by our default formatters and needs custom formatter to be parsed. Is that OK? time_format = if self.nanosecond include_milliseconds=True == 0 then "HH:mm:ss" else "HH:mm:ss.n" - self.format "yyyy-MM-dd "+time_format+" tt" + self.format "yyyy-MM-dd "+time_format+" TT" ## PRIVATE Convert to a JavaScript Object representing a Date_Time. diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso index 1f2888904e0b..6608c460e388 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso @@ -80,6 +80,8 @@ type Date_Time_Formatter - T: If repeated 3 or less times - Time zone ID (e.g. Europe/Warsaw, Z, -08:30), otherwise - Time zone name (e.g. Central European Time, CET). - Z: Zone offset (e.g. +0000, -0830, +08:30:15). + - v: Time zone name (same as TTTT). + - V: Time zone ID (same as T). Some parts, like fractions of a second may not be required. The square brackets `[]` can be used to surround such optional sections. diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso index 7ffa1951f7d4..9465a73c7d5b 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso @@ -262,6 +262,9 @@ type Parser Time_Patterns.Fraction_Of_Second (Numeric_Representation.Value count) "a" -> Time_Patterns.AM_PM "t" -> if count <= 3 then Time_Zone_Patterns.Time_Zone_ID else Time_Zone_Patterns.Time_Zone_Name + "v" -> case character of + "V" -> Time_Zone_Patterns.Time_Zone_ID + "v" -> Time_Zone_Patterns.Time_Zone_Name "z" -> Time_Zone_Patterns.Time_Zone_Offset "x" -> self.fail_invalid_pattern character count " If you want to represent the time zone offset use `zz`, or use `from_java` for the Java syntax." _ -> Nothing diff --git a/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso b/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso index 728a3cb49040..346be2242495 100644 --- a/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso +++ b/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso @@ -56,6 +56,9 @@ spec = Date.new 2023 09 21 . format (Date_Time_Formatter.from_iso_week_date_pattern "ee, 'W'WW ''yy") . should_equal "Thursday, W38 '23" Date.new 2023 09 21 . format "'Q'Q ''yy{1999}" . should_equal "Q3 '23" + tz = Time_Zone.parse "US/Hawaii" + Date_Time.new 2023 09 21 12 zone=tz . format "yyyy/MM/dd HH:mm:ss VV" . should_equal "2023/09/21 12:00:00 US/Hawaii" + Test.group "Parsing date/time values" <| Test.specify "should allow short month names" <| Date.parse "30. Jun 2020" "d. MMM yyyy" . should_equal (Date.new 2020 6 30) @@ -147,7 +150,7 @@ spec = Test.fail ("Unexpected result: " + result.to_text) Test.specify "should parse custom format of zoned time" <| - time = Date_Time.parse "2020-05-06 04:30:20 UTC" "yyyy-MM-dd HH:mm:ss tt" + time = Date_Time.parse "2020-05-06 04:30:20 UTC" "yyyy-MM-dd HH:mm:ss VV" time . year . should_equal 2020 time . month . should_equal 5 time . day . should_equal 6 From f597ecda08e8e61e201cb9e4f0d66551ab16bd2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Thu, 21 Sep 2023 14:29:05 +0200 Subject: [PATCH 65/76] changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2887e652eb00..b38c05cee03f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -576,6 +576,8 @@ - [Added `Data.post` method to write to HTTP endpoints.][7700] - [Added support for S3. Using `Input_Stream` more for reading.][7776] - [Renamed `Decimal` to `Float`.][7807] +- [Implemented `Date_Time_Formatter` for more user-friendly date/time format + parsing.][7826] [debug-shortcuts]: https://github.com/enso-org/enso/blob/develop/app/gui/docs/product/shortcuts.md#debug @@ -817,6 +819,7 @@ [7709]: https://github.com/enso-org/enso/pull/7709 [7776]: https://github.com/enso-org/enso/pull/7776 [7807]: https://github.com/enso-org/enso/pull/7807 +[7826]: https://github.com/enso-org/enso/pull/7826 #### Enso Compiler From b1f7332bffc330961852cf3b9779659a5c5b143b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Thu, 21 Sep 2023 14:31:05 +0200 Subject: [PATCH 66/76] fix test: default locale uses short day names even for long form... --- test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso b/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso index 346be2242495..d569e834a6ed 100644 --- a/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso +++ b/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso @@ -52,7 +52,7 @@ spec = Test.specify "should handle various formats" <| Date.new 2023 09 21 . format "E, dd.MM.yy" . should_equal "Thu, 21.09.23" - Date.new 2023 09 21 . format "DDDD" . should_equal "Thursday" + Date.new 2023 09 21 . format (Date_Time_Formatter.from "DDDD" Locale.poland) . should_equal "czwartek" Date.new 2023 09 21 . format (Date_Time_Formatter.from_iso_week_date_pattern "ee, 'W'WW ''yy") . should_equal "Thursday, W38 '23" Date.new 2023 09 21 . format "'Q'Q ''yy{1999}" . should_equal "Q3 '23" From 6e758150e13d385e32e0b5a405a29fc48724b2d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Thu, 21 Sep 2023 14:34:24 +0200 Subject: [PATCH 67/76] add tests for week-based year (TODO), fix obsolete docs --- .../Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso | 8 ++++---- .../Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso | 2 +- test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso | 8 ++++++++ 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso index 6608c460e388..78767be91fd7 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso @@ -147,14 +147,14 @@ type Date_Time_Formatter > Example Parsing a date in the ISO week date format - Date.parse (ISO_Week_Date "YYYY-'W'WW-d" "1976-W53-6") == (Date.new 1977 01 01) - Date.parse (ISO_Week_Date "YYYY-'W'WW, eee" "1978-W01, Mon") == (Date.new 1978 01 02) - Date_Time.parse (ISO_Week_Date "YYYY-'W'WW-d HH:mm:ss" "1978-W01-4 12:34:56") == (Date_Time.new 1978 01 05 12 34 56) + Date.parse (Date_Time_Formatter.from_iso_week_date_pattern "YYYY-'W'WW-d" "1976-W53-6") == (Date.new 1977 01 01) + Date.parse (Date_Time_Formatter.from_iso_week_date_pattern "YYYY-'W'WW, eee" "1978-W01, Mon") == (Date.new 1978 01 02) + Date_Time.parse (Date_Time_Formatter.from_iso_week_date_pattern "YYYY-'W'WW-d HH:mm:ss" "1978-W01-4 12:34:56") == (Date_Time.new 1978 01 05 12 34 56) > Example Omitting the day of the week will result in the first day of that week. - Date.parse (ISO_Week_Date "YYYY-'W'WW" "1978-W01") == (Date.new 1978 01 02) + Date.parse (Date_Time_Formatter.from_iso_week_date_pattern "YYYY-'W'WW" "1978-W01") == (Date.new 1978 01 02) @locale Locale.default_widget from_iso_week_date_pattern pattern:Text locale:Locale=Locale.default = java_formatter = Tokenizer.tokenize pattern |> Parser.parse_iso_week_year_pattern |> diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso index 9465a73c7d5b..d4072586cd4c 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso @@ -108,7 +108,7 @@ type Parser_Mode ## PRIVATE pattern_format_name self = case self of Parser_Mode.Simple -> "Simple" - Parser_Mode.ISO_Week_Year -> "ISO_Week_Date" + Parser_Mode.ISO_Week_Year -> "ISO Week-Date" ## PRIVATE type Parser diff --git a/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso b/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso index d569e834a6ed..ccf3aa67c5bd 100644 --- a/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso +++ b/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso @@ -209,4 +209,12 @@ spec = Date.parse "49-03-04" "yy{3099}-MM-dd" . should_equal (Date.new 3049 3 4) Date.parse "50-03-04" "yy{2099}-MM-dd" . should_equal (Date.new 2050 3 4) + Test.specify "should be able to parse a week-based year format" <| + Date.parse (Date_Time_Formatter.from_iso_week_date_pattern "YYYY-'W'WW-d" "1976-W53-6") . should_equal (Date.new 1977 01 01) + Date_Time.parse (Date_Time_Formatter.from_iso_week_date_pattern "YYYY-'W'WW-d HH:mm:ss" "1978-W01-4 12:34:56") . should_equal (Date_Time.new 1978 01 05 12 34 56) + + Date.parse (Date_Time_Formatter.from_iso_week_date_pattern "YYYY-'W'WW, eee" "1978-W01, Mon") . should_equal (Date.new 1978 01 02) + # Just week will parse to first day of the week: + Date.parse (Date_Time_Formatter.from_iso_week_date_pattern "YYYY-'W'WW" "1978-W01") . should_equal (Date.new 1978 01 02) + main = Test_Suite.run_main spec From b9ec35e6ed191e70e5ba753ee8e85c9b4520e412 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Thu, 21 Sep 2023 14:46:07 +0200 Subject: [PATCH 68/76] move ) --- .../Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso | 8 ++++---- test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso index 78767be91fd7..c316830cadb7 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso @@ -147,14 +147,14 @@ type Date_Time_Formatter > Example Parsing a date in the ISO week date format - Date.parse (Date_Time_Formatter.from_iso_week_date_pattern "YYYY-'W'WW-d" "1976-W53-6") == (Date.new 1977 01 01) - Date.parse (Date_Time_Formatter.from_iso_week_date_pattern "YYYY-'W'WW, eee" "1978-W01, Mon") == (Date.new 1978 01 02) - Date_Time.parse (Date_Time_Formatter.from_iso_week_date_pattern "YYYY-'W'WW-d HH:mm:ss" "1978-W01-4 12:34:56") == (Date_Time.new 1978 01 05 12 34 56) + Date.parse (Date_Time_Formatter.from_iso_week_date_pattern "YYYY-'W'WW-d") "1976-W53-6" == (Date.new 1977 01 01) + Date.parse (Date_Time_Formatter.from_iso_week_date_pattern "YYYY-'W'WW, eee") "1978-W01, Mon" == (Date.new 1978 01 02) + Date_Time.parse (Date_Time_Formatter.from_iso_week_date_pattern "YYYY-'W'WW-d HH:mm:ss") "1978-W01-4 12:34:56" == (Date_Time.new 1978 01 05 12 34 56) > Example Omitting the day of the week will result in the first day of that week. - Date.parse (Date_Time_Formatter.from_iso_week_date_pattern "YYYY-'W'WW" "1978-W01") == (Date.new 1978 01 02) + Date.parse (Date_Time_Formatter.from_iso_week_date_pattern "YYYY-'W'WW") "1978-W01" == (Date.new 1978 01 02) @locale Locale.default_widget from_iso_week_date_pattern pattern:Text locale:Locale=Locale.default = java_formatter = Tokenizer.tokenize pattern |> Parser.parse_iso_week_year_pattern |> diff --git a/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso b/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso index ccf3aa67c5bd..323d044f22f1 100644 --- a/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso +++ b/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso @@ -210,11 +210,11 @@ spec = Date.parse "50-03-04" "yy{2099}-MM-dd" . should_equal (Date.new 2050 3 4) Test.specify "should be able to parse a week-based year format" <| - Date.parse (Date_Time_Formatter.from_iso_week_date_pattern "YYYY-'W'WW-d" "1976-W53-6") . should_equal (Date.new 1977 01 01) - Date_Time.parse (Date_Time_Formatter.from_iso_week_date_pattern "YYYY-'W'WW-d HH:mm:ss" "1978-W01-4 12:34:56") . should_equal (Date_Time.new 1978 01 05 12 34 56) + Date.parse (Date_Time_Formatter.from_iso_week_date_pattern "YYYY-'W'WW-d") "1976-W53-6" . should_equal (Date.new 1977 01 01) + Date_Time.parse (Date_Time_Formatter.from_iso_week_date_pattern "YYYY-'W'WW-d HH:mm:ss") "1978-W01-4 12:34:56" . should_equal (Date_Time.new 1978 01 05 12 34 56) - Date.parse (Date_Time_Formatter.from_iso_week_date_pattern "YYYY-'W'WW, eee" "1978-W01, Mon") . should_equal (Date.new 1978 01 02) + Date.parse (Date_Time_Formatter.from_iso_week_date_pattern "YYYY-'W'WW, eee") "1978-W01, Mon" . should_equal (Date.new 1978 01 02) # Just week will parse to first day of the week: - Date.parse (Date_Time_Formatter.from_iso_week_date_pattern "YYYY-'W'WW" "1978-W01") . should_equal (Date.new 1978 01 02) + Date.parse (Date_Time_Formatter.from_iso_week_date_pattern "YYYY-'W'WW") "1978-W01" . should_equal (Date.new 1978 01 02) main = Test_Suite.run_main spec From fd9b8676aa78484c46d82799225a1a94e2f06e36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Thu, 21 Sep 2023 17:14:02 +0200 Subject: [PATCH 69/76] Improving zone offset customization --- .../0.0.0-dev/src/Data/Time/Date_Time.enso | 18 ++++++++++-- .../src/Data/Time/Date_Time_Formatter.enso | 29 ++++++++++++------- .../Format/As_Java_Formatter_Interpreter.enso | 29 +++++++++---------- .../src/Internal/Time/Format/Parser.enso | 27 +++++++++++++---- .../Data/Time/Date_Time_Formatter_Spec.enso | 17 ++++++++--- 5 files changed, 81 insertions(+), 39 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso index 278b63d5b93f..acf52203cfba 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso @@ -203,7 +203,14 @@ type Date_Time - a: AM/PM marker. - T: If repeated 3 or less times - Time zone ID (e.g. Europe/Warsaw, Z, -08:30), otherwise - Time zone name (e.g. Central European Time, CET). - - Z: Zone offset (e.g. +0000, -0830, +08:30:15). + - Z: Zone offset. + - z, zz, zz: A short offset form (+HHmm). + No offset is indicated by "+0000". This can be customized by setting + an alternative no offset string in curly braces, e.g. `zz{Z}`. + - zzzz: A full offset form (+HH:mm:ss). + No offset is indicated by "Z". This can be customized as above, e.g. + `zzzz{0}`. + - zzzzz: Localized offset. ? Default Date_Time Format Unless you provide a custom format, the text must represent a valid @@ -804,7 +811,14 @@ type Date_Time - a: AM/PM marker. - T: If repeated 3 or less times - Time zone ID (e.g. Europe/Warsaw, Z, -08:30), otherwise - Time zone name (e.g. Central European Time, CET). - - Z: Zone offset (e.g. +0000, -0830, +08:30:15). + - Z: Zone offset. + - z, zz, zz: A short offset form (+HHmm). + No offset is indicated by "+0000". This can be customized by setting + an alternative no offset string in curly braces, e.g. `zz{Z}`. + - zzzz: A full offset form (+HH:mm:ss). + No offset is indicated by "Z". This can be customized as above, e.g. + `zzzz{0}`. + - zzzzz: Localized offset. > Example Format "2020-10-08T16:41:13+03:00[Europe/Moscow]" as diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso index c316830cadb7..9562b1134881 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso @@ -79,7 +79,14 @@ type Date_Time_Formatter Time zone pattern characters: - T: If repeated 3 or less times - Time zone ID (e.g. Europe/Warsaw, Z, -08:30), otherwise - Time zone name (e.g. Central European Time, CET). - - Z: Zone offset (e.g. +0000, -0830, +08:30:15). + - Z: Zone offset. + - z, zz, zz: A short offset form (+HHmm). + No offset is indicated by "+0000". This can be customized by setting + an alternative no offset string in curly braces, e.g. `zz{Z}`. + - zzzz: A full offset form (+HH:mm:ss). + No offset is indicated by "Z". This can be customized as above, e.g. + `zzzz{0}`. + - zzzzz: Localized offset. - v: Time zone name (same as TTTT). - V: Time zone ID (same as T). @@ -89,25 +96,25 @@ type Date_Time_Formatter > Example Parsing date/time values - Date_Time.parse "yyyy-MM-dd'T'HH:mm:ss.fZ" "2021-10-12T12:34:56.789+0200" == (Date_Time.new 2021 10 12 12 34 56 milliseconds=789 (Time_Zone.new hours=2)) - Date.parse "ddd, d MMM yyyy" "Tue, 12 Oct 2021" == (Date.new 2021 10 12) - Date.parse "dddd, dd MMMM ''yy" "Thursday, 1 October '98" == (Date.new 1998 10 01) - Date_Time.parse "d/M/Y h:mm a" "12/10/2021 5:34 PM" == (Date_Time.new 2021 10 12 17 34 00) + Date_Time.parse "2021-10-12T12:34:56.789+0200" "yyyy-MM-dd'T'HH:mm:ss.fZ" == (Date_Time.new 2021 10 12 12 34 56 millisecond=789 zone=(Time_Zone.new hours=2)) + Date.parse "Tue, 12 Oct 2021" "ddd, d MMM yyyy" == (Date.new 2021 10 12) + Date.parse "Thursday, 1 October '98" "dddd, dd MMMM ''yy" == (Date.new 1998 10 01) + Date_Time.parse "12/10/2021 5:34 PM" "d/M/Y h:mm a" == (Date_Time.new 2021 10 12 17 34 00) > Example Omitting the day will yield the first day of the month. - Date.parse "yyyy-MM" "2021-10" == (Date.new 2021 10 01) + Date.parse "2021-10" "yyyy-MM" == (Date.new 2021 10 01) > Example Omitting the year will yield the current year. - Date.parse "MM-dd" "10-12" == (Date.new (Date.today.year) 10 12) + Date.parse "10-12" "MM-dd" == (Date.new (Date.today.year) 10 12) > Example Parsing a two-digit year with a custom base year. - Date.parse "dd MMMM ''yy{2099}" "1 November '95" == (Date.new 2095 11 01) + Date.parse "1 November '95" "dd MMMM ''yy{2099}" == (Date.new 2095 11 01) @locale Locale.default_widget from_simple_pattern pattern:Text locale:Locale=Locale.default = java_formatter = Tokenizer.tokenize pattern |> Parser.parse_simple_date_pattern |> @@ -147,9 +154,9 @@ type Date_Time_Formatter > Example Parsing a date in the ISO week date format - Date.parse (Date_Time_Formatter.from_iso_week_date_pattern "YYYY-'W'WW-d") "1976-W53-6" == (Date.new 1977 01 01) - Date.parse (Date_Time_Formatter.from_iso_week_date_pattern "YYYY-'W'WW, eee") "1978-W01, Mon" == (Date.new 1978 01 02) - Date_Time.parse (Date_Time_Formatter.from_iso_week_date_pattern "YYYY-'W'WW-d HH:mm:ss") "1978-W01-4 12:34:56" == (Date_Time.new 1978 01 05 12 34 56) + Date.parse "1976-W53-6" (Date_Time_Formatter.from_iso_week_date_pattern "YYYY-'W'WW-d") == (Date.new 1977 01 01) + Date.parse "1978-W01, Mon" (Date_Time_Formatter.from_iso_week_date_pattern "YYYY-'W'WW, eee") == (Date.new 1978 01 02) + Date_Time.parse "1978-W01-4 12:34:56" (Date_Time_Formatter.from_iso_week_date_pattern "YYYY-'W'WW-d HH:mm:ss") == (Date_Time.new 1978 01 05 12 34 56) > Example Omitting the day of the week will result in the first day of that week. diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/As_Java_Formatter_Interpreter.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/As_Java_Formatter_Interpreter.enso index 02cbf79074a1..1be40a02e963 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/As_Java_Formatter_Interpreter.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/As_Java_Formatter_Interpreter.enso @@ -31,18 +31,12 @@ interpret locale nodes = Time_Zone_Patterns.Time_Zone_ID -> builder.appendZoneId - Time_Zone_Patterns.Time_Zone_Name -> - # TODO maybe ability to SHORT if less than 4 chars - builder.appendZoneText TextStyle.FULL - Time_Zone_Patterns.Time_Zone_Offset -> - ## TODO - appendOffset("+HHMM", "+0000"); - count == 4 - appendLocalizedOffset(TextStyle.FULL); - count == 5 - appendOffset("+HH:MM:ss","Z"); - builder.appendLocalizedOffset TextStyle.FULL - + Time_Zone_Patterns.Time_Zone_Name representation -> + builder.appendZoneText (text_representation_to_java_style representation) + Time_Zone_Patterns.Time_Zone_Offset pattern zero -> + builder.appendOffset pattern zero + Time_Zone_Patterns.Time_Zone_Localized_Offset representation -> + builder.appendLocalizedOffset (text_representation_to_java_style representation) Time_Patterns.AM_PM -> builder.appendText ChronoField.AMPM_OF_DAY Time_Patterns.Fraction_Of_Second representation -> @@ -87,9 +81,12 @@ append_field builder field representation = case representation of Numeric_Representation.Value digits -> case digits of 2 -> builder.appendValue field 2 _ -> builder.appendValue field digits 19 SignStyle.NORMAL - Text_Representation.Short_Form -> - builder.appendText field TextStyle.SHORT - Text_Representation.Long_Form -> - builder.appendText field TextStyle.FULL + text_representation : Text_Representation -> + builder.appendText field (text_representation_to_java_style text_representation) Two_Digit_Year_Representation.Value max_year -> Time_Utils.appendTwoDigitYear builder field max_year + +## PRIVATE +text_representation_to_java_style representation = case representation of + Text_Representation.Short_Form -> TextStyle.SHORT + Text_Representation.Long_Form -> TextStyle.FULL diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso index d4072586cd4c..0254d672ccde 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/Parser.enso @@ -89,13 +89,16 @@ type Time_Patterns ## PRIVATE type Time_Zone_Patterns ## PRIVATE - Time_Zone_Name + Time_Zone_Name representation:Text_Representation ## PRIVATE Time_Zone_ID ## PRIVATE - Time_Zone_Offset + Time_Zone_Offset pattern:Text zero:Text + + ## PRIVATE + Time_Zone_Localized_Offset representation:Text_Representation ## PRIVATE type Parser_Mode @@ -153,7 +156,7 @@ type Parser Format_Token.Literal text -> Common_Nodes.Literal text Format_Token.Curly_Section inner_text -> - Panic.throw (Date_Time_Format_Parse_Error.Error "Unexpected section in curly braces: {"+inner_text+"}. Currently, the curly braces are only used to parametrize the two-digit year with the max year, e.g. `yy{2049}`. If you want to include a curly brace as literal, escape it with single quotes like '{"+inner_text+"}'") + Panic.throw (Date_Time_Format_Parse_Error.Error "Unexpected section in curly braces: {"+inner_text+"}. If you want to include a curly brace as literal, escape it with single quotes like '{"+inner_text+"}'") Format_Token.Pattern character count -> date_pattern = case self.mode of Parser_Mode.Simple -> self.parse_simple_date_pattern character count @@ -175,6 +178,10 @@ type Parser self.tokens.at current_position ## PRIVATE + Checks if the next token is a curly brace parameter. + If it is, it is consumed and its value (as Text) is returned. + Otherwise, returns Nothing and does not move the cursor. + consume_curly_parameter_if_exists : Text | Nothing consume_curly_parameter_if_exists self = current_position = self.position.get case self.tokens.at current_position of @@ -247,6 +254,7 @@ type Parser ## PRIVATE parse_time_or_timezone_pattern self character count = lowercase = character.to_case Case.Lower + text_representation = if count <= 3 then Text_Representation.Short_Form else Text_Representation.Long_Form case lowercase of "h" -> case character of "H" -> Time_Patterns.Hour (Numeric_Representation.Value count) is_24h=True @@ -261,11 +269,18 @@ type Parser Panic.throw (Date_Time_Format_Parse_Error.Error "It is meaningless to have more than 9 digits in the fractional-of-second pattern, because at most nanosecond precision of seconds is currently supported.") Time_Patterns.Fraction_Of_Second (Numeric_Representation.Value count) "a" -> Time_Patterns.AM_PM - "t" -> if count <= 3 then Time_Zone_Patterns.Time_Zone_ID else Time_Zone_Patterns.Time_Zone_Name + "t" -> if count <= 3 then Time_Zone_Patterns.Time_Zone_ID else Time_Zone_Patterns.Time_Zone_Name text_representation "v" -> case character of "V" -> Time_Zone_Patterns.Time_Zone_ID - "v" -> Time_Zone_Patterns.Time_Zone_Name - "z" -> Time_Zone_Patterns.Time_Zone_Offset + "v" -> Time_Zone_Patterns.Time_Zone_Name text_representation + "z" -> case count of + 4 -> Time_Zone_Patterns.Time_Zone_Localized_Offset Text_Representation.Long_Form + 5 -> + no_offset_string = self.consume_curly_parameter_if_exists.if_nothing "Z" + Time_Zone_Patterns.Time_Zone_Offset "+HH:MM:ss" no_offset_string + _ -> if count > 5 then self.fail_invalid_pattern character count " Too many characters for timezone format, 5 is a maximum." else + no_offset_string = self.consume_curly_parameter_if_exists.if_nothing "+0000" + Time_Zone_Patterns.Time_Zone_Offset "+HHMM" no_offset_string "x" -> self.fail_invalid_pattern character count " If you want to represent the time zone offset use `zz`, or use `from_java` for the Java syntax." _ -> Nothing diff --git a/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso b/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso index 323d044f22f1..a792122c7c7a 100644 --- a/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso +++ b/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso @@ -209,12 +209,21 @@ spec = Date.parse "49-03-04" "yy{3099}-MM-dd" . should_equal (Date.new 3049 3 4) Date.parse "50-03-04" "yy{2099}-MM-dd" . should_equal (Date.new 2050 3 4) + Test.specify "should work like in examples" <| + Date.parse "Tue, 12 Oct 2021" "ddd, d MMM yyyy" . should_equal (Date.new 2021 10 12) + Date.parse "Thursday, 1 October '98" "dddd, dd MMMM ''yy" . should_equal (Date.new 1998 10 01) + Date_Time.parse "12/10/2021 5:34 PM" "d/M/Y h:mm a" . should_equal (Date_Time.new 2021 10 12 17 34 00) + Date.parse "2021-10" "yyyy-MM" . should_equal (Date.new 2021 10 01) + Date.parse "10-12" "MM-dd" . should_equal (Date.new (Date.today.year) 10 12) + Date.parse "1 November '95" "dd MMMM ''yy{2099}" . should_equal (Date.new 2095 11 01) + Date_Time.parse "2021-10-12T12:34:56.789+0200" "yyyy-MM-dd'T'HH:mm:ss.fZ" . should_equal (Date_Time.new 2021 10 12 12 34 56 millisecond=789 zone=(Time_Zone.new hours=2)) + Test.specify "should be able to parse a week-based year format" <| - Date.parse (Date_Time_Formatter.from_iso_week_date_pattern "YYYY-'W'WW-d") "1976-W53-6" . should_equal (Date.new 1977 01 01) - Date_Time.parse (Date_Time_Formatter.from_iso_week_date_pattern "YYYY-'W'WW-d HH:mm:ss") "1978-W01-4 12:34:56" . should_equal (Date_Time.new 1978 01 05 12 34 56) + Date.parse "1976-W53-6" (Date_Time_Formatter.from_iso_week_date_pattern "YYYY-'W'WW-d") . should_equal (Date.new 1977 01 01) + Date_Time.parse "1978-W01-4 12:34:56" (Date_Time_Formatter.from_iso_week_date_pattern "YYYY-'W'WW-d HH:mm:ss") . should_equal (Date_Time.new 1978 01 05 12 34 56) - Date.parse (Date_Time_Formatter.from_iso_week_date_pattern "YYYY-'W'WW, eee") "1978-W01, Mon" . should_equal (Date.new 1978 01 02) + Date.parse "1978-W01, Mon" (Date_Time_Formatter.from_iso_week_date_pattern "YYYY-'W'WW, eee") . should_equal (Date.new 1978 01 02) # Just week will parse to first day of the week: - Date.parse (Date_Time_Formatter.from_iso_week_date_pattern "YYYY-'W'WW") "1978-W01" . should_equal (Date.new 1978 01 02) + Date.parse "1978-W01" (Date_Time_Formatter.from_iso_week_date_pattern "YYYY-'W'WW") . should_equal (Date.new 1978 01 02) main = Test_Suite.run_main spec From 95a98973c05b2307baff3c4fb107aeadb5ad58e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Thu, 21 Sep 2023 17:17:34 +0200 Subject: [PATCH 70/76] add a test --- .../Tests/src/Data/Time/Date_Time_Formatter_Spec.enso | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso b/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso index a792122c7c7a..6a72a13c486b 100644 --- a/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso +++ b/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso @@ -59,6 +59,17 @@ spec = tz = Time_Zone.parse "US/Hawaii" Date_Time.new 2023 09 21 12 zone=tz . format "yyyy/MM/dd HH:mm:ss VV" . should_equal "2023/09/21 12:00:00 US/Hawaii" + Test.specify "should allow to customize the 'zero' of a zone offset" <| + dt = Date_Time.new 2020 01 02 12 zone=(Time_Zone.utc) + dt.format "yyyy/MM/dd HH:mm:ss ZZ" . should_equal "2020/01/02 12:00:00 +0000" + dt.format "yyyy/MM/dd HH:mm:ss ZZ{Z}" . should_equal "2020/01/02 12:00:00 Z" + dt.format "yyyy/MM/dd HH:mm:ss ZZ{}" . should_equal "2020/01/02 12:00:00 " + + dt2 = Date_Time.new 2020 01 02 12 zone=(Time_Zone.parse "US/Hawaii") + dt2.format "yyyy/MM/dd HH:mm:ss ZZ" . should_equal "2020/01/02 12:00:00 -1000" + dt2.format "yyyy/MM/dd HH:mm:ss ZZ{Z}" . should_equal "2020/01/02 12:00:00 -1000" + dt2.format "yyyy/MM/dd HH:mm:ss ZZZZZ{}" . should_equal "2020/01/02 12:00:00 -10:00" + Test.group "Parsing date/time values" <| Test.specify "should allow short month names" <| Date.parse "30. Jun 2020" "d. MMM yyyy" . should_equal (Date.new 2020 6 30) From c982793a42a50fb381b3a8c72204393629a6e68d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Thu, 21 Sep 2023 18:06:17 +0200 Subject: [PATCH 71/76] Fix parsing year-week without day (defaulting to Monday) --- .../Format/As_Java_Formatter_Interpreter.enso | 2 -- .../enso/base/time/EnsoDateTimeFormatter.java | 21 ++++++++++++++++++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/As_Java_Formatter_Interpreter.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/As_Java_Formatter_Interpreter.enso index 1be40a02e963..0ee6975ad1d1 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/As_Java_Formatter_Interpreter.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/As_Java_Formatter_Interpreter.enso @@ -42,8 +42,6 @@ interpret locale nodes = Time_Patterns.Fraction_Of_Second representation -> min_digits = 1 max_digits = case representation.digits of - # If just one digit provided, we allow as many as possible. - # TODO 1 -> 9 digits -> digits includes_decimal_point = False diff --git a/std-bits/base/src/main/java/org/enso/base/time/EnsoDateTimeFormatter.java b/std-bits/base/src/main/java/org/enso/base/time/EnsoDateTimeFormatter.java index 74ac1a09721c..e0fafa5c05e3 100644 --- a/std-bits/base/src/main/java/org/enso/base/time/EnsoDateTimeFormatter.java +++ b/std-bits/base/src/main/java/org/enso/base/time/EnsoDateTimeFormatter.java @@ -10,11 +10,16 @@ import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; +import java.time.format.ResolverStyle; import java.time.temporal.ChronoField; import java.time.temporal.IsoFields; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalField; import java.time.temporal.TemporalQueries; import java.util.Arrays; +import java.util.HashMap; import java.util.Locale; +import java.util.Map; import static java.time.temporal.ChronoField.INSTANT_SECONDS; import static java.time.temporal.ChronoField.NANO_OF_SECOND; @@ -103,7 +108,7 @@ public String toString() { public LocalDate parseLocalDate(String dateString) { dateString = normaliseInput(dateString); - var parsed = formatter.parse(dateString); + TemporalAccessor parsed = formatter.parse(dateString); if (parsed.isSupported(ChronoField.EPOCH_DAY)) { return LocalDate.ofEpochDay(parsed.getLong(ChronoField.EPOCH_DAY)); @@ -137,6 +142,20 @@ public LocalDate parseLocalDate(String dateString) { parsed.get(ChronoField.DAY_OF_MONTH)); } + if (parsed.isSupported(IsoFields.WEEK_BASED_YEAR) && parsed.isSupported(IsoFields.WEEK_OF_WEEK_BASED_YEAR)) { + // Get the day of week or default to first day if not present. + long dayOfWeek = parsed.isSupported(ChronoField.DAY_OF_WEEK) ? parsed.get(ChronoField.DAY_OF_WEEK) : 1; + HashMap fields = new HashMap<>(); + fields.put(IsoFields.WEEK_BASED_YEAR, parsed.getLong(IsoFields.WEEK_BASED_YEAR)); + fields.put(IsoFields.WEEK_OF_WEEK_BASED_YEAR, parsed.getLong(IsoFields.WEEK_OF_WEEK_BASED_YEAR)); + fields.put(ChronoField.DAY_OF_WEEK, dayOfWeek); + + TemporalAccessor resolved = IsoFields.WEEK_OF_WEEK_BASED_YEAR.resolve(fields, parsed, ResolverStyle.SMART); + if (resolved.isSupported(ChronoField.EPOCH_DAY)) { + return LocalDate.ofEpochDay(resolved.getLong(ChronoField.EPOCH_DAY)); + } + } + // This will usually throw at this point, but it will construct a more informative exception than we could. return LocalDate.from(parsed); } From be58e5cdfe7cf61b373202abc63c61e0523b2c76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Thu, 21 Sep 2023 18:10:07 +0200 Subject: [PATCH 72/76] fix a test --- .../Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso | 6 +++++- test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso index 9562b1134881..530c8480fd04 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso @@ -98,9 +98,13 @@ type Date_Time_Formatter Date_Time.parse "2021-10-12T12:34:56.789+0200" "yyyy-MM-dd'T'HH:mm:ss.fZ" == (Date_Time.new 2021 10 12 12 34 56 millisecond=789 zone=(Time_Zone.new hours=2)) Date.parse "Tue, 12 Oct 2021" "ddd, d MMM yyyy" == (Date.new 2021 10 12) - Date.parse "Thursday, 1 October '98" "dddd, dd MMMM ''yy" == (Date.new 1998 10 01) Date_Time.parse "12/10/2021 5:34 PM" "d/M/Y h:mm a" == (Date_Time.new 2021 10 12 17 34 00) + > Example + Note that the default locale may not support full-length day/month names, so you may need to set a specific locale for this to work. + + Date.parse "Thursday, 1 October '98" (Date_Time_Formatter.from "dddd, d MMMM ''yy" Locale.uk) == (Date.new 1998 10 01) + > Example Omitting the day will yield the first day of the month. diff --git a/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso b/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso index 6a72a13c486b..7854213dea21 100644 --- a/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso +++ b/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso @@ -222,7 +222,7 @@ spec = Test.specify "should work like in examples" <| Date.parse "Tue, 12 Oct 2021" "ddd, d MMM yyyy" . should_equal (Date.new 2021 10 12) - Date.parse "Thursday, 1 October '98" "dddd, dd MMMM ''yy" . should_equal (Date.new 1998 10 01) + Date.parse "Thursday, 1 October '98" (Date_Time_Formatter.from "dddd, d MMMM ''yy" Locale.uk) . should_equal (Date.new 1998 10 01) Date_Time.parse "12/10/2021 5:34 PM" "d/M/Y h:mm a" . should_equal (Date_Time.new 2021 10 12 17 34 00) Date.parse "2021-10" "yyyy-MM" . should_equal (Date.new 2021 10 01) Date.parse "10-12" "MM-dd" . should_equal (Date.new (Date.today.year) 10 12) From a21b94ea8cd93b6b0d3a8459f19cb82c79464a12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Thu, 21 Sep 2023 18:11:22 +0200 Subject: [PATCH 73/76] fix another test --- .../Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso | 2 +- test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso index 530c8480fd04..ea5d0f2326fd 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso @@ -118,7 +118,7 @@ type Date_Time_Formatter > Example Parsing a two-digit year with a custom base year. - Date.parse "1 November '95" "dd MMMM ''yy{2099}" == (Date.new 2095 11 01) + Date.parse "1 Nov '95" "d MMM ''yy{2099}" == (Date.new 2095 11 01) @locale Locale.default_widget from_simple_pattern pattern:Text locale:Locale=Locale.default = java_formatter = Tokenizer.tokenize pattern |> Parser.parse_simple_date_pattern |> diff --git a/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso b/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso index 7854213dea21..13e4f7450b83 100644 --- a/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso +++ b/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso @@ -226,7 +226,7 @@ spec = Date_Time.parse "12/10/2021 5:34 PM" "d/M/Y h:mm a" . should_equal (Date_Time.new 2021 10 12 17 34 00) Date.parse "2021-10" "yyyy-MM" . should_equal (Date.new 2021 10 01) Date.parse "10-12" "MM-dd" . should_equal (Date.new (Date.today.year) 10 12) - Date.parse "1 November '95" "dd MMMM ''yy{2099}" . should_equal (Date.new 2095 11 01) + Date.parse "1 Nov '95" "d MMM ''yy{2099}" . should_equal (Date.new 2095 11 01) Date_Time.parse "2021-10-12T12:34:56.789+0200" "yyyy-MM-dd'T'HH:mm:ss.fZ" . should_equal (Date_Time.new 2021 10 12 12 34 56 millisecond=789 zone=(Time_Zone.new hours=2)) Test.specify "should be able to parse a week-based year format" <| From 1d27c79622c16ce7800b988f6fc3fe12efb234a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Thu, 21 Sep 2023 18:13:23 +0200 Subject: [PATCH 74/76] fix --- std-bits/base/src/main/java/org/enso/base/Time_Utils.java | 5 +---- test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/std-bits/base/src/main/java/org/enso/base/Time_Utils.java b/std-bits/base/src/main/java/org/enso/base/Time_Utils.java index 2be3bcef3235..c381a24fa794 100644 --- a/std-bits/base/src/main/java/org/enso/base/Time_Utils.java +++ b/std-bits/base/src/main/java/org/enso/base/Time_Utils.java @@ -2,8 +2,6 @@ import org.enso.base.time.Date_Time_Utils; import org.enso.base.time.Date_Utils; -import org.enso.base.time.EnsoDateTimeFormatter; -import org.enso.base.time.FormatterKind; import org.enso.base.time.TimeUtilsBase; import org.enso.base.time.Time_Of_Day_Utils; import org.enso.polyglot.common_utils.Core_Date_Utils; @@ -17,7 +15,6 @@ import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; -import java.time.temporal.ChronoField; import java.time.temporal.ChronoUnit; import java.time.temporal.TemporalField; import java.time.temporal.TemporalUnit; @@ -240,7 +237,7 @@ public static ZonedDateTime unit_datetime_add(TemporalUnit unit, ZonedDateTime d * This helper method is needed, because calling `appendValueReduced` directly from Enso fails to convert an EnsoDate * to a LocalDate due to polyglot unable to handle the polymorphism of the method. */ - public static void appendTwoDigitYear(DateTimeFormatterBuilder builder, ChronoField yearField, int maxYear) { + public static void appendTwoDigitYear(DateTimeFormatterBuilder builder, TemporalField yearField, int maxYear) { int minYear = maxYear - 99; LocalDate baseDate = LocalDate.of(minYear, 1, 1); builder.appendValueReduced(yearField, 2, 2, baseDate); diff --git a/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso b/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso index 13e4f7450b83..8444098263ce 100644 --- a/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso +++ b/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso @@ -53,7 +53,7 @@ spec = Test.specify "should handle various formats" <| Date.new 2023 09 21 . format "E, dd.MM.yy" . should_equal "Thu, 21.09.23" Date.new 2023 09 21 . format (Date_Time_Formatter.from "DDDD" Locale.poland) . should_equal "czwartek" - Date.new 2023 09 21 . format (Date_Time_Formatter.from_iso_week_date_pattern "ee, 'W'WW ''yy") . should_equal "Thursday, W38 '23" + Date.new 2023 09 21 . format (Date_Time_Formatter.from_iso_week_date_pattern "eee, 'W'WW ''yy" Locale.uk) . should_equal "Thursday, W38 '23" Date.new 2023 09 21 . format "'Q'Q ''yy{1999}" . should_equal "Q3 '23" tz = Time_Zone.parse "US/Hawaii" From 5ac12585255b3cb2caa3fe7bc2201a098aeccdcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Thu, 21 Sep 2023 18:25:46 +0200 Subject: [PATCH 75/76] fix parsing quarters --- .../Internal/Time/Format/As_Java_Formatter_Interpreter.enso | 6 ++++++ test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/As_Java_Formatter_Interpreter.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/As_Java_Formatter_Interpreter.enso index 0ee6975ad1d1..07a65f5e483a 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/As_Java_Formatter_Interpreter.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Internal/Time/Format/As_Java_Formatter_Interpreter.enso @@ -47,6 +47,12 @@ interpret locale nodes = includes_decimal_point = False builder.appendFraction ChronoField.NANO_OF_SECOND min_digits max_digits includes_decimal_point + Standard_Date_Patterns.Quarter _ -> + field = get_field_for node + append_field builder field node.representation + # We currently don't even have a way to specify day of quarter, we expect just (year, quarter) pairs - so to make them parseable, we default to first day of the quarter. + builder.parseDefaulting IsoFields.DAY_OF_QUARTER 1 + _ -> field = get_field_for node append_field builder field node.representation diff --git a/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso b/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso index 8444098263ce..e9fba5579993 100644 --- a/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso +++ b/test/Tests/src/Data/Time/Date_Time_Formatter_Spec.enso @@ -200,7 +200,7 @@ spec = Test.specify "should be able to parse year-month without day" <| Date.parse "2022-05" "yyyy-MM" . should_equal (Date.new 2022 5 1) - Test.specify "should be able to parse a quarter without day" pending="TODO" <| + Test.specify "should be able to parse a quarter without day" <| Date.parse "Q2 of 2022" "'Q'Q 'of' yyyy" . should_equal (Date.new 2022 4 1) Test.specify "should be able to parse a day and month without year - defaulting to current year" <| From b3efabc39db366ac27b9bff9ec28f6fe7732b3d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Fri, 22 Sep 2023 11:06:30 +0200 Subject: [PATCH 76/76] fix offset docs and test --- .../Base/0.0.0-dev/src/Data/Time/Date_Time.enso | 16 ++++++++-------- .../src/Data/Time/Date_Time_Formatter.enso | 8 ++++---- .../src/Formatting/Data_Formatter_Spec.enso | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso index acf52203cfba..9e2181617230 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time.enso @@ -204,13 +204,13 @@ type Date_Time - T: If repeated 3 or less times - Time zone ID (e.g. Europe/Warsaw, Z, -08:30), otherwise - Time zone name (e.g. Central European Time, CET). - Z: Zone offset. - - z, zz, zz: A short offset form (+HHmm). + - Z, ZZ, ZZZ: A short offset form (+HHmm). No offset is indicated by "+0000". This can be customized by setting an alternative no offset string in curly braces, e.g. `zz{Z}`. - - zzzz: A full offset form (+HH:mm:ss). + - ZZZZ: Localized offset (e.g. GMT-08:00). + - ZZZZZ: A full offset form (+HH:mm:ss). No offset is indicated by "Z". This can be customized as above, e.g. - `zzzz{0}`. - - zzzzz: Localized offset. + `ZZZZZ{0}`. ? Default Date_Time Format Unless you provide a custom format, the text must represent a valid @@ -812,13 +812,13 @@ type Date_Time - T: If repeated 3 or less times - Time zone ID (e.g. Europe/Warsaw, Z, -08:30), otherwise - Time zone name (e.g. Central European Time, CET). - Z: Zone offset. - - z, zz, zz: A short offset form (+HHmm). + - Z, ZZ, ZZZ: A short offset form (+HHmm). No offset is indicated by "+0000". This can be customized by setting an alternative no offset string in curly braces, e.g. `zz{Z}`. - - zzzz: A full offset form (+HH:mm:ss). + - ZZZZ: Localized offset (e.g. GMT-08:00). + - ZZZZZ: A full offset form (+HH:mm:ss). No offset is indicated by "Z". This can be customized as above, e.g. - `zzzz{0}`. - - zzzzz: Localized offset. + `ZZZZZ{0}`. > Example Format "2020-10-08T16:41:13+03:00[Europe/Moscow]" as diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso index ea5d0f2326fd..da83facdda11 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date_Time_Formatter.enso @@ -80,13 +80,13 @@ type Date_Time_Formatter - T: If repeated 3 or less times - Time zone ID (e.g. Europe/Warsaw, Z, -08:30), otherwise - Time zone name (e.g. Central European Time, CET). - Z: Zone offset. - - z, zz, zz: A short offset form (+HHmm). + - Z, ZZ, ZZZ: A short offset form (+HHmm). No offset is indicated by "+0000". This can be customized by setting an alternative no offset string in curly braces, e.g. `zz{Z}`. - - zzzz: A full offset form (+HH:mm:ss). + - ZZZZ: Localized offset (e.g. GMT-08:00). + - ZZZZZ: A full offset form (+HH:mm:ss). No offset is indicated by "Z". This can be customized as above, e.g. - `zzzz{0}`. - - zzzzz: Localized offset. + `ZZZZZ{0}`. - v: Time zone name (same as TTTT). - V: Time zone ID (same as T). diff --git a/test/Table_Tests/src/Formatting/Data_Formatter_Spec.enso b/test/Table_Tests/src/Formatting/Data_Formatter_Spec.enso index 08448253f263..405e7523a86c 100644 --- a/test/Table_Tests/src/Formatting/Data_Formatter_Spec.enso +++ b/test/Table_Tests/src/Formatting/Data_Formatter_Spec.enso @@ -202,7 +202,7 @@ spec = formatter.format (Time_Of_Day.new) . should_equal "00:00:00" Test.specify "should allow custom date formats" <| - formatter = Data_Formatter.Value.with_datetime_formats date_formats=["ddd, d MMM y", Date_Time_Formatter.from_java "d MMM y[ G]"] datetime_formats=["dd/MM/yyyy HH:mm [zz]"] time_formats=["h:mma"] + formatter = Data_Formatter.Value.with_datetime_formats date_formats=["ddd, d MMM y", Date_Time_Formatter.from_java "d MMM y[ G]"] datetime_formats=["dd/MM/yyyy HH:mm [ZZZZ]"] time_formats=["h:mma"] formatter.format (Date.new 2022 06 21) . should_equal "Tue, 21 Jun 2022" formatter.format (Date_Time.new 1999 02 03 04 56 11 zone=Time_Zone.utc) . should_equal "03/02/1999 04:56 GMT" formatter.format (Date_Time.new 1999 02 03 04 56 11 zone=(Time_Zone.parse "America/Los_Angeles")) . should_equal "03/02/1999 04:56 GMT-08:00"