108 lines
3.7 KiB
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().Token(0)
|
|
|
|
layout := ""
|
|
for _, l := range token.Value.([]*tokenize.Token) {
|
|
layout += l.Type.(string)
|
|
}
|
|
value, err := time.Parse(layout, string(token.Runes))
|
|
if err != nil {
|
|
p.Error("invalid date/time value %s: %s", string(token.Runes), err)
|
|
return nil, false
|
|
}
|
|
|
|
return ast.NewValue(token.Type.(ast.ValueType), value), true
|
|
}
|