Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 53 additions & 5 deletions test/time.carp
Original file line number Diff line number Diff line change
Expand Up @@ -318,10 +318,8 @@
(Maybe.Nothing))
&(Timedelta.days 1)))
"Timedelta.add 1 day advances the day")
; NOTE: add-seconds has an off-by-one bug with negative values;
; subtracting 5 minutes from 00:00:00 yields 23:54:59 instead of 23:55:00.
(assert-equal test
&@"23:54:59"
&@"23:55:00"
&(Datetime.isotime
&(Timedelta.sub
&(Datetime.init 2024
Expand All @@ -333,7 +331,7 @@
(Maybe.Nothing)
(Maybe.Nothing))
&(Timedelta.minutes 5)))
"Timedelta.sub 5 minutes wraps back (off-by-one bug)")
"Timedelta.sub 5 minutes wraps back")

; --- Datetime.strptime ---
; basic date parsing
Expand Down Expand Up @@ -570,4 +568,54 @@
(Maybe.Nothing))
"%Y-%m-%d %H:%M:%S")
"%Y-%m-%d %H:%M:%S"))
"strptime roundtrips with strftime for datetime"))
"strptime roundtrips with strftime for datetime")

; --- Datetime comparison operators ---
(assert-true test
(= &(Datetime.date 2024 3 15) &(Datetime.date 2024 3 15))
"equal dates are equal")
(assert-false test
(= &(Datetime.date 2024 1 15) &(Datetime.date 2024 2 15))
"different months are not equal")
(assert-true test
(> &(Datetime.date 2024 3 15) &(Datetime.date 2024 2 15))
"greater-than with same year different month")
(assert-true test
(< &(Datetime.date 2023 12 31) &(Datetime.date 2024 1 1))
"less-than with year priority over month")
(assert-false test
(> &(Datetime.date 2024 3 15) &(Datetime.date 2024 3 15))
"equal datetimes return false for >")
(assert-false test
(< &(Datetime.date 2024 3 15) &(Datetime.date 2024 3 15))
"equal datetimes return false for <")

; --- add-seconds negative edge cases ---
(assert-equal test
&@"23:59:59"
&(Datetime.isotime
&(Datetime.add-seconds
&(Datetime.init 2024
1
2
(Maybe.Just 0)
(Maybe.Just 0)
(Maybe.Just 0)
(Maybe.Nothing)
(Maybe.Nothing))
-1))
"subtract 1 second from 00:00:00")
(assert-equal test
&@"22:58:59"
&(Datetime.isotime
&(Datetime.add-seconds
&(Datetime.init 2024
1
2
(Maybe.Just 0)
(Maybe.Just 0)
(Maybe.Just 0)
(Maybe.Nothing)
(Maybe.Nothing))
-3661))
"subtract 3661 seconds (1h1m1s) from 00:00:00"))
64 changes: 46 additions & 18 deletions time.carp
Original file line number Diff line number Diff line change
Expand Up @@ -241,31 +241,34 @@ in the range of `1` to `365` or `366`, depending on whether it’s a leap year."
(defn mod- [a n]
(- a (* n (to-int (floor (/ (Double.from-int a) (Double.from-int n)))))))

(hidden div-)
(defn div- [a n] (to-int (floor (/ (Double.from-int a) (Double.from-int n)))))

