144 lines
3.9 KiB
Go
144 lines
3.9 KiB
Go
package timefmt
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// ParseGoFormat parses a Go time format string and returns a Format.
|
|
// It analyzes the Go reference time format (e.g., "2006-01-02 15:04:05")
|
|
// and converts it into a Format with the appropriate fragments.
|
|
//
|
|
// Example:
|
|
//
|
|
// format, err := ParseGoFormat("2006-01-02 15:04:05")
|
|
// if err != nil {
|
|
// log.Fatal(err)
|
|
// }
|
|
// fmt.Println(format.LDML()) // "yyyy-MM-dd HH:mm:ss"
|
|
// fmt.Println(format.Description()) // Full English description
|
|
func ParseGoFormat(goFormat string) (*Format, error) {
|
|
if goFormat == "" {
|
|
return nil, fmt.Errorf("empty format string")
|
|
}
|
|
|
|
// Define token mappings in order of precedence to avoid partial matches
|
|
// CRITICAL: Patterns that could conflict must be ordered carefully!
|
|
// - Longer patterns before shorter
|
|
// - More specific patterns before general
|
|
// - Unique identifiers (like "15" for hour, "04" for minute) before ambiguous ones
|
|
tokens := []struct {
|
|
goToken string
|
|
fragment Fragment
|
|
}{
|
|
// Subseconds (must come before other number patterns due to decimal point)
|
|
{".000000000", Nanosecond},
|
|
{".999999999", NanosecondTrim},
|
|
{".000000", Microsecond},
|
|
{".999999", MicrosecondTrim},
|
|
{".000", Millisecond},
|
|
{".999", MillisecondTrim},
|
|
|
|
// Timezone (longer patterns first)
|
|
{"-07:00:00", TimezoneOffsetColonSeconds},
|
|
{"-070000", TimezoneOffsetSeconds},
|
|
{"Z07:00", TimezoneISO8601Colon},
|
|
{"Z0700", TimezoneISO8601},
|
|
{"-07:00", TimezoneOffsetColon},
|
|
{"-0700", TimezoneOffset},
|
|
{"-07", TimezoneOffsetHourOnly},
|
|
{"MST", TimezoneName},
|
|
|
|
// Year (must come before month numbers)
|
|
{"2006", Year4Digit},
|
|
{"06", Year2Digit},
|
|
|
|
// Month names (before numeric months to avoid conflicts)
|
|
{"January", MonthFull},
|
|
{"Jan", MonthShort},
|
|
|
|
// Weekday names (before numeric days)
|
|
{"Monday", WeekdayFull},
|
|
{"Mon", WeekdayShort},
|
|
|
|
// Day of year (before regular days, longer patterns first)
|
|
{"__2", DayOfYearSpacePadded},
|
|
{"002", DayOfYearNumeric},
|
|
|
|
// Time components (MUST come before month/day numbers to avoid conflicts!)
|
|
// "15" is unique to 24-hour format, "04" is unique to minutes, "05" to seconds
|
|
{"15", Hour24}, // Must come before "1" (month) and "5" (second)
|
|
{"04", Minute}, // Must come before "4" (minute unpadded)
|
|
{"05", Second}, // Must come before "5" (second unpadded)
|
|
{"03", Hour12Padded}, // Must come before "3" (hour)
|
|
|
|
// Month and day numbers (after time components!)
|
|
{"01", MonthNumeric2}, // Padded month
|
|
{"02", DayNumeric2}, // Padded day
|
|
{"_2", DaySpacePadded}, // Space-padded day
|
|
|
|
// Single digit patterns (LAST to avoid premature matching!)
|
|
{"1", MonthNumeric},
|
|
{"2", DayNumeric},
|
|
{"3", Hour12},
|
|
{"4", MinuteUnpadded},
|
|
{"5", SecondUnpadded},
|
|
|
|
// AM/PM
|
|
{"PM", AMPM},
|
|
{"pm", AMPMLower},
|
|
}
|
|
|
|
var fragments []interface{}
|
|
i := 0
|
|
|
|
for i < len(goFormat) {
|
|
matched := false
|
|
|
|
// Try to match tokens (longest first)
|
|
for _, token := range tokens {
|
|
if strings.HasPrefix(goFormat[i:], token.goToken) {
|
|
fragments = append(fragments, token.fragment)
|
|
i += len(token.goToken)
|
|
matched = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !matched {
|
|
// This is a literal character
|
|
// Collect consecutive literal characters
|
|
literalStart := i
|
|
i++
|
|
// Continue collecting until we hit a token
|
|
for i < len(goFormat) {
|
|
foundToken := false
|
|
for _, token := range tokens {
|
|
if strings.HasPrefix(goFormat[i:], token.goToken) {
|
|
foundToken = true
|
|
break
|
|
}
|
|
}
|
|
if foundToken {
|
|
break
|
|
}
|
|
i++
|
|
}
|
|
literal := goFormat[literalStart:i]
|
|
fragments = append(fragments, literal)
|
|
}
|
|
}
|
|
|
|
return &Format{fragments: fragments}, nil
|
|
}
|
|
|
|
// MustParseGoFormat is like ParseGoFormat but panics on error.
|
|
// It's useful for initialization of package-level variables.
|
|
func MustParseGoFormat(goFormat string) *Format {
|
|
format, err := ParseGoFormat(goFormat)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("MustParseGoFormat: %v", err))
|
|
}
|
|
return format
|
|
}
|