diff options
Diffstat (limited to 'cisa/date.go')
-rw-r--r-- | cisa/date.go | 139 |
1 files changed, 139 insertions, 0 deletions
diff --git a/cisa/date.go b/cisa/date.go new file mode 100644 index 0000000..b36b73c --- /dev/null +++ b/cisa/date.go @@ -0,0 +1,139 @@ +package cisa + +import ( + "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("%d-%d-%d", y, m, d) +} |