(doc add-seconds "adds a number of seconds `n` to a `Datetime`.

If `n` is negative, it will be subtracted instead.")
(defn add-seconds [d n]
(if (neg? n)
(let-do [nd @d
s (Maybe.from @(seconds d) 0)
a (dec (+ s n))]
a (+ s n)]
(set-seconds! &nd (Maybe.Just (mod- a 60)))
(if (>= a 0)
nd
(let-do [m (/ a 60)
(let-do [m (div- a 60)
dm (Maybe.from @(minutes d) 0)
ma (dec (+ m dm))]
ma (+ m dm)]
(set-minutes! &nd (Maybe.Just (mod- ma 60)))
(if (>= ma 0)
nd
(let-do [h (/ ma 60)
(let-do [h (div- ma 60)
dh (Maybe.from @(hours d) 0)
ha (dec (+ h dh))]
ha (+ h dh)]
(set-hours! &nd (Maybe.Just (mod- ha 24)))
(if (>= ha 0)
nd
(let-do [ord (to-ordinal d)
nnd (from-ordinal (dec (+ ord (/ ha 24))))]
nnd (from-ordinal (+ ord (div- ha 24)))]
(set-day! &nd @(day &nnd))
(set-month! &nd @(month &nnd))
(set-year! &nd @(year &nnd))
Expand Down Expand Up @@ -862,6 +865,7 @@ Example:
optional ones.")
(defn = [a b]
(and (= (year a) (year b))
(= (month a) (month b))
(= (day a) (day b))
(= (hours a) (hours b))
(= (minutes a) (minutes b))
Expand All @@ -873,22 +877,46 @@ optional ones.")

(doc > "is defined as the timezone-unaware comparison of `a` and `b`.")
(defn > [a b]
(or (> @(year a) @(year b))
(> @(day a) @(day b))
(> (Maybe.from @(hours a) 0) (Maybe.from @(hours b) 0))
(> (Maybe.from @(minutes a) 0) (Maybe.from @(minutes b) 0))
(> (Maybe.from @(seconds a) 0) (Maybe.from @(seconds b) 0))
(> (Maybe.from @(nanoseconds a) 0) (Maybe.from @(nanoseconds b) 0))))
(cond
(/= @(year a) @(year b)) (> @(year a) @(year b))
(/= @(month a) @(month b)) (> @(month a) @(month b))
(/= @(day a) @(day b)) (> @(day a) @(day b))
(let [ha (Maybe.from @(hours a) 0)
hb (Maybe.from @(hours b) 0)]
(cond
(/= ha hb) (> ha hb)
(let [ma (Maybe.from @(minutes a) 0)
mb (Maybe.from @(minutes b) 0)]
(cond
(/= ma mb) (> ma mb)
(let [sa (Maybe.from @(seconds a) 0)
sb (Maybe.from @(seconds b) 0)]
(cond
(/= sa sb) (> sa sb)
(> (Maybe.from @(nanoseconds a) 0)
(Maybe.from @(nanoseconds b) 0))))))))))
(implements > Datetime.>)

(doc < "is defined as the timezone-unaware comparison of `a` and `b`.")
(defn < [a b]
(or (< @(year a) @(year b))
(< @(day a) @(day b))
(< (Maybe.from @(hours a) 0) (Maybe.from @(hours b) 0))
(< (Maybe.from @(minutes a) 0) (Maybe.from @(minutes b) 0))
(< (Maybe.from @(seconds a) 0) (Maybe.from @(seconds b) 0))
(< (Maybe.from @(nanoseconds a) 0) (Maybe.from @(nanoseconds b) 0))))
(cond
(/= @(year a) @(year b)) (< @(year a) @(year b))
(/= @(month a) @(month b)) (< @(month a) @(month b))
(/= @(day a) @(day b)) (< @(day a) @(day b))
(let [ha (Maybe.from @(hours a) 0)
hb (Maybe.from @(hours b) 0)]
(cond
(/= ha hb) (< ha hb)
(let [ma (Maybe.from @(minutes a) 0)
mb (Maybe.from @(minutes b) 0)]
(cond
(/= ma mb) (< ma mb)
(let [sa (Maybe.from @(seconds a) 0)
sb (Maybe.from @(seconds b) 0)]
(cond
(/= sa sb) (< sa sb)
(< (Maybe.from @(nanoseconds a) 0)
(Maybe.from @(nanoseconds b) 0))))))))))
(implements < Datetime.<))

(deftype Timedelta [seconds- Int])
Expand Down
Loading