aboutsummaryrefslogtreecommitdiff
path: root/cisa/date.go
diff options
context:
space:
mode:
authorPaul Duncan <pabs@pablotron.org>2022-03-13 00:09:46 -0500
committerPaul Duncan <pabs@pablotron.org>2022-03-13 00:09:46 -0500
commit8ffd821fd1d5e227f15ca3e6a0f428cfdbd45398 (patch)
treea6b97c8cb76835abe9688c6c99033375e1fd41c5 /cisa/date.go
parente3de4db85b0b537194cbfff4f469cb89e8e258b1 (diff)
downloadcvez-8ffd821fd1d5e227f15ca3e6a0f428cfdbd45398.tar.bz2
cvez-8ffd821fd1d5e227f15ca3e6a0f428cfdbd45398.zip
add cisa/date.go and cisa/date_test.go
Diffstat (limited to 'cisa/date.go')
-rw-r--r--cisa/date.go139
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)
+}