quando/clock_test.go
Oliver Jakoubek f47897f3fd Change Now() and DefaultClock to use UTC instead of local time
Changed quando.Now() and DefaultClock.Now() to return dates in UTC
timezone instead of local server timezone. This aligns with industry
standards for date/time libraries and prevents server-timezone-dependent
behavior.

Changes:
- date.go: Now() uses time.Now().UTC() instead of time.Now()
- Updated documentation comments to reflect UTC default
- date_test.go: TestNow() now verifies UTC location
- clock_test.go: TestDefaultClock_Now() now verifies UTC location
- parse_test.go: TestParseRelative() verifies all results are UTC
- parse.go: Updated comment from "local timezone" to "UTC timezone"

Users needing local time can use:
- quando.From(time.Now()) for explicit local time
- quando.Now().In("Europe/Berlin") to convert to specific timezone

Closes: quando-67n
2026-02-12 16:39:01 +01:00

196 lines
5.2 KiB
Go

package quando
import (
"testing"
"time"
)
func TestNewClock(t *testing.T) {
clock := NewClock()
if clock == nil {
t.Fatal("NewClock() returned nil")
}
// Verify it's a DefaultClock
if _, ok := clock.(*DefaultClock); !ok {
t.Errorf("NewClock() returned %T, want *DefaultClock", clock)
}
}
func TestDefaultClock_Now(t *testing.T) {
clock := NewClock()
before := time.Now().UTC()
date := clock.Now()
after := time.Now().UTC()
// Verify that Now() returns a time between before and after
if date.Time().Before(before) || date.Time().After(after) {
t.Errorf("DefaultClock.Now() returned time outside expected range")
}
// Verify location is UTC
if date.Time().Location() != time.UTC {
t.Errorf("DefaultClock.Now().Time().Location() = %v, want UTC", date.Time().Location())
}
}
func TestDefaultClock_From(t *testing.T) {
clock := NewClock()
testTime := time.Date(2026, 2, 9, 12, 30, 45, 0, time.UTC)
date := clock.From(testTime)
if !date.Time().Equal(testTime) {
t.Errorf("DefaultClock.From() = %v, want %v", date.Time(), testTime)
}
}
func TestNewFixedClock(t *testing.T) {
fixedTime := time.Date(2026, 2, 9, 12, 0, 0, 0, time.UTC)
clock := NewFixedClock(fixedTime)
if clock == nil {
t.Fatal("NewFixedClock() returned nil")
}
// Verify it's a FixedClock
if _, ok := clock.(*FixedClock); !ok {
t.Errorf("NewFixedClock() returned %T, want *FixedClock", clock)
}
}
func TestFixedClock_Now(t *testing.T) {
fixedTime := time.Date(2026, 2, 9, 12, 0, 0, 0, time.UTC)
clock := NewFixedClock(fixedTime)
// Call Now() multiple times to verify it always returns the same time
date1 := clock.Now()
time.Sleep(1 * time.Millisecond)
date2 := clock.Now()
time.Sleep(1 * time.Millisecond)
date3 := clock.Now()
// All should return the same fixed time
if !date1.Time().Equal(fixedTime) {
t.Errorf("FixedClock.Now() (call 1) = %v, want %v", date1.Time(), fixedTime)
}
if !date2.Time().Equal(fixedTime) {
t.Errorf("FixedClock.Now() (call 2) = %v, want %v", date2.Time(), fixedTime)
}
if !date3.Time().Equal(fixedTime) {
t.Errorf("FixedClock.Now() (call 3) = %v, want %v", date3.Time(), fixedTime)
}
// Verify all three are equal to each other
if !date1.Time().Equal(date2.Time()) || !date2.Time().Equal(date3.Time()) {
t.Error("FixedClock.Now() returned different times on successive calls")
}
}
func TestFixedClock_From(t *testing.T) {
fixedTime := time.Date(2026, 2, 9, 12, 0, 0, 0, time.UTC)
clock := NewFixedClock(fixedTime)
// Test with different time
testTime := time.Date(2025, 5, 15, 8, 30, 0, 0, time.UTC)
date := clock.From(testTime)
// From() should use the provided time, not the fixed time
if !date.Time().Equal(testTime) {
t.Errorf("FixedClock.From() = %v, want %v", date.Time(), testTime)
}
}
// TestFixedClock_DeterministicTesting demonstrates how FixedClock enables deterministic tests
func TestFixedClock_DeterministicTesting(t *testing.T) {
// This test demonstrates a deterministic test pattern using FixedClock
// Create a fixed clock for a specific test scenario
testTime := time.Date(2026, 2, 9, 12, 0, 0, 0, time.UTC)
clock := NewFixedClock(testTime)
// Use the clock in test code
now := clock.Now()
// We can now make deterministic assertions
expected := "2026-02-09 12:00:00"
if now.String() != expected {
t.Errorf("String() = %v, want %v", now.String(), expected)
}
expectedUnix := int64(1770638400)
if now.Unix() != expectedUnix {
t.Errorf("Unix() = %d, want %d", now.Unix(), expectedUnix)
}
}
// TestClock_Interface verifies that both implementations satisfy the Clock interface
func TestClock_Interface(t *testing.T) {
var _ Clock = &DefaultClock{}
var _ Clock = &FixedClock{}
// This test will fail at compile time if either type doesn't implement Clock
}
// TestClock_EdgeCases tests edge cases for clock implementations
func TestClock_EdgeCases(t *testing.T) {
tests := []struct {
name string
fixedTime time.Time
}{
{
name: "epoch",
fixedTime: time.Unix(0, 0),
},
{
name: "year 0001",
fixedTime: time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC),
},
{
name: "year 9999",
fixedTime: time.Date(9999, 12, 31, 23, 59, 59, 0, time.UTC),
},
{
name: "with nanoseconds",
fixedTime: time.Date(2026, 2, 9, 12, 30, 45, 123456789, time.UTC),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
clock := NewFixedClock(tt.fixedTime)
date := clock.Now()
if !date.Time().Equal(tt.fixedTime) {
t.Errorf("FixedClock.Now() = %v, want %v", date.Time(), tt.fixedTime)
}
})
}
}
// TestClock_Timezones verifies that clocks preserve timezone information
func TestClock_Timezones(t *testing.T) {
loc, err := time.LoadLocation("Europe/Berlin")
if err != nil {
t.Skipf("Skipping timezone test: %v", err)
}
berlinTime := time.Date(2026, 2, 9, 12, 0, 0, 0, loc)
// Test DefaultClock
defaultClock := NewClock()
date1 := defaultClock.From(berlinTime)
if date1.Time().Location() != loc {
t.Errorf("DefaultClock.From() location = %v, want %v", date1.Time().Location(), loc)
}
// Test FixedClock
fixedClock := NewFixedClock(berlinTime)
date2 := fixedClock.Now()
if date2.Time().Location() != loc {
t.Errorf("FixedClock.Now() location = %v, want %v", date2.Time().Location(), loc)
}
}