Skip to content

Commit 4f78f7e

Browse files
author
Christopher Doris
committed
fix(numpydates): use floor div and fix atto scale
- Replace ÷ with fld in conversions to apply floor semantics for negative values, matching NumPy datetime64 behavior - Correct ATTOSECONDS multiplier to 1e15 - Add comprehensive DateTime -> DateTime64/InlineDateTime64 tests, including pre-epoch cases, and refine existing Date tests - Add script to generate NumPy reference values for tests
1 parent b66d37d commit 4f78f7e

File tree

5 files changed

+142
-12
lines changed

5 files changed

+142
-12
lines changed

src/NumpyDates/AbstractDateTime64.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@ function Dates.DateTime(d::AbstractDateTime64)
3131
elseif u == NANOSECONDS
3232
b + Dates.Nanosecond(v)
3333
elseif u == PICOSECONDS
34-
b + Dates.Nanosecond(v ÷ 1_000)
34+
b + Dates.Nanosecond(fld(v, 1_000))
3535
elseif u == FEMTOSECONDS
36-
b + Dates.Nanosecond(v ÷ 1_000_000)
36+
b + Dates.Nanosecond(fld(v, 1_000_000))
3737
elseif u == ATTOSECONDS
38-
b + Dates.Nanosecond(v ÷ 1_000_000_000)
38+
b + Dates.Nanosecond(fld(v, 1_000_000_000))
3939
else
4040
error("Unsupported units: $unit_base")
4141
end

src/NumpyDates/DateTime64.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,11 @@ function DateTime64(d::Dates.DateTime, unit::UnitArg = defaultunit(d))
8181
elseif u == FEMTOSECONDS
8282
v = mul(v, 1000_000_000_000)
8383
elseif u == ATTOSECONDS
84-
v = mul(v, 1000_000_000_000)
84+
v = mul(v, 1000_000_000_000_000)
8585
else
8686
error("unknown unit: $u")
8787
end
88-
v = v ÷ m
88+
v = fld(v, m)
8989
DateTime64(v, unit)
9090
end
9191

@@ -124,7 +124,7 @@ function DateTime64(d::Dates.Date, unit::UnitArg = defaultunit(d))
124124
error("unknown unit: $u")
125125
end
126126
end
127-
v = v ÷ m
127+
v = fld(v, m)
128128
DateTime64(v, unit)
129129
end
130130

src/NumpyDates/TimeDelta64.jl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,17 +57,17 @@ function TimeDelta64(p::Dates.Period, unit::UnitArg = defaultunit(p))
5757
if u == YEARS
5858
if p isa Dates.Year
5959
v = value(p)
60-
return TimeDelta64(v ÷ m, unit)
60+
return TimeDelta64(fld(v, m), unit)
6161
else
6262
error("cannot convert $(typeof(p)) to years")
6363
end
6464
elseif u == MONTHS
6565
if p isa Dates.Month
6666
v = value(p)
67-
return TimeDelta64(v ÷ m, unit)
67+
return TimeDelta64(fld(v, m), unit)
6868
elseif p isa Dates.Year
6969
v = mul(value(p), 12)
70-
return TimeDelta64(v ÷ m, unit)
70+
return TimeDelta64(fld(v, m), unit)
7171
else
7272
error("cannot convert $(typeof(p)) to months")
7373
end
@@ -76,13 +76,13 @@ function TimeDelta64(p::Dates.Period, unit::UnitArg = defaultunit(p))
7676
ns = _period_to_ns(p)
7777
scale = u == PICOSECONDS ? 1_000 : u == FEMTOSECONDS ? 1_000_000 : 1_000_000_000
7878
ns_scaled = mul(ns, scale)
79-
return TimeDelta64(ns_scaled ÷ m, unit)
79+
return TimeDelta64(fld(ns_scaled, m), unit)
8080
else
8181
# weeks..nanoseconds: convert via nanoseconds
8282
ns = _period_to_ns(p)
8383
unit_ns = _unit_to_ns(u)
8484
denom = mul(unit_ns, Int64(m))
85-
return TimeDelta64(ns ÷ denom, unit)
85+
return TimeDelta64(fld(ns, denom), unit)
8686
end
8787
end
8888

test/NumpyDates.jl

Lines changed: 108 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@
8383
(Date(2100, 1, 1), :ns, 4102444800000000000),
8484
]
8585

