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
64 changes: 46 additions & 18 deletions test/time.carp
Original file line number Diff line number Diff line change
Expand Up @@ -186,20 +186,50 @@
"different dates are not equal")

; --- Datetime.to-unix-timestamp / from-unix-timestamp ---
; NOTE: to-unix-timestamp and from-unix-timestamp have pre-existing bugs
; (out-of-bounds array access). Tests are skipped until the library is fixed.
; (assert-equal test
; 0
; (Datetime.to-unix-timestamp &(Datetime.init 1970 1 1
; (Maybe.Just 0) (Maybe.Just 0) (Maybe.Just 0)
; (Maybe.Nothing) (Maybe.Nothing)))
; "unix epoch is timestamp 0")
; (assert-equal test
; &(Datetime.init 1970 1 1
; (Maybe.Just 0) (Maybe.Just 0) (Maybe.Just 0)
; (Maybe.Nothing) (Maybe.Nothing))
; &(Datetime.from-unix-timestamp 0)
; "from-unix-timestamp 0 is epoch")
(assert-equal test
0
(Datetime.to-unix-timestamp
&(Datetime.init 1970
1
1
(Maybe.Just 0)
(Maybe.Just 0)
(Maybe.Just 0)
(Maybe.Nothing)
(Maybe.Nothing)))
"unix epoch is timestamp 0")
(assert-equal test
&(Datetime.init 1970
1
1
(Maybe.Just 0)
(Maybe.Just 0)
(Maybe.Just 0)
(Maybe.Nothing)
(Maybe.Nothing))
&(Datetime.from-unix-timestamp 0)
"from-unix-timestamp 0 is epoch")
; Feb 29 leap year round-trip (1972 is a leap year, ts = 68169600)
(assert-equal test
&(Datetime.init 1972
2
29
(Maybe.Just 0)
(Maybe.Just 0)
(Maybe.Just 0)
(Maybe.Nothing)
(Maybe.Nothing))
&(Datetime.from-unix-timestamp
(Datetime.to-unix-timestamp
&(Datetime.init 1972
2
29
(Maybe.Just 0)
(Maybe.Just 0)
(Maybe.Just 0)
(Maybe.Nothing)
(Maybe.Nothing))))
"from-unix-timestamp round-trips Feb 29 in leap year")

; --- Datetime.add-seconds ---
(assert-equal test
Expand Down Expand Up @@ -318,10 +348,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 +361,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
55 changes: 34 additions & 21 deletions time.carp
Original file line number Diff line number Diff line change
Expand Up @@ -236,10 +236,13 @@ in the range of `1` to `365` or `366`, depending on whether it’s a leap year."
(doc leap? "checks whether the `Datetime` `dt` is a leap year.")
(defn leap? [dt] (is-leap @(year dt)))

; floor division (matches Python's //)
(hidden div-)
(defn div- [a n] (to-int (floor (/ (Double.from-int a) (Double.from-int n)))))

; weird mod for our purposes (behaves more or less like in Python)
(hidden mod-)
(defn mod- [a n]
(- a (* n (to-int (floor (/ (Double.from-int a) (Double.from-int n)))))))
(defn mod- [a n] (- a (* n (div- a n))))

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

Expand All @@ -248,24 +251,24 @@ If `n` is negative, it will be subtracted instead.")
(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 @@ -303,10 +306,12 @@ If `n` is negative, it will be subtracted instead.")
(private to-unix)
(hidden to-unix)
(defn to-unix [y m d hh mm ss delta]
(let [yy (for-year
&(Result.unsafe-from-success (Array.range 1970 y 1))
366
365)]
(let [yy (if (<= y 1970)
0
(for-year
&(Result.unsafe-from-success (Array.range 1970 (Int.dec y) 1))
366
365))]
(+ (* yy DAY)
(+
(* @(Array.unsafe-nth &DAYS-BEFORE-MONTH m) DAY)
Expand All @@ -326,23 +331,31 @@ a UNIX timestamp, i.e. the number of seconds elapsed since the 1st of January,
(Maybe.from @(seconds dt) 0)
@(Timezone.delta &tz))))

(private find-month-from-days)
(hidden find-month-from-days)
(defn find-month-from-days [d leap]
(let-do [m 1]
(for [i 2 13]
(let [adj (if (and leap (>= i 3)) 1 0)]
(when (<= (+ @(Array.unsafe-nth &DAYS-BEFORE-MONTH i) adj) d)
(set! m i))))
m))

(doc from-unix-timestamp "returns the `Datetime` equivalent to the UNIX
timestamp `ts`, i.e. the number of seconds elapsed since the 1st of January,
1970.")
(defn from-unix-timestamp [ts]
(let [y (/ ts YEAR)
ld (Int.dec
ld (if (<= y 0)
0
(for-year
&(Result.unsafe-from-success (Array.range 1970 (+ y 1971) 1))
&(Result.unsafe-from-success (Array.range 1970 (+ y 1969) 1))
1
0))
d (- (- (/ ts DAY) (* y 365)) ld)
get-m-until (fn [n]
(Array.reduce &(fn [acc x] (if (< @x n) @x acc))
0
&DAYS-BEFORE-MONTH))
mdays (get-m-until d)
m (Maybe.unsafe-from (Array.index-of &DAYS-BEFORE-MONTH &mdays))
yr (+ y 1970)
m (find-month-from-days d (is-leap yr))
mdays (days-before-month yr m)
nd (- d mdays)
tmpy (* y (* 365 24))
tmpd (* (+ nd (+ ld mdays)) 24)
Expand All @@ -352,7 +365,7 @@ timestamp `ts`, i.e. the number of seconds elapsed since the 1st of January,
tmh (* hh 60)
mm (- (- (- (/ ts 60) tmh) tmd) tmy)
ss (- (- (- (- ts (* 60 mm)) (* tmh 60)) (* tmd 60)) (* tmy 60))]
(init (+ y 1970)
(init yr
m
(Int.inc nd)
(Maybe.Just hh)
Expand Down
Loading