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