86-
for (d, usym, expected) in cases
86+
@testset "$d $usym" for (d, usym, expected) in cases
8787
# 1) DateTime64 with UnitArg symbol
8888
dt64 = NumpyDates.DateTime64(d, usym)
8989
@test Dates.value(dt64) == expected
@@ -99,3 +99,110 @@
9999
@test NumpyDates.unitpair(inline_dyn) == NumpyDates.unitpair(usym)
100100
end
101101
end
102+
103+
@testitem "DateTime -> DateTime64/InlineDateTime64" begin
104+
using Dates
105+
using PythonCall: NumpyDates
106+
107+
# Data generated by: uv run test/scripts/np_dates.py
108+
# Format: (Date, unit_symbol, numpy_int_value)
109+
cases = [
110+
(DateTime(1969, 12, 31, 23, 0, 0), :Y, -1),
111+
(DateTime(1969, 12, 31, 23, 0, 0), :M, -1),
112+
(DateTime(1969, 12, 31, 23, 0, 0), :D, -1),
113+
(DateTime(1969, 12, 31, 23, 0, 0), :h, -1),
114+
(DateTime(1969, 12, 31, 23, 0, 0), :m, -60),
115+
(DateTime(1969, 12, 31, 23, 0, 0), :s, -3600),
116+
(DateTime(1969, 12, 31, 23, 0, 0), :ms, -3_600_000),
117+
(DateTime(1969, 12, 31, 23, 0, 0), :us, -3_600_000_000),
118+
(DateTime(1969, 12, 31, 23, 0, 0), :ns, -3_600_000_000_000),
119+
(DateTime(1969, 12, 31, 23, 59, 59), :Y, -1),
120+
(DateTime(1969, 12, 31, 23, 59, 59), :M, -1),
121+
(DateTime(1969, 12, 31, 23, 59, 59), :D, -1),
122+
(DateTime(1969, 12, 31, 23, 59, 59), :h, -1),
123+
(DateTime(1969, 12, 31, 23, 59, 59), :m, -1),
124+
(DateTime(1969, 12, 31, 23, 59, 59), :s, -1),
125+
(DateTime(1969, 12, 31, 23, 59, 59), :ms, -1_000),
126+
(DateTime(1969, 12, 31, 23, 59, 59), :us, -1_000_000),
127+
(DateTime(1969, 12, 31, 23, 59, 59), :ns, -1_000_000_000),
128+
(DateTime(1970, 1, 1, 0, 0, 0), :Y, 0),
129+
(DateTime(1970, 1, 1, 0, 0, 0), :M, 0),
130+
(DateTime(1970, 1, 1, 0, 0, 0), :D, 0),
131+
(DateTime(1970, 1, 1, 0, 0, 0), :h, 0),
132+
(DateTime(1970, 1, 1, 0, 0, 0), :m, 0),
133+
(DateTime(1970, 1, 1, 0, 0, 0), :s, 0),
134+
(DateTime(1970, 1, 1, 0, 0, 0), :ms, 0),
135+
(DateTime(1970, 1, 1, 0, 0, 0), :us, 0),
136+
(DateTime(1970, 1, 1, 0, 0, 0), :ns, 0),
137+
(DateTime(1970, 1, 1, 0, 0, 1), :Y, 0),
138+
(DateTime(1970, 1, 1, 0, 0, 1), :M, 0),
139+
(DateTime(1970, 1, 1, 0, 0, 1), :D, 0),
140+
(DateTime(1970, 1, 1, 0, 0, 1), :h, 0),
141+
(DateTime(1970, 1, 1, 0, 0, 1), :m, 0),
142+
(DateTime(1970, 1, 1, 0, 0, 1), :s, 1),
143+
(DateTime(1970, 1, 1, 0, 0, 1), :ms, 1_000),
144+
(DateTime(1970, 1, 1, 0, 0, 1), :us, 1_000_000),
145+
(DateTime(1970, 1, 1, 0, 0, 1), :ns, 1_000_000_000),
146+
(DateTime(1970, 1, 1, 1, 0, 0), :Y, 0),
147+
(DateTime(1970, 1, 1, 1, 0, 0), :M, 0),
148+
(DateTime(1970, 1, 1, 1, 0, 0), :D, 0),
149+
(DateTime(1970, 1, 1, 1, 0, 0), :h, 1),
150+
(DateTime(1970, 1, 1, 1, 0, 0), :m, 60),
151+
(DateTime(1970, 1, 1, 1, 0, 0), :s, 3_600),
152+
(DateTime(1970, 1, 1, 1, 0, 0), :ms, 3_600_000),
153+
(DateTime(1970, 1, 1, 1, 0, 0), :us, 3_600_000_000),
154+
(DateTime(1970, 1, 1, 1, 0, 0), :ns, 3_600_000_000_000),
155+
(DateTime(1999, 12, 31, 23, 59, 59), :Y, 29),
156+
(DateTime(1999, 12, 31, 23, 59, 59), :M, 359),
157+
(DateTime(1999, 12, 31, 23, 59, 59), :D, 10_956),
158+
(DateTime(1999, 12, 31, 23, 59, 59), :h, 262_967),
159+
(DateTime(1999, 12, 31, 23, 59, 59), :m, 15_778_079),
160+
(DateTime(1999, 12, 31, 23, 59, 59), :s, 946_684_799),
161+
(DateTime(1999, 12, 31, 23, 59, 59), :ms, 946_684_799_000),
162+
(DateTime(1999, 12, 31, 23, 59, 59), :us, 946_684_799_000_000),
163+
(DateTime(1999, 12, 31, 23, 59, 59), :ns, 946_684_799_000_000_000),
164+
(DateTime(2000, 2, 29, 12, 34, 56), :Y, 30),
165+
(DateTime(2000, 2, 29, 12, 34, 56), :M, 361),
166+
(DateTime(2000, 2, 29, 12, 34, 56), :D, 11_016),
167+
(DateTime(2000, 2, 29, 12, 34, 56), :h, 264_396),
168+
(DateTime(2000, 2, 29, 12, 34, 56), :m, 15_863_794),
169+
(DateTime(2000, 2, 29, 12, 34, 56), :s, 951_827_696),
170+
(DateTime(2000, 2, 29, 12, 34, 56), :ms, 951_827_696_000),
171+
(DateTime(2000, 2, 29, 12, 34, 56), :us, 951_827_696_000_000),
172+
(DateTime(2000, 2, 29, 12, 34, 56), :ns, 951_827_696_000_000_000),
173+
(DateTime(1900, 1, 1, 0, 0, 0), :Y, -70),
174+
(DateTime(1900, 1, 1, 0, 0, 0), :M, -840),
175+
(DateTime(1900, 1, 1, 0, 0, 0), :D, -25_567),
176+
(DateTime(1900, 1, 1, 0, 0, 0), :h, -613_608),
177+
(DateTime(1900, 1, 1, 0, 0, 0), :m, -36_816_480),
178+
(DateTime(1900, 1, 1, 0, 0, 0), :s, -2_208_988_800),
179+
(DateTime(1900, 1, 1, 0, 0, 0), :ms, -2_208_988_800_000),
180+
(DateTime(1900, 1, 1, 0, 0, 0), :us, -2_208_988_800_000_000),
181+
(DateTime(1900, 1, 1, 0, 0, 0), :ns, -2_208_988_800_000_000_000),
182+
(DateTime(2100, 1, 1, 0, 0, 0), :Y, 130),
183+
(DateTime(2100, 1, 1, 0, 0, 0), :M, 1_560),
184+
(DateTime(2100, 1, 1, 0, 0, 0), :D, 47_482),
185+
(DateTime(2100, 1, 1, 0, 0, 0), :h, 1_139_568),
186+
(DateTime(2100, 1, 1, 0, 0, 0), :m, 68_374_080),
187+
(DateTime(2100, 1, 1, 0, 0, 0), :s, 4_102_444_800),
188+
(DateTime(2100, 1, 1, 0, 0, 0), :ms, 4_102_444_800_000),
189+
(DateTime(2100, 1, 1, 0, 0, 0), :us, 4_102_444_800_000_000),
190+
(DateTime(2100, 1, 1, 0, 0, 0), :ns, 4_102_444_800_000_000_000),
191+
]
192+
193+
@testset "$dt $usym" for (dt, usym, expected) in cases
194+
# 1) DateTime64 with UnitArg symbol
195+
dt64 = NumpyDates.DateTime64(dt, usym)
196+
@test Dates.value(dt64) == expected
197+
198+
# 2) InlineDateTime64 with type parameter Unit constant
199+
Uconst = NumpyDates.Unit(usym)
200+
inline_typed = NumpyDates.InlineDateTime64{Uconst}(dt)
201+
@test Dates.value(inline_typed) == expected
202+
203+
# 3) InlineDateTime64 with runtime UnitArg symbol
204+
inline_dyn = NumpyDates.InlineDateTime64(dt, usym)
205+
@test Dates.value(inline_dyn) == expected
206+
@test NumpyDates.unitpair(inline_dyn) == NumpyDates.unitpair(usym)
207+
end
208+
end

test/scripts/np_datetimes.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#!/usr/bin/env python
2+
import numpy as np
3+
4+
datetimes = [
5+
"1969-12-31T23:00:00",
6+
"1969-12-31T23:59:59",
7+
"1970-01-01T00:00:00",
8+
"1970-01-01T00:00:01",
9+
"1970-01-01T01:00:00",
10+
"1999-12-31T23:59:59",
11+
"2000-02-29T12:34:56",
12+
"1900-01-01T00:00:00",
13+
"2100-01-01T00:00:00",
14+
]
15+
16+
# Note: skip 'W' due to week-anchor semantics.
17+
units = ["Y", "M", "D", "h", "m", "s", "ms", "us", "ns"]
18+
19+
# Output format: "<ISO_DATETIME> <UNIT> <INT_VALUE>"
20+
for s in datetimes:
21+
for u in units:
22+
d = np.datetime64(s, u)
23+
print(f"{s} {u} {int(d.astype('int64'))}")

0 commit comments

Comments
 (0)