initial commit
This commit is contained in:
143
format.go
Normal file
143
format.go
Normal file
@@ -0,0 +1,143 @@
|
||||
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 + "'"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user