Add JSON marshalling and unmarshalling support for time intervals

Signed-off-by: Ben Ridley <benridley29@gmail.com>
This commit is contained in:
Ben Ridley 2021-01-24 20:37:46 +11:00
parent d9d7511ad0
commit d0217a80b0
2 changed files with 103 additions and 15 deletions

View File

@ -14,22 +14,25 @@
package timeinterval
import (
"encoding/json"
"errors"
"fmt"
"regexp"
"strconv"
"strings"
"time"
"gopkg.in/yaml.v2"
)
// TimeInterval describes intervals of time. ContainsTime will tell you if a golang time is contained
// within the interval.
type TimeInterval struct {
Times []TimeRange `yaml:"times,omitempty"`
Weekdays []WeekdayRange `yaml:"weekdays,flow,omitempty"`
DaysOfMonth []DayOfMonthRange `yaml:"days_of_month,flow,omitempty"`
Months []MonthRange `yaml:"months,flow,omitempty"`
Years []YearRange `yaml:"years,flow,omitempty"`
Times []TimeRange `yaml:"times,omitempty" json:"times,omitempty"`
Weekdays []WeekdayRange `yaml:"weekdays,flow,omitempty" json:"weekdays,omitempty"`
DaysOfMonth []DayOfMonthRange `yaml:"days_of_month,flow,omitempty" json:"days_of_month,omitempty"`
Months []MonthRange `yaml:"months,flow,omitempty" json:"months,omitempty"`
Years []YearRange `yaml:"years,flow,omitempty" json:"years,omitempty"`
}
// TimeRange represents a range of minutes within a 1440 minute day, exclusive of the End minute. A day consists of 1440 minutes.
@ -66,8 +69,8 @@ type YearRange struct {
}
type yamlTimeRange struct {
StartTime string `yaml:"start_time"`
EndTime string `yaml:"end_time"`
StartTime string `yaml:"start_time" json:"start_time"`
EndTime string `yaml:"end_time" json:"end_time"`
}
// A range with a Beginning and End that can be represented as strings.
@ -183,6 +186,12 @@ func (r *WeekdayRange) UnmarshalYAML(unmarshal func(interface{}) error) error {
return nil
}
// UnmarshalJSON implements the json.Unmarshaler interface for WeekdayRange.
// It delegates to the YAML unmarshaller as it can parse JSON and has validation logic.
func (r *WeekdayRange) UnmarshalJSON(in []byte) error {
return yaml.Unmarshal(in, r)
}
// UnmarshalYAML implements the Unmarshaller interface for DayOfMonthRange.
func (r *DayOfMonthRange) UnmarshalYAML(unmarshal func(interface{}) error) error {
var str string
@ -220,6 +229,12 @@ func (r *DayOfMonthRange) UnmarshalYAML(unmarshal func(interface{}) error) error
return nil
}
// UnmarshalJSON implements the json.Unmarshaler interface for DayOfMonthRange.
// It delegates to the YAML unmarshaller as it can parse JSON and has validation logic.
func (r *DayOfMonthRange) UnmarshalJSON(in []byte) error {
return yaml.Unmarshal(in, r)
}
// UnmarshalYAML implements the Unmarshaller interface for MonthRange.
func (r *MonthRange) UnmarshalYAML(unmarshal func(interface{}) error) error {
var str string
@ -237,6 +252,12 @@ func (r *MonthRange) UnmarshalYAML(unmarshal func(interface{}) error) error {
return nil
}
// UnmarshalJSON implements the json.Unmarshaler interface for MonthRange.
// It delegates to the YAML unmarshaller as it can parse JSON and has validation logic.
func (r *MonthRange) UnmarshalJSON(in []byte) error {
return yaml.Unmarshal(in, r)
}
// UnmarshalYAML implements the Unmarshaller interface for YearRange.
func (r *YearRange) UnmarshalYAML(unmarshal func(interface{}) error) error {
var str string
@ -252,6 +273,12 @@ func (r *YearRange) UnmarshalYAML(unmarshal func(interface{}) error) error {
return nil
}
// UnmarshalJSON implements the json.Unmarshaler interface for YearRange.
// It delegates to the YAML unmarshaller as it can parse JSON and has validation logic.
func (r *YearRange) UnmarshalJSON(in []byte) error {
return yaml.Unmarshal(in, r)
}
// UnmarshalYAML implements the Unmarshaller interface for TimeRanges.
func (tr *TimeRange) UnmarshalYAML(unmarshal func(interface{}) error) error {
var y yamlTimeRange
@ -276,24 +303,38 @@ func (tr *TimeRange) UnmarshalYAML(unmarshal func(interface{}) error) error {
return nil
}
// UnmarshalJSON implements the json.Unmarshaler interface for Timerange.
// It delegates to the YAML unmarshaller as it can parse JSON and has validation logic.
func (tr *TimeRange) UnmarshalJSON(in []byte) error {
return yaml.Unmarshal(in, tr)
}
// MarshalYAML implements the yaml.Marshaler interface for WeekdayRange.
func (r WeekdayRange) MarshalYAML() (interface{}, error) {
bytes, err := r.MarshalText()
return string(bytes), err
}
// MarshalText implements the econding.TextMarshaler interface for WeekdayRange.
// It converts the range into a colon-seperated string, or a single weekday if possible.
// e.g. "monday:friday" or "saturday".
func (r WeekdayRange) MarshalText() ([]byte, error) {
beginStr, ok := daysOfWeekInv[r.Begin]
if !ok {
return nil, fmt.Errorf("unable to convert %d into weekday string", r.Begin)
}
if r.Begin == r.End {
return interface{}(beginStr), nil
return []byte(beginStr), nil
}
endStr, ok := daysOfWeekInv[r.End]
if !ok {
return nil, fmt.Errorf("unable to convert %d into weekday string", r.End)
}
rangeStr := fmt.Sprintf("%s:%s", beginStr, endStr)
return interface{}(rangeStr), nil
return []byte(rangeStr), nil
}
//MarshalYAML implements the yaml.Marshaler interface for TimeRange.
// MarshalYAML implements the yaml.Marshaler interface for TimeRange.
func (tr TimeRange) MarshalYAML() (out interface{}, err error) {
startHr := tr.StartMinute / 60
endHr := tr.EndMinute / 60
@ -307,13 +348,35 @@ func (tr TimeRange) MarshalYAML() (out interface{}, err error) {
return interface{}(yTr), err
}
//MarshalYAML implements the yaml.Marshaler interface for InclusiveRange.
func (ir InclusiveRange) MarshalYAML() (interface{}, error) {
// MarshalJSON implements the json.Marshaler interface for TimeRange.
func (tr TimeRange) MarshalJSON() (out []byte, err error) {
startHr := tr.StartMinute / 60
endHr := tr.EndMinute / 60
startMin := tr.StartMinute % 60
endMin := tr.EndMinute % 60
startStr := fmt.Sprintf("%02d:%02d", startHr, startMin)
endStr := fmt.Sprintf("%02d:%02d", endHr, endMin)
yTr := yamlTimeRange{startStr, endStr}
return json.Marshal(yTr)
}
// MarshalText implements the encoding.TextMarshaler interface for InclusiveRange.
// It converts the struct into a colon-separated string, or a single element if
// appropriate. e.g. "monday:friday" or "monday"
func (ir InclusiveRange) MarshalText() ([]byte, error) {
if ir.Begin == ir.End {
return strconv.Itoa(ir.Begin), nil
return []byte(strconv.Itoa(ir.Begin)), nil
}
out := fmt.Sprintf("%d:%d", ir.Begin, ir.End)
return interface{}(out), nil
return []byte(out), nil
}
//MarshalYAML implements the yaml.Marshaler interface for InclusiveRange.
func (ir InclusiveRange) MarshalYAML() (interface{}, error) {
bytes, err := ir.MarshalText()
return string(bytes), err
}
// TimeLayout specifies the layout to be used in time.Parse() calls for time intervals.

View File

@ -14,6 +14,7 @@
package timeinterval
import (
"encoding/json"
"reflect"
"testing"
"time"
@ -507,7 +508,31 @@ func TestYamlMarshal(t *testing.T) {
var ti2 []TimeInterval
yaml.Unmarshal(out, &ti2)
if !reflect.DeepEqual(ti, ti2) {
t.Errorf("Re-marshalling %s produced a different TimeInterval", tc.in)
t.Errorf("Re-marshalling %s produced a different TimeInterval.", tc.in)
}
}
}
// Test JSON marshalling by marshalling a time interval
// and then unmarshalling to ensure they're identical
func TestJsonMarshal(t *testing.T) {
for _, tc := range yamlUnmarshalTestCases {
if tc.expectError {
continue
}
var ti []TimeInterval
err := yaml.Unmarshal([]byte(tc.in), &ti)
if err != nil {
t.Error(err)
}
out, err := json.Marshal(&ti)
if err != nil {
t.Error(err)
}
var ti2 []TimeInterval
json.Unmarshal(out, &ti2)
if !reflect.DeepEqual(ti, ti2) {
t.Errorf("Re-marshalling %s produced a different TimeInterval. Used:\n%s and got:\n%v", tc.in, out, ti2)
}
}
}