From 60f313cbfb75634f8d69dd63794efd89867980ab Mon Sep 17 00:00:00 2001 From: andig Date: Sun, 26 Jul 2026 12:31:26 +0200 Subject: [PATCH] Tariff: keep fixed tariff rates ordered when chargesZones markers are combined (#32165) --- tariff/fixed/zone.go | 23 ++++++++--------------- tariff/fixed/zone_test.go | 35 +++++++++++++++++++++++++++++++++++ tariff/fixed_test.go | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 15 deletions(-) diff --git a/tariff/fixed/zone.go b/tariff/fixed/zone.go index 82bb0d469..723d12661 100644 --- a/tariff/fixed/zone.go +++ b/tariff/fixed/zone.go @@ -104,25 +104,18 @@ func (r Zones) TimeTableMarkers() []HourMin { } } -HOURS: // 1hr intervals for hour := range 24 { - for _, m := range res { - if m.Hour == hour && m.Min == 0 { - continue HOURS - } + hm := HourMin{Hour: hour} + if !slices.Contains(res, hm) { + res = append(res, hm) } - - // hour is missing - for i, m := range res { - if m.Hour >= hour { - res = slices.Insert(res, i, HourMin{Hour: hour, Min: 0}) - continue HOURS - } - } - - res = append(res, HourMin{Hour: hour, Min: 0}) } + // zones may be unsorted, e.g. when price and charges zones are combined + slices.SortFunc(res, func(i, j HourMin) int { + return i.Minutes() - j.Minutes() + }) + return res } diff --git a/tariff/fixed/zone_test.go b/tariff/fixed/zone_test.go index 72d325ea6..0b809d48f 100644 --- a/tariff/fixed/zone_test.go +++ b/tariff/fixed/zone_test.go @@ -81,3 +81,38 @@ func TestZonesTimeTableMarkers(t *testing.T) { assert.Equal(t, expect, zones.TimeTableMarkers()) } + +func TestZonesTimeTableMarkersUnsorted(t *testing.T) { + // price and charges zones are concatenated unsorted, see Fixed.Rates + zones := Zones{ + {Hours: TimeRange{ + From: HourMin{12, 0}, + To: HourMin{14, 0}, + }}, + {Hours: TimeRange{ + From: HourMin{10, 15}, // minutes must not sort before the earlier hour's 9:45 + To: HourMin{11, 0}, + }}, + {Hours: TimeRange{ + From: HourMin{9, 45}, + To: HourMin{10, 0}, + }}, + {Hours: TimeRange{ + From: HourMin{0, 0}, + To: HourMin{5, 30}, + }}, + } + + expect := []HourMin{{0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}, {5, 0}, {5, 30}} + for hour := 6; hour < 24; hour++ { + expect = append(expect, HourMin{hour, 0}) + switch hour { + case 9: + expect = append(expect, HourMin{9, 45}) + case 10: + expect = append(expect, HourMin{10, 15}) + } + } + + assert.Equal(t, expect, zones.TimeTableMarkers()) +} diff --git a/tariff/fixed_test.go b/tariff/fixed_test.go index 56151b33e..a9be2807c 100644 --- a/tariff/fixed_test.go +++ b/tariff/fixed_test.go @@ -133,3 +133,36 @@ func TestFixedChargesZonesMarkers(t *testing.T) { assert.Equal(t, dayStart.Add(5*time.Hour+30*time.Minute), rates[6].Start) assert.Equal(t, 0.5, rates[6].Value) } + +func TestFixedChargesZonesOrder(t *testing.T) { + at, err := NewFixedFromConfig(map[string]any{ + "price": 0.5, + "zones": []struct { + Price float64 + Hours string + }{ + {0.2, "12-14"}, + }, + "chargesZones": []struct { + Charges float64 + Hours string + }{ + {0.1, "0-5:30"}, + }, + }) + require.NoError(t, err) + + tf := at.(*Fixed) + tf.clock = clock.NewMock() + + rates, err := tf.Rates() + require.NoError(t, err) + + // rates must stay ordered and contiguous even if charges zone markers don't align with price zones + for i, r := range rates { + require.True(t, r.End.After(r.Start), "invalid rate %d: %v", i, r) + if i > 0 { + require.Equal(t, rates[i-1].End, r.Start, "non-contiguous rate %d: %v", i, r) + } + } +}