From 9b9421190e9fed51b19b3e26276f535403f3dda1 Mon Sep 17 00:00:00 2001 From: Nathan Hattersley Date: Fri, 15 Dec 2023 14:15:09 -0600 Subject: [PATCH 1/3] Add parsing for datetime.time Using same patterns as elsewhere in this file (and avoiding timezones like in DateTime), add support for parsing times from the Python datetime package to Julia's Dates.Time struct --- src/pydates.jl | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/pydates.jl b/src/pydates.jl index 14940956..1b811026 100644 --- a/src/pydates.jl +++ b/src/pydates.jl @@ -60,9 +60,11 @@ const PyDate_HEAD = sizeof(Int)+sizeof(PyPtr)+sizeof(Py_hash_t)+1 const DateType = Ref{PyPtr}(0) const DateTimeType = Ref{PyPtr}(0) const DeltaType = Ref{PyPtr}(0) +const TimeType = Ref{PyPtr}(0) const Date_FromDate = Ref{Ptr{Cvoid}}(0) const DateTime_FromDateAndTime = Ref{Ptr{Cvoid}}(0) const Delta_FromDelta = Ref{Ptr{Cvoid}}(0) +const Time_FromTime = Ref{Ptr{Cvoid}}(0) function init_datetime() # emulate PyDateTime_IMPORT: PyDateTimeAPI = unsafe_load(@pycheckn ccall((@pysym :PyCapsule_Import), @@ -71,9 +73,11 @@ function init_datetime() DateType[] = PyDateTimeAPI.DateType DateTimeType[] = PyDateTimeAPI.DateTimeType DeltaType[] = PyDateTimeAPI.DeltaType + TimeType[] = PyDateTimeAPI.TimeType Date_FromDate[] = PyDateTimeAPI.Date_FromDate DateTime_FromDateAndTime[] = PyDateTimeAPI.DateTime_FromDateAndTime Delta_FromDelta[] = PyDateTimeAPI.Delta_FromDelta + Time_FromTime[] = PyDateTimeAPI.Time_FromTime end PyObject(d::Dates.Date) = @@ -99,6 +103,13 @@ PyDelta_FromDSU(days, seconds, useconds) = PyObject(p::Dates.Day) = PyDelta_FromDSU(Dates.value(p), 0, 0) +PyObject(t::Dates.Time) = + PyObject(@pycheckn ccall(Time_FromTime[], PyPtr, + (Cint, Cint, Cint, Cint, PyPtr, PyPtr), + Dates.hour(t), Dates.minute(t), Dates.second(t), + Dates.millisecond(t) * 1000, + pynothing[], TimeType[])) + function PyObject(p::Dates.Second) # normalize to make Cint overflow less likely s = Dates.value(p) @@ -120,12 +131,15 @@ end PyDate_Check(o::PyObject) = pyisinstance(o, DateType[]) PyDateTime_Check(o::PyObject) = pyisinstance(o, DateTimeType[]) PyDelta_Check(o::PyObject) = pyisinstance(o, DeltaType[]) +PyTime_Check(o::PyObject) = pyisinstance(o, TimeType[]) function pydate_query(o::PyObject) if PyDate_Check(o) return PyDateTime_Check(o) ? Dates.DateTime : Dates.Date elseif PyDelta_Check(o) return Dates.Millisecond + elseif PyTime_Check(o) + return Dates.Time else return Union{} end @@ -163,6 +177,20 @@ function convert(::Type{Dates.Date}, o::PyObject) end end +function convert(::Type{Dates.Time}, o::PyObject) + if PyTime_Check(o) + GC.@preserve o let dt = convert(Ptr{UInt8}, PyPtr(o)) + PyDate_HEAD + Dates.Time(unsafe_load(dt,1), unsafe_load(dt,2), # hour, minute + unsafe_load(dt,3), # second + div((UInt(unsafe_load(dt,4)) << 16) | + (UInt(unsafe_load(dt,5)) << 8) | + unsafe_load(dt,6), 1000)) # μs ÷ 1000 + end + else + throw(ArgumentError("unknown Time type $o")) + end +end + function delta_dsμ(o::PyObject) PyDelta_Check(o) || throw(ArgumentError("$o is not a timedelta instance")) p = GC.@preserve o unsafe_load(convert(Ptr{PyDateTime_Delta{Py_hash_t}}, PyPtr(o))) From f3b5d453b15578eef8a11e400c293b9e8146db40 Mon Sep 17 00:00:00 2001 From: Nathan Hattersley Date: Sun, 17 Dec 2023 10:03:46 -0600 Subject: [PATCH 2/3] test roundtripeq for Dates.Time --- test/runtests.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/test/runtests.jl b/test/runtests.jl index 1486c480..13cc2edf 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -168,6 +168,7 @@ const PyInt = pyversion < v"3" ? Int : Clonglong @test roundtripeq(Dates.Date(2012,3,4)) @test roundtripeq(Dates.DateTime(2012,3,4, 7,8,9,11)) + @test roundtripeq(Dates.Time(7,8,9,11)) @test roundtripeq(Dates.Millisecond(typemax(Int32))) @test roundtripeq(Dates.Millisecond(typemin(Int32))) @test roundtripeq(Dates.Second, Dates.Second(typemax(Int32))) From c74af347a1aa1f3b97f51f7b79b13ccd91ab7963 Mon Sep 17 00:00:00 2001 From: Nathan Hattersley Date: Sun, 17 Dec 2023 10:10:30 -0600 Subject: [PATCH 3/3] satisfy coverage checker --- test/runtests.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/runtests.jl b/test/runtests.jl index 13cc2edf..5e4e31ee 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -167,8 +167,11 @@ const PyInt = pyversion < v"3" ? Int : Clonglong end @test roundtripeq(Dates.Date(2012,3,4)) + @test_throws ArgumentError convert(Dates.Date, PyObject(42)) @test roundtripeq(Dates.DateTime(2012,3,4, 7,8,9,11)) + @test_throws ArgumentError convert(Dates.DateTime, PyObject(42)) @test roundtripeq(Dates.Time(7,8,9,11)) + @test_throws ArgumentError convert(Dates.Time, PyObject(42)) @test roundtripeq(Dates.Millisecond(typemax(Int32))) @test roundtripeq(Dates.Millisecond(typemin(Int32))) @test roundtripeq(Dates.Second, Dates.Second(typemax(Int32)))