Skip to content
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions tariff/fixed.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ func NewFixedFromConfig(other map[string]any) (api.Tariff, error) {
func (t *Fixed) Rates() (api.Rates, error) {
var res api.Rates

start := now.With(t.clock.Now().Local()).BeginningOfDay()
start := now.With(t.clock.Now()).BeginningOfDay()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

required, fix clock dependencies

for i := range 7 {
dayStart := start.AddDate(0, 0, i)
dow := fixed.Day((int(start.Weekday()) + i) % 7)
Expand All @@ -110,8 +110,9 @@ func (t *Fixed) Rates() (api.Rates, error) {
var zone *fixed.Zone
for j := len(zones) - 1; j >= 0; j-- {
if zones[j].Hours.Contains(m) {
zone = &zones[j]
break
if zone == nil || fixed.MoreSpecific(zones[j], *zone) {
zone = &zones[j]
}
}
}

Expand Down
22 changes: 22 additions & 0 deletions tariff/fixed/zone.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,5 +78,27 @@ HOURS:
res = append(res, HourMin{Hour: hour, Min: 0})
}

// Sort markers by time to ensure correct ordering
slices.SortFunc(res, func(a, b HourMin) int {
return a.Minutes() - b.Minutes()
})

Comment on lines +81 to +85
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

major required fix - paired with correcting rates clock

return res
}

// MoreSpecific returns true if zone a is more specific than zone b.
// A zone is more specific if it has constraints on fewer days or months.
func MoreSpecific(a, b Zone) bool {
return specificity(a) > specificity(b)
}

func specificity(z Zone) int {
spec := 0
if len(z.Days) > 0 && len(z.Days) < 7 {
spec++
}
if len(z.Months) > 0 && len(z.Months) < 12 {
spec++
}
return spec
}
57 changes: 57 additions & 0 deletions tariff/fixed_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,60 @@ func TestFixedSplitZones(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, expect, rates)
}

func TestFixedMonthsSorting(t *testing.T) {
at, err := NewFixedFromConfig(map[string]any{
"zones": []struct {
Price float64
Hours string
Months string
}{
{0.1, "0-5", ""}, // all year
{0.2, "5-0", ""}, // all year
{0.3, "2-4", "Jun"}, // Jun only
{0.4, "18-20", "Jun"}, // Jun only
// TODO: specific days
},
})
require.NoError(t, err)

tc := []struct {
m, d, h int
rate float64
}{
// all year
{1, 1, 0, 0.1},
{1, 1, 2, 0.1},
{1, 1, 5, 0.2},
{1, 1, 18, 0.2},

// Jun only
{6, 1, 0, 0.1},
{6, 1, 2, 0.3},
{6, 1, 5, 0.2},
{6, 1, 18, 0.4},
}

// Test both UTC and Local timezones to verify clock timezone is respected
timezones := []*time.Location{time.UTC, time.Local}
for _, tz := range timezones {
t.Run(tz.String(), func(t *testing.T) {
for _, tc := range tc {
clock := clock.NewMock()
at.(*Fixed).clock = clock

clock.Set(time.Date(2025, time.Month(tc.m), tc.d, 0, 0, 0, 0, tz))

rr, err := at.Rates()
require.NoError(t, err)

r, err := rr.At(clock.Now().Add(time.Duration(tc.h) * time.Hour))
require.NoError(t, err)

assert.Equal(t, tc.rate, r.Value,
"TZ=%s, %04d-%02d-%02d %02d:00",
tz, 2025, tc.m, tc.d, tc.h)
}
})
}
}