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()) }