| package datetime |
|
|
| import ( |
| "testing" |
| "time" |
|
|
| "github.com/stretchr/testify/assert" |
| "github.com/stretchr/testify/require" |
| ) |
|
|
| func TestDateTimeParseFormat(t *testing.T) { |
| tests := []struct { |
| name string |
| input string |
| layout string |
| wantTime time.Time |
| }{ |
| { |
| name: "RFC9557 with America/New_York summer time", |
| input: "2021-07-01T12:34:56-04:00[America/New_York]", |
| layout: RFC9557Layout, |
| wantTime: time.Date(2021, 7, 1, 12, 34, 56, 0, MustLoadLocation(t, "America/New_York")), |
| }, |
| { |
| name: "RFC9557 with America/New_York winter time", |
| input: "2021-12-01T12:34:56-05:00[America/New_York]", |
| layout: RFC9557Layout, |
| wantTime: time.Date(2021, 12, 1, 12, 34, 56, 0, MustLoadLocation(t, "America/New_York")), |
| }, |
| { |
| name: "RFC9557 with Europe/Berlin", |
| input: "2021-07-01T18:34:56+02:00[Europe/Berlin]", |
| layout: RFC9557Layout, |
| wantTime: time.Date(2021, 7, 1, 18, 34, 56, 0, MustLoadLocation(t, "Europe/Berlin")), |
| }, |
| { |
| name: "RFC9557 with Asia/Tokyo", |
| input: "2021-07-01T21:34:56+09:00[Asia/Tokyo]", |
| layout: RFC9557Layout, |
| wantTime: time.Date(2021, 7, 1, 21, 34, 56, 0, MustLoadLocation(t, "Asia/Tokyo")), |
| }, |
| { |
| name: "RFC9557 with fractional seconds", |
| input: "2021-07-01T12:34:56.123456789-04:00[America/New_York]", |
| layout: RFC9557NanoLayout, |
| wantTime: time.Date(2021, 7, 1, 12, 34, 56, 123456789, MustLoadLocation(t, "America/New_York")), |
| }, |
| } |
|
|
| for _, tt := range tests { |
| t.Run(tt.name, func(t *testing.T) { |
| |
| parsed, err := Parse(tt.input) |
| assert.NoError(t, err, "Parse should not return an error") |
| assert.True(t, parsed.Equal(tt.wantTime), "Parse should parse the datetime correctly") |
|
|
| |
| formatted := parsed.Format(tt.layout) |
|
|
| |
| assert.Equal(t, tt.input, formatted, "round-trip formatting should preserve the original input") |
| }) |
| } |
| } |
|
|
| func TestDateTimeAdd(t *testing.T) { |
| tests := []struct { |
| name string |
| start string |
| duration ISODuration |
| expected string |
| outputTz *time.Location |
| }{ |
| { |
| name: "add 1 month correctly", |
| start: "2024-12-31T00:00:00Z", |
| duration: MustParseDuration(t, "P1M"), |
| expected: "2025-01-31T00:00:00Z", |
| }, |
| { |
| name: "add 1 year correctly with leap year handling", |
| start: "2024-02-29T00:00:00Z", |
| duration: MustParseDuration(t, "P1Y"), |
| expected: "2025-02-28T00:00:00Z", |
| }, |
| { |
| name: "handle leap year transition over multiple years", |
| start: "2024-02-29T00:00:00Z", |
| duration: MustParseDuration(t, "P2Y"), |
| expected: "2026-02-28T00:00:00Z", |
| }, |
| { |
| name: "add 6 months with month-end handling", |
| start: "2024-12-31T00:00:00Z", |
| duration: MustParseDuration(t, "P6M"), |
| expected: "2025-06-30T00:00:00Z", |
| }, |
| { |
| name: "add 1 week correctly", |
| start: "2025-02-01T12:00:00Z", |
| duration: MustParseDuration(t, "P1W"), |
| expected: "2025-02-08T12:00:00Z", |
| }, |
| { |
| name: "add 3 hours correctly", |
| start: "2025-02-01T12:00:00Z", |
| duration: MustParseDuration(t, "PT3H"), |
| expected: "2025-02-01T15:00:00Z", |
| }, |
| { |
| name: "add 30 minutes correctly", |
| start: "2025-02-01T12:00:00Z", |
| duration: MustParseDuration(t, "PT30M"), |
| expected: "2025-02-01T12:30:00Z", |
| }, |
| { |
| name: "add 45 seconds correctly", |
| start: "2025-02-01T12:00:00Z", |
| duration: MustParseDuration(t, "PT45S"), |
| expected: "2025-02-01T12:00:45Z", |
| }, |
| { |
| name: "handle daylight savings transition", |
| start: "2025-03-25T01:00:00+01:00[Europe/Budapest]", |
| duration: MustParseDuration(t, "PT2H"), |
| expected: "2025-03-25T04:00:00+02:00[Europe/Budapest]", |
| outputTz: MustLoadLocation(t, "Europe/Budapest"), |
| }, |
| { |
| name: "handle complex duration with multiple components", |
| start: "2024-01-15T10:30:00Z", |
| duration: MustParseDuration(t, "P1Y2M10DT5H30M45S"), |
| expected: "2025-03-25T16:00:45Z", |
| }, |
| { |
| name: "handle negative duration", |
| start: "2025-06-15T12:00:00Z", |
| duration: MustParseDuration(t, "P-3M"), |
| expected: "2025-03-15T12:00:00Z", |
| }, |
| { |
| name: "handle end of month edge case", |
| start: "2025-01-31T00:00:00Z", |
| duration: MustParseDuration(t, "P1M"), |
| expected: "2025-02-28T00:00:00Z", |
| }, |
| { |
| name: "handle leap second period (December 2016)", |
| start: "2016-11-30T00:00:00Z", |
| duration: MustParseDuration(t, "P1M"), |
| expected: "2016-12-30T00:00:00Z", |
| }, |
| { |
| name: "handle zero duration", |
| start: "2025-02-01T12:00:00Z", |
| duration: MustParseDuration(t, "PT0S"), |
| expected: "2025-02-01T12:00:00Z", |
| }, |
| } |
|
|
| for _, tt := range tests { |
| t.Run(tt.name, func(t *testing.T) { |
| start, err := Parse(tt.start) |
| assert.NoError(t, err, "failed to parse start time") |
|
|
| result := start.Add(tt.duration) |
|
|
| expected, err := Parse(tt.expected) |
| assert.NoError(t, err, "failed to parse expected time") |
|
|
| |
| tz := time.UTC |
| if tt.outputTz != nil { |
| tz = tt.outputTz |
| } |
|
|
| assert.Equal(t, |
| expected.In(tz).Format(RFC9557Layout), |
| result.In(tz).Format(RFC9557Layout), |
| "duration addition result mismatch") |
|
|
| |
| assert.True(t, result.Equal(expected.Time), |
| "times should be equal: got %v, want %v", |
| result.Format(RFC9557Layout), |
| expected.Format(RFC9557Layout)) |
| }) |
| } |
| } |
|
|
| func TestDateTimeShiftClockTo(t *testing.T) { |
| someTime := time.Now() |
| later := someTime.Add(time.Second) |
|
|
| require.Equal(t, NewDateTime(someTime).shiftClockTo(later).AsTime(), someTime.Add(time.Second)) |
| } |
|
|
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|