package parse import ( "time" "git.makaay.nl/mauricem/go-parsekit/parse" "git.makaay.nl/mauricem/go-parsekit/tokenize" "git.makaay.nl/mauricem/go-toml/ast" ) var ( // Note: in the definitions below, the token types are chosen based on the // formatting definitions as used by https://golang.org/src/time/format.go // To unambiguously represent a specific instant in time, you may use an // RFC 3339 formatted date-time with offset. // // odt1 = 1979-05-27T07:32:00Z // odt2 = 1979-05-27T00:32:00-07:00 // odt3 = 1979-05-27T00:32:00.999999-07:00 // // If you include only the time portion of an RFC 3339 formatted date-time, // it will represent that time of day without any relation to a specific // day or any offset or timezone. // // lt1 = 07:32:00 // lt2 = 00:32:00.999999 year = a.Digit.Times(4) month = a.Digit.Times(2) day = a.Digit.Times(2) yyyymmdd = c.Seq(year, a.Minus, month, a.Minus, day) dateTok = tok.Str("2006-01-02", yyyymmdd) hour = a.Digit.Times(2) minute = a.Digit.Times(2) seconds = a.Digit.Times(2) hhmmss = c.Seq(hour, a.Colon, minute, a.Colon, seconds) timeTok = tok.Str("15:04:05", hhmmss) // The precision of fractional seconds is implementation-specific, but at // least millisecond precision is expected. If the value contains greater // precision than the implementation can support, the additional precision // must be truncated, not rounded. micro = a.Dot.Then(c.MinMax(1, 9, a.Digit).Then(m.Drop(c.ZeroOrMore(a.Digit)))) microTok = c.Optional(tok.Str(".999999999", micro)) // For the sake of readability, you may replace the T delimiter between // date and time with a space (as permitted by RFC 3339 section 5.6). // Note that RFC 3339 also allows the use of a lower case delimiter. // // odt4 = 1979-05-27 07:32:00Z tdelimTok = c.Any( tok.Str("T", a.Rune('T')), tok.Str("t", a.Rune('t')), tok.Str(" ", a.Rune(' '))) // If you omit the offset from an RFC 3339 formatted date-time, it will // represent the given date-time without any relation to an offset or // timezone. // // ldt1 = 1979-05-27T07:32:00 // ldt2 = 1979-05-27T00:32:00.999999 // // It cannot be converted to an instant in time without additional // information. Conversion to an instant, if required, is // implementation-specific. // // Note that RFC 3339 also allows the use of a lower case 'z'. // Here we replace it with a capital 'Z' to make the Go date parser work. zulu = m.Replace(a.Runes('Z', 'z'), "Z") offset = c.Seq(a.Runes('+', '-'), hour, a.Colon, minute) tzTok = tok.Str("Z07:00", zulu.Or(offset)) // The full date/time parse format, based on the above definitions. // The token denotes the type of date/time value. // The contained tokens contain layout fragments for time.Parse(). offsetDateTime = tok.Group(ast.TypeOffsetDateTime, c.Seq(dateTok, tdelimTok, timeTok, microTok, tzTok)) localDateTime = tok.Group(ast.TypeLocalDateTime, c.Seq(dateTok, tdelimTok, timeTok, microTok)) localDate = tok.Group(ast.TypeLocalDate, dateTok) localTime = tok.Group(ast.TypeLocalTime, c.Seq(timeTok, microTok)) datetime = c.Any(offsetDateTime, localDateTime, localDate, localTime) ) func (t *parser) parseDateTime(p *parse.API) (*ast.Value, bool) { if !p.Accept(datetime) { p.Expected("a date and/or time") return nil, false } token := p.Result.Tokens[0] layout := "" for _, l := range token.Value.([]tokenize.Token) { layout += l.Type.(string) } value, err := time.Parse(layout, p.Result.String()) if err != nil { p.Error("invalid date/time value %s: %s", p.Result.String(), err) return nil, false } return ast.NewValue(token.Type.(ast.ValueType), value), true }