From a00d4d0fd4c7e9b4e5ecf97e3bb978f680541dba Mon Sep 17 00:00:00 2001 From: Paul Duncan Date: Tue, 15 Mar 2022 00:17:07 -0400 Subject: move cisa/date.go to rfc3339/date.go --- cisa/catalog.go | 5 +- cisa/catalog_test.go | 3 +- cisa/date.go | 164 ---------------------- cisa/date_test.go | 374 --------------------------------------------------- rfc3339/date.go | 164 ++++++++++++++++++++++ rfc3339/date_test.go | 374 +++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 543 insertions(+), 541 deletions(-) delete mode 100644 cisa/date.go delete mode 100644 cisa/date_test.go create mode 100644 rfc3339/date.go create mode 100644 rfc3339/date_test.go diff --git a/cisa/catalog.go b/cisa/catalog.go index 38116f6..4e9cc1a 100644 --- a/cisa/catalog.go +++ b/cisa/catalog.go @@ -3,6 +3,7 @@ package cisa import ( "time" "github.com/pablotron/cvez/feed" + "github.com/pablotron/cvez/rfc3339" ) // Vulnerability entry. @@ -20,7 +21,7 @@ type Vulnerability struct { Name string `json:"vulnerabilityName"` // Date vulnerability was added to catalog. - DateAdded Date `json:"dateAdded"` + DateAdded rfc3339.Date `json:"dateAdded"` // Short description of vulnerability. ShortDescription string `json:"shortDescription"` @@ -29,7 +30,7 @@ type Vulnerability struct { RequiredAction string `json:"requiredAction"` // Date that required action is due. - DueDate Date `json:"dueDate"` + DueDate rfc3339.Date `json:"dueDate"` } // Known exploited vulnerabilities catalog. diff --git a/cisa/catalog_test.go b/cisa/catalog_test.go index b86d21c..1e4ba7e 100644 --- a/cisa/catalog_test.go +++ b/cisa/catalog_test.go @@ -4,6 +4,7 @@ import ( "compress/gzip" "encoding/json" "github.com/pablotron/cvez/feed" + "github.com/pablotron/cvez/rfc3339" "os" "reflect" "testing" @@ -13,7 +14,7 @@ import ( // catalog test data type catalogTestData struct { CveIds map[string]feed.CveId `json:"cves"` - Dates map[string]Date `json:"dates"` + Dates map[string]rfc3339.Date `json:"dates"` Times map[string]time.Time `json:"times"` } diff --git a/cisa/date.go b/cisa/date.go deleted file mode 100644 index b3b6528..0000000 --- a/cisa/date.go +++ /dev/null @@ -1,164 +0,0 @@ -package cisa - -import ( - "encoding/json" - "fmt" - "regexp" - "strconv" -) - -// YYYY-MM-DD -type Date uint16 - -// Returns true if the year is a leap year, and false otherwise. -// -// Reference: -// https://www.timeanddate.com/date/leapyear.html -func isLeapYear(y uint16) bool { - // leap year rules: - // 1. year is evenly divisible by 4 and is not evenly divisible by 100 - // 2. year is evenly divisible by 4 and is evenly divisible by 400 - return (((y % 4) == 0) && ((y % 100) != 0)) || // rule 1 - (((y % 4) == 0) && ((y % 400) == 0)) // rule 2 -} - -// maximum month days -var maxMonthDays = []uint16 { - 31, // jan - 28, // feb (incorrect for leap years!) - 31, // mar - 30, // apr - 31, // may - 30, // jun - 31, // jul - 31, // aug - 30, // sep - 31, // oct - 30, // nov - 31, // dec -} - -// Get the maximum day for the given year and month. -func getMaxMonthDay(y, m uint16) uint16 { - if m == 2 && isLeapYear(y) { - return 29 - } else { - return maxMonthDays[m - 1] - } -} - -// Parse date component and check range. -func parseDateComponent(name string, s []byte, min, max uint16) (uint16, error) { - // parse value - vr, err := strconv.ParseUint(string(s), 10, 32) - if err != nil { - return 0, err - } - - v := uint16(vr) - - // check range - if v < min || v > max { - return 0, fmt.Errorf("%s component out of range [%d, %d]: %d", name, v, min, max) - } - - return uint16(v), nil -} - -var dateRe = regexp.MustCompile(`^\d{4}-\d{2}-\d{2}$`) - -// Create date from byte slice. -func NewDate(s []byte) (Date, error) { - var r Date - - if !dateRe.Match(s) { - return r, fmt.Errorf("invalid date: \"%s\"", s) - } - - // parse year - y, err := parseDateComponent("year", s[0:4], 1999, 2126) - if err != nil { - return r, err - } - - // parse month - m, err := parseDateComponent("month", s[5:7], 1, 12) - if err != nil { - return r, err - } - - // parse day - d, err := parseDateComponent("month", s[8:10], 1, getMaxMonthDay(y, m)) - if err != nil { - return r, err - } - - // encode return date - r = Date((((y - 1999) & 0x3f) << 9) | - (((m - 1) & 0xf) << 5) | - ((d - 1) & 0x1f)) - - // return result - return r, nil -} - -// Get year, month, and day components. -func (me Date) GetComponents() (uint16, uint16, uint16) { - // extract components - y := uint16((uint16(me) >> 9) + 1999) - m := uint16(((uint16(me) >> 5) & 0xf) + 1) - d := uint16((uint16(me) & 0x1f) + 1) - - return y, m, d -} - -// Get year component. -func (me Date) Year() uint16 { - y, _, _ := me.GetComponents() - return y -} - -// Get month component. -func (me Date) Month() uint16 { - _, m, _ := me.GetComponents() - return m -} - -// Get day component. -func (me Date) Day() uint16 { - _, _, d := me.GetComponents() - return d -} - -// Convert to string. -func (me Date) String() string { - // extract date components - y, m, d := me.GetComponents() - - // return string - return fmt.Sprintf("%04d-%02d-%02d", y, m, d) -} - -// Unmarshal date from JSON string. -func (me *Date) UnmarshalJSON(b []byte) error { - // decode json string - var s string - if err := json.Unmarshal(b, &s); err != nil { - return err - } - - // create date - d, err := NewDate([]byte(s)) - if err != nil { - return err - } - - // save result, return success - *me = d - return nil -} - -// Marshal Date as JSON string. -func (d Date) MarshalJSON() ([]byte, error) { - return json.Marshal(d.String()) -} diff --git a/cisa/date_test.go b/cisa/date_test.go deleted file mode 100644 index 47e152e..0000000 --- a/cisa/date_test.go +++ /dev/null @@ -1,374 +0,0 @@ -package cisa - -import ( - "encoding/json" - "fmt" - "reflect" - "testing" -) - -func TestIsLeapYear(t *testing.T) { - tests := []struct { - val uint16 - exp bool - } { - { 1800, false }, - { 1900, false }, - { 1996, true }, - { 1997, false }, - { 1998, false }, - { 1999, false }, - { 2000, true }, - { 2001, false }, - { 2002, false }, - { 2003, false }, - { 2004, true }, - { 2005, false }, - { 2006, false }, - { 2007, false }, - { 2008, true }, - { 2009, false }, - { 2010, false }, - { 2011, false }, - { 2012, true }, - { 2013, false }, - { 2014, false }, - { 2015, false }, - { 2016, true }, - { 2017, false }, - { 2018, false }, - { 2019, false }, - { 2020, true }, - { 2021, false }, - { 2022, false }, - { 2023, false }, - { 2024, true }, - { 2100, false }, - { 2400, true }, - } - - for _, test := range(tests) { - t.Run(fmt.Sprintf("%d", test.val), func(t *testing.T) { - got := isLeapYear(test.val) - if got != test.exp { - t.Errorf("got %v, exp %v", got, test.exp) - } - }) - } -} - -func TestGetMaxMonthDay(t *testing.T) { - tests := []struct { - y uint16 - m uint16 - exp uint16 - } { - { 2000, 2, 29 }, // test leap year - { 2001, 2, 28 }, // test non-leap year - { 2022, 1, 31 }, // test full non-leap year - { 2022, 2, 28 }, - { 2022, 3, 31 }, - { 2022, 4, 30 }, - { 2022, 5, 31 }, - { 2022, 6, 30 }, - { 2022, 7, 31 }, - { 2022, 8, 31 }, - { 2022, 9, 30 }, - { 2022, 10, 31 }, - { 2022, 11, 30 }, - { 2022, 12, 31 }, - } - - for _, test := range(tests) { - t.Run(fmt.Sprintf("%04d-%02d", test.y, test.m), func(t *testing.T) { - got := getMaxMonthDay(test.y, test.m) - if got != test.exp { - t.Errorf("got %v, exp %v", got, test.exp) - } - }) - } -} - -func TestParseDateComponent(t *testing.T) { - tests := []struct { - name string - val string - min uint16 - max uint16 - exp bool - } { - { "bad-uint", "abcd", 2000, 2001, false }, - { "y-pass", "2000", 2000, 2001, true }, - { "y-fail-lo", "1999", 2000, 2001, false }, - { "y-fail-hi", "2002", 2000, 2001, false }, - { "m-pass-1", "1", 1, 12, true }, - { "m-pass-2", "2", 1, 12, true }, - { "m-pass-3", "3", 1, 12, true }, - { "m-pass-4", "4", 1, 12, true }, - { "m-pass-5", "5", 1, 12, true }, - { "m-pass-6", "6", 1, 12, true }, - { "m-pass-7", "7", 1, 12, true }, - { "m-pass-8", "8", 1, 12, true }, - { "m-pass-9", "9", 1, 12, true }, - { "m-pass-10", "10", 1, 12, true }, - { "m-pass-11", "11", 1, 12, true }, - { "m-pass-12", "12", 1, 12, true }, - { "m-fail-lo", "0", 1, 12, false }, - { "m-fail-hi", "13", 1, 12, false }, - { "d-pass-1", "1", 1, 31, true }, - { "d-pass-10", "10", 1, 31, true }, - { "d-pass-30", "10", 1, 31, true }, - { "d-fail-lo", "0", 1, 31, false }, - { "d-fail-hi", "32", 1, 31, false }, - } - - for _, test := range(tests) { - t.Run(test.name, func(t *testing.T) { - got, err := parseDateComponent("a", []byte(test.val), test.min, test.max) - if err != nil && test.exp == true { - t.Error(err) - } else if err == nil && test.exp == false { - t.Errorf("got %v, exp error", got) - } - }) - } -} - -func TestNewDate(t *testing.T) { - tests := []struct { - name string - val string - exp bool - } { - { "pass", "2000-01-03", true }, - { "pass-lo", "1999-01-01", true }, - { "pass-hi", "2126-12-31", true }, - { "fail", "asdf", false }, - { "fail-y-lo", "1998-01-03", false }, - { "fail-y-hi", "2128-01-03", false }, - { "fail-m-lo", "2126-00-03", false }, - { "fail-m-hi", "2126-13-03", false }, - { "fail-d-lo", "2126-01-00", false }, - { "fail-d-hi", "2126-01-32", false }, - { "fail-d-hi", "2126-02-29", false }, - { "fail-d-hi", "2126-03-32", false }, - { "fail-d-hi", "2126-04-31", false }, - { "fail-d-hi", "2126-05-32", false }, - { "fail-d-hi", "2126-06-31", false }, - } - - for _, test := range(tests) { - t.Run(test.name, func(t *testing.T) { - if got, err := NewDate([]byte(test.val)); err != nil && test.exp { - t.Error(err) - } else if err == nil && !test.exp { - t.Errorf("got %v, exp error", got) - } - }) - } -} - -func TestGetComponents(t *testing.T) { - type date struct { - y uint16 - m uint16 - d uint16 - } - - tests := []struct { - val string - exp date - } { - { "2022-10-31", date { 2022, 10, 31 } }, - } - - for _, test := range(tests) { - t.Run(test.val, func(t *testing.T) { - // create date - dt, err := NewDate([]byte(test.val)) - if err != nil { - t.Error(err) - return - } - - // get components - y, m, d := dt.GetComponents() - got := date { y, m, d } - - // check components - if !reflect.DeepEqual(got, test.exp) { - t.Errorf("got %v, exp %v", got, test.exp) - } - }) - } -} - -func TestYear(t *testing.T) { - tests := []struct { - val string - exp uint16 - } { - { "2022-10-31", 2022 }, - } - - for _, test := range(tests) { - t.Run(test.val, func(t *testing.T) { - // create date - dt, err := NewDate([]byte(test.val)) - if err != nil { - t.Error(err) - return - } - - // get year - got := dt.Year() - - // check components - if got != test.exp { - t.Errorf("got year %d, exp %d", got, test.exp) - } - }) - } -} - -func TestMonth(t *testing.T) { - tests := []struct { - val string - exp uint16 - } { - { "2022-10-31", 10 }, - } - - for _, test := range(tests) { - t.Run(test.val, func(t *testing.T) { - // create date - dt, err := NewDate([]byte(test.val)) - if err != nil { - t.Error(err) - return - } - - // get month - got := dt.Month() - - // check components - if got != test.exp { - t.Errorf("got month %d, exp %d", got, test.exp) - } - }) - } -} - -func TestDay(t *testing.T) { - tests := []struct { - val string - exp uint16 - } { - { "2022-10-31", 31 }, - } - - for _, test := range(tests) { - t.Run(test.val, func(t *testing.T) { - // create date - dt, err := NewDate([]byte(test.val)) - if err != nil { - t.Error(err) - return - } - - // get day - got := dt.Day() - - // check components - if got != test.exp { - t.Errorf("got day %d, exp %d", got, test.exp) - } - }) - } -} - -func TestString(t *testing.T) { - tests := []string { - "2022-10-31", - } - - for _, test := range(tests) { - t.Run(test, func(t *testing.T) { - // create date - dt, err := NewDate([]byte(test)) - if err != nil { - t.Error(err) - return - } - - // get/check string - got := dt.String() - if got != test { - t.Errorf("got %s, exp %s", got, test) - } - }) - } -} - -func TestDateUnmarshalJSON(t *testing.T) { - passTests := []struct { - val string - exp string - } { - { `"2022-02-03"`, "2022-02-03" }, - } - - for _, test := range(passTests) { - var got Date - if err := json.Unmarshal([]byte(test.val), &got); err != nil { - t.Error(err) - } else if got.String() != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", got.String(), test.exp) - } - } - - failTests := []struct { - name string - val string - } { - { "fail-str", "asdf" }, - { "fail-parse", "\"asdf\"" }, - } - - for _, test := range(failTests) { - t.Run(test.name, func(t *testing.T) { - var got Date - if err := got.UnmarshalJSON([]byte(test.val)); err == nil { - t.Errorf("got \"%v\" exp error", got) - } - }) - } - -} - -func TestDateMarshalJSON(t *testing.T) { - tests := []struct { - val string - exp string - } { - { "2022-10-31", `"2022-10-31"` }, - } - - for _, test := range(tests) { - t.Run(test.val, func(t *testing.T) { - // create date - dt, err := NewDate([]byte(test.val)) - if err != nil { - t.Error(err) - return - } - - // get/check string - if got, err := dt.MarshalJSON(); err != nil { - t.Error(err) - } else if string(got) != test.exp { - t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) - } - }) - } -} diff --git a/rfc3339/date.go b/rfc3339/date.go new file mode 100644 index 0000000..4f11d03 --- /dev/null +++ b/rfc3339/date.go @@ -0,0 +1,164 @@ +package rfc3339 + +import ( + "encoding/json" + "fmt" + "regexp" + "strconv" +) + +// RFC3339 date. +type Date uint16 + +// Returns true if the year is a leap year, and false otherwise. +// +// Reference: +// https://www.timeanddate.com/date/leapyear.html +func isLeapYear(y uint16) bool { + // leap year rules: + // 1. year is evenly divisible by 4 and is not evenly divisible by 100 + // 2. year is evenly divisible by 4 and is evenly divisible by 400 + return (((y % 4) == 0) && ((y % 100) != 0)) || // rule 1 + (((y % 4) == 0) && ((y % 400) == 0)) // rule 2 +} + +// maximum month days +var maxMonthDays = []uint16 { + 31, // jan + 28, // feb (incorrect for leap years!) + 31, // mar + 30, // apr + 31, // may + 30, // jun + 31, // jul + 31, // aug + 30, // sep + 31, // oct + 30, // nov + 31, // dec +} + +// Get the maximum day for the given year and month. +func getMaxMonthDay(y, m uint16) uint16 { + if m == 2 && isLeapYear(y) { + return 29 + } else { + return maxMonthDays[m - 1] + } +} + +// Parse date component and check range. +func parseDateComponent(name string, s []byte, min, max uint16) (uint16, error) { + // parse value + vr, err := strconv.ParseUint(string(s), 10, 32) + if err != nil { + return 0, err + } + + v := uint16(vr) + + // check range + if v < min || v > max { + return 0, fmt.Errorf("%s component out of range [%d, %d]: %d", name, v, min, max) + } + + return uint16(v), nil +} + +var dateRe = regexp.MustCompile(`^\d{4}-\d{2}-\d{2}$`) + +// Create date from byte slice. +func NewDate(s []byte) (Date, error) { + var r Date + + if !dateRe.Match(s) { + return r, fmt.Errorf("invalid date: \"%s\"", s) + } + + // parse year + y, err := parseDateComponent("year", s[0:4], 1999, 2126) + if err != nil { + return r, err + } + + // parse month + m, err := parseDateComponent("month", s[5:7], 1, 12) + if err != nil { + return r, err + } + + // parse day + d, err := parseDateComponent("month", s[8:10], 1, getMaxMonthDay(y, m)) + if err != nil { + return r, err + } + + // encode return date + r = Date((((y - 1999) & 0x3f) << 9) | + (((m - 1) & 0xf) << 5) | + ((d - 1) & 0x1f)) + + // return result + return r, nil +} + +// Get year, month, and day components. +func (me Date) GetComponents() (uint16, uint16, uint16) { + // extract components + y := uint16((uint16(me) >> 9) + 1999) + m := uint16(((uint16(me) >> 5) & 0xf) + 1) + d := uint16((uint16(me) & 0x1f) + 1) + + return y, m, d +} + +// Get year component. +func (me Date) Year() uint16 { + y, _, _ := me.GetComponents() + return y +} + +// Get month component. +func (me Date) Month() uint16 { + _, m, _ := me.GetComponents() + return m +} + +// Get day component. +func (me Date) Day() uint16 { + _, _, d := me.GetComponents() + return d +} + +// Convert to string. +func (me Date) String() string { + // extract date components + y, m, d := me.GetComponents() + + // return string + return fmt.Sprintf("%04d-%02d-%02d", y, m, d) +} + +// Unmarshal date from JSON string. +func (me *Date) UnmarshalJSON(b []byte) error { + // decode json string + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + + // create date + d, err := NewDate([]byte(s)) + if err != nil { + return err + } + + // save result, return success + *me = d + return nil +} + +// Marshal Date as JSON string. +func (d Date) MarshalJSON() ([]byte, error) { + return json.Marshal(d.String()) +} diff --git a/rfc3339/date_test.go b/rfc3339/date_test.go new file mode 100644 index 0000000..ab70377 --- /dev/null +++ b/rfc3339/date_test.go @@ -0,0 +1,374 @@ +package rfc3339 + +import ( + "encoding/json" + "fmt" + "reflect" + "testing" +) + +func TestIsLeapYear(t *testing.T) { + tests := []struct { + val uint16 + exp bool + } { + { 1800, false }, + { 1900, false }, + { 1996, true }, + { 1997, false }, + { 1998, false }, + { 1999, false }, + { 2000, true }, + { 2001, false }, + { 2002, false }, + { 2003, false }, + { 2004, true }, + { 2005, false }, + { 2006, false }, + { 2007, false }, + { 2008, true }, + { 2009, false }, + { 2010, false }, + { 2011, false }, + { 2012, true }, + { 2013, false }, + { 2014, false }, + { 2015, false }, + { 2016, true }, + { 2017, false }, + { 2018, false }, + { 2019, false }, + { 2020, true }, + { 2021, false }, + { 2022, false }, + { 2023, false }, + { 2024, true }, + { 2100, false }, + { 2400, true }, + } + + for _, test := range(tests) { + t.Run(fmt.Sprintf("%d", test.val), func(t *testing.T) { + got := isLeapYear(test.val) + if got != test.exp { + t.Errorf("got %v, exp %v", got, test.exp) + } + }) + } +} + +func TestGetMaxMonthDay(t *testing.T) { + tests := []struct { + y uint16 + m uint16 + exp uint16 + } { + { 2000, 2, 29 }, // test leap year + { 2001, 2, 28 }, // test non-leap year + { 2022, 1, 31 }, // test full non-leap year + { 2022, 2, 28 }, + { 2022, 3, 31 }, + { 2022, 4, 30 }, + { 2022, 5, 31 }, + { 2022, 6, 30 }, + { 2022, 7, 31 }, + { 2022, 8, 31 }, + { 2022, 9, 30 }, + { 2022, 10, 31 }, + { 2022, 11, 30 }, + { 2022, 12, 31 }, + } + + for _, test := range(tests) { + t.Run(fmt.Sprintf("%04d-%02d", test.y, test.m), func(t *testing.T) { + got := getMaxMonthDay(test.y, test.m) + if got != test.exp { + t.Errorf("got %v, exp %v", got, test.exp) + } + }) + } +} + +func TestParseDateComponent(t *testing.T) { + tests := []struct { + name string + val string + min uint16 + max uint16 + exp bool + } { + { "bad-uint", "abcd", 2000, 2001, false }, + { "y-pass", "2000", 2000, 2001, true }, + { "y-fail-lo", "1999", 2000, 2001, false }, + { "y-fail-hi", "2002", 2000, 2001, false }, + { "m-pass-1", "1", 1, 12, true }, + { "m-pass-2", "2", 1, 12, true }, + { "m-pass-3", "3", 1, 12, true }, + { "m-pass-4", "4", 1, 12, true }, + { "m-pass-5", "5", 1, 12, true }, + { "m-pass-6", "6", 1, 12, true }, + { "m-pass-7", "7", 1, 12, true }, + { "m-pass-8", "8", 1, 12, true }, + { "m-pass-9", "9", 1, 12, true }, + { "m-pass-10", "10", 1, 12, true }, + { "m-pass-11", "11", 1, 12, true }, + { "m-pass-12", "12", 1, 12, true }, + { "m-fail-lo", "0", 1, 12, false }, + { "m-fail-hi", "13", 1, 12, false }, + { "d-pass-1", "1", 1, 31, true }, + { "d-pass-10", "10", 1, 31, true }, + { "d-pass-30", "10", 1, 31, true }, + { "d-fail-lo", "0", 1, 31, false }, + { "d-fail-hi", "32", 1, 31, false }, + } + + for _, test := range(tests) { + t.Run(test.name, func(t *testing.T) { + got, err := parseDateComponent("a", []byte(test.val), test.min, test.max) + if err != nil && test.exp == true { + t.Error(err) + } else if err == nil && test.exp == false { + t.Errorf("got %v, exp error", got) + } + }) + } +} + +func TestNewDate(t *testing.T) { + tests := []struct { + name string + val string + exp bool + } { + { "pass", "2000-01-03", true }, + { "pass-lo", "1999-01-01", true }, + { "pass-hi", "2126-12-31", true }, + { "fail", "asdf", false }, + { "fail-y-lo", "1998-01-03", false }, + { "fail-y-hi", "2128-01-03", false }, + { "fail-m-lo", "2126-00-03", false }, + { "fail-m-hi", "2126-13-03", false }, + { "fail-d-lo", "2126-01-00", false }, + { "fail-d-hi", "2126-01-32", false }, + { "fail-d-hi", "2126-02-29", false }, + { "fail-d-hi", "2126-03-32", false }, + { "fail-d-hi", "2126-04-31", false }, + { "fail-d-hi", "2126-05-32", false }, + { "fail-d-hi", "2126-06-31", false }, + } + + for _, test := range(tests) { + t.Run(test.name, func(t *testing.T) { + if got, err := NewDate([]byte(test.val)); err != nil && test.exp { + t.Error(err) + } else if err == nil && !test.exp { + t.Errorf("got %v, exp error", got) + } + }) + } +} + +func TestGetComponents(t *testing.T) { + type date struct { + y uint16 + m uint16 + d uint16 + } + + tests := []struct { + val string + exp date + } { + { "2022-10-31", date { 2022, 10, 31 } }, + } + + for _, test := range(tests) { + t.Run(test.val, func(t *testing.T) { + // create date + dt, err := NewDate([]byte(test.val)) + if err != nil { + t.Error(err) + return + } + + // get components + y, m, d := dt.GetComponents() + got := date { y, m, d } + + // check components + if !reflect.DeepEqual(got, test.exp) { + t.Errorf("got %v, exp %v", got, test.exp) + } + }) + } +} + +func TestYear(t *testing.T) { + tests := []struct { + val string + exp uint16 + } { + { "2022-10-31", 2022 }, + } + + for _, test := range(tests) { + t.Run(test.val, func(t *testing.T) { + // create date + dt, err := NewDate([]byte(test.val)) + if err != nil { + t.Error(err) + return + } + + // get year + got := dt.Year() + + // check components + if got != test.exp { + t.Errorf("got year %d, exp %d", got, test.exp) + } + }) + } +} + +func TestMonth(t *testing.T) { + tests := []struct { + val string + exp uint16 + } { + { "2022-10-31", 10 }, + } + + for _, test := range(tests) { + t.Run(test.val, func(t *testing.T) { + // create date + dt, err := NewDate([]byte(test.val)) + if err != nil { + t.Error(err) + return + } + + // get month + got := dt.Month() + + // check components + if got != test.exp { + t.Errorf("got month %d, exp %d", got, test.exp) + } + }) + } +} + +func TestDay(t *testing.T) { + tests := []struct { + val string + exp uint16 + } { + { "2022-10-31", 31 }, + } + + for _, test := range(tests) { + t.Run(test.val, func(t *testing.T) { + // create date + dt, err := NewDate([]byte(test.val)) + if err != nil { + t.Error(err) + return + } + + // get day + got := dt.Day() + + // check components + if got != test.exp { + t.Errorf("got day %d, exp %d", got, test.exp) + } + }) + } +} + +func TestString(t *testing.T) { + tests := []string { + "2022-10-31", + } + + for _, test := range(tests) { + t.Run(test, func(t *testing.T) { + // create date + dt, err := NewDate([]byte(test)) + if err != nil { + t.Error(err) + return + } + + // get/check string + got := dt.String() + if got != test { + t.Errorf("got %s, exp %s", got, test) + } + }) + } +} + +func TestDateUnmarshalJSON(t *testing.T) { + passTests := []struct { + val string + exp string + } { + { `"2022-02-03"`, "2022-02-03" }, + } + + for _, test := range(passTests) { + var got Date + if err := json.Unmarshal([]byte(test.val), &got); err != nil { + t.Error(err) + } else if got.String() != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", got.String(), test.exp) + } + } + + failTests := []struct { + name string + val string + } { + { "fail-str", "asdf" }, + { "fail-parse", "\"asdf\"" }, + } + + for _, test := range(failTests) { + t.Run(test.name, func(t *testing.T) { + var got Date + if err := got.UnmarshalJSON([]byte(test.val)); err == nil { + t.Errorf("got \"%v\" exp error", got) + } + }) + } + +} + +func TestDateMarshalJSON(t *testing.T) { + tests := []struct { + val string + exp string + } { + { "2022-10-31", `"2022-10-31"` }, + } + + for _, test := range(tests) { + t.Run(test.val, func(t *testing.T) { + // create date + dt, err := NewDate([]byte(test.val)) + if err != nil { + t.Error(err) + return + } + + // get/check string + if got, err := dt.MarshalJSON(); err != nil { + t.Error(err) + } else if string(got) != test.exp { + t.Errorf("got \"%s\", exp \"%s\"", got, test.exp) + } + }) + } +} -- cgit v1.2.3