From 52073264333281e4c0dbccc332672aa9c1396907 Mon Sep 17 00:00:00 2001 From: Jan Galek Date: Sat, 1 Feb 2025 07:35:12 +0100 Subject: [PATCH 1/5] [Add] DateRange --- HistoricDate.go | 12 ++++++++ dateRange.go | 74 +++++++++++++++++++++++++++++++++++++++++++++++++ datetime.go | 10 +++++-- range.go | 42 ++++++++++++++++++++++++---- 4 files changed, 130 insertions(+), 8 deletions(-) diff --git a/HistoricDate.go b/HistoricDate.go index c216d01..b8f7e72 100644 --- a/HistoricDate.go +++ b/HistoricDate.go @@ -1 +1,13 @@ package datetime + +type Historic string + +var ( + BeforeChrist Historic = "bc" + AfterChrist Historic = "ac" +) + +type HistoricDate struct { + historic Historic + date Date +} diff --git a/dateRange.go b/dateRange.go index c216d01..597894b 100644 --- a/dateRange.go +++ b/dateRange.go @@ -1 +1,75 @@ package datetime + +import ( + "errors" + "fmt" + "github.com/gouef/validator" + "github.com/gouef/validator/constraints" + "regexp" + "time" +) + +const ( + dateRangeRegexp = `^([\[\(])(\d{4}-\d{2}-\d{2})?,(\d{4}-\d{2}-\d{2})?([\]\)])$` +) + +type DateRange struct { + from string + to string + start RangeStart + end RangeEnd +} + +func NewDateRange(from, to string, start RangeStart, end RangeEnd) *DateRange { + return &DateRange{ + from: from, + to: to, + start: start, + end: end, + } +} + +func (d *DateRange) String() string { + return fmt.Sprintf("%s%s,%s%s", d.start, d.from, d.to, d.end) +} + +func (d *DateRange) Is(value any) bool { + date, err := d.format(value) + + if err != nil { + return false + } + + start, _ := DateFromString(string(d.start)) + end, _ := DateFromString(string(d.end)) + return date.Between(start, end) +} + +func DateRangeFromString(dateRange string) (*DateRange, error) { + errs := validator.Validate(dateRange, constraints.RegularExpression{Regexp: dateRangeRegexp}) + + if len(errs) != 0 { + return nil, errors.New(fmt.Sprintf("unsupported format of date range \"%s\"", dateRange)) + } + + re := regexp.MustCompile(dateRangeRegexp) + match := re.FindStringSubmatch(dateRange) + openBracket, date1, date2, closeBracket := match[1], match[2], match[3], match[4] + + return NewDateRange(date1, date2, RangeStart(openBracket), RangeEnd(closeBracket)), nil +} + +func (d *DateRange) format(date any) (*Date, error) { + switch i := date.(type) { + case time.Time: + return NewDate(i.Year(), int(i.Month()), i.Day()) + case *Date: + return i, nil + case Date: + return &i, nil + case string: + return DateFromString(i) + default: + return nil, errors.New("unsupported format of date") + } +} diff --git a/datetime.go b/datetime.go index b3ca6c1..dabe504 100644 --- a/datetime.go +++ b/datetime.go @@ -100,7 +100,7 @@ func DateFromString(value string) (*Date, error) { return NewDate(year, month, day) } -func GetDate(year int, month int, day int) time.Time { +func GetDate(year, month, day int) time.Time { return time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.UTC) } @@ -115,14 +115,18 @@ func (d *Date) Time() time.Time { // Compare compares the date instant d with u. If d is before u, it returns -1; // if d is after u, it returns +1; if they're the same, it returns 0. -func (d Date) Compare(u Date) int { +func (d *Date) Compare(u *Date) int { return d.Time().Compare(u.Time()) } -func (d Date) Equal(u Date) bool { +func (d *Date) Equal(u *Date) bool { return d.Time().Equal(u.Time()) } +func (d *Date) Between(start, end *Date) bool { + return d.Time().Before(end.Time()) && d.Time().After(start.Time()) +} + func DaysInMonthList(year int, month int) []int { days := make([]int, DaysInMonth(year, month)) diff --git a/range.go b/range.go index 70018ee..4722bf4 100644 --- a/range.go +++ b/range.go @@ -4,8 +4,40 @@ type RangeStart string type RangeEnd string var ( - RANGE_START_STRICT RangeStart = "[" - RANGE_START_OPTIONAL RangeStart = "(" - RANGE_END_STRICT RangeEnd = "]" - RANGE_END_OPTIONAL RangeEnd = ")" -) \ No newline at end of file + // RangeStartStrict can not be equal + RangeStartStrict RangeStart = "[" + // RangeStartOptional can be equal + RangeStartOptional RangeStart = "(" + // RangeEndStrict can not be equal + RangeEndStrict RangeEnd = "]" + // RangeEndOptional can be equal + RangeEndOptional RangeEnd = ")" +) + +type Range struct { + start RangeStart + end RangeEnd +} + +func NewRange(start RangeStart, end RangeEnd) *Range { + return &Range{ + start: start, + end: end, + } +} + +func NewRangeOptional() *Range { + return NewRange(RangeStartOptional, RangeEndOptional) +} + +func NewRangeStrict() *Range { + return NewRange(RangeStartStrict, RangeEndStrict) +} + +func NewRangeStartStrict() *Range { + return NewRange(RangeStartStrict, RangeEndOptional) +} + +func NewRangeStartOptional() *Range { + return NewRange(RangeStartOptional, RangeEndStrict) +} From 38627c2899814de45c9ca87b05b542642ef73d4f Mon Sep 17 00:00:00 2001 From: Jan Galek Date: Sat, 1 Feb 2025 07:38:33 +0100 Subject: [PATCH 2/5] fixup! [Add] DateRange --- tests/datetime_test.go | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/tests/datetime_test.go b/tests/datetime_test.go index c829ea3..afa7773 100644 --- a/tests/datetime_test.go +++ b/tests/datetime_test.go @@ -54,16 +54,16 @@ func TestIsWeekend(t *testing.T) { func TestCompare(t *testing.T) { tests := []struct { - date1 datetime.Date - date2 datetime.Date + date1 *datetime.Date + date2 *datetime.Date expected int }{ - {datetime.Date{Year: 2024, Month: 12, Day: 25, DateTime: time.Date(2024, 12, 25, 0, 0, 0, 0, time.UTC)}, - datetime.Date{Year: 2024, Month: 12, Day: 25, DateTime: time.Date(2024, 12, 25, 0, 0, 0, 0, time.UTC)}, 0}, - {datetime.Date{Year: 2024, Month: 12, Day: 25, DateTime: time.Date(2024, 12, 25, 0, 0, 0, 0, time.UTC)}, - datetime.Date{Year: 2024, Month: 12, Day: 26, DateTime: time.Date(2024, 12, 26, 0, 0, 0, 0, time.UTC)}, -1}, // 25th < 26th - {datetime.Date{Year: 2024, Month: 12, Day: 26, DateTime: time.Date(2024, 12, 26, 0, 0, 0, 0, time.UTC)}, - datetime.Date{Year: 2024, Month: 12, Day: 25, DateTime: time.Date(2024, 12, 25, 0, 0, 0, 0, time.UTC)}, 1}, // 26th > 25th + {&datetime.Date{Year: 2024, Month: 12, Day: 25, DateTime: time.Date(2024, 12, 25, 0, 0, 0, 0, time.UTC)}, + &datetime.Date{Year: 2024, Month: 12, Day: 25, DateTime: time.Date(2024, 12, 25, 0, 0, 0, 0, time.UTC)}, 0}, + {&datetime.Date{Year: 2024, Month: 12, Day: 25, DateTime: time.Date(2024, 12, 25, 0, 0, 0, 0, time.UTC)}, + &datetime.Date{Year: 2024, Month: 12, Day: 26, DateTime: time.Date(2024, 12, 26, 0, 0, 0, 0, time.UTC)}, -1}, // 25th < 26th + {&datetime.Date{Year: 2024, Month: 12, Day: 26, DateTime: time.Date(2024, 12, 26, 0, 0, 0, 0, time.UTC)}, + &datetime.Date{Year: 2024, Month: 12, Day: 25, DateTime: time.Date(2024, 12, 25, 0, 0, 0, 0, time.UTC)}, 1}, // 26th > 25th } for _, tt := range tests { @@ -75,32 +75,32 @@ func TestCompare(t *testing.T) { func TestDateEqual(t *testing.T) { tests := []struct { - date1 datetime.Date - date2 datetime.Date + date1 *datetime.Date + date2 *datetime.Date expected bool }{ // Test 1: Equal Date instances { - date1: datetime.Date{Year: 2024, Month: 3, Day: 31}, - date2: datetime.Date{Year: 2024, Month: 3, Day: 31}, + date1: &datetime.Date{Year: 2024, Month: 3, Day: 31}, + date2: &datetime.Date{Year: 2024, Month: 3, Day: 31}, expected: true, }, // Test 2: Different Date instances (different day) { - date1: datetime.Date{Year: 2024, Month: 3, Day: 31}, - date2: datetime.Date{Year: 2024, Month: 3, Day: 30}, + date1: &datetime.Date{Year: 2024, Month: 3, Day: 31}, + date2: &datetime.Date{Year: 2024, Month: 3, Day: 30}, expected: false, }, // Test 3: Different Date instances (different month) { - date1: datetime.Date{Year: 2024, Month: 3, Day: 31}, - date2: datetime.Date{Year: 2024, Month: 4, Day: 1}, + date1: &datetime.Date{Year: 2024, Month: 3, Day: 31}, + date2: &datetime.Date{Year: 2024, Month: 4, Day: 1}, expected: false, }, // Test 4: Different Date instances (different year) { - date1: datetime.Date{Year: 2024, Month: 3, Day: 31}, - date2: datetime.Date{Year: 2025, Month: 3, Day: 31}, + date1: &datetime.Date{Year: 2024, Month: 3, Day: 31}, + date2: &datetime.Date{Year: 2025, Month: 3, Day: 31}, expected: false, }, } From 8553b124a71ffb6447f86ebbb0573880cb2feadd Mon Sep 17 00:00:00 2001 From: Jan Galek Date: Sat, 1 Feb 2025 07:51:44 +0100 Subject: [PATCH 3/5] fixup! [Add] DateRange --- tests/datetime_test.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/datetime_test.go b/tests/datetime_test.go index afa7773..7eeee4d 100644 --- a/tests/datetime_test.go +++ b/tests/datetime_test.go @@ -149,6 +149,29 @@ func TestDaysInMonth(t *testing.T) { } } +func TestBetween(t *testing.T) { + date1, _ := datetime.NewDate(2025, 2, 1) + date2, _ := datetime.NewDate(2024, 2, 1) + date3, _ := datetime.NewDate(2026, 2, 1) + + tests := []struct { + date *datetime.Date + start *datetime.Date + end *datetime.Date + expected bool + }{ + {date1, date2, date3, true}, + {date2, date1, date3, false}, + {date3, date2, date3, false}, + } + + for _, tt := range tests { + t.Run("TestDaysInMonth", func(t *testing.T) { + assert.Equal(t, tt.expected, tt.date.Between(tt.start, tt.end)) + }) + } +} + func TestDaysInMonthByDate(t *testing.T) { tests := []struct { date time.Time From 30a071040ac8239834361d9d05a6a5fe2b2f4234 Mon Sep 17 00:00:00 2001 From: Jan Galek Date: Sun, 2 Feb 2025 19:24:32 +0100 Subject: [PATCH 4/5] [Refactor] date separate to date, time and datetime --- HistoricDate.go | 2 +- date.go | 89 ++++++++++++++++++++++++++++++++++++++++++ dateRange.go | 44 ++++++++++++--------- dateTimeValue.go | 30 ++++++++++++++ dateValue.go | 30 ++++++++++++++ datetime.go | 41 ++++++++++++------- tests/datetime_test.go | 60 +++++++++++++--------------- time.go | 83 +++++++++++++++++++++++++++++++++++++++ timeValue.go | 30 ++++++++++++++ 9 files changed, 343 insertions(+), 66 deletions(-) create mode 100644 date.go create mode 100644 dateTimeValue.go create mode 100644 dateValue.go create mode 100644 time.go create mode 100644 timeValue.go diff --git a/HistoricDate.go b/HistoricDate.go index b8f7e72..43becfa 100644 --- a/HistoricDate.go +++ b/HistoricDate.go @@ -9,5 +9,5 @@ var ( type HistoricDate struct { historic Historic - date Date + date DateTime } diff --git a/date.go b/date.go new file mode 100644 index 0000000..a1f8a56 --- /dev/null +++ b/date.go @@ -0,0 +1,89 @@ +package datetime + +import ( + "errors" + "fmt" + "github.com/gouef/utils" + "github.com/gouef/validator" + "github.com/gouef/validator/constraints" + "regexp" + "strconv" + "time" +) + +const ( + dateRegexp = `^(\d{4})-(\d{2})-(\d{2})?$` +) + +type Date struct { + Year int + Month int `validate:"min=1,max=12"` + Day int `validate:"min=1,max=31"` + DateTime time.Time +} + +func NewDate(year, month, day int) (*Date, error) { + errs := validator.Validate(year, constraints.GreaterOrEqual{Value: 0}) + + if len(errs) > 0 { + return nil, errors.New(fmt.Sprintf("year must be 0 or greater get \"%d\"", year)) + } + + errs = validator.Validate(month, constraints.Range{Min: 1, Max: 12}) + + if len(errs) > 0 { + return nil, errors.New(fmt.Sprintf("month must be between 1-12 get \"%d\"", month)) + } + daysInMonth := DaysInMonth(year, month) + errs = validator.Validate(day, constraints.Range{Min: 1, Max: float64(daysInMonth)}) + + if len(errs) > 0 { + return nil, errors.New(fmt.Sprintf("day must be between 1-%d for month %d of year %d get \"%d\"", daysInMonth, month, year, day)) + } + + return &Date{ + Year: year, + Month: month, + Day: day, + DateTime: time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.UTC), + }, nil +} + +func DateFromString(value string) (*Date, error) { + errs := validator.Validate(value, constraints.RegularExpression{Regexp: dateTimeRegexp}) + + if len(errs) != 0 { + return nil, errors.New(fmt.Sprintf("unsupported format of date \"%s\"", value)) + } + + re := regexp.MustCompile(dateTimeRegexp) + match := re.FindStringSubmatch(value) + year, _ := strconv.Atoi(match[1]) + month, _ := strconv.Atoi(match[2]) + day, _ := strconv.Atoi(match[3]) + + return NewDate(year, month, day) +} + +func (d *Date) IsWeekend() bool { + weekendDays := []time.Weekday{time.Sunday, time.Saturday} + return utils.InArray(d.DateTime.Weekday(), weekendDays) +} + +func (d *Date) Time() time.Time { + return time.Date(d.Year, time.Month(d.Month), d.Day, 0, 0, 0, 0, time.UTC) +} + +// Compare compares the date instant d with u. If d is before u, it returns -1; +// if d is after u, it returns +1; if they're the same, it returns 0. +func (d *Date) Compare(u *Date) int { + return d.Time().Compare(u.Time()) +} + +func (d *Date) Equal(u *Date) bool { + return d.Time().Equal(u.Time()) +} + +func (d *Date) Between(start, end *Date) bool { + return d.Time().Before(end.Time()) && d.Time().After(start.Time()) +} diff --git a/dateRange.go b/dateRange.go index 597894b..1f3facc 100644 --- a/dateRange.go +++ b/dateRange.go @@ -14,21 +14,43 @@ const ( ) type DateRange struct { - from string - to string + from DateValue + to DateValue start RangeStart end RangeEnd } func NewDateRange(from, to string, start RangeStart, end RangeEnd) *DateRange { return &DateRange{ - from: from, - to: to, + from: DateValue(from), + to: DateValue(to), start: start, end: end, } } +func DateRangeFromString(dateRange string) (*DateRange, error) { + errs := validator.Validate(dateRange, constraints.RegularExpression{Regexp: dateRangeRegexp}) + + if len(errs) != 0 { + return nil, errors.New(fmt.Sprintf("unsupported format of date range \"%s\"", dateRange)) + } + + re := regexp.MustCompile(dateRangeRegexp) + match := re.FindStringSubmatch(dateRange) + openBracket, date1, date2, closeBracket := match[1], match[2], match[3], match[4] + + return NewDateRange(date1, date2, RangeStart(openBracket), RangeEnd(closeBracket)), nil +} + +func (d *DateRange) Start() RangeStart { + return d.start +} + +func (d *DateRange) End() RangeEnd { + return d.end +} + func (d *DateRange) String() string { return fmt.Sprintf("%s%s,%s%s", d.start, d.from, d.to, d.end) } @@ -45,20 +67,6 @@ func (d *DateRange) Is(value any) bool { return date.Between(start, end) } -func DateRangeFromString(dateRange string) (*DateRange, error) { - errs := validator.Validate(dateRange, constraints.RegularExpression{Regexp: dateRangeRegexp}) - - if len(errs) != 0 { - return nil, errors.New(fmt.Sprintf("unsupported format of date range \"%s\"", dateRange)) - } - - re := regexp.MustCompile(dateRangeRegexp) - match := re.FindStringSubmatch(dateRange) - openBracket, date1, date2, closeBracket := match[1], match[2], match[3], match[4] - - return NewDateRange(date1, date2, RangeStart(openBracket), RangeEnd(closeBracket)), nil -} - func (d *DateRange) format(date any) (*Date, error) { switch i := date.(type) { case time.Time: diff --git a/dateTimeValue.go b/dateTimeValue.go new file mode 100644 index 0000000..87dffd3 --- /dev/null +++ b/dateTimeValue.go @@ -0,0 +1,30 @@ +package datetime + +import ( + "errors" + "fmt" + "github.com/gouef/validator" + "github.com/gouef/validator/constraints" +) + +type DateTimeValue string + +func StringToDateTimeValue(value string) (DateTimeValue, error) { + errs := validator.Validate(value, constraints.RegularExpression{Regexp: dateTimeRegexp}) + + if len(errs) != 0 { + return "", errors.New(fmt.Sprintf("unsupported format of date time \"%s\"", value)) + } + + return DateTimeValue(value), nil +} + +func (d DateTimeValue) Date() *DateTime { + date, err := DateTimeFromString(string(d)) + + if err != nil { + return nil + } + + return date +} diff --git a/dateValue.go b/dateValue.go new file mode 100644 index 0000000..5573bd3 --- /dev/null +++ b/dateValue.go @@ -0,0 +1,30 @@ +package datetime + +import ( + "errors" + "fmt" + "github.com/gouef/validator" + "github.com/gouef/validator/constraints" +) + +type DateValue string + +func StringToDateValue(value string) (DateValue, error) { + errs := validator.Validate(value, constraints.RegularExpression{Regexp: dateRegexp}) + + if len(errs) != 0 { + return "", errors.New(fmt.Sprintf("unsupported format of date \"%s\"", value)) + } + + return DateValue(value), nil +} + +func (d DateValue) Date() *Date { + date, err := DateFromString(string(d)) + + if err != nil { + return nil + } + + return date +} diff --git a/datetime.go b/datetime.go index dabe504..4225835 100644 --- a/datetime.go +++ b/datetime.go @@ -12,10 +12,10 @@ import ( ) const ( - dateRegexp = `^(\d{4})-(\d{2})-(\d{2})( (\d{2}):(\d{2}):(\d{2}))?$` + dateTimeRegexp = `^(\d{4})-(\d{2})-(\d{2})( (\d{2}):(\d{2}):(\d{2}))?$` ) -type Date struct { +type DateTime struct { Year int Month int `validate:"min=1,max=12"` Day int `validate:"min=1,max=31"` @@ -25,11 +25,21 @@ type Date struct { DateTime time.Time } -func NewDate(year, month, day int) (*Date, error) { - return NewDateTime(year, month, day, 0, 0, 0) +func Now() *DateTime { + now := time.Now() + + return &DateTime{ + Year: now.Year(), + Month: int(now.Month()), + Day: now.Day(), + Hour: now.Hour(), + Minute: now.Minute(), + Second: now.Second(), + DateTime: now, + } } -func NewDateTime(year, month, day, hour, minute, second int) (*Date, error) { +func NewDateTime(year, month, day, hour, minute, second int) (*DateTime, error) { errs := validator.Validate(year, constraints.GreaterOrEqual{Value: 0}) if len(errs) > 0 { @@ -66,7 +76,7 @@ func NewDateTime(year, month, day, hour, minute, second int) (*Date, error) { return nil, errors.New(fmt.Sprintf("second must be between 0-59 get \"%d\"", second)) } - return &Date{ + return &DateTime{ Year: year, Month: month, Day: day, @@ -77,14 +87,14 @@ func NewDateTime(year, month, day, hour, minute, second int) (*Date, error) { }, nil } -func DateFromString(value string) (*Date, error) { - errs := validator.Validate(value, constraints.RegularExpression{Regexp: dateRegexp}) +func DateTimeFromString(value string) (*DateTime, error) { + errs := validator.Validate(value, constraints.RegularExpression{Regexp: dateTimeRegexp}) if len(errs) != 0 { return nil, errors.New(fmt.Sprintf("unsupported format of date \"%s\"", value)) } - re := regexp.MustCompile(dateRegexp) + re := regexp.MustCompile(dateTimeRegexp) match := re.FindStringSubmatch(value) year, _ := strconv.Atoi(match[1]) month, _ := strconv.Atoi(match[2]) @@ -97,33 +107,34 @@ func DateFromString(value string) (*Date, error) { return NewDateTime(year, month, day, hour, minute, second) } - return NewDate(year, month, day) + + return nil, errors.New(fmt.Sprintf("unsupported format of datetime \"%s\". time is missing.", value)) } func GetDate(year, month, day int) time.Time { return time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.UTC) } -func (d *Date) IsWeekend() bool { +func (d *DateTime) IsWeekend() bool { weekendDays := []time.Weekday{time.Sunday, time.Saturday} return utils.InArray(d.DateTime.Weekday(), weekendDays) } -func (d *Date) Time() time.Time { +func (d *DateTime) Time() time.Time { return time.Date(d.Year, time.Month(d.Month), d.Day, 0, 0, 0, 0, time.UTC) } // Compare compares the date instant d with u. If d is before u, it returns -1; // if d is after u, it returns +1; if they're the same, it returns 0. -func (d *Date) Compare(u *Date) int { +func (d *DateTime) Compare(u *DateTime) int { return d.Time().Compare(u.Time()) } -func (d *Date) Equal(u *Date) bool { +func (d *DateTime) Equal(u *DateTime) bool { return d.Time().Equal(u.Time()) } -func (d *Date) Between(start, end *Date) bool { +func (d *DateTime) Between(start, end *DateTime) bool { return d.Time().Before(end.Time()) && d.Time().After(start.Time()) } diff --git a/tests/datetime_test.go b/tests/datetime_test.go index 7eeee4d..c7cb06e 100644 --- a/tests/datetime_test.go +++ b/tests/datetime_test.go @@ -37,12 +37,12 @@ func TestNewDate(t *testing.T) { func TestIsWeekend(t *testing.T) { tests := []struct { - date *datetime.Date + date *datetime.DateTime expected bool }{ - {&datetime.Date{Year: 2024, Month: 12, Day: 21, DateTime: time.Date(2024, 12, 21, 0, 0, 0, 0, time.UTC)}, true}, // Saturday - {&datetime.Date{Year: 2024, Month: 12, Day: 22, DateTime: time.Date(2024, 12, 22, 0, 0, 0, 0, time.UTC)}, true}, // Sunday - {&datetime.Date{Year: 2024, Month: 12, Day: 23, DateTime: time.Date(2024, 12, 23, 0, 0, 0, 0, time.UTC)}, false}, // Monday + {&datetime.DateTime{Year: 2024, Month: 12, Day: 21, DateTime: time.Date(2024, 12, 21, 0, 0, 0, 0, time.UTC)}, true}, // Saturday + {&datetime.DateTime{Year: 2024, Month: 12, Day: 22, DateTime: time.Date(2024, 12, 22, 0, 0, 0, 0, time.UTC)}, true}, // Sunday + {&datetime.DateTime{Year: 2024, Month: 12, Day: 23, DateTime: time.Date(2024, 12, 23, 0, 0, 0, 0, time.UTC)}, false}, // Monday } for _, tt := range tests { @@ -54,16 +54,16 @@ func TestIsWeekend(t *testing.T) { func TestCompare(t *testing.T) { tests := []struct { - date1 *datetime.Date - date2 *datetime.Date + date1 *datetime.DateTime + date2 *datetime.DateTime expected int }{ - {&datetime.Date{Year: 2024, Month: 12, Day: 25, DateTime: time.Date(2024, 12, 25, 0, 0, 0, 0, time.UTC)}, - &datetime.Date{Year: 2024, Month: 12, Day: 25, DateTime: time.Date(2024, 12, 25, 0, 0, 0, 0, time.UTC)}, 0}, - {&datetime.Date{Year: 2024, Month: 12, Day: 25, DateTime: time.Date(2024, 12, 25, 0, 0, 0, 0, time.UTC)}, - &datetime.Date{Year: 2024, Month: 12, Day: 26, DateTime: time.Date(2024, 12, 26, 0, 0, 0, 0, time.UTC)}, -1}, // 25th < 26th - {&datetime.Date{Year: 2024, Month: 12, Day: 26, DateTime: time.Date(2024, 12, 26, 0, 0, 0, 0, time.UTC)}, - &datetime.Date{Year: 2024, Month: 12, Day: 25, DateTime: time.Date(2024, 12, 25, 0, 0, 0, 0, time.UTC)}, 1}, // 26th > 25th + {&datetime.DateTime{Year: 2024, Month: 12, Day: 25, DateTime: time.Date(2024, 12, 25, 0, 0, 0, 0, time.UTC)}, + &datetime.DateTime{Year: 2024, Month: 12, Day: 25, DateTime: time.Date(2024, 12, 25, 0, 0, 0, 0, time.UTC)}, 0}, + {&datetime.DateTime{Year: 2024, Month: 12, Day: 25, DateTime: time.Date(2024, 12, 25, 0, 0, 0, 0, time.UTC)}, + &datetime.DateTime{Year: 2024, Month: 12, Day: 26, DateTime: time.Date(2024, 12, 26, 0, 0, 0, 0, time.UTC)}, -1}, // 25th < 26th + {&datetime.DateTime{Year: 2024, Month: 12, Day: 26, DateTime: time.Date(2024, 12, 26, 0, 0, 0, 0, time.UTC)}, + &datetime.DateTime{Year: 2024, Month: 12, Day: 25, DateTime: time.Date(2024, 12, 25, 0, 0, 0, 0, time.UTC)}, 1}, // 26th > 25th } for _, tt := range tests { @@ -75,32 +75,32 @@ func TestCompare(t *testing.T) { func TestDateEqual(t *testing.T) { tests := []struct { - date1 *datetime.Date - date2 *datetime.Date + date1 *datetime.DateTime + date2 *datetime.DateTime expected bool }{ - // Test 1: Equal Date instances + // Test 1: Equal DateTime instances { - date1: &datetime.Date{Year: 2024, Month: 3, Day: 31}, - date2: &datetime.Date{Year: 2024, Month: 3, Day: 31}, + date1: &datetime.DateTime{Year: 2024, Month: 3, Day: 31}, + date2: &datetime.DateTime{Year: 2024, Month: 3, Day: 31}, expected: true, }, - // Test 2: Different Date instances (different day) + // Test 2: Different DateTime instances (different day) { - date1: &datetime.Date{Year: 2024, Month: 3, Day: 31}, - date2: &datetime.Date{Year: 2024, Month: 3, Day: 30}, + date1: &datetime.DateTime{Year: 2024, Month: 3, Day: 31}, + date2: &datetime.DateTime{Year: 2024, Month: 3, Day: 30}, expected: false, }, - // Test 3: Different Date instances (different month) + // Test 3: Different DateTime instances (different month) { - date1: &datetime.Date{Year: 2024, Month: 3, Day: 31}, - date2: &datetime.Date{Year: 2024, Month: 4, Day: 1}, + date1: &datetime.DateTime{Year: 2024, Month: 3, Day: 31}, + date2: &datetime.DateTime{Year: 2024, Month: 4, Day: 1}, expected: false, }, - // Test 4: Different Date instances (different year) + // Test 4: Different DateTime instances (different year) { - date1: &datetime.Date{Year: 2024, Month: 3, Day: 31}, - date2: &datetime.Date{Year: 2025, Month: 3, Day: 31}, + date1: &datetime.DateTime{Year: 2024, Month: 3, Day: 31}, + date2: &datetime.DateTime{Year: 2025, Month: 3, Day: 31}, expected: false, }, } @@ -191,20 +191,16 @@ func TestDaysInMonthByDate(t *testing.T) { func TestDateFromString(t *testing.T) { validDate, _ := datetime.NewDate(2025, 1, 31) - validDateTime, _ := datetime.NewDateTime(2025, 1, 31, 23, 27, 15) tests := []struct { date string expectedErr bool expectedDate *datetime.Date }{ {"2025-01-31", false, validDate}, - {"2025-01-31 23:27:15", false, validDateTime}, + {"2025-01-31 23:27:15", false, validDate}, {"2025-02-31", true, nil}, {"2025-13-32", true, nil}, - {"2025-01-31 24:27:15", true, nil}, - {"2025-01-31 23:65:15", true, nil}, - {"2025-01-31 23:27:95", true, nil}, - {"-2025-01-31 23:27:15", true, nil}, + {"-2025-01-31", true, nil}, {"invalid", true, nil}, } diff --git a/time.go b/time.go new file mode 100644 index 0000000..d3b3ec7 --- /dev/null +++ b/time.go @@ -0,0 +1,83 @@ +package datetime + +import ( + "errors" + "fmt" + "github.com/gouef/validator" + "github.com/gouef/validator/constraints" + "regexp" + "strconv" + "time" +) + +const ( + timeRegexp = `^(\d{2}):(\d{2}):(\d{2})?$` +) + +type Time struct { + Hour int `validate:"min=0,max=23"` + Minute int `validate:"min=0,max=59"` + Second int `validate:"min=0,max=59"` + DateTime time.Time +} + +func NewTime(hour, minute, second int) (*Time, error) { + errs := validator.Validate(hour, constraints.Range{Min: 0, Max: 23}) + + if len(errs) > 0 { + return nil, errors.New(fmt.Sprintf("hour must be between 0-23 get \"%d\"", hour)) + } + + errs = validator.Validate(minute, constraints.Range{Min: 0, Max: 59}) + + if len(errs) > 0 { + return nil, errors.New(fmt.Sprintf("minute must be between 0-59 get \"%d\"", minute)) + } + + errs = validator.Validate(second, constraints.Range{Min: 0, Max: 59}) + + if len(errs) > 0 { + return nil, errors.New(fmt.Sprintf("second must be between 0-59 get \"%d\"", second)) + } + + return &Time{ + Hour: hour, + Minute: minute, + Second: second, + DateTime: time.Date(0, time.Month(0), 0, hour, minute, second, 0, time.UTC), + }, nil +} + +func TimeFromString(value string) (*Time, error) { + errs := validator.Validate(value, constraints.RegularExpression{Regexp: dateTimeRegexp}) + + if len(errs) != 0 { + return nil, errors.New(fmt.Sprintf("unsupported format of date \"%s\"", value)) + } + + re := regexp.MustCompile(dateTimeRegexp) + match := re.FindStringSubmatch(value) + hour, _ := strconv.Atoi(match[1]) + minute, _ := strconv.Atoi(match[2]) + second, _ := strconv.Atoi(match[3]) + + return NewTime(hour, minute, second) +} + +func (d *Time) Time() time.Time { + return time.Date(0, time.Month(0), 0, d.Hour, d.Minute, d.Second, 0, time.UTC) +} + +// Compare compares the date instant d with u. If d is before u, it returns -1; +// if d is after u, it returns +1; if they're the same, it returns 0. +func (d *Time) Compare(u *Time) int { + return d.Time().Compare(u.Time()) +} + +func (d *Time) Equal(u *Time) bool { + return d.Time().Equal(u.Time()) +} + +func (d *Time) Between(start, end *Time) bool { + return d.Time().Before(end.Time()) && d.Time().After(start.Time()) +} diff --git a/timeValue.go b/timeValue.go new file mode 100644 index 0000000..6ba32dc --- /dev/null +++ b/timeValue.go @@ -0,0 +1,30 @@ +package datetime + +import ( + "errors" + "fmt" + "github.com/gouef/validator" + "github.com/gouef/validator/constraints" +) + +type TimeValue string + +func StringToTimeValue(value string) (TimeValue, error) { + errs := validator.Validate(value, constraints.RegularExpression{Regexp: timeRegexp}) + + if len(errs) != 0 { + return "", errors.New(fmt.Sprintf("unsupported format of time \"%s\"", value)) + } + + return TimeValue(value), nil +} + +func (d TimeValue) Date() *Time { + date, err := TimeFromString(string(d)) + + if err != nil { + return nil + } + + return date +} From 7ebf52c3bc8e4a744dca3fdfdd05f5daef1d35c1 Mon Sep 17 00:00:00 2001 From: Jan Galek Date: Sun, 2 Feb 2025 19:39:25 +0100 Subject: [PATCH 5/5] [Test] add for dateValue, timeValue and dateTimeValue --- tests/value_test.go | 47 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 tests/value_test.go diff --git a/tests/value_test.go b/tests/value_test.go new file mode 100644 index 0000000..24a1c31 --- /dev/null +++ b/tests/value_test.go @@ -0,0 +1,47 @@ +package tests + +import ( + "github.com/gouef/datetime" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestValue(t *testing.T) { + + t.Run("DateValue", func(t *testing.T) { + str := "2025-02-02" + dateValue, err := datetime.StringToDateValue(str) + + assert.Nil(t, err) + expected, _ := datetime.DateFromString(str) + assert.Equal(t, expected, dateValue.Date()) + }) + + t.Run("TimeValue", func(t *testing.T) { + str := "18:30:05" + timeValue, err := datetime.StringToTimeValue(str) + + assert.Nil(t, err) + expected, _ := datetime.TimeFromString(str) + assert.Equal(t, expected, timeValue.Date()) + }) + + t.Run("TimeValue invalid", func(t *testing.T) { + str := "18:60:05" + timeValue, err := datetime.StringToTimeValue(str) + timeValueDate := timeValue.Date() + + assert.Nil(t, err) + assert.Nil(t, timeValueDate) + }) + + t.Run("DateTimeValue", func(t *testing.T) { + str := "2025-02-02 18:30:05" + dateTimeValue, err := datetime.StringToDateTimeValue(str) + + assert.Nil(t, err) + expected, _ := datetime.DateTimeFromString(str) + assert.Equal(t, expected, dateTimeValue.Date()) + }) + +}