Repository: evcc-io/evcc
Pull request #5963 (2023-01-31 09:29:26)

Tariff: treat zone without days as whole week
Refs https://github.com/evcc-io/evcc/issues/5961


Patch:
diff --git a/cmd/demo.yaml b/cmd/demo.yaml
index 362951fa..e32909db 100644
--- a/cmd/demo.yaml
+++ b/cmd/demo.yaml
@@ -150,7 +150,7 @@ vehicles:
       vm: shared
       script: |
         var lp = state.loadpoints[0];
-        if (lp.chargepower > 0) lp.vehicleSoc++; else lp.vehicleSoc--;
+        if (lp.chargepower > 0) lp.vehicleSoc+=0.1; else lp.vehicleSoc-=0.1;
         if (lp.vehicleSoc < 15) lp.vehicleSoc = 80;
         if (lp.vehicleSoc > 80) lp.vehicleSoc = 15;
         lp.vehicleSoc;
diff --git a/tariff/awattar.go b/tariff/awattar.go
index f1c16ca6..592b08c7 100644
--- a/tariff/awattar.go
+++ b/tariff/awattar.go
@@ -10,6 +10,7 @@ import (
 	"github.com/evcc-io/evcc/tariff/awattar"
 	"github.com/evcc-io/evcc/util"
 	"github.com/evcc-io/evcc/util/request"
+	"golang.org/x/exp/slices"
 )
 
 type Awattar struct {
@@ -103,5 +104,5 @@ func (t *Awattar) Unit() string {
 func (t *Awattar) Rates() (api.Rates, error) {
 	t.mux.Lock()
 	defer t.mux.Unlock()
-	return append([]api.Rate{}, t.data...), outdatedError(t.updated, time.Hour)
+	return slices.Clone(t.data), outdatedError(t.updated, time.Hour)
 }
diff --git a/tariff/fixed/day.go b/tariff/fixed/day.go
index 27d56733..bbee9961 100644
--- a/tariff/fixed/day.go
+++ b/tariff/fixed/day.go
@@ -78,7 +78,14 @@ func ParseDays(s string) ([]Day, error) {
 			return nil, fmt.Errorf("invalid day range: %s", segment)
 		}
 
-		from, err := ParseDay(fromto[0])
+		fromToFrom := fromto[0]
+
+		// single empty segment
+		if len(fromto) == 1 && strings.TrimSpace(fromToFrom) == "" {
+			return slices.Clone(Week), nil
+		}
+
+		from, err := ParseDay(fromToFrom)
 		if err != nil {
 			return nil, err
 		}
diff --git a/tariff/fixed/timerange.go b/tariff/fixed/timerange.go
index ec91c2db..310c11a4 100644
--- a/tariff/fixed/timerange.go
+++ b/tariff/fixed/timerange.go
@@ -75,6 +75,10 @@ func ParseTimeRange(s string) (TimeRange, error) {
 		return TimeRange{}, err
 	}
 
+	if tom := to.Minutes(); tom != 0 && from.Minutes() >= tom {
+		return TimeRange{}, fmt.Errorf("invalid time range: %s, <from> must be before <to>", s)
+	}
+
 	return TimeRange{from, to}, nil
 }
 
diff --git a/tariff/gruenstromindex.go b/tariff/gruenstromindex.go
index 85530768..556b5be6 100644
--- a/tariff/gruenstromindex.go
+++ b/tariff/gruenstromindex.go
@@ -9,6 +9,7 @@ import (
 	"github.com/evcc-io/evcc/api"
 	"github.com/evcc-io/evcc/util"
 	"github.com/evcc-io/evcc/util/request"
+	"golang.org/x/exp/slices"
 )
 
 type GrünStromIndex struct {
@@ -132,5 +133,5 @@ func (t *GrünStromIndex) Unit() string {
 func (t *GrünStromIndex) Rates() (api.Rates, error) {
 	t.mux.Lock()
 	defer t.mux.Unlock()
-	return append(api.Rates{}, t.data...), outdatedError(t.updated, time.Hour)
+	return slices.Clone(t.data), outdatedError(t.updated, time.Hour)
 }
diff --git a/tariff/octopus.go b/tariff/octopus.go
index 5c280b8e..b25588c9 100644
--- a/tariff/octopus.go
+++ b/tariff/octopus.go
@@ -2,13 +2,14 @@ package tariff
 
 import (
 	"errors"
-	"github.com/evcc-io/evcc/tariff/octopus"
 	"sync"
 	"time"
 
 	"github.com/evcc-io/evcc/api"
+	"github.com/evcc-io/evcc/tariff/octopus"
 	"github.com/evcc-io/evcc/util"
 	"github.com/evcc-io/evcc/util/request"
+	"golang.org/x/exp/slices"
 )
 
 type Octopus struct {
@@ -99,5 +100,5 @@ func (t *Octopus) Unit() string {
 func (t *Octopus) Rates() (api.Rates, error) {
 	t.mux.Lock()
 	defer t.mux.Unlock()
-	return append([]api.Rate{}, t.data...), outdatedError(t.updated, time.Hour)
+	return slices.Clone(t.data), outdatedError(t.updated, time.Hour)
 }
diff --git a/tariff/tibber.go b/tariff/tibber.go
index 1ab21de5..9a41d846 100644
--- a/tariff/tibber.go
+++ b/tariff/tibber.go
@@ -11,6 +11,7 @@ import (
 	"github.com/evcc-io/evcc/util"
 	"github.com/evcc-io/evcc/util/request"
 	"github.com/shurcooL/graphql"
+	"golang.org/x/exp/slices"
 )
 
 type Tibber struct {
@@ -144,5 +145,5 @@ func (t *Tibber) Unit() string {
 func (t *Tibber) Rates() (api.Rates, error) {
 	t.mux.Lock()
 	defer t.mux.Unlock()
-	return append([]api.Rate{}, t.data...), outdatedError(t.updated, time.Hour)
+	return slices.Clone(t.data), outdatedError(t.updated, time.Hour)
 }


Test patch:
diff --git a/tariff/fixed/day_test.go b/tariff/fixed/day_test.go
index 9c63438a..d98aa28c 100644
--- a/tariff/fixed/day_test.go
+++ b/tariff/fixed/day_test.go
@@ -35,10 +35,17 @@ func TestParseDays(t *testing.T) {
 	assert.NoError(t, err)
 	assert.Equal(t, []Day{Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday}, d)
 
+	_, err = ParseDays(" ")
+	assert.NoError(t, err)
+	assert.Equal(t, []Day{Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday}, d)
+
 	d, err = ParseDays("1, 3-7")
 	assert.NoError(t, err)
 	assert.Equal(t, []Day{Monday, Wednesday, Thursday, Friday, Saturday, Sunday}, d)
 
+	_, err = ParseDays("-")
+	assert.EqualError(t, err, "invalid day: ")
+
 	_, err = ParseDays("-1")
 	assert.EqualError(t, err, "invalid day: ")
 
diff --git a/tariff/fixed/timerange_test.go b/tariff/fixed/timerange_test.go
index 9af73121..a7a59892 100644
--- a/tariff/fixed/timerange_test.go
+++ b/tariff/fixed/timerange_test.go
@@ -11,11 +11,24 @@ func TestParseTimeRange(t *testing.T) {
 	assert.NoError(t, err)
 	assert.Equal(t, []TimeRange{{HourMin{7, 0}, HourMin{12, 30}}}, d, "hour:min range")
 
-	d, err = ParseTimeRanges("8-10")
+	d, err = ParseTimeRanges(" 8-10 ")
 	assert.NoError(t, err)
 	assert.Equal(t, []TimeRange{{HourMin{8, 0}, HourMin{10, 0}}}, d, "hour range")
 
-	d, err = ParseTimeRanges("8-10,20-22:30")
+	_, err = ParseTimeRanges("-1-10")
+	assert.EqualError(t, err, "invalid time: ")
+
+	_, err = ParseTimeRanges("8-25")
+	assert.EqualError(t, err, "invalid time: 25", "<to> after midnight")
+
+	d, err = ParseTimeRanges("8-0")
+	assert.NoError(t, err)
+	assert.Equal(t, []TimeRange{{HourMin{8, 0}, HourMin{0, 0}}}, d, "hour till midnight (0)")
+
+	_, err = ParseTimeRanges("10-8")
+	assert.EqualError(t, err, "invalid time range: 10-8, <from> must be before <to>")
+
+	d, err = ParseTimeRanges("8-10, 20-22:30")
 	assert.NoError(t, err)
 	assert.Equal(t, []TimeRange{
 		{HourMin{8, 0}, HourMin{10, 0}},