generated from Hochfrequenz/go-template-repository
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgastagconverter.go
148 lines (133 loc) · 6.56 KB
/
gastagconverter.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package mako_time_converter
import (
"fmt"
"github.com/go-playground/validator/v10"
"github.com/hochfrequenz/mako_time_converter/enddatetimekind"
"log"
"time"
)
// GasTagConverter is a struct to convert to and from German "Gas-Tag" (which always starts at 6AM German local time)
type GasTagConverter interface {
// IsGermanMidnight returns true iff the given timestamp is the beginning of a German Stromtag (midnight local time)
IsGermanMidnight(timestamp time.Time) bool
// IsGerman6Am returns true if the given timestamp is the beginning of a German Gastag (6AM local time)
IsGerman6Am(timestamp time.Time) bool
// Convert6AamToMidnight converts the given local 6Am timestamp to German midnight of the same German day
Convert6AamToMidnight(timestamp time.Time) (time.Time, error)
// ConvertMidnightTo6Am converts the given local 6Am timestamp to German midnight of the same German day
ConvertMidnightTo6Am(timestamp time.Time) (time.Time, error)
// StripTime removes all hours, minutes, seconds, milliseconds (in german local time) from the given timestamp. This is similar to a "round down" or "floor" in German local time.
StripTime(timestamp time.Time) time.Time
// Convert converts the given timestamp to a DateTimeConversionConfiguration.Target by applying all transformations which are derived from the given configuration time is described by DateTimeConversionConfiguration.Source.
Convert(timestamp time.Time, configuration DateTimeConversionConfiguration) (time.Time, error)
}
type locationBasedGasTagConverter struct {
location *time.Location
}
// NewGasTagConverter returns a GasTagConverter that internally uses the timezone data from the timezone with the given zoneName (e.g. "Europe/Berlin"). It requires the tzdata to be available on the system and will panic if this is not the case.
func NewGasTagConverter(zoneName string) GasTagConverter {
location, err := time.LoadLocation(zoneName)
if err != nil {
errorMsg := fmt.Errorf("The timezone data for '%s' could not be found. Import \"time/tzdata\" anywhere in your project or build with `-tags timetzdata`: https://pkg.go.dev/time/tzdata", zoneName)
log.Panic(errorMsg)
}
return locationBasedGasTagConverter{location: location}
}
// ToLocalTimeConverter contains a method to convert a time into a local time. This will, in most cases, happen on the basis of timezone data, but you are free to write your own conversion, although you're probably missing out on details at one point.
type ToLocalTimeConverter interface {
// toLocalTime converts a timestamp to a "local" time by adjusting date, time and UTC-offset. The actual point in time in UTC or Unix does _not_ change.
toLocalTime(timestamp time.Time) time.Time
}
func (l locationBasedGasTagConverter) toLocalTime(timestamp time.Time) time.Time {
return timestamp.In(l.location)
}
func (l locationBasedGasTagConverter) IsGermanMidnight(timestamp time.Time) bool {
localTime := l.toLocalTime(timestamp)
hour, minute, sec := localTime.Clock()
return hour == 0 && minute == 0 && sec == 0
}
func (l locationBasedGasTagConverter) IsGerman6Am(timestamp time.Time) bool {
localTime := l.toLocalTime(timestamp)
hour, minute, sec := localTime.Clock()
return hour == 6 && minute == 0 && sec == 0
}
func (l locationBasedGasTagConverter) Convert6AamToMidnight(timestamp time.Time) (time.Time, error) {
if !l.IsGerman6Am(timestamp) {
return time.Time{}, fmt.Errorf("The given time %v is not German 6am", timestamp)
}
return l.StripTime(timestamp), nil
}
func (l locationBasedGasTagConverter) ConvertMidnightTo6Am(timestamp time.Time) (time.Time, error) {
if !l.IsGermanMidnight(timestamp) {
return time.Time{}, fmt.Errorf("The given time %v is not German midnight", timestamp)
}
localMidnight := l.toLocalTime(timestamp)
year, month, day := localMidnight.Date()
local6Am := time.Date(year, month, day, 6, 0, 0, 0, l.location)
return local6Am.UTC(), nil
}
func (l locationBasedGasTagConverter) StripTime(timestamp time.Time) time.Time {
localTime := l.toLocalTime(timestamp)
year, month, day := localTime.Date()
localMidnight := time.Date(year, month, day, 0, 0, 0, 0, l.location)
return localMidnight.UTC()
}
func (l locationBasedGasTagConverter) addGermanDay(timestamp time.Time) time.Time {
localtime := l.toLocalTime(timestamp)
return localtime.AddDate(0, 0, 1).UTC()
}
func (l locationBasedGasTagConverter) subtractGermanDay(timestamp time.Time) time.Time {
localtime := l.toLocalTime(timestamp)
return localtime.AddDate(0, 0, -1).UTC()
}
func (l locationBasedGasTagConverter) Convert(timestamp time.Time, configuration DateTimeConversionConfiguration) (time.Time, error) {
validate := validator.New()
validate.RegisterStructValidation(DateTimeConversionConfigurationStructLevelValidator, DateTimeConversionConfiguration{})
err := validate.Struct(configuration)
if err != nil {
return time.Time{}, err
}
result := timestamp
if configuration.Source.StripTime {
result = l.StripTime(result)
}
if configuration.Source == configuration.Target {
// both are the same, no conversion needed
return result.UTC(), nil
}
if configuration.Source.IsGas { // this implies that the target is also gas, because otherwise the configuration would be invalid
// handle gas stuff here
if *configuration.Source.IsGasTagAware && !*configuration.Target.IsGasTagAware {
// convert from gas-tag to non-gas-tag
if l.IsGerman6Am(result) {
result, err = l.Convert6AamToMidnight(result)
if err != nil { // the error won't happen because Convert6AmToMidnight only returns an error if the datetime is not 6Am (which we checked before)
return time.Time{}, err
}
}
}
if !*configuration.Source.IsGasTagAware && *configuration.Target.IsGasTagAware {
if l.IsGermanMidnight(result) {
result, err = l.ConvertMidnightTo6Am(result)
if err != nil { // the error won't happen because ConvertMidnightTo6Am only returns an error if the datetime is not midnight (which we checked before)
return time.Time{}, err
}
}
}
}
// else { handle strom-only stuff here }
if configuration.Source.IsEndDate && configuration.Target.IsEndDate && *configuration.Source.EndDateTimeKind != *configuration.Target.EndDateTimeKind {
if *configuration.Source.EndDateTimeKind == enddatetimekind.INCLUSIVE { // implicit: target is exclusive
// convert from inclusive to exclusive
result = l.addGermanDay(result)
}
if *configuration.Source.EndDateTimeKind == enddatetimekind.EXCLUSIVE { // implicit: target is inclusive
// convert from exclusive to inclusive
result = l.subtractGermanDay(result)
}
}
if configuration.Target.StripTime {
result = l.StripTime(result)
}
return result.UTC(), nil
}