/*----------------------------------------------------------------------------
   Copyright 2012-2021, Microsoft Research, Daan Leijen

   Licensed under the Apache License, Version 2.0 ("The Licence"). You may not
   use this file except in compliance with the License. A copy of the License
   can be found in the LICENSE file at the root of this distribution.
----------------------------------------------------------------------------*/

// Parsing of time strings.
module std/time/parsestd/time/parse

import std/text/parsestd/text/parse
import std/num/ddoublestd/num/ddouble
import std/time/durationstd/time/duration
import std/time/instantstd/time/instant
import std/time/datestd/time/date
import std/time/calendarstd/time/calendar
import std/time/calendarsstd/time/calendars  // iso-week, iso-month
import std/time/timestd/time/time
import std/time/localestd/time/locale
import std/time/utcstd/time/utc

// -----------------------------------------------------------
// Parsing  
// -----------------------------------------------------------

/* Parse an [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) time string.
The optional `calendar` (=`cal-iso`) argument can be given to interpret
the date in a different calendar. 

````plain
2008-12-31               // just date
2009-W01-3               // week number and day
2008-366                 // day of the year

20081231, 2009W013, 2008366  // without dashes

2008-12-31T09            // with time part, use T or space
2008-12-31 09
2008-12-31T09:20:16
2008-12-31T09:20:16.345  // with milliseconds (can be any fraction)
2008-12-30T24:00:00      // next day at midnight
20081231T092016.345      // without separators

2008-12-31T09Z           // UTC time zone (Z)
2008-12-31 09-07:00      // UTC-07:00 time zone
2008-12-31T09:20:16+0830 // UTC+08:30 time zone (without colon)
````

And also parses the ISO month dates (see `cal-iso-month`):

````plain
2009-M01-03              // just month date
2009-M003                // month date with day of the year
````
.
*/

pub fun parse-isostd/time/parse/parse-iso: (s : string, calendar : ? calendar) -> utc maybe<time>( ss: string : stringstd/core/types/string: V, calendarcalendar: ? calendar : calendarstd/time/calendar/calendar: V = cal-isostd/time/calendar/cal-iso: calendar )result: -> utc maybe<time> : <std/core/types/total: Eutcstd/time/utc/utc: (E, V) -> V> maybestd/core/types/maybe: V -> V<timestd/time/time/time: V>
  ss: string.slicestd/core/sslice/slice: (s : string) -> utc sslice.parse-eofstd/text/parse/parse-eof: (input : sslice, p : () -> <parse,utc> time) -> utc parse-error<time>( { pisostd/time/parse/piso: (calendar : calendar) -> <parse,utc> time(calendarcalendar: calendar) } ).maybestd/text/parse/maybe: (perr : parse-error<time>) -> utc maybe<time> 

fun pisostd/time/parse/piso: (calendar : calendar) -> <parse,utc> time(calendarcalendar: calendar : calendarstd/time/calendar/calendar: V)result: -> <parse,utc> time : <std/core/types/total: Eparsestd/text/parse/parse: (E, V) -> V,utcstd/time/utc/utc: (E, V) -> V> timestd/time/time/time: V
  val yearyear: int = numstd/time/parse/num: (n : int) -> <parse,utc> int(4literal: int
dec = 4
hex8 = 0x04
bit8 = 0b00000100
) dashstd/time/parse/dash: () -> <parse,utc> char() val (std/core/types/Tuple2: forall<a,b> (fst : a, snd : b) -> (a, b)datedate: date,calcal: calendar)std/core/types/Tuple2: forall<a,b> (fst : a, snd : b) -> (a, b) = choosestd/text/parse/choose: (ps : list<parser<utc,(date, calendar)>>) -> <parse,utc> (date, calendar)([std/core/types/Cons: forall<a> (head : a, tail : list<a>) -> list<a> { val mm: int = numstd/time/parse/num: (n : int) -> <parse,utc> int(2literal: int
dec = 2
hex8 = 0x02
bit8 = 0b00000010
) // month-day dashstd/time/parse/dash: () -> <parse,utc> char() val dd: int = numstd/time/parse/num: (n : int) -> <parse,utc> int(2literal: int
dec = 2
hex8 = 0x02
bit8 = 0b00000010
) (std/core/types/Tuple2: forall<a,b> (fst : a, snd : b) -> (a, b)Datestd/time/date/Date: (year : int, month : int, day : int) -> date(yearyear: int,mm: int,dd: int),calendarcalendar: calendar)std/core/types/Tuple2: forall<a,b> (fst : a, snd : b) -> (a, b) }, { charstd/text/parse/char: (c : char) -> <parse,utc> char('W'literal: char
unicode= u0057
) // ISO week-day val ww: int = numstd/time/parse/num: (n : int) -> <parse,utc> int(2literal: int
dec = 2
hex8 = 0x02
bit8 = 0b00000010
) dashstd/time/parse/dash: () -> <parse,utc> char() val dd: int = optionalstd/text/parse/optional: (default : int, p : parser<utc,int>) -> <parse,utc> int(1literal: int
dec = 1
hex8 = 0x01
bit8 = 0b00000001
){ numstd/time/parse/num: (n : int) -> <parse,utc> int(1literal: int
dec = 1
hex8 = 0x01
bit8 = 0b00000001
) } (std/core/types/Tuple2: forall<a,b> (fst : a, snd : b) -> (a, b)Datestd/time/date/Date: (year : int, month : int, day : int) -> date(yearyear: int,ww: int,dd: int),cal-iso-weekstd/time/calendars/cal-iso-week: calendar)std/core/types/Tuple2: forall<a,b> (fst : a, snd : b) -> (a, b) }, { val dd: int = numstd/time/parse/num: (n : int) -> <parse,utc> int(3literal: int
dec = 3
hex8 = 0x03
bit8 = 0b00000011
) // ISO day of year (std/core/types/Tuple2: forall<a,b> (fst : a, snd : b) -> (a, b)Datestd/time/date/Date: (year : int, month : int, day : int) -> date(yearyear: int,1literal: int
dec = 1
hex8 = 0x01
bit8 = 0b00000001
,dd: int),calendarcalendar: calendar)std/core/types/Tuple2: forall<a,b> (fst : a, snd : b) -> (a, b) }, { charstd/text/parse/char: (c : char) -> <parse,utc> char('M'literal: char
unicode= u004D
) val mm: int = numstd/time/parse/num: (n : int) -> <parse,utc> int(2literal: int
dec = 2
hex8 = 0x02
bit8 = 0b00000010
) // ISO month-day dashstd/time/parse/dash: () -> <parse,utc> char() val dd: int = numstd/time/parse/num: (n : int) -> <parse,utc> int(2literal: int
dec = 2
hex8 = 0x02
bit8 = 0b00000010
) (std/core/types/Tuple2: forall<a,b> (fst : a, snd : b) -> (a, b)Datestd/time/date/Date: (year : int, month : int, day : int) -> date(yearyear: int,mm: int,dd: int),cal-iso-monthstd/time/calendars/cal-iso-month: calendar)std/core/types/Tuple2: forall<a,b> (fst : a, snd : b) -> (a, b) }, { charstd/text/parse/char: (c : char) -> <parse,utc> char('M'literal: char
unicode= u004D
) // ISO month day-of-year val dd: int = numstd/time/parse/num: (n : int) -> <parse,utc> int(3literal: int
dec = 3
hex8 = 0x03
bit8 = 0b00000011
) (std/core/types/Tuple2: forall<a,b> (fst : a, snd : b) -> (a, b)Datestd/time/date/Date: (year : int, month : int, day : int) -> date(yearyear: int,1literal: int
dec = 1
hex8 = 0x01
bit8 = 0b00000001
,dd: int),cal-iso-monthstd/time/calendars/cal-iso-month: calendar)std/core/types/Tuple2: forall<a,b> (fst : a, snd : b) -> (a, b) } ]std/core/types/Nil: forall<a> list<a>) optionalstd/text/parse/optional: (default : time, p : parser<utc,time>) -> <parse,utc> time(timestd/time/time/date/time: (d : date, c : ? clock, tz : ? timezone, cal : ? calendar, ts : ? timescale) -> <utc,parse> time(datedate: date,cal=calcal: calendar)){ ptimestd/time/parse/ptime: (date : date, cal : calendar) -> <parse,utc> time(datedate: date,calcal: calendar)
} fun ptimestd/time/parse/ptime: (date : date, cal : calendar) -> <parse,utc> time(datedate: date : datestd/time/date/date: V, calcal: calendar : calendarstd/time/calendar/calendar: V)result: -> <parse,utc> time : <std/core/types/total: Eparsestd/text/parse/parse: (E, V) -> V,utcstd/time/utc/utc: (E, V) -> V> timestd/time/time/time: V one-ofstd/text/parse/one-of: (chars : string) -> <parse,utc> char("T "literal: string
count= 2
) val hourhour: int = numstd/time/parse/num: (n : int) -> <parse,utc> int(2literal: int
dec = 2
hex8 = 0x02
bit8 = 0b00000010
) colonstd/time/parse/colon: () -> <parse,utc> char() val minmin: int = optionalstd/text/parse/optional: (default : int, p : parser<utc,int>) -> <parse,utc> int(0literal: int
dec = 0
hex8 = 0x00
bit8 = 0b00000000
){numstd/time/parse/num: (n : int) -> <parse,utc> int(2literal: int
dec = 2
hex8 = 0x02
bit8 = 0b00000010
)} colonstd/time/parse/colon: () -> <parse,utc> char() val secssecs: ddouble = optionalstd/text/parse/optional: (default : ddouble, p : parser<utc,ddouble>) -> <parse,utc> ddouble(ddouble/zerostd/num/ddouble/zero: ddouble) { val ss: int = numstd/time/parse/num: (n : int) -> <parse,utc> int(2literal: int
dec = 2
hex8 = 0x02
bit8 = 0b00000010
) val ff: string = optionalstd/text/parse/optional: (default : string, p : parser<utc,string>) -> <parse,utc> string(""literal: string
count= 0
){ one-ofstd/text/parse/one-of: (chars : string) -> <parse,utc> char(".,"literal: string
count= 2
); digitsstd/text/parse/digits: () -> <parse,utc> string() } (ss: int.ddoublestd/num/ddouble/int/ddouble: (i : int) -> <parse,utc> ddouble +std/num/ddouble/(+): (x : ddouble, y : ddouble) -> <parse,utc> ddouble (if ff: string.is-emptystd/core/string/is-empty: (s : string) -> <parse,utc> bool then zerostd/num/ddouble/zero: ddouble else ("0."literal: string
count= 2
++std/core/types/(++): (x : string, y : string) -> <parse,utc> string ff: string).parse-ddoublestd/num/ddouble/parse-ddouble: (s : string) -> <parse,utc> maybe<ddouble>.defaultstd/core/maybe/default: (m : maybe<ddouble>, nothing : ddouble) -> <parse,utc> ddouble(zerostd/num/ddouble/zero: ddouble))) } val tzonetzone: timezone = choosestd/text/parse/choose: (ps : list<parser<utc,timezone>>) -> <parse,utc> timezone([std/core/types/Cons: forall<a> (head : a, tail : list<a>) -> list<a> { val signsign: char = one-ofstd/text/parse/one-of: (chars : string) -> <parse,utc> char("+-"literal: string
count= 2
) val tzhourtzhour: int = numstd/time/parse/num: (n : int) -> <parse,utc> int(2literal: int
dec = 2
hex8 = 0x02
bit8 = 0b00000010
) colonstd/time/parse/colon: () -> <parse,utc> char() val tzmintzmin: int = numstd/time/parse/num: (n : int) -> <parse,utc> int(2literal: int
dec = 2
hex8 = 0x02
bit8 = 0b00000010
) tz-fixedstd/time/calendar/tz-fixed: (hours : int, mins : ? int, name : ? string, abbrv : ? string, hourwidth : ? int) -> <parse,utc> timezone( if signsign: char==std/core/char/(==): (char, char) -> <parse,utc> bool'-'literal: char
unicode= u002D
then tzhourtzhour: int.negatestd/core/int/negate: (i : int) -> <parse,utc> int else tzhourtzhour: int, tzmintzmin: int ) }, { optionalstd/text/parse/optional: (default : char, p : parser<utc,char>) -> <parse,utc> char('Z'literal: char
unicode= u005A
){ charstd/text/parse/char: (c : char) -> <parse,utc> char('Z'literal: char
unicode= u005A
) }; tz-utcstd/time/calendar/tz-utc: timezone } ]std/core/types/Nil: forall<a> list<a>) val clkclk: clock = Clockstd/time/date/Clock: (hours : int, minutes : int, seconds : ddouble) -> clock(hourhour: int,minmin: int,secssecs: ddouble) timestd/time/time/date/time: (d : date, c : ? clock, tz : ? timezone, cal : ? calendar, ts : ? timescale) -> <utc,parse> time(datedate: date,clkclk: clock,tz=tzonetzone: timezone,cal=calcal: calendar
) fun dashstd/time/parse/dash: () -> parse char()result: -> parse char : parsestd/text/parse/parse: (E, V) -> V charstd/core/types/char: V optcharstd/time/parse/optchar: (c : char) -> parse char('-'literal: char
unicode= u002D
) fun colonstd/time/parse/colon: () -> parse char()result: -> parse char : parsestd/text/parse/parse: (E, V) -> V charstd/core/types/char: V optcharstd/time/parse/optchar: (c : char) -> parse char(':'literal: char
unicode= u003A
) fun optcharstd/time/parse/optchar: (c : char) -> parse char(cc: char : charstd/core/types/char: V )result: -> parse char : parsestd/text/parse/parse: (E, V) -> V charstd/core/types/char: V optionalstd/text/parse/optional: (default : char, p : parser<total,char>) -> parse char(cc: char,{charstd/text/parse/char: (c : char) -> parse char(cc: char)}) fun numstd/time/parse/num: (n : int) -> parse int( nn: int : intstd/core/types/int: V )result: -> parse int : parsestd/text/parse/parse: (E, V) -> V intstd/core/types/int: V countstd/text/parse/count: (n : int, p : parser<total,int>) -> parse list<int>(nn: int,digitstd/text/parse/digit: () -> parse int).foldlstd/core/list/foldl: (xs : list<int>, z : int, f : (int, int) -> parse int) -> parse int(0literal: int
dec = 0
hex8 = 0x00
bit8 = 0b00000000
, fnfn: (d : int, x : int) -> parse int(dd: int,xx: int){ xx: int*std/core/int/(*): (int, int) -> parse int10literal: int
dec = 10
hex8 = 0x0A
bit8 = 0b00001010
+std/core/int/(+): (x : int, y : int) -> parse int dd: int }
) /* pub fun parse-iso( s : string, calendar : calendar = cal-iso ) : <utc> maybe<time> match(s.find(rx-iso-date)) Nothing -> Nothing Just(capd) -> val xyear = capd.num(1) val (date,cal) = if capd.groups[4] != "" then // ISO week date (Date(xyear,capd.num(4),capd.num(5,1)),cal-iso-week) elif capd.groups[6] != "" then // ISO day of the year (Date(xyear,1,capd.num(6)), calendar) elif capd.groups[7] != "" then // ISO month date (Date(xyear,capd.num(7),capd.num(8)), cal-iso-month) elif capd.groups[9] != "" then // ISO month date, day of the year (Date(xyear,1,capd.num(9)), cal-iso-month) else // (capd.groups[2] != "") // regular ISO date (Date(xyear,capd.num(2),capd.num(3)),calendar) match(capd.slice.after.string.find(rx-iso-time)) Nothing -> Nothing Just(capt) -> val hours = capt.num(1) val mins = capt.num(2) val secs = parse-ddouble(capt.groups[3] + "." + capt.groups[4]).default(zero) val tzsign= if capt.groups[5]=="+" then 1 else ~1 val tzhours= capt.num(6) val tzmins = capt.num(7) val tzone = tz-fixed(tzsign*tzhours, tzmins) val clock = Clock(hours,mins,secs) Just(time(date,clock,tz=tzone,cal=cal)) val rx-iso-date = regex(r"^(\d\d\d\d)\-?(?:(\d\d)\-?(\d\d)|W(\d\d)\-?(\d)?|(\d\d\d)|M(\d\d)\-?(\d\d)|M(\d\d\d))(?=$|[T\+\- ]\d|Z)"); val rx-iso-time = regex(r"^(?:[T ](\d\d)(?:\:?(\d\d)(?:\:?(\d\d))?)?(?:[\.,](\d+))?)?(?:Z|([\+\-])(\d\d)(?:\:?(\d\d))?)?$") fun num( cap : matched, group : int, default : int = 0 ) : int parse-int-default(cap.groups[group], default = default) // Parse an [Internet Message Format](https://tools.ietf.org/html/rfc2822#section-3.3) time string. // For example: `"Tue, 27 Sep 2016 06:36:55 -0700"`. pub fun parse-imf( s : string ) : <utc> maybe<time> match(s.find(rx-imf)) Nothing -> Nothing Just(cap) -> val day = cap.num(2) val month = month-from-name(cap.groups[3]).default(1) val xyear = cap.num(4) val hours = cap.num(5) val mins = cap.num(6) val secs = cap.num(7) val tzsign= if cap.groups[8]=="+" then 1 else ~1 val tzhours= cap.num(9) val tzmins = cap.num(10) val tzone = tz-fixed(tzsign*tzhours, tzmins) // adjust 2 digit year val year = if cap.groups[4].count <= 2 then (if xyear < 50 then 2000 + xyear else 1900 + xyear) else xyear Just(time(year,month,day,hours,mins,secs,tz=tzone)) val rx-imf = regex(r"^ *(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun) *, *)? (\d\d?) *(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) *(\d\d(?:\d\d)?) +(\d\d?):(\d\d?)(?::(\d\d))? *(?:([\+\-])(\d\d)(\d\d) *)?$") fun month-from-name( mname : string ) : maybe<int> val lname = mname.to-lower fun search( names : list<string>, month : int ) : maybe<int> match names Nil -> Nothing Cons(name,rest) -> if (name.to-lower.starts-with(lname).bool) then Just(month) else search(rest,month+1) search(time-locale-en-iso.month-names,1) */