Skip to content
Merged
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
180 changes: 179 additions & 1 deletion test/time.carp
Original file line number Diff line number Diff line change
Expand Up @@ -618,4 +618,182 @@
(Maybe.Nothing)
(Maybe.Nothing))
-3661))
"subtract 3661 seconds (1h1m1s) from 00:00:00"))
"subtract 3661 seconds (1h1m1s) from 00:00:00")

; --- Datetime.diff ---
(assert-equal test
0
(Datetime.diff &(Datetime.date 2024 3 15) &(Datetime.date 2024 3 15))
"diff: same date is 0")
(assert-equal test
86400
(Datetime.diff &(Datetime.date 2024 3 16) &(Datetime.date 2024 3 15))
"diff: one day apart is 86400")
(assert-equal test
-86400
(Datetime.diff &(Datetime.date 2024 3 15) &(Datetime.date 2024 3 16))
"diff: reversed order is negative")
(assert-equal test
3600
(Datetime.diff
&(Datetime.init 2024
3
15
(Maybe.Just 1)
(Maybe.Just 0)
(Maybe.Just 0)
(Maybe.Nothing)
(Maybe.Nothing))
&(Datetime.init 2024
3
15
(Maybe.Just 0)
(Maybe.Just 0)
(Maybe.Just 0)
(Maybe.Nothing)
(Maybe.Nothing)))
"diff: one hour apart is 3600")
(assert-equal test
90061
(Datetime.diff
&(Datetime.init 2024
3
16
(Maybe.Just 1)
(Maybe.Just 1)
(Maybe.Just 1)
(Maybe.Nothing)
(Maybe.Nothing))
&(Datetime.init 2024
3
15
(Maybe.Just 0)
(Maybe.Just 0)
(Maybe.Just 0)
(Maybe.Nothing)
(Maybe.Nothing)))
"diff: 1 day 1 hour 1 minute 1 second = 90061")
(assert-equal test
(* 365 86400)
(Datetime.diff &(Datetime.date 2023 1 1) &(Datetime.date 2022 1 1))
"diff: non-leap year is 365 days")
(assert-equal test
(* 366 86400)
(Datetime.diff &(Datetime.date 2025 1 1) &(Datetime.date 2024 1 1))
"diff: leap year 2024 has 366 days")

; --- Datetime.add-months ---
(assert-equal test
&(Datetime.date 2024 2 15)
&(Datetime.add-months &(Datetime.date 2024 1 15) 1)
"add-months: Jan to Feb")
(assert-equal test
&(Datetime.date 2024 2 29)
&(Datetime.add-months &(Datetime.date 2024 1 31) 1)
"add-months: Jan 31 to Feb 29 (leap year clamp)")
(assert-equal test
&(Datetime.date 2023 2 28)
&(Datetime.add-months &(Datetime.date 2023 1 31) 1)
"add-months: Jan 31 to Feb 28 (non-leap year clamp)")
(assert-equal test
&(Datetime.date 2024 4 30)
&(Datetime.add-months &(Datetime.date 2024 3 31) 1)
"add-months: Mar 31 to Apr 30 (30-day month clamp)")
(assert-equal test
&(Datetime.date 2025 1 15)
&(Datetime.add-months &(Datetime.date 2024 12 15) 1)
"add-months: Dec to Jan crosses year")
(assert-equal test
&(Datetime.date 2024 2 15)
&(Datetime.add-months &(Datetime.date 2024 3 15) -1)
"add-months: negative, Mar to Feb")
(assert-equal test
&(Datetime.date 2023 12 15)
&(Datetime.add-months &(Datetime.date 2024 1 15) -1)
"add-months: negative, Jan to Dec crosses year")
(assert-equal test
&(Datetime.date 2025 1 15)
&(Datetime.add-months &(Datetime.date 2024 1 15) 12)
"add-months: 12 months = 1 year")
(assert-equal test
&(Datetime.date 2022 1 15)
&(Datetime.add-months &(Datetime.date 2024 1 15) -24)
"add-months: -24 months = -2 years")
(assert-equal test
&(Datetime.date 2024 7 31)
&(Datetime.add-months &(Datetime.date 2024 5 31) 2)
"add-months: May 31 + 2 = Jul 31 (no clamp needed)")
(assert-equal test
&(Datetime.init 2024
2
15
(Maybe.Just 10)
(Maybe.Just 30)
(Maybe.Just 45)
(Maybe.Nothing)
(Maybe.Nothing))
&(Datetime.add-months
&(Datetime.init 2024
1
15
(Maybe.Just 10)
(Maybe.Just 30)
(Maybe.Just 45)
(Maybe.Nothing)
(Maybe.Nothing))
1)
"add-months: preserves time components")

; --- Datetime.add-years ---
(assert-equal test
&(Datetime.date 2025 3 15)
&(Datetime.add-years &(Datetime.date 2024 3 15) 1)
"add-years: simple +1")
(assert-equal test
&(Datetime.date 2025 2 28)
&(Datetime.add-years &(Datetime.date 2024 2 29) 1)
"add-years: Feb 29 to non-leap year clamps to 28")
(assert-equal test
&(Datetime.date 2028 2 29)
&(Datetime.add-years &(Datetime.date 2024 2 29) 4)
"add-years: Feb 29 to next leap year stays 29")
(assert-equal test
&(Datetime.date 2023 3 15)
&(Datetime.add-years &(Datetime.date 2024 3 15) -1)
"add-years: negative, -1 year")
(assert-equal test
&(Datetime.date 2023 2 28)
&(Datetime.add-years &(Datetime.date 2024 2 29) -1)
"add-years: Feb 29 backward to non-leap clamps to 28")
(assert-equal test
&(Datetime.date 2024 7 15)
&(Datetime.add-years &(Datetime.date 2024 7 15) 0)
"add-years: +0 is identity")
(assert-equal test
&(Datetime.date 1900 2 28)
&(Datetime.add-years &(Datetime.date 2000 2 29) -100)
"add-years: 2000 Feb 29 -100 to 1900 (not leap) clamps to 28")
(assert-equal test
&(Datetime.date 2400 2 29)
&(Datetime.add-years &(Datetime.date 2000 2 29) 400)
"add-years: 2000 Feb 29 +400 to 2400 (leap) stays 29")
(assert-equal test
&(Datetime.init 2025
3
15
(Maybe.Just 14)
(Maybe.Just 30)
(Maybe.Just 0)
(Maybe.Nothing)
(Maybe.Nothing))
&(Datetime.add-years
&(Datetime.init 2024
3
15
(Maybe.Just 14)
(Maybe.Just 30)
(Maybe.Just 0)
(Maybe.Nothing)
(Maybe.Nothing))
1)
"add-years: preserves time components"))
58 changes: 57 additions & 1 deletion time.carp
Original file line number Diff line number Diff line change
Expand Up @@ -917,7 +917,63 @@ optional ones.")
(/= sa sb) (< sa sb)
(< (Maybe.from @(nanoseconds a) 0)
(Maybe.from @(nanoseconds b) 0))))))))))
(implements < Datetime.<))
(implements < Datetime.<)

(doc diff "returns the signed difference in seconds between `a` and `b`.

The result is positive when `a` is later than `b`, and negative when `a` is
earlier. This is a timezone-unaware comparison, similar to `>` and `<`.")
(defn diff [a b]
(let [ord-a (to-ordinal a)
ord-b (to-ordinal b)
time-a (+ (* (Maybe.from @(hours a) 0) HOUR)
(+ (* (Maybe.from @(minutes a) 0) MINUTE)
(Maybe.from @(seconds a) 0)))
time-b (+ (* (Maybe.from @(hours b) 0) HOUR)
(+ (* (Maybe.from @(minutes b) 0) MINUTE)
(Maybe.from @(seconds b) 0)))]
(+ (* (- ord-a ord-b) DAY) (- time-a time-b))))

(doc add-months "adds `n` months to the `Datetime` `dt`.

If the resulting month has fewer days than the original day, the day is clamped
to the last day of the month. For example, adding 1 month to January 31 yields
February 28 (or 29 in a leap year).

`n` may be negative to subtract months.")
(defn add-months [dt n]
(let [total (+ (+ (* @(year dt) 12) (Int.dec @(month dt))) n)
new-year (div- total 12)
new-month (+ (mod- total 12) 1)
max-day (days-in-month new-year new-month)
new-day (min @(day dt) max-day)]
(init new-year
new-month
new-day
@(hours dt)
@(minutes dt)
@(seconds dt)
@(nanoseconds dt)
@(tz dt))))

(doc add-years "adds `n` years to the `Datetime` `dt`.

If the date is February 29 and the resulting year is not a leap year, the day is
clamped to February 28.

`n` may be negative to subtract years.")
(defn add-years [dt n]
(let [new-year (+ @(year dt) n)
max-day (days-in-month new-year @(month dt))
new-day (min @(day dt) max-day)]
(init new-year
@(month dt)
new-day
@(hours dt)
@(minutes dt)
@(seconds dt)
@(nanoseconds dt)
@(tz dt)))))

(deftype Timedelta [seconds- Int])

Expand Down
Loading