go-toml/parse/value_datetime.go

108 lines
3.7 KiB
Go

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.SetError("invalid date/time value %s: %s", p.Result.String(), err)
return nil, false
}
return ast.NewValue(token.Type.(ast.ValueType), value), true
}