144 lines
4.0 KiB
Go
144 lines
4.0 KiB
Go
package timefmt
|
|
|
|
import (
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// Format represents a time format pattern composed of fragments and literals.
|
|
// It can convert between Go's reference time format, LDML tokens, and human-readable descriptions.
|
|
type Format struct {
|
|
fragments []interface{} // Can be Fragment or string (for literals)
|
|
}
|
|
|
|
// NewFormat creates a new Format from a slice of fragments and literals.
|
|
// Each element can be either a Fragment or a string literal.
|
|
func NewFormat(fragments ...interface{}) *Format {
|
|
return &Format{fragments: fragments}
|
|
}
|
|
|
|
// GoFormat returns the Go reference time format string.
|
|
// Example: "2006-01-02 15:04:05"
|
|
func (f *Format) GoFormat() string {
|
|
var sb strings.Builder
|
|
for _, frag := range f.fragments {
|
|
switch v := frag.(type) {
|
|
case Fragment:
|
|
sb.WriteString(v.GoFormat)
|
|
case string:
|
|
sb.WriteString(v)
|
|
}
|
|
}
|
|
return sb.String()
|
|
}
|
|
|
|
// LDML returns the LDML-style human-readable format string.
|
|
// Example: "yyyy-MM-dd HH:mm:ss"
|
|
func (f *Format) LDML() string {
|
|
var sb strings.Builder
|
|
for _, frag := range f.fragments {
|
|
switch v := frag.(type) {
|
|
case Fragment:
|
|
sb.WriteString(v.LDML)
|
|
case string:
|
|
// Escape literals that might conflict with LDML tokens
|
|
// by wrapping them in single quotes if they contain letters
|
|
if needsEscaping(v) {
|
|
sb.WriteRune('\'')
|
|
sb.WriteString(v)
|
|
sb.WriteRune('\'')
|
|
} else {
|
|
sb.WriteString(v)
|
|
}
|
|
}
|
|
}
|
|
return sb.String()
|
|
}
|
|
|
|
// Description returns a full English description of the format.
|
|
// Example: "Year (4-digit), dash, Month (2-digit), dash, Day (2-digit), space, Hour (24-hour, 2-digit), colon, Minute (2-digit), colon, Second (2-digit)"
|
|
func (f *Format) Description() string {
|
|
var parts []string
|
|
for _, frag := range f.fragments {
|
|
switch v := frag.(type) {
|
|
case Fragment:
|
|
parts = append(parts, v.Description)
|
|
case string:
|
|
parts = append(parts, describeLiteral(v))
|
|
}
|
|
}
|
|
return strings.Join(parts, ", ")
|
|
}
|
|
|
|
// Format formats a time.Time using this format pattern.
|
|
// It uses Go's time.Format internally.
|
|
func (f *Format) Format(t time.Time) string {
|
|
return t.Format(f.GoFormat())
|
|
}
|
|
|
|
// Parse parses a time string using this format pattern.
|
|
// It uses Go's time.Parse internally.
|
|
func (f *Format) Parse(value string) (time.Time, error) {
|
|
return time.Parse(f.GoFormat(), value)
|
|
}
|
|
|
|
// ParseInLocation parses a time string using this format pattern in the specified location.
|
|
// It uses Go's time.ParseInLocation internally.
|
|
func (f *Format) ParseInLocation(value string, loc *time.Location) (time.Time, error) {
|
|
return time.ParseInLocation(f.GoFormat(), value, loc)
|
|
}
|
|
|
|
// Example returns an example of what this format looks like using a reference time.
|
|
// The reference time used is: February 8, 2026 at 15:04:05.999999999 in UTC-7
|
|
func (f *Format) Example() string {
|
|
// Use a specific reference time that shows all components clearly
|
|
referenceTime := time.Date(2026, time.February, 8, 15, 4, 5, 999999999, time.FixedZone("MST", -7*3600))
|
|
return f.Format(referenceTime)
|
|
}
|
|
|
|
// Fragments returns a copy of the format's fragments slice.
|
|
// This is useful for inspecting the format's structure.
|
|
func (f *Format) Fragments() []interface{} {
|
|
result := make([]interface{}, len(f.fragments))
|
|
copy(result, f.fragments)
|
|
return result
|
|
}
|
|
|
|
// needsEscaping returns true if a literal string contains letters that might
|
|
// be confused with LDML format tokens and should be escaped with quotes.
|
|
func needsEscaping(s string) bool {
|
|
for _, r := range s {
|
|
if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// describeLiteral converts a literal string into a human-readable description.
|
|
func describeLiteral(s string) string {
|
|
switch s {
|
|
case "-":
|
|
return "dash"
|
|
case "/":
|
|
return "slash"
|
|
case ":":
|
|
return "colon"
|
|
case " ":
|
|
return "space"
|
|
case ".":
|
|
return "period"
|
|
case ",":
|
|
return "comma"
|
|
case ", ":
|
|
return "comma-space"
|
|
case "T":
|
|
return "literal 'T'"
|
|
case "Z":
|
|
return "literal 'Z'"
|
|
default:
|
|
// For other literals, describe as "literal 'X'"
|
|
return "literal '" + s + "'"
|
|
}
|
|
}
|