feat: scaffold interpres project with TOML parser and CI
This commit is contained in:
+103
@@ -0,0 +1,103 @@
|
||||
package interpres
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TOML distinguishes four date-time kinds. interpres decodes an offset
|
||||
// date-time to a plain time.Time (it carries a zone), and uses the wrapper
|
||||
// types below for the local variants so callers can tell them apart.
|
||||
|
||||
// LocalDateTime is a TOML local date-time with no offset, e.g.
|
||||
// 1979-05-27T07:32:00. The embedded time.Time is in UTC.
|
||||
type LocalDateTime struct{ time.Time }
|
||||
|
||||
// LocalDate is a TOML local date with no time or offset, e.g. 1979-05-27.
|
||||
// The embedded time.Time is at midnight UTC.
|
||||
type LocalDate struct{ time.Time }
|
||||
|
||||
// LocalTime is a TOML local time with no date or offset, e.g. 07:32:00.999999.
|
||||
// The embedded time.Time uses the zero date.
|
||||
type LocalTime struct{ time.Time }
|
||||
|
||||
var (
|
||||
offsetDateTimeLayouts = []string{
|
||||
"2006-01-02T15:04:05.999999999Z07:00",
|
||||
"2006-01-02T15:04:05Z07:00",
|
||||
"2006-01-02 15:04:05.999999999Z07:00",
|
||||
"2006-01-02 15:04:05Z07:00",
|
||||
}
|
||||
localDateTimeLayouts = []string{
|
||||
"2006-01-02T15:04:05.999999999",
|
||||
"2006-01-02T15:04:05",
|
||||
"2006-01-02 15:04:05.999999999",
|
||||
"2006-01-02 15:04:05",
|
||||
}
|
||||
localTimeLayouts = []string{
|
||||
"15:04:05.999999999",
|
||||
"15:04:05",
|
||||
}
|
||||
)
|
||||
|
||||
// dateTimeShape enforces the strict TOML grammar (two-digit components) that
|
||||
// time.Parse would otherwise accept loosely (e.g. a single-digit hour).
|
||||
var dateTimeShape = regexp.MustCompile(
|
||||
`^\d{4}-\d{2}-\d{2}([Tt ]\d{2}:\d{2}:\d{2}(\.\d+)?([Zz]|[+-]\d{2}:\d{2})?)?$` +
|
||||
`|^\d{2}:\d{2}:\d{2}(\.\d+)?$`,
|
||||
)
|
||||
|
||||
// parseDateTime classifies and parses a bare token as a TOML date-time value.
|
||||
// It returns the decoded value (time.Time, LocalDateTime, LocalDate, or
|
||||
// LocalTime) and whether the token was a date-time at all.
|
||||
func parseDateTime(tok string) (any, bool) {
|
||||
if tok == "" || tok[0] < '0' || tok[0] > '9' {
|
||||
return nil, false
|
||||
}
|
||||
if !strings.ContainsAny(tok, "-:") {
|
||||
return nil, false
|
||||
}
|
||||
if !dateTimeShape.MatchString(tok) {
|
||||
return nil, false
|
||||
}
|
||||
// The ABNF accepts lowercase "t"/"z"; time.Parse only matches uppercase.
|
||||
norm := strings.ToUpper(tok)
|
||||
for _, layout := range offsetDateTimeLayouts {
|
||||
if t, err := time.Parse(layout, norm); err == nil {
|
||||
return t, true
|
||||
}
|
||||
}
|
||||
for _, layout := range localDateTimeLayouts {
|
||||
if t, err := time.Parse(layout, norm); err == nil {
|
||||
return LocalDateTime{t}, true
|
||||
}
|
||||
}
|
||||
if t, err := time.Parse("2006-01-02", norm); err == nil {
|
||||
return LocalDate{t}, true
|
||||
}
|
||||
for _, layout := range localTimeLayouts {
|
||||
if t, err := time.Parse(layout, norm); err == nil {
|
||||
return LocalTime{t}, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// isDateToken reports whether s is exactly a YYYY-MM-DD date, used to detect a
|
||||
// space-separated date-time written as "date<space>time".
|
||||
func isDateToken(s string) bool {
|
||||
if len(s) != 10 {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < len(s); i++ {
|
||||
if i == 4 || i == 7 {
|
||||
if s[i] != '-' {
|
||||
return false
|
||||
}
|
||||
} else if !isDecDigit(s[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
Reference in New Issue
Block a user