Skip to content

Commit 8e6a7dc

Browse files
author
Christopher Doris
committed
add NumpyDates module with DateTime64 and TimeDelta64 types, plus conversion support
1 parent 0b32b08 commit 8e6a7dc

File tree

14 files changed

+556
-10
lines changed

14 files changed

+556
-10
lines changed

src/Convert/Convert.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ using ..PythonCall
99
using ..Utils
1010
using ..C
1111
using ..Core
12+
using ..NumpyDates
1213

1314
using Dates: Date, Time, DateTime, Second, Millisecond, Microsecond, Nanosecond
1415

@@ -20,8 +21,7 @@ import ..PythonCall:
2021
pyconvert,
2122
PyConvertPriority
2223

23-
export
24-
pyconvert_isunconverted,
24+
export pyconvert_isunconverted,
2525
pyconvert_result,
2626
pyconvert_result,
2727
pyconvert_tryconvert,

src/Convert/numpy.jl

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,59 @@ function (::pyconvert_rule_numpysimplevalue{R,SAFE})(::Type{T}, x::Py) where {R,
99
end
1010
end
1111

12+
function pyconvert_rule_datetime64(::Type{DateTime64}, x::Py)
13+
pyconvert_return(C.PySimpleObject_GetValue(DateTime64, x))
14+
end
15+
16+
function pyconvert_rule_datetime64(::Type{DateTime}, x::Py)
17+
d = C.PySimpleObject_GetValue(DateTime64, x)
18+
if isnan(d)
19+
pyconvert_unconverted()
20+
else
21+
pyconvert_tryconvert(DateTime, d)
22+
end
23+
end
24+
25+
function pyconvert_rule_datetime64(::Type{Missing}, x::Py)
26+
d = C.PySimpleObject_GetValue(DateTime64, x)
27+
if isnan(d)
28+
pyconvert_return(missing)
29+
else
30+
pyconvert_unconverted()
31+
end
32+
end
33+
34+
function pyconvert_rule_datetime64(::Type{Nothing}, x::Py)
35+
d = C.PySimpleObject_GetValue(DateTime64, x)
36+
if isnan(d)
37+
pyconvert_return(nothing)
38+
else
39+
pyconvert_unconverted()
40+
end
41+
end
42+
43+
function pyconvert_rule_timedelta64(::Type{TimeDelta64}, x::Py)
44+
pyconvert_return(C.PySimpleObject_GetValue(TimeDelta64, x))
45+
end
46+
47+
function pyconvert_rule_timedelta64(::Type{Missing}, x::Py)
48+
d = C.PySimpleObject_GetValue(TimeDelta64, x)
49+
if isnan(d)
50+
pyconvert_return(missing)
51+
else
52+
pyconvert_unconverted()
53+
end
54+
end
55+
56+
function pyconvert_rule_timedelta64(::Type{Nothing}, x::Py)
57+
d = C.PySimpleObject_GetValue(TimeDelta64, x)
58+
if isnan(d)
59+
pyconvert_return(missing)
60+
else
61+
pyconvert_unconverted()
62+
end
63+
end
64+
1265
const NUMPY_SIMPLE_TYPES = [
1366
("bool_", Bool),
1467
("int8", Int8),
@@ -28,6 +81,7 @@ const NUMPY_SIMPLE_TYPES = [
2881
]
2982

3083
function init_numpy()
84+
# simple numeric scalar types
3185
for (t, T) in NUMPY_SIMPLE_TYPES
3286
isbool = occursin("bool", t)
3387
isint = occursin("int", t) || isbool
@@ -54,4 +108,25 @@ function init_numpy()
54108
iscomplex && pyconvert_add_rule(name, Complex, rule)
55109
isnumber && pyconvert_add_rule(name, Number, rule)
56110
end
111+
112+
# datetime64
113+
pyconvert_add_rule(
114+
"numpy:datetime64",
115+
DateTime64,
116+
pyconvert_rule_datetime64,
117+
PYCONVERT_PRIORITY_ARRAY,
118+
)
119+
pyconvert_add_rule("numpy:datetime64", DateTime, pyconvert_rule_datetime64)
120+
pyconvert_add_rule("numpy:datetime64", Missing, pyconvert_rule_datetime64)
121+
pyconvert_add_rule("numpy:datetime64", Nothing, pyconvert_rule_datetime64)
122+
123+
# timedelta64
124+
pyconvert_add_rule(
125+
"numpy:timedelta64",
126+
TimeDelta64,
127+
pyconvert_rule_timedelta64,
128+
PYCONVERT_PRIORITY_ARRAY,
129+
)
130+
pyconvert_add_rule("numpy:timedelta64", Missing, pyconvert_rule_timedelta64)
131+
pyconvert_add_rule("numpy:timedelta64", Nothing, pyconvert_rule_timedelta64)
57132
end
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
abstract type AbstractDateTime64 <: Dates.TimeType end
2+
3+
function Dates.value(d::AbstractDateTime64)
4+
d.value
5+
end
6+
7+
function Dates.DateTime(d::AbstractDateTime64)
8+
isnan(d) && error("Cannot convert NaT to DateTime")
9+
v = value(d)
10+
u, s = unitpair(d)
11+
v = v * s # TODO: check overflow
12+
b = Dates.DateTime(1970)
13+
if u == YEARS
14+
b + Dates.Year(v)
15+
elseif u == MONTHS
16+
b + Dates.Month(v)
17+
elseif u == WEEKS
18+
b + Dates.Week(v)
19+
elseif u == DAYS
20+
b + Dates.Day(v)
21+
elseif u == HOURS
22+
b + Dates.Hour(v)
23+
elseif u == MINUTES
24+
b + Dates.Minute(v)
25+
elseif u == SECONDS
26+
b + Dates.Second(v)
27+
elseif u == MILLISECONDS
28+
b + Dates.Millisecond(v)
29+
elseif u == MICROSECONDS
30+
b + Dates.Microsecond(v)
31+
elseif u == NANOSECONDS
32+
b + Dates.Nanosecond(v)
33+
elseif u == PICOSECONDS
34+
b + Dates.Nanosecond(v ÷ 1_000)
35+
elseif u == FEMTOSECONDS
36+
b + Dates.Nanosecond(v ÷ 1_000_000)
37+
elseif u == ATTOSECONDS
38+
b + Dates.Nanosecond(v ÷ 1_000_000_000)
39+
else
40+
error("Unsupported units: $unit_base")
41+
end
42+
end
43+
44+
function Dates.Date(d::AbstractDateTime64)
45+
isnan(d) && error("Cannot convert NaT to Date")
46+
Dates.Date(Dates.DateTime(d))
47+
end
48+
49+
function Base.isnan(d::AbstractDateTime64)
50+
value(d) == typemin(Int64)
51+
end
52+
53+
function showvalue(io::IO, d::AbstractDateTime64)
54+
u, m = unit = unitpair(d)
55+
if isnan(d)
56+
show(io, "NaT")
57+
elseif u DAYS
58+
d2 = Dates.Date(d)
59+
if value(DateTime64(d2, unit)) == value(d)
60+
show(io, string(d2))
61+
else
62+
show(io, value(d))
63+
end
64+
else
65+
d2 = Dates.DateTime(d)
66+
if value(DateTime64(d2, unit)) == value(d)
67+
show(io, string(d2))
68+
else
69+
show(io, value(d))
70+
end
71+
end
72+
nothing
73+
end
74+
75+
function defaultunit(d::AbstractDateTime64)
76+
unitpair(d)
77+
end
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
abstract type AbstractTimeDelta64 <: Dates.Period end
2+
3+
Dates.value(d::AbstractTimeDelta64) = d.value
4+
5+
function Base.isnan(d::AbstractTimeDelta64)
6+
value(d) == typemin(Int64)
7+
end

src/NumpyDates/DateTime64.jl

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# type
2+
3+
"""
4+
DateTime64(value, [unit])
5+
DateTime64(value, format, [unit])
6+
7+
Construct an `DateTime64` with the given value and unit.
8+
"""
9+
struct DateTime64 <: AbstractDateTime64
10+
value::Int64
11+
unit::Tuple{Unit,Cint}
12+
function DateTime64(value::Integer, unit::UnitArg)
13+
new(Int(value), unitpair(unit))
14+
end
15+
end
16+
17+
# accessors
18+
19+
function Dates.value(d::DateTime64)
20+
d.value
21+
end
22+
23+
function unitpair(d::DateTime64)
24+
d.unit
25+
end
26+
27+
# constructors
28+
29+
function DateTime64(d::AbstractDateTime64, unit::UnitArg = defaultunit(d))
30+
unit = unitpair(unit)
31+
if unit == unitpair(d)
32+
DateTime64(value(d), unit)
33+
elseif isnan(d)
34+
DateTime64(NAT, unit)
35+
else
36+
error(
37+
"not implemented: changing units: $(unitparam(unitpair(d))) to $(unitparam(unit))",
38+
)
39+
end
40+
end
41+
42+
function DateTime64(d::AbstractString, unit::UnitArg = defaultunit(d))
43+
unit = unitpair(unit)
44+
if d in NAT_STRINGS
45+
DateTime64(NAT, unit)
46+
else
47+
DateTime64(Dates.DateTime(d), unit)
48+
end
49+
end
50+
51+
function DateTime64(d::AbstractString, f::Dates.DateFormat, unit::UnitArg = defaultunit(d))
52+
unit = unitpair(unit)
53+
if d in NAT_STRINGS
54+
DateTime64(NAT, unit)
55+
else
56+
DateTime64(Dates.DateTime(d, f), unit)
57+
end
58+
end
59+
60+
function DateTime64(d::Dates.DateTime, unit::UnitArg = defaultunit(d))
61+
u, m = unit = unitpair(unit)
62+
if u DAYS
63+
return DateTime64(Dates.Date(d), unit)
64+
end
65+
v = value((d - Dates.DateTime(1970))::Dates.Millisecond)
66+
if u == HOURS
67+
m = mul(m, 1000 * 60 * 60)
68+
elseif u == MINUTES
69+
m = mul(m, 1000 * 60)
70+
elseif u == SECONDS
71+
m = mul(m, 1000)
72+
elseif u == MILLISECONDS
73+
# nothing
74+
elseif u == MICROSECONDS
75+
v = mul(v, 1000)
76+
elseif u == NANOSECONDS
77+
v = mul(v, 1000_000)
78+
elseif u == PICOSECONDS
79+
v = mul(v, 1000_000_000)
80+
elseif u == FEMTOSECONDS
81+
v = mul(v, 1000_000_000_000)
82+
elseif u == ATTOSECONDS
83+
v = mul(v, 1000_000_000_000)
84+
else
85+
error("unknown unit: $u")
86+
end
87+
v = v ÷ m
88+
DateTime64(v, unit)
89+
end
90+
91+
function DateTime64(d::Dates.Date, unit::UnitArg = defaultunit(d))
92+
u, m = unit = unitpair(unit)
93+
if u == YEARS
94+
v = value(Dates.Year(d)) - 1970
95+
elseif u == MONTHS
96+
v = 12 * (value(Dates.Year(d)) - 1970) + (value(Dates.Month(d)) - 1)
97+
else
98+
v = value((d - Dates.Date(1970))::Dates.Day)
99+
if u == WEEKS
100+
m = mul(m, 7)
101+
elseif u == DAYS
102+
# nothing
103+
elseif u == HOURS
104+
v = mul(v, 24)
105+
elseif u == MINUTES
106+
v = mul(v, 24 * 60)
107+
elseif u == SECONDS
108+
v = mul(v, 24 * 60 * 60)
109+
elseif u == MILLISECONDS
110+
v = mul(v, 24 * 60 * 60 * 1000)
111+
elseif u == MICROSECONDS
112+
v = mul(v, 24 * 60 * 60 * 1000_000)
113+
elseif u == NANOSECONDS
114+
v = mul(v, 24 * 60 * 60 * 1000_000_000)
115+
elseif u == PICOSECONDS
116+
v = mul(v, 24 * 60 * 60 * 1000_000_000_000)
117+
elseif u == FEMTOSECONDS
118+
throw(OverflowError(""))
119+
elseif u == ATTOSECONDS
120+
throw(OverflowError(""))
121+
else
122+
error("unknown unit: $u")
123+
end
124+
end
125+
v = v ÷ m
126+
DateTime64(v, unit)
127+
end
128+
129+
# show
130+
131+
function Base.show(io::IO, d::DateTime64)
132+
if get(io, :typeinfo, Any) == typeof(d)
133+
showvalue(io, d)
134+
else
135+
show(io, typeof(d))
136+
print(io, "(")
137+
showvalue(io, d)
138+
print(io, ", ", unitparam(unitpair(d)), ")")
139+
end
140+
nothing
141+
end

0 commit comments

Comments
 (0)