303 lines
8.7 KiB
Go
303 lines
8.7 KiB
Go
package timefmt
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestParseGoFormat(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
goFormat string
|
|
wantLDML string
|
|
wantDesc string
|
|
shouldError bool
|
|
}{
|
|
{
|
|
name: "ISO 8601 date",
|
|
goFormat: "2006-01-02",
|
|
wantLDML: "yyyy-MM-dd",
|
|
wantDesc: "Year (4-digit), dash, Month (2-digit), dash, Day (2-digit)",
|
|
},
|
|
{
|
|
name: "24-hour time",
|
|
goFormat: "15:04:05",
|
|
wantLDML: "HH:mm:ss",
|
|
wantDesc: "Hour (24-hour, 2-digit), colon, Minute (2-digit), colon, Second (2-digit)",
|
|
},
|
|
{
|
|
name: "Full datetime",
|
|
goFormat: "2006-01-02 15:04:05",
|
|
wantLDML: "yyyy-MM-dd HH:mm:ss",
|
|
wantDesc: "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)",
|
|
},
|
|
{
|
|
name: "12-hour with AM/PM",
|
|
goFormat: "3:04 PM",
|
|
wantLDML: "h:mm a",
|
|
wantDesc: "Hour (12-hour), colon, Minute (2-digit), space, AM/PM (uppercase)",
|
|
},
|
|
{
|
|
name: "US date format",
|
|
goFormat: "01/02/2006",
|
|
wantLDML: "MM/dd/yyyy",
|
|
wantDesc: "Month (2-digit), slash, Day (2-digit), slash, Year (4-digit)",
|
|
},
|
|
{
|
|
name: "European date format",
|
|
goFormat: "02/01/2006",
|
|
wantLDML: "dd/MM/yyyy",
|
|
wantDesc: "Day (2-digit), slash, Month (2-digit), slash, Year (4-digit)",
|
|
},
|
|
{
|
|
name: "RFC3339",
|
|
goFormat: "2006-01-02T15:04:05Z07:00",
|
|
wantLDML: "yyyy-MM-dd'T'HH:mm:ssZZZZZ",
|
|
wantDesc: "Year (4-digit), dash, Month (2-digit), dash, Day (2-digit), literal 'T', Hour (24-hour, 2-digit), colon, Minute (2-digit), colon, Second (2-digit), ISO 8601 timezone (Z or ±HH:MM)",
|
|
},
|
|
{
|
|
name: "With milliseconds",
|
|
goFormat: "2006-01-02 15:04:05.000",
|
|
wantLDML: "yyyy-MM-dd HH:mm:ss.SSS",
|
|
wantDesc: "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), Millisecond (3-digit)",
|
|
},
|
|
{
|
|
name: "With nanoseconds",
|
|
goFormat: "2006-01-02 15:04:05.000000000",
|
|
wantLDML: "yyyy-MM-dd HH:mm:ss.SSSSSSSSS",
|
|
wantDesc: "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), Nanosecond (9-digit)",
|
|
},
|
|
{
|
|
name: "Month names",
|
|
goFormat: "January 2, 2006",
|
|
wantLDML: "MMMM d, yyyy",
|
|
wantDesc: "Month (full name), space, Day (numeric), comma-space, Year (4-digit)",
|
|
},
|
|
{
|
|
name: "Abbreviated month and weekday",
|
|
goFormat: "Mon, Jan 2 2006",
|
|
wantLDML: "EEE, MMM d yyyy",
|
|
wantDesc: "Weekday (abbreviated), comma-space, Month (abbreviated), space, Day (numeric), space, Year (4-digit)",
|
|
},
|
|
{
|
|
name: "Full weekday and month",
|
|
goFormat: "Monday, January 2, 2006",
|
|
wantLDML: "EEEE, MMMM d, yyyy",
|
|
wantDesc: "Weekday (full name), comma-space, Month (full name), space, Day (numeric), comma-space, Year (4-digit)",
|
|
},
|
|
{
|
|
name: "With timezone name",
|
|
goFormat: "2006-01-02 15:04:05 MST",
|
|
wantLDML: "yyyy-MM-dd HH:mm:ss zzz",
|
|
wantDesc: "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), space, Timezone abbreviation",
|
|
},
|
|
{
|
|
name: "With timezone offset",
|
|
goFormat: "2006-01-02 15:04:05 -0700",
|
|
wantLDML: "yyyy-MM-dd HH:mm:ss ZZZ",
|
|
wantDesc: "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), space, Timezone offset (±HHMM)",
|
|
},
|
|
{
|
|
name: "Kitchen time",
|
|
goFormat: "3:04PM",
|
|
wantLDML: "h:mma",
|
|
wantDesc: "Hour (12-hour), colon, Minute (2-digit), AM/PM (uppercase)",
|
|
},
|
|
{
|
|
name: "With literal text",
|
|
goFormat: "2006-01-02 at 15:04",
|
|
wantLDML: "yyyy-MM-dd' at 'HH:mm",
|
|
wantDesc: "Year (4-digit), dash, Month (2-digit), dash, Day (2-digit), literal ' at ', Hour (24-hour, 2-digit), colon, Minute (2-digit)",
|
|
},
|
|
{
|
|
name: "Space-padded day",
|
|
goFormat: "Jan _2 15:04:05",
|
|
wantLDML: "MMM d HH:mm:ss",
|
|
wantDesc: "Month (abbreviated), space, Day (space-padded), space, Hour (24-hour, 2-digit), colon, Minute (2-digit), colon, Second (2-digit)",
|
|
},
|
|
{
|
|
name: "Empty string",
|
|
goFormat: "",
|
|
shouldError: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
format, err := ParseGoFormat(tt.goFormat)
|
|
|
|
if tt.shouldError {
|
|
if err == nil {
|
|
t.Error("Expected error but got none")
|
|
}
|
|
return
|
|
}
|
|
|
|
if err != nil {
|
|
t.Fatalf("ParseGoFormat() error = %v", err)
|
|
}
|
|
|
|
// Verify the format can reproduce the original Go format
|
|
gotGoFormat := format.GoFormat()
|
|
if gotGoFormat != tt.goFormat {
|
|
t.Errorf("GoFormat() = %q, want %q", gotGoFormat, tt.goFormat)
|
|
}
|
|
|
|
// Verify LDML conversion
|
|
gotLDML := format.LDML()
|
|
if gotLDML != tt.wantLDML {
|
|
t.Errorf("LDML() = %q, want %q", gotLDML, tt.wantLDML)
|
|
}
|
|
|
|
// Verify description
|
|
gotDesc := format.Description()
|
|
if gotDesc != tt.wantDesc {
|
|
t.Errorf("Description() = %q, want %q", gotDesc, tt.wantDesc)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParseGoFormat_StdlibFormats(t *testing.T) {
|
|
// Test that we can parse all the standard library time format constants
|
|
tests := []struct {
|
|
name string
|
|
goFormat string
|
|
}{
|
|
{"time.ANSIC", "Mon Jan _2 15:04:05 2006"},
|
|
{"time.UnixDate", "Mon Jan _2 15:04:05 MST 2006"},
|
|
{"time.RubyDate", "Mon Jan 02 15:04:05 -0700 2006"},
|
|
{"time.RFC822", "02 Jan 06 15:04 MST"},
|
|
{"time.RFC822Z", "02 Jan 06 15:04 -0700"},
|
|
{"time.RFC850", "Monday, 02-Jan-06 15:04:05 MST"},
|
|
{"time.RFC1123", "Mon, 02 Jan 2006 15:04:05 MST"},
|
|
{"time.RFC1123Z", "Mon, 02 Jan 2006 15:04:05 -0700"},
|
|
{"time.RFC3339", "2006-01-02T15:04:05Z07:00"},
|
|
{"time.RFC3339Nano", "2006-01-02T15:04:05.999999999Z07:00"},
|
|
{"time.Kitchen", "3:04PM"},
|
|
{"time.Stamp", "Jan _2 15:04:05"},
|
|
{"time.StampMilli", "Jan _2 15:04:05.000"},
|
|
{"time.StampMicro", "Jan _2 15:04:05.000000"},
|
|
{"time.StampNano", "Jan _2 15:04:05.000000000"},
|
|
{"time.DateTime", "2006-01-02 15:04:05"},
|
|
{"time.DateOnly", "2006-01-02"},
|
|
{"time.TimeOnly", "15:04:05"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
format, err := ParseGoFormat(tt.goFormat)
|
|
if err != nil {
|
|
t.Fatalf("ParseGoFormat() error = %v", err)
|
|
}
|
|
|
|
// Verify roundtrip
|
|
gotGoFormat := format.GoFormat()
|
|
if gotGoFormat != tt.goFormat {
|
|
t.Errorf("GoFormat() = %q, want %q", gotGoFormat, tt.goFormat)
|
|
}
|
|
|
|
// Verify it can actually format a time
|
|
testTime := time.Date(2026, time.February, 8, 15, 4, 5, 123456789, time.FixedZone("MST", -7*3600))
|
|
formatted := format.Format(testTime)
|
|
if formatted == "" {
|
|
t.Error("Format() returned empty string")
|
|
}
|
|
|
|
// Verify LDML and Description don't panic
|
|
_ = format.LDML()
|
|
_ = format.Description()
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParseGoFormat_RoundTrip(t *testing.T) {
|
|
// Test that parsing a format and converting back to Go format is lossless
|
|
formats := []string{
|
|
"2006-01-02",
|
|
"15:04:05",
|
|
"2006-01-02 15:04:05",
|
|
"01/02/2006 3:04:05 PM",
|
|
"Monday, January 2, 2006",
|
|
"Jan _2 15:04:05.000",
|
|
"2006-01-02T15:04:05Z07:00",
|
|
"02 Jan 06 15:04 MST",
|
|
}
|
|
|
|
for _, original := range formats {
|
|
t.Run(original, func(t *testing.T) {
|
|
format, err := ParseGoFormat(original)
|
|
if err != nil {
|
|
t.Fatalf("ParseGoFormat() error = %v", err)
|
|
}
|
|
|
|
result := format.GoFormat()
|
|
if result != original {
|
|
t.Errorf("Round trip failed: got %q, want %q", result, original)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMustParseGoFormat(t *testing.T) {
|
|
t.Run("Valid format", func(t *testing.T) {
|
|
// Should not panic
|
|
format := MustParseGoFormat("2006-01-02")
|
|
if format == nil {
|
|
t.Error("MustParseGoFormat returned nil")
|
|
}
|
|
})
|
|
|
|
t.Run("Invalid format panics", func(t *testing.T) {
|
|
defer func() {
|
|
if r := recover(); r == nil {
|
|
t.Error("MustParseGoFormat should panic on empty string")
|
|
}
|
|
}()
|
|
MustParseGoFormat("")
|
|
})
|
|
}
|
|
|
|
func TestParseGoFormat_EdgeCases(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
goFormat string
|
|
wantGo string
|
|
}{
|
|
{
|
|
name: "Consecutive literals",
|
|
goFormat: "2006-01-02T15:04:05",
|
|
wantGo: "2006-01-02T15:04:05",
|
|
},
|
|
{
|
|
name: "Just literals",
|
|
goFormat: "Hello, World!",
|
|
wantGo: "Hello, World!",
|
|
},
|
|
{
|
|
name: "Mixed tokens and literals",
|
|
goFormat: "Year: 2006, Month: 01",
|
|
wantGo: "Year: 2006, Month: 01",
|
|
},
|
|
{
|
|
name: "Special characters",
|
|
goFormat: "2006/01/02 @ 15:04:05",
|
|
wantGo: "2006/01/02 @ 15:04:05",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
format, err := ParseGoFormat(tt.goFormat)
|
|
if err != nil {
|
|
t.Fatalf("ParseGoFormat() error = %v", err)
|
|
}
|
|
|
|
gotGo := format.GoFormat()
|
|
if gotGo != tt.wantGo {
|
|
t.Errorf("GoFormat() = %q, want %q", gotGo, tt.wantGo)
|
|
}
|
|
})
|
|
}
|
|
}
|