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) tz = zulu.Or(offset) tzTok = tok.Str("Z07:00", tz) // The full date/time parse format, based on the above definitions. // The first token denotes the type of date/time value. // The rest of the tokens contain layout fragments for time.Parse(). offsetDateTime = tok.Str(ast.TypeOffsetDateTime, c.Seq(dateTok, tdelimTok, timeTok, microTok, tzTok)) localDateTime = tok.Str(ast.TypeLocalDateTime, c.Seq(dateTok, tdelimTok, timeTok, microTok)) localDate = tok.Str(ast.TypeLocalDate, dateTok) localTime = tok.Str(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 } tokens := p.Result().Tokens() valueType := getDateTimeValueType(&tokens) input, value, err := getDateTimeValue(&tokens) if err == nil { return ast.NewValue(valueType, value), true } p.Error("invalid date/time value %s: %s", input, err) return nil, false } // The first token is a token that wraps the complete date/time input. // Its type denotes the type of date/time value that it wraps. func getDateTimeValueType(tokens *[]*tokenize.Token) ast.ValueType { return (*tokens)[0].Type.(ast.ValueType) } // The rest of the tokens contain fragments that can be used with // time.Parse() to parse the provided date/time input. Here, these fragments // are combined into a layout string, which is then used to parse // the input string. func getDateTimeValue(tokens *[]*tokenize.Token) (string, time.Time, error) { layout := "" for _, l := range (*tokens)[1:] { layout += l.Type.(string) } input := string((*tokens)[0].Runes) value, err := time.Parse(layout, input) return input, value, err }