diff --git a/common/date.go b/common/date.go new file mode 100644 index 00000000..05f207f7 --- /dev/null +++ b/common/date.go @@ -0,0 +1,67 @@ +package common + +import ( + "fmt" + "strconv" + "strings" + "time" + + "github.com/pkg/errors" +) + +// ParseDate parses a date string of the format YYYY-MM-DD +func ParseDate(date string) (t time.Time, err error) { + + //get the time of invoice + str := strings.Split(date, "-") + var ymd = []int{} + for _, i := range str { + j, err := strconv.Atoi(i) + if err != nil { + return t, err + } + ymd = append(ymd, j) + } + if len(ymd) != 3 { + return t, fmt.Errorf("Bad date parsing, not 3 segments") //never stack trace + } + if ymd[1] < 1 || ymd[1] > 12 { + return t, fmt.Errorf("Month not between 1 and 12") //never stack trace + } + if ymd[2] > 31 { + return t, fmt.Errorf("Day over 31") //never stack trace + } + + t = time.Date(ymd[0], time.Month(ymd[1]), ymd[2], 0, 0, 0, 0, time.UTC) + + return t, nil +} + +// ParseDateRange parses a date range string of the format start:end +// where the start and end date are of the format YYYY-MM-DD. +// The parsed dates are *time.Time and will return nil pointers for +// unbounded dates, ex: +// unbounded start: :2000-12-31 +// unbounded end: 2000-12-31: +func ParseDateRange(dateRange string) (startDate, endDate *time.Time, err error) { + dates := strings.Split(dateRange, ":") + if len(dates) != 2 { + return nil, nil, errors.New("bad date range, must be in format date:date") + } + parseDate := func(date string) (*time.Time, error) { + if len(date) == 0 { + return nil, nil + } + d, err := ParseDate(date) + return &d, err + } + startDate, err = parseDate(dates[0]) + if err != nil { + return nil, nil, err + } + endDate, err = parseDate(dates[1]) + if err != nil { + return nil, nil, err + } + return +} diff --git a/common/date_test.go b/common/date_test.go new file mode 100644 index 00000000..42fd91aa --- /dev/null +++ b/common/date_test.go @@ -0,0 +1,76 @@ +package common + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +var ( + date = time.Date(2015, time.Month(12), 31, 0, 0, 0, 0, time.UTC) + date2 = time.Date(2016, time.Month(12), 31, 0, 0, 0, 0, time.UTC) +) + +func TestParseDate(t *testing.T) { + assert := assert.New(t) + + var testDates = []struct { + dateStr string + date time.Time + errNil bool + }{ + {"2015-12-31", date, true}, + {"2015-31-12", date, false}, + {"12-31-2015", date, false}, + {"31-12-2015", date, false}, + } + + for _, test := range testDates { + parsed, err := ParseDate(test.dateStr) + switch test.errNil { + case true: + assert.Nil(err) + assert.True(parsed.Equal(test.date), "parsed: %v, want %v", parsed, test.date) + case false: + assert.NotNil(err, "parsed %v, expected err %v", parsed, err) + } + } +} + +func TestParseDateRange(t *testing.T) { + assert := assert.New(t) + + var testDates = []struct { + dateStr string + start *time.Time + end *time.Time + errNil bool + }{ + {"2015-12-31:2016-12-31", &date, &date2, true}, + {"2015-12-31:", &date, nil, true}, + {":2016-12-31", nil, &date2, true}, + {"2016-12-31", nil, nil, false}, + {"2016-31-12:", nil, nil, false}, + {":2016-31-12", nil, nil, false}, + } + + for _, test := range testDates { + start, end, err := ParseDateRange(test.dateStr) + switch test.errNil { + case true: + assert.Nil(err) + testPtr := func(want, have *time.Time) { + if want == nil { + assert.Nil(have) + } else { + assert.True((*have).Equal(*want)) + } + } + testPtr(test.start, start) + testPtr(test.end, end) + case false: + assert.NotNil(err) + } + } +}