From 093bdcb09b38f60f9b277fd96978bb5628fc933e Mon Sep 17 00:00:00 2001
From: hhaensel <helmut.haensel@merckgroup.com>
Date: Mon, 21 Feb 2022 16:26:28 +0100
Subject: [PATCH 01/22] add support for `@py from <module> import`

---
 src/py_macro.jl | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/src/py_macro.jl b/src/py_macro.jl
index abf3de40..a4a5c7ce 100644
--- a/src/py_macro.jl
+++ b/src/py_macro.jl
@@ -793,10 +793,26 @@ For example:
 - `x.foo` is translated to `pygetattr(x, "foo")`
 
 Compound statements such as `begin`, `if`, `while` and `for` are supported.
+Import statements are supported, e.g.
+- `import foo, bar`
+- `from os.path import join as py_joinpath, exists`
 
 See the online documentation for more details.
 """
 macro py(ex)
     esc(py_macro(ex, __module__, __source__))
 end
+
+macro py(keyword, modulename, ex)
+    keyword == :from || return :( nothing )
+
+    d = Dict(isa(a.args[1], Symbol) ? a.args[1] => a.args[1] : a.args[1].args[1] => a.args[2]  for a in ex.args)
+    vars = Expr(:tuple, values(d)...)
+    imports = Tuple(keys(d))
+
+    esc(quote
+        $vars = pyimport($(string(modulename)) => $(string.(imports)))
+    end)
+end
+
 export @py

From 5740b592cc6f59c237b27d3d349bd78852db37d3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Helmut=20H=C3=A4nsel?= <helmut.haensel@gmx.de>
Date: Sun, 16 Jun 2024 23:54:41 +0200
Subject: [PATCH 02/22] support timedelta, timedelta64, datetime64 and
 respective conversions

---
 src/Convert/Convert.jl   |  1 +
 src/Convert/numpy.jl     | 74 ++++++++++++++++++++++++++++++++++++++++
 src/Convert/pyconvert.jl |  6 ++++
 src/Convert/rules.jl     | 13 +++++++
 src/Core/Core.jl         |  2 ++
 src/Core/Py.jl           |  1 +
 src/Core/builtins.jl     | 18 ++++++++++
 7 files changed, 115 insertions(+)

diff --git a/src/Convert/Convert.jl b/src/Convert/Convert.jl
index 0cccbf58..20c70f80 100644
--- a/src/Convert/Convert.jl
+++ b/src/Convert/Convert.jl
@@ -8,6 +8,7 @@ module Convert
 using ..Core
 using ..Core: C, Utils, @autopy, getptr, incref, pynew, PyNULL, pyisnull, pydel!, pyisint, iserrset_ambig, pyisnone, pyisTrue, pyisFalse, pyfloat_asdouble, pycomplex_ascomplex, pyisstr, pystr_asstring, pyisbytes, pybytes_asvector, pybytes_asUTF8string, pyisfloat, pyisrange, pytuple_getitem, unsafe_pynext, pyistuple, pydatetimetype, pytime_isaware, pydatetime_isaware, _base_pydatetime, _base_datetime, errmatches, errclear, errset, pyiscomplex, pythrow, pybool_asbool
 using Dates: Date, Time, DateTime, Second, Millisecond, Microsecond, Nanosecond
+using Dates: Year, Month, Day, Hour, Minute, Week, Period, CompoundPeriod, canonicalize
 
 import ..Core: pyconvert
 
diff --git a/src/Convert/numpy.jl b/src/Convert/numpy.jl
index f9dadbd4..2634ac0d 100644
--- a/src/Convert/numpy.jl
+++ b/src/Convert/numpy.jl
@@ -27,6 +27,70 @@ const NUMPY_SIMPLE_TYPES = [
     ("complex128", ComplexF64),
 ]
 
+function pydatetime64(
+    _year::Int=0, _month::Int=1, _day::Int=1, _hour::Int=0, _minute::Int=0,_second::Int=0, _millisecond::Int=0, _microsecond::Int=0, _nanosecond::Int=0;
+    year::Int=_year, month::Int=_month, day::Int=_day, hour::Int=_hour, minute::Int=_minute, second::Int=_second,
+    millisecond::Int=_millisecond, microsecond::Int=_microsecond, nanosecond::Int=_nanosecond
+)
+    pyimport("numpy").datetime64("$(DateTime(year, month, day, hour, minute, second))") + pytimedelta64(;millisecond, microsecond, nanosecond)
+end
+function pydatetime64(@nospecialize(x::T)) where T <: Period
+    T <: Union{Week, Day, Hour, Minute, Second, Millisecond, Microsecond} || 
+        error("Unsupported Period type: ", "Year, Month and Nanosecond are not supported, consider using pytimedelta64 instead.")
+    args = T .== (Day, Second, Millisecond, Microsecond,  Minute, Hour, Week)
+    pydatetime64(x.value .* args...)
+end
+function pydatetime64(x::CompoundPeriod)
+    x =  canonicalize(x)
+    isempty(x.periods) ? pydatetime64(Second(0)) : sum(pydatetime64.(x.periods))
+end
+export pydatetime64
+
+function pytimedelta64(
+    _year::Int=0, _month::Int=0, _day::Int=0, _hour::Int=0, _minute::Int=0, _second::Int=0, _millisecond::Int=0, _microsecond::Int=0, _nanosecond::Int=0, _week::Int=0;
+    year::Int=_year, month::Int=_month, day::Int=_day, hour::Int=_hour, minute::Int=_minute, second::Int=_second, microsecond::Int=_microsecond, millisecond::Int=_millisecond, nanosecond::Int=_nanosecond, week::Int=_week)
+    pytimedelta64(sum((
+        Year(year), Month(month), # you cannot mix year or month with any of the below units in python, the error will be thrown by `pytimedelta64(::CompoundPeriod)`
+        Day(day), Hour(hour), Minute(minute), Second(second), Millisecond(millisecond), Microsecond(microsecond), Nanosecond(nanosecond))
+    ))
+end
+function pytimedelta64(@nospecialize(x::T)) where T <: Period
+    index = findfirst(==(T), (Year, Month, Week, Day, Hour, Minute, Second, Millisecond, Microsecond, Nanosecond, T))
+    unit = ("Y", "M", "W", "D", "h", "m", "s", "ms", "us", "ns", "")[index]
+    pyimport("numpy").timedelta64(x.value, unit)
+end
+function pytimedelta64(x::CompoundPeriod)
+    x =  canonicalize(x)
+    isempty(x.periods) ? pytimedelta64(Second(0)) : sum(pytimedelta64.(x.periods))
+end
+export pytimedelta64
+
+function pyconvert_rule_datetime64(::Type{DateTime}, x::Py)
+    unit, value = pyconvert(Tuple, pyimport("numpy").datetime_data(x))
+    # strangely, datetime_data does not return the value correctly
+    # so we retrieve the value from the byte representation
+    value = reinterpret(Int64, pyconvert(Vector, x))[1]
+    units = ("Y", "M", "W", "D", "h", "m", "s", "ms", "us", "ns")
+    types = (Year, Month, Week, Day, Hour, Minute, Second, Millisecond, Microsecond, Nanosecond)
+    T = types[findfirst(==(unit), units)]
+    pyconvert_return(DateTime(_base_datetime) + T(value))
+end
+
+function pyconvert_rule_timedelta64(::Type{CompoundPeriod}, x::Py)
+    unit, value = pyconvert(Tuple, pyimport("numpy").datetime_data(x))
+    # strangely, datetime_data does not return the value correctly
+    # so we retrieve the value from the byte representation 
+    value = reinterpret(Int64, pyconvert(Vector, x))[1]
+    units = ("Y", "M", "W", "D", "h", "m", "s", "ms", "us", "ns")
+    types = (Year, Month, Week, Day, Hour, Minute, Second, Millisecond, Microsecond, Nanosecond)
+    T = types[findfirst(==(unit), units)]
+    pyconvert_return(CompoundPeriod(T(value)) |> canonicalize)
+end
+
+function pyconvert_rule_timedelta64(::Type{T}, x::Py) where T<:Period
+    pyconvert_return(convert(T, pyconvert_rule_timedelta64(CompoundPeriod, x)))
+end
+
 function init_numpy()
     for (t, T) in NUMPY_SIMPLE_TYPES
         isbool = occursin("bool", t)
@@ -52,4 +116,14 @@ function init_numpy()
         iscomplex && pyconvert_add_rule(name, Complex, rule)
         isnumber && pyconvert_add_rule(name, Number, rule)
     end
+
+    priority = PYCONVERT_PRIORITY_ARRAY
+    pyconvert_add_rule("numpy:datetime64", DateTime, pyconvert_rule_datetime64, priority)
+    for T in (CompoundPeriod, Year, Month, Day, Hour, Minute, Second, Millisecond, Microsecond, Nanosecond, Week)
+        pyconvert_add_rule("numpy:timedelta64", T, pyconvert_rule_timedelta64, priority)
+    end
+
+    priority = PYCONVERT_PRIORITY_CANONICAL
+    pyconvert_add_rule("numpy:datetime64", DateTime, pyconvert_rule_datetime64, priority)
+    pyconvert_add_rule("numpy:timedelta64", Nanosecond, pyconvert_rule_timedelta, priority)    
 end
diff --git a/src/Convert/pyconvert.jl b/src/Convert/pyconvert.jl
index 9963940f..c63f5ad4 100644
--- a/src/Convert/pyconvert.jl
+++ b/src/Convert/pyconvert.jl
@@ -391,6 +391,12 @@ function init_pyconvert()
     push!(PYCONVERT_EXTRATYPES, pyimport("numbers" => ("Number", "Complex", "Real", "Rational", "Integral"))...)
     push!(PYCONVERT_EXTRATYPES, pyimport("collections.abc" => ("Iterable", "Sequence", "Set", "Mapping"))...)
 
+    priority = PYCONVERT_PRIORITY_ARRAY
+    pyconvert_add_rule("datetime:datetime", DateTime, pyconvert_rule_datetime, priority)
+    for T in (Millisecond, Second, Nanosecond, Day, Hour, Minute, Second, Millisecond, Week, CompoundPeriod)
+        pyconvert_add_rule("datetime:timedelta", T, pyconvert_rule_timedelta, priority)
+    end
+    
     priority = PYCONVERT_PRIORITY_CANONICAL
     pyconvert_add_rule("builtins:NoneType", Nothing, pyconvert_rule_none, priority)
     pyconvert_add_rule("builtins:bool", Bool, pyconvert_rule_bool, priority)
diff --git a/src/Convert/rules.jl b/src/Convert/rules.jl
index 829a796a..ba9d689b 100644
--- a/src/Convert/rules.jl
+++ b/src/Convert/rules.jl
@@ -439,3 +439,16 @@ function pyconvert_rule_timedelta(::Type{Second}, x::Py)
     end
     return Second(days * 3600 * 24 + seconds)
 end
+
+function pyconvert_rule_timedelta(::Type{<:CompoundPeriod}, x::Py)
+    days = pyconvert(Int, x.days)
+    seconds = pyconvert(Int, x.seconds)
+    microseconds = pyconvert(Int, x.microseconds)
+    nanoseconds = pyhasattr(x, "nanoseconds") ? pyconvert(Int, x.nanoseconds) : 0
+    timedelta = Day(days) + Second(seconds) + Microsecond(microseconds) + Nanosecond(nanoseconds)
+    return pyconvert_return(timedelta)
+end
+
+function pyconvert_rule_timedelta(::Type{T}, x::Py) where T<:Period
+    pyconvert_return(convert(T, pyconvert_rule_timedelta(CompoundPeriod, x)))
+end
diff --git a/src/Core/Core.jl b/src/Core/Core.jl
index a6e27c37..15cb0b79 100644
--- a/src/Core/Core.jl
+++ b/src/Core/Core.jl
@@ -14,6 +14,8 @@ using ..GC: GC
 using ..Utils: Utils
 using Base: @propagate_inbounds, @kwdef
 using Dates: Date, Time, DateTime, year, month, day, hour, minute, second, millisecond, microsecond, nanosecond
+using Dates: Day, Second, Millisecond, Microsecond, Minute, Hour, Week
+using Dates: Period, CompoundPeriod, canonicalize
 using MacroTools: MacroTools, @capture
 using Markdown: Markdown
 
diff --git a/src/Core/Py.jl b/src/Core/Py.jl
index 396b39d8..945b6939 100644
--- a/src/Core/Py.jl
+++ b/src/Core/Py.jl
@@ -149,6 +149,7 @@ Py(x::AbstractRange{<:Union{Int8,Int16,Int32,Int64,Int128,UInt8,UInt16,UInt32,UI
 Py(x::Date) = pydate(x)
 Py(x::Time) = pytime(x)
 Py(x::DateTime) = pydatetime(x)
+Py(x::Union{Period, CompoundPeriod}) = pytimedelta(x)
 
 Base.string(x::Py) = pyisnull(x) ? "<py NULL>" : pystr(String, x)
 Base.print(io::IO, x::Py) = print(io, string(x))
diff --git a/src/Core/builtins.jl b/src/Core/builtins.jl
index 165e281c..6df4201f 100644
--- a/src/Core/builtins.jl
+++ b/src/Core/builtins.jl
@@ -1065,6 +1065,24 @@ end
 pydatetime(x::Date) = pydatetime(year(x), month(x), day(x))
 export pydatetime
 
+function pytimedelta(
+    _day::Int=0, _second::Int=0, _microsecond::Int=0, _millisecond::Int=0, _minute::Int=0, _hour::Int=0, _week::Int=0;
+    day::Int=_day, second::Int=_second, microsecond::Int=_microsecond, millisecond::Int=_millisecond, minute::Int=_minute, hour::Int=_hour, week::Int=_week
+)
+    pyimport("datetime").timedelta(day, second, microsecond, millisecond, minute, hour, week)
+end
+function pytimedelta(@nospecialize(x::T)) where T <: Period
+    T <: Union{Week, Day, Hour, Minute, Second, Millisecond, Microsecond} || 
+        error("Unsupported Period type: ", "Year, Month and Nanosecond are not supported, consider using pytimedelta64 instead.")
+    args = T .== (Day, Second, Millisecond, Microsecond,  Minute, Hour, Week)
+    pytimedelta(x.value .* args...)
+end
+function pytimedelta(x::CompoundPeriod)
+    x =  canonicalize(x)
+    isempty(x.periods) ? pytimedelta(Second(0)) : sum(pytimedelta.(x.periods))
+end
+export pytimedelta
+
 function pytime_isaware(x)
     tzinfo = pygetattr(x, "tzinfo")
     if pyisnone(tzinfo)

From f897600b25fedc31ce2b3c378dca61d68d1e95b0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Helmut=20H=C3=A4nsel?= <helmut.haensel@gmx.de>
Date: Mon, 17 Jun 2024 00:20:12 +0200
Subject: [PATCH 03/22] fix week kw in pytimedelta64, typo (space) in builtins

---
 src/Convert/numpy.jl | 2 +-
 src/Core/builtins.jl | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/Convert/numpy.jl b/src/Convert/numpy.jl
index 2634ac0d..9909fed4 100644
--- a/src/Convert/numpy.jl
+++ b/src/Convert/numpy.jl
@@ -51,7 +51,7 @@ function pytimedelta64(
     year::Int=_year, month::Int=_month, day::Int=_day, hour::Int=_hour, minute::Int=_minute, second::Int=_second, microsecond::Int=_microsecond, millisecond::Int=_millisecond, nanosecond::Int=_nanosecond, week::Int=_week)
     pytimedelta64(sum((
         Year(year), Month(month), # you cannot mix year or month with any of the below units in python, the error will be thrown by `pytimedelta64(::CompoundPeriod)`
-        Day(day), Hour(hour), Minute(minute), Second(second), Millisecond(millisecond), Microsecond(microsecond), Nanosecond(nanosecond))
+        Day(day), Hour(hour), Minute(minute), Second(second), Millisecond(millisecond), Microsecond(microsecond), Nanosecond(nanosecond), Week(week))
     ))
 end
 function pytimedelta64(@nospecialize(x::T)) where T <: Period
diff --git a/src/Core/builtins.jl b/src/Core/builtins.jl
index 6df4201f..9b78c7e8 100644
--- a/src/Core/builtins.jl
+++ b/src/Core/builtins.jl
@@ -1074,7 +1074,7 @@ end
 function pytimedelta(@nospecialize(x::T)) where T <: Period
     T <: Union{Week, Day, Hour, Minute, Second, Millisecond, Microsecond} || 
         error("Unsupported Period type: ", "Year, Month and Nanosecond are not supported, consider using pytimedelta64 instead.")
-    args = T .== (Day, Second, Millisecond, Microsecond,  Minute, Hour, Week)
+    args = T .== (Day, Second, Millisecond, Microsecond, Minute, Hour, Week)
     pytimedelta(x.value .* args...)
 end
 function pytimedelta(x::CompoundPeriod)

From 1e8d4105c2aa14fdef0078e7f911d8a875e54327 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Helmut=20H=C3=A4nsel?= <helmut.haensel@gmx.com>
Date: Mon, 17 Jun 2024 10:43:12 +0200
Subject: [PATCH 04/22] correct handling of count in 64-bit conversion rules

---
 src/Convert/numpy.jl | 12 ++++--------
 1 file changed, 4 insertions(+), 8 deletions(-)

diff --git a/src/Convert/numpy.jl b/src/Convert/numpy.jl
index 9909fed4..bb7bacee 100644
--- a/src/Convert/numpy.jl
+++ b/src/Convert/numpy.jl
@@ -66,25 +66,21 @@ end
 export pytimedelta64
 
 function pyconvert_rule_datetime64(::Type{DateTime}, x::Py)
-    unit, value = pyconvert(Tuple, pyimport("numpy").datetime_data(x))
-    # strangely, datetime_data does not return the value correctly
-    # so we retrieve the value from the byte representation
+    unit, count = pyconvert(Tuple, pyimport("numpy").datetime_data(x))
     value = reinterpret(Int64, pyconvert(Vector, x))[1]
     units = ("Y", "M", "W", "D", "h", "m", "s", "ms", "us", "ns")
     types = (Year, Month, Week, Day, Hour, Minute, Second, Millisecond, Microsecond, Nanosecond)
     T = types[findfirst(==(unit), units)]
-    pyconvert_return(DateTime(_base_datetime) + T(value))
+    pyconvert_return(DateTime(_base_datetime) + T(value * count))
 end
 
 function pyconvert_rule_timedelta64(::Type{CompoundPeriod}, x::Py)
-    unit, value = pyconvert(Tuple, pyimport("numpy").datetime_data(x))
-    # strangely, datetime_data does not return the value correctly
-    # so we retrieve the value from the byte representation 
+    unit, count = pyconvert(Tuple, pyimport("numpy").datetime_data(x))
     value = reinterpret(Int64, pyconvert(Vector, x))[1]
     units = ("Y", "M", "W", "D", "h", "m", "s", "ms", "us", "ns")
     types = (Year, Month, Week, Day, Hour, Minute, Second, Millisecond, Microsecond, Nanosecond)
     T = types[findfirst(==(unit), units)]
-    pyconvert_return(CompoundPeriod(T(value)) |> canonicalize)
+    pyconvert_return(CompoundPeriod(T(value * count)) |> canonicalize)
 end
 
 function pyconvert_rule_timedelta64(::Type{T}, x::Py) where T<:Period

From 9dfc0ddaaabb997f887b07302ebfe67d766708e5 Mon Sep 17 00:00:00 2001
From: hhaensel <31985040+hhaensel@users.noreply.github.com>
Date: Fri, 6 Sep 2024 22:42:12 +0200
Subject: [PATCH 05/22] Apply suggestions from code review

Co-authored-by: Miles Cranmer <miles.cranmer@gmail.com>
---
 src/Convert/numpy.jl | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/Convert/numpy.jl b/src/Convert/numpy.jl
index 45b4c8bc..716eb50c 100644
--- a/src/Convert/numpy.jl
+++ b/src/Convert/numpy.jl
@@ -36,13 +36,13 @@ function pydatetime64(
 end
 function pydatetime64(@nospecialize(x::T)) where T <: Period
     T <: Union{Week, Day, Hour, Minute, Second, Millisecond, Microsecond} || 
-        error("Unsupported Period type: ", "Year, Month and Nanosecond are not supported, consider using pytimedelta64 instead.")
-    args = T .== (Day, Second, Millisecond, Microsecond,  Minute, Hour, Week)
+        error("Unsupported Period type: `$x::$T`. Consider using pytimedelta64 instead.")
+    args = map(Base.Fix1(isa, x), (Day, Second, Millisecond, Microsecond,  Minute, Hour, Week))
     pydatetime64(x.value .* args...)
 end
 function pydatetime64(x::CompoundPeriod)
     x =  canonicalize(x)
-    isempty(x.periods) ? pydatetime64(Second(0)) : sum(pydatetime64.(x.periods))
+    isempty(x.periods) ? pydatetime64(Second(0)) : sum(pydatetime64, x.periods)
 end
 export pydatetime64
 
@@ -55,7 +55,7 @@ function pytimedelta64(
     ))
 end
 function pytimedelta64(@nospecialize(x::T)) where T <: Period
-    index = findfirst(==(T), (Year, Month, Week, Day, Hour, Minute, Second, Millisecond, Microsecond, Nanosecond, T))
+    index = findfirst(==(T), (Year, Month, Week, Day, Hour, Minute, Second, Millisecond, Microsecond, Nanosecond, T))::Int
     unit = ("Y", "M", "W", "D", "h", "m", "s", "ms", "us", "ns", "")[index]
     pyimport("numpy").timedelta64(x.value, unit)
 end
@@ -70,7 +70,7 @@ function pyconvert_rule_datetime64(::Type{DateTime}, x::Py)
     value = reinterpret(Int64, pyconvert(Vector, x))[1]
     units = ("Y", "M", "W", "D", "h", "m", "s", "ms", "us", "ns")
     types = (Year, Month, Week, Day, Hour, Minute, Second, Millisecond, Microsecond, Nanosecond)
-    T = types[findfirst(==(unit), units)]
+    T = types[findfirst(==(unit), units)::Int]
     pyconvert_return(DateTime(_base_datetime) + T(value * count))
 end
 
@@ -79,7 +79,7 @@ function pyconvert_rule_timedelta64(::Type{CompoundPeriod}, x::Py)
     value = reinterpret(Int64, pyconvert(Vector, x))[1]
     units = ("Y", "M", "W", "D", "h", "m", "s", "ms", "us", "ns")
     types = (Year, Month, Week, Day, Hour, Minute, Second, Millisecond, Microsecond, Nanosecond)
-    T = types[findfirst(==(unit), units)]
+    T = types[findfirst(==(unit), units)::Int]
     pyconvert_return(CompoundPeriod(T(value * count)) |> canonicalize)
 end
 

From a3a2b9713ba7d873b89efa19c765726037333daf Mon Sep 17 00:00:00 2001
From: hhaensel <31985040+hhaensel@users.noreply.github.com>
Date: Fri, 6 Sep 2024 22:44:41 +0200
Subject: [PATCH 06/22] Apply suggestions from code review part II

Co-authored-by: Miles Cranmer <miles.cranmer@gmail.com>
---
 src/Convert/numpy.jl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/Convert/numpy.jl b/src/Convert/numpy.jl
index 716eb50c..0c025e37 100644
--- a/src/Convert/numpy.jl
+++ b/src/Convert/numpy.jl
@@ -38,7 +38,7 @@ function pydatetime64(@nospecialize(x::T)) where T <: Period
     T <: Union{Week, Day, Hour, Minute, Second, Millisecond, Microsecond} || 
         error("Unsupported Period type: `$x::$T`. Consider using pytimedelta64 instead.")
     args = map(Base.Fix1(isa, x), (Day, Second, Millisecond, Microsecond,  Minute, Hour, Week))
-    pydatetime64(x.value .* args...)
+    pydatetime64(map(Base.Fix1(*, x.value), args)...)
 end
 function pydatetime64(x::CompoundPeriod)
     x =  canonicalize(x)

From daf97594acfb0e7a46d86bb1d975938cc23fc59f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Helmut=20H=C3=A4nsel?= <helmut.haensel@gmx.de>
Date: Sat, 7 Sep 2024 01:34:33 +0200
Subject: [PATCH 07/22] reviewers suggestions part III

---
 src/Convert/numpy.jl | 18 ++++++++++--------
 1 file changed, 10 insertions(+), 8 deletions(-)

diff --git a/src/Convert/numpy.jl b/src/Convert/numpy.jl
index 0c025e37..6d830573 100644
--- a/src/Convert/numpy.jl
+++ b/src/Convert/numpy.jl
@@ -28,9 +28,9 @@ const NUMPY_SIMPLE_TYPES = [
 ]
 
 function pydatetime64(
-    _year::Int=0, _month::Int=1, _day::Int=1, _hour::Int=0, _minute::Int=0,_second::Int=0, _millisecond::Int=0, _microsecond::Int=0, _nanosecond::Int=0;
-    year::Int=_year, month::Int=_month, day::Int=_day, hour::Int=_hour, minute::Int=_minute, second::Int=_second,
-    millisecond::Int=_millisecond, microsecond::Int=_microsecond, nanosecond::Int=_nanosecond
+    _year::Integer=0, _month::Integer=1, _day::Integer=1, _hour::Integer=0, _minute::Integer=0,_second::Integer=0, _millisecond::Integer=0, _microsecond::Integer=0, _nanosecond::Integer=0;
+    year::Integer=_year, month::Integer=_month, day::Integer=_day, hour::Integer=_hour, minute::Integer=_minute, second::Integer=_second,
+    millisecond::Integer=_millisecond, microsecond::Integer=_microsecond, nanosecond::Integer=_nanosecond
 )
     pyimport("numpy").datetime64("$(DateTime(year, month, day, hour, minute, second))") + pytimedelta64(;millisecond, microsecond, nanosecond)
 end
@@ -47,10 +47,12 @@ end
 export pydatetime64
 
 function pytimedelta64(
-    _year::Int=0, _month::Int=0, _day::Int=0, _hour::Int=0, _minute::Int=0, _second::Int=0, _millisecond::Int=0, _microsecond::Int=0, _nanosecond::Int=0, _week::Int=0;
-    year::Int=_year, month::Int=_month, day::Int=_day, hour::Int=_hour, minute::Int=_minute, second::Int=_second, microsecond::Int=_microsecond, millisecond::Int=_millisecond, nanosecond::Int=_nanosecond, week::Int=_week)
+    _year::Integer=0, _month::Integer=0, _day::Integer=0, _hour::Integer=0, _minute::Integer=0, _second::Integer=0, _millisecond::Integer=0, _microsecond::Integer=0, _nanosecond::Integer=0, _week::Integer=0;
+    year::Integer=_year, month::Integer=_month, day::Integer=_day, hour::Integer=_hour, minute::Integer=_minute, second::Integer=_second, microsecond::Integer=_microsecond, millisecond::Integer=_millisecond, nanosecond::Integer=_nanosecond, week::Integer=_week)
     pytimedelta64(sum((
-        Year(year), Month(month), # you cannot mix year or month with any of the below units in python, the error will be thrown by `pytimedelta64(::CompoundPeriod)`
+        Year(year), Month(month),
+        # you cannot mix year or month with any of the below units in python
+        # in case of wrong usage a descriptive error message will by thrown by the underlying python function
         Day(day), Hour(hour), Minute(minute), Second(second), Millisecond(millisecond), Microsecond(microsecond), Nanosecond(nanosecond), Week(week))
     ))
 end
@@ -117,8 +119,8 @@ function init_numpy()
 
     priority = PYCONVERT_PRIORITY_ARRAY
     pyconvert_add_rule("numpy:datetime64", DateTime, pyconvert_rule_datetime64, priority)
-    for T in (CompoundPeriod, Year, Month, Day, Hour, Minute, Second, Millisecond, Microsecond, Nanosecond, Week)
-        pyconvert_add_rule("numpy:timedelta64", T, pyconvert_rule_timedelta64, priority)
+    let TT = (CompoundPeriod, Year, Month, Day, Hour, Minute, Second, Millisecond, Microsecond, Nanosecond, Week)
+        Base.Cartesian.@nexprs 11 i -> pyconvert_add_rule("numpy:timedelta64", TT[i], pyconvert_rule_timedelta64, priority)
     end
 
     priority = PYCONVERT_PRIORITY_CANONICAL

From 7391b8d3682b149806368741af0ae29c517f9ca2 Mon Sep 17 00:00:00 2001
From: hhaensel <helmut.haensel@gmx.com>
Date: Sun, 19 Jan 2025 12:08:57 +0100
Subject: [PATCH 08/22] add tests for pytimedelta

---
 test/Core.jl | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/test/Core.jl b/test/Core.jl
index c5a9b48e..feadf855 100644
--- a/test/Core.jl
+++ b/test/Core.jl
@@ -698,6 +698,9 @@ end
     x7 = pydatetime(DateTime(2001, 2, 3, 4, 5, 6, 7))
     @test pyisinstance(x7, dt.datetime)
     @test pyeq(Bool, x7, dt.datetime(2001, 2, 3, 4, 5, 6, 7000))
+    x8 = pydatetime(2001, 2, 3, 4, 5, 6, 7)
+    dx = pytimedelta(366, 3661, 1)
+    pyeq(Bool, x8 - x6, dx)
 end
 
 @testitem "code" begin

From 8f285670d13362b041d4e1cd23fdf96e8171d6df Mon Sep 17 00:00:00 2001
From: hhaensel <helmut.haensel@gmx.com>
Date: Mon, 20 Jan 2025 16:23:46 +0100
Subject: [PATCH 09/22] fix micro/millisecond in pytimedelta, append 's' to
 keywords in pytimedelta and pytimedelta64

---
 src/Convert/numpy.jl | 11 ++++++-----
 src/Core/builtins.jl |  8 ++++----
 2 files changed, 10 insertions(+), 9 deletions(-)

diff --git a/src/Convert/numpy.jl b/src/Convert/numpy.jl
index 6d830573..b43544e7 100644
--- a/src/Convert/numpy.jl
+++ b/src/Convert/numpy.jl
@@ -32,7 +32,8 @@ function pydatetime64(
     year::Integer=_year, month::Integer=_month, day::Integer=_day, hour::Integer=_hour, minute::Integer=_minute, second::Integer=_second,
     millisecond::Integer=_millisecond, microsecond::Integer=_microsecond, nanosecond::Integer=_nanosecond
 )
-    pyimport("numpy").datetime64("$(DateTime(year, month, day, hour, minute, second))") + pytimedelta64(;millisecond, microsecond, nanosecond)
+    pyimport("numpy").datetime64("$(DateTime(year, month, day, hour, minute, second))") + 
+        pytimedelta64(; milliseconds = millisecond, microseconds = microsecond, nanoseconds = nanosecond)
 end
 function pydatetime64(@nospecialize(x::T)) where T <: Period
     T <: Union{Week, Day, Hour, Minute, Second, Millisecond, Microsecond} || 
@@ -47,13 +48,13 @@ end
 export pydatetime64
 
 function pytimedelta64(
-    _year::Integer=0, _month::Integer=0, _day::Integer=0, _hour::Integer=0, _minute::Integer=0, _second::Integer=0, _millisecond::Integer=0, _microsecond::Integer=0, _nanosecond::Integer=0, _week::Integer=0;
-    year::Integer=_year, month::Integer=_month, day::Integer=_day, hour::Integer=_hour, minute::Integer=_minute, second::Integer=_second, microsecond::Integer=_microsecond, millisecond::Integer=_millisecond, nanosecond::Integer=_nanosecond, week::Integer=_week)
+    _years::Integer=0, _months::Integer=0, _days::Integer=0, _hours::Integer=0, _minutes::Integer=0, _seconds::Integer=0, _milliseconds::Integer=0, _microseconds::Integer=0, _nanoseconds::Integer=0, _weeks::Integer=0;
+    years::Integer=_years, months::Integer=_months, days::Integer=_days, hours::Integer=_hours, minutes::Integer=_minutes, seconds::Integer=_seconds, microseconds::Integer=_microseconds, milliseconds::Integer=_milliseconds, nanoseconds::Integer=_nanoseconds, weeks::Integer=_weeks)
     pytimedelta64(sum((
-        Year(year), Month(month),
+        Year(years), Month(months),
         # you cannot mix year or month with any of the below units in python
         # in case of wrong usage a descriptive error message will by thrown by the underlying python function
-        Day(day), Hour(hour), Minute(minute), Second(second), Millisecond(millisecond), Microsecond(microsecond), Nanosecond(nanosecond), Week(week))
+        Day(days), Hour(hours), Minute(minutes), Second(seconds), Millisecond(milliseconds), Microsecond(microseconds), Nanosecond(nanoseconds), Week(weeks))
     ))
 end
 function pytimedelta64(@nospecialize(x::T)) where T <: Period
diff --git a/src/Core/builtins.jl b/src/Core/builtins.jl
index 97c80770..4559428a 100644
--- a/src/Core/builtins.jl
+++ b/src/Core/builtins.jl
@@ -1168,15 +1168,15 @@ pydatetime(x::Date) = pydatetime(year(x), month(x), day(x))
 export pydatetime
 
 function pytimedelta(
-    _day::Int=0, _second::Int=0, _microsecond::Int=0, _millisecond::Int=0, _minute::Int=0, _hour::Int=0, _week::Int=0;
-    day::Int=_day, second::Int=_second, microsecond::Int=_microsecond, millisecond::Int=_millisecond, minute::Int=_minute, hour::Int=_hour, week::Int=_week
+    _days::Int=0, _seconds::Int=0, _microseconds::Int=0, _milliseconds::Int=0, _minutes::Int=0, _hours::Int=0, _weeks::Int=0;
+    days::Int=_days, seconds::Int=_seconds, microseconds::Int=_microseconds, milliseconds::Int=_milliseconds, minutes::Int=_minutes, hours::Int=_hours, weeks::Int=_weeks
 )
-    pyimport("datetime").timedelta(day, second, microsecond, millisecond, minute, hour, week)
+    pyimport("datetime").timedelta(days, seconds, microseconds, milliseconds, minutes, hours, weeks)
 end
 function pytimedelta(@nospecialize(x::T)) where T <: Period
     T <: Union{Week, Day, Hour, Minute, Second, Millisecond, Microsecond} || 
         error("Unsupported Period type: ", "Year, Month and Nanosecond are not supported, consider using pytimedelta64 instead.")
-    args = T .== (Day, Second, Millisecond, Microsecond, Minute, Hour, Week)
+    args = T .== (Day, Second, Microsecond, Millisecond, Minute, Hour, Week)
     pytimedelta(x.value .* args...)
 end
 function pytimedelta(x::CompoundPeriod)

From 46efe539cf147345a5207619fcf626ad8a7facd1 Mon Sep 17 00:00:00 2001
From: hhaensel <helmut.haensel@gmx.com>
Date: Mon, 20 Jan 2025 16:24:02 +0100
Subject: [PATCH 10/22] add tests for pytimedelta, pytimedelta64 and conversion
 of pytimedelta64 in DataFrames

---
 test/Convert.jl  | 53 ++++++++++++++++++++++++++++++++++++++++++++++++
 test/Core.jl     | 31 ++++++++++++++++++++++++++++
 test/runtests.jl |  2 +-
 3 files changed, 85 insertions(+), 1 deletion(-)

diff --git a/test/Convert.jl b/test/Convert.jl
index 4f137459..becb06e8 100644
--- a/test/Convert.jl
+++ b/test/Convert.jl
@@ -305,6 +305,59 @@ end
     @test_throws Exception pyconvert(Second, td(microseconds = 1000))
 end
 
+@testitem "timedelta64" begin
+    using Dates
+    using CondaPkg
+    CondaPkg.add("pandas")
+    using DataFrames
+
+    dt1 = pytimedelta(seconds = 1)
+    dt2 = pytimedelta64(seconds = 1)
+    @test pyeq(Bool, dt1, dt2)
+
+    @test pyeq(Bool, pytimedelta64(seconds = 10), pyimport("numpy").timedelta64(10, "s"))
+    @test pyeq(Bool, pytimedelta64(years = 10), pyimport("numpy").timedelta64(10, "Y"))
+    @test_throws Exception pytimedelta64(years = 10, seconds = 1)
+
+    @testset for x in [
+        -1_000_000_000,
+        -1_000_000,
+        -1_000,
+        -1,
+        0,
+        1,
+        1_000,
+        1_000_000,
+        1_000_000_000,
+    ], (Unit, unit) in [
+        (Nanosecond, :nanoseconds),
+        (Microsecond, :microseconds),
+        (Millisecond, :milliseconds),
+        (Second, :seconds),
+        (Minute, :minutes),
+        (Hour, :hours),
+        (Day, :days),
+        (Week, :weeks),
+        (Month, :months),
+        (Year, :years),
+    ]
+        y = pyconvert(Unit, pytimedelta64(; [unit => x]...))
+        @test y === Unit(x)
+    end
+    @test_throws Exception pyconvert(Second, td(microseconds = 1000))
+
+    jdf = DataFrame(x = [now() + Second(rand(1:1000)) for _ in 1:100], y = [Second(n) for n in 1:100])
+    pdf = pytable(jdf)
+    @test ispy(pdf.y)
+    @test pyeq(Bool, pdf.y[0], pytimedelta64(seconds = 1))
+    # automatic conversion from pytimedelta64 converts to Dates.CompoundPeriod
+    jdf2 = DataFrame(PyPandasDataFrame(pdf))
+    @test eltype(jdf2.y) == Dates.CompoundPeriod
+    # convert y column back to Seconds
+    jdf2.y = convert.(Second, jdf2.y)
+    @test pyeq(Bool, jdf, jdf2)
+end
+
 @testitem "pyconvert_add_rule (#364)" begin
     id = string(rand(UInt128), base = 16)
     pyexec(
diff --git a/test/Core.jl b/test/Core.jl
index feadf855..e8375f00 100644
--- a/test/Core.jl
+++ b/test/Core.jl
@@ -676,6 +676,9 @@ end
 
 @testitem "datetime" begin
     using Dates
+    using CondaPkg
+    CondaPkg.add("numpy")
+
     dt = pyimport("datetime")
     x1 = pydate(2001, 2, 3)
     @test pyisinstance(x1, dt.date)
@@ -701,6 +704,34 @@ end
     x8 = pydatetime(2001, 2, 3, 4, 5, 6, 7)
     dx = pytimedelta(366, 3661, 1)
     pyeq(Bool, x8 - x6, dx)
+
+    td = pyimport("datetime").timedelta
+    @testset for x in [
+        -1_000_000_000,
+        -1_000_000,
+        -1_000,
+        -1,
+        0,
+        1,
+        1_000,
+        1_000_000,
+        1_000_000_000,
+    ], (Unit, unit, pyunit, factor) in [
+        (Microsecond, :microseconds, :microseconds, 1),
+        (Millisecond, :milliseconds, :milliseconds, 1),
+        (Second, :seconds, :seconds, 1),
+        (Minute, :minutes, :seconds, 60),
+        (Hour, :hours, :hours, 1),
+        (Day, :days, :days, 1),
+        (Week, :weeks, :days, 7)
+    ]
+        # for day and week units, skip large values due to overflow
+        unit in [:days, :weeks] && abs(x) > 1_000_000 && continue
+        y = pytimedelta(; [unit => x]...)
+        y2 = pytimedelta(Unit(x))
+        @test pyeq(Bool, y, y2)
+        @test pyeq(Bool, y, td(; [pyunit => x * factor]...))
+    end
 end
 
 @testitem "code" begin
diff --git a/test/runtests.jl b/test/runtests.jl
index b9e874db..0660c407 100644
--- a/test/runtests.jl
+++ b/test/runtests.jl
@@ -1,3 +1,3 @@
 using TestItemRunner
-
+using DataFrames
 @run_package_tests

From d36c113d5ea8356593ff8680672affce1ce32836 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Helmut=20H=C3=A4nsel?= <helmut.haensel@gmx.de>
Date: Tue, 21 Jan 2025 01:13:27 +0100
Subject: [PATCH 11/22] fix pytimdelta(years/months=0), add
 pydatetime64(::Union{Date, DateTime}), remove pydatetime64(::CompoundPeriod)

---
 src/Convert/numpy.jl | 24 ++++++++++++++++--------
 1 file changed, 16 insertions(+), 8 deletions(-)

diff --git a/src/Convert/numpy.jl b/src/Convert/numpy.jl
index b43544e7..b33a1424 100644
--- a/src/Convert/numpy.jl
+++ b/src/Convert/numpy.jl
@@ -41,21 +41,29 @@ function pydatetime64(@nospecialize(x::T)) where T <: Period
     args = map(Base.Fix1(isa, x), (Day, Second, Millisecond, Microsecond,  Minute, Hour, Week))
     pydatetime64(map(Base.Fix1(*, x.value), args)...)
 end
-function pydatetime64(x::CompoundPeriod)
-    x =  canonicalize(x)
-    isempty(x.periods) ? pydatetime64(Second(0)) : sum(pydatetime64, x.periods)
+function pydatetime64(x::Union{Date, DateTime})
+    pyimport("numpy").datetime64("$x")
 end
 export pydatetime64
 
 function pytimedelta64(
-    _years::Integer=0, _months::Integer=0, _days::Integer=0, _hours::Integer=0, _minutes::Integer=0, _seconds::Integer=0, _milliseconds::Integer=0, _microseconds::Integer=0, _nanoseconds::Integer=0, _weeks::Integer=0;
-    years::Integer=_years, months::Integer=_months, days::Integer=_days, hours::Integer=_hours, minutes::Integer=_minutes, seconds::Integer=_seconds, microseconds::Integer=_microseconds, milliseconds::Integer=_milliseconds, nanoseconds::Integer=_nanoseconds, weeks::Integer=_weeks)
-    pytimedelta64(sum((
-        Year(years), Month(months),
+    _years::Union{Nothing,Integer}=nothing, _months::Union{Nothing,Integer}=nothing, _days::Integer=0, _hours::Integer=0, _minutes::Integer=0, _seconds::Integer=0, _milliseconds::Integer=0, _microseconds::Integer=0, _nanoseconds::Integer=0, _weeks::Integer=0;
+    years::Union{Nothing,Integer}=_years, months::Union{Nothing,Integer}=_years, days::Integer=_days, hours::Integer=_hours, minutes::Integer=_minutes, seconds::Integer=_seconds, microseconds::Integer=_microseconds, milliseconds::Integer=_milliseconds, nanoseconds::Integer=_nanoseconds, weeks::Integer=_weeks)
+    year_or_month_given = (years !== nothing || months !== nothing)
+    y::Integer = something(years, 0)
+    m::Integer = something(months, 0)
+    cp = sum((
+        Year(y), Month(m),
         # you cannot mix year or month with any of the below units in python
         # in case of wrong usage a descriptive error message will by thrown by the underlying python function
         Day(days), Hour(hours), Minute(minutes), Second(seconds), Millisecond(milliseconds), Microsecond(microseconds), Nanosecond(nanoseconds), Week(weeks))
-    ))
+    )
+    # make sure the correct unit is used when value is 0
+    if isempty(cp.periods) && year_or_month_given
+        pytimedelta64(Month(0))
+    else
+        pytimedelta64(cp)
+    end
 end
 function pytimedelta64(@nospecialize(x::T)) where T <: Period
     index = findfirst(==(T), (Year, Month, Week, Day, Hour, Minute, Second, Millisecond, Microsecond, Nanosecond, T))::Int

From 66459c6472f1632273fa4414195fe7794a360163 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Helmut=20H=C3=A4nsel?= <helmut.haensel@gmx.de>
Date: Tue, 21 Jan 2025 01:13:47 +0100
Subject: [PATCH 12/22] add tests for pytimedelta64, pydatetime64

---
 test/Core.jl  |  3 ---
 test/Numpy.jl | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 57 insertions(+), 3 deletions(-)
 create mode 100644 test/Numpy.jl

diff --git a/test/Core.jl b/test/Core.jl
index e8375f00..d9e319d3 100644
--- a/test/Core.jl
+++ b/test/Core.jl
@@ -676,9 +676,6 @@ end
 
 @testitem "datetime" begin
     using Dates
-    using CondaPkg
-    CondaPkg.add("numpy")
-
     dt = pyimport("datetime")
     x1 = pydate(2001, 2, 3)
     @test pyisinstance(x1, dt.date)
diff --git a/test/Numpy.jl b/test/Numpy.jl
new file mode 100644
index 00000000..1d812106
--- /dev/null
+++ b/test/Numpy.jl
@@ -0,0 +1,57 @@
+@testitem "timedelta64" begin
+    using Dates
+    using CondaPkg
+    CondaPkg.add("numpy")
+    
+    td = pyimport("numpy").timedelta64
+    @testset for x in [
+        -1_000_000_000,
+        -1_000_000,
+        -1_000,
+        -1,
+        0,
+        1,
+        1_000,
+        1_000_000,
+        1_000_000_000,
+    ], (Unit, unit, pyunit) in [
+        (Nanosecond, :nanoseconds, :ns),
+        (Microsecond, :microseconds, :us),
+        (Millisecond, :milliseconds, :ms),
+        (Second, :seconds, :s),
+        (Minute, :minutes, :m),
+        (Hour, :hours, :h),
+        (Day, :days, :D),
+        (Week, :weeks, :W),
+        (Month, :months, :M),
+        (Year, :years, :Y),
+    ]
+        y = pytimedelta64(; [unit => x]...)
+        y2 = pytimedelta64(Unit(x))
+        @test pyeq(Bool, y, y2)
+        @test pyeq(Bool, y, td(x, "$pyunit"))
+    end
+end
+
+@testitem "datetime64" begin
+    using Dates
+    using CondaPkg
+    CondaPkg.add("numpy")
+    
+    y = 2024
+    m = 2
+    d = 29
+    h = 23
+    min = 59
+    s = 58
+    ms = 999
+    us = 998
+    ns = 997
+
+    date = DateTime(y, m, d, h, min, s, ms)
+    pydate = pydatetime64(date)
+    pydate2 = pydatetime64(year = y, month = m, day = d, hour = h, minute = min, second = s, millisecond = ms)
+    dt = date - Second(0)
+    pydate3 = pydatetime64(dt)
+    @test pyeq(Bool, pydate, pydate2)
+end
\ No newline at end of file

From 3c51b54cd69054f1e605de63b28605a0d64d549f Mon Sep 17 00:00:00 2001
From: hhaensel <helmut.haensel@gmx.com>
Date: Tue, 21 Jan 2025 09:42:27 +0100
Subject: [PATCH 13/22] support unitless timedelta64, keep unit per default,
 add keyword canonicalize, add global setting CANONICALIZE_TIMEDELTA64 for
 rule conversion

---
 src/Convert/numpy.jl | 55 ++++++++++++++++++++++++++++++++------------
 1 file changed, 40 insertions(+), 15 deletions(-)

diff --git a/src/Convert/numpy.jl b/src/Convert/numpy.jl
index b33a1424..1013290f 100644
--- a/src/Convert/numpy.jl
+++ b/src/Convert/numpy.jl
@@ -1,3 +1,5 @@
+const CANONICALIZE_TIMEDELTA64 = Ref(false)
+
 struct pyconvert_rule_numpysimplevalue{R,S} <: Function end
 
 function (::pyconvert_rule_numpysimplevalue{R,SAFE})(::Type{T}, x::Py) where {R,SAFE,T}
@@ -47,33 +49,54 @@ end
 export pydatetime64
 
 function pytimedelta64(
-    _years::Union{Nothing,Integer}=nothing, _months::Union{Nothing,Integer}=nothing, _days::Integer=0, _hours::Integer=0, _minutes::Integer=0, _seconds::Integer=0, _milliseconds::Integer=0, _microseconds::Integer=0, _nanoseconds::Integer=0, _weeks::Integer=0;
-    years::Union{Nothing,Integer}=_years, months::Union{Nothing,Integer}=_years, days::Integer=_days, hours::Integer=_hours, minutes::Integer=_minutes, seconds::Integer=_seconds, microseconds::Integer=_microseconds, milliseconds::Integer=_milliseconds, nanoseconds::Integer=_nanoseconds, weeks::Integer=_weeks)
-    year_or_month_given = (years !== nothing || months !== nothing)
+    _years::Union{Nothing,Integer}=nothing, _months::Union{Nothing,Integer}=nothing,
+    _days::Union{Nothing,Integer}=nothing, _hours::Union{Nothing,Integer}=nothing,
+    _minutes::Union{Nothing,Integer}=nothing, _seconds::Union{Nothing,Integer}=nothing,
+    _milliseconds::Union{Nothing,Integer}=nothing, _microseconds::Union{Nothing,Integer}=nothing,
+    _nanoseconds::Union{Nothing,Integer}=nothing, _weeks::Union{Nothing,Integer}=nothing;
+    years::Union{Nothing,Integer}=_years, months::Union{Nothing,Integer}=_months,
+    days::Union{Nothing,Integer}=_days, hours::Union{Nothing,Integer}=_hours,
+    minutes::Union{Nothing,Integer}=_minutes, seconds::Union{Nothing,Integer}=_seconds,
+    milliseconds::Union{Nothing,Integer}=_milliseconds, microseconds::Union{Nothing,Integer}=_microseconds,
+    nanoseconds::Union{Nothing,Integer}=_nanoseconds, weeks::Union{Nothing,Integer}=_weeks,
+    canonicalize::Bool = false)
+
     y::Integer = something(years, 0)
     m::Integer = something(months, 0)
+    d::Integer = something(days, 0)
+    h::Integer = something(hours, 0)
+    min::Integer = something(minutes, 0)
+    s::Integer = something(seconds, 0)
+    ms::Integer = something(milliseconds, 0)
+    µs::Integer = something(microseconds, 0)
+    ns::Integer = something(nanoseconds, 0)
+    w::Integer = something(weeks, 0)
     cp = sum((
-        Year(y), Month(m),
-        # you cannot mix year or month with any of the below units in python
-        # in case of wrong usage a descriptive error message will by thrown by the underlying python function
-        Day(days), Hour(hours), Minute(minutes), Second(seconds), Millisecond(milliseconds), Microsecond(microseconds), Nanosecond(nanoseconds), Week(weeks))
+        Year(y), Month(m), Week(w), Day(d), Hour(h), Minute(min), Second(s), Millisecond(ms), Microsecond(µs), Nanosecond(ns))
     )
     # make sure the correct unit is used when value is 0
-    if isempty(cp.periods) && year_or_month_given
-        pytimedelta64(Month(0))
+    if isempty(cp.periods)
+        Units = (Second, Year, Month, Week, Day, Hour, Minute, Second, Millisecond, Microsecond, Nanosecond)
+        index::Integer = findlast(!isnothing, (0, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds));
+        pytimedelta64(Units[index](0))
     else
-        pytimedelta64(cp)
+        pytimedelta64(cp; canonicalize)
     end
 end
-function pytimedelta64(@nospecialize(x::T)) where T <: Period
-    index = findfirst(==(T), (Year, Month, Week, Day, Hour, Minute, Second, Millisecond, Microsecond, Nanosecond, T))::Int
+function pytimedelta64(@nospecialize(x::T); canonicalize::Bool = false) where T <: Period
+    canonicalize && return pytimedelta64(@__MODULE__().canonicalize(x))
+
+    index = findfirst(T .== (Year, Month, Week, Day, Hour, Minute, Second, Millisecond, Microsecond, Nanosecond, T))::Int
     unit = ("Y", "M", "W", "D", "h", "m", "s", "ms", "us", "ns", "")[index]
     pyimport("numpy").timedelta64(x.value, unit)
 end
-function pytimedelta64(x::CompoundPeriod)
-    x =  canonicalize(x)
+function pytimedelta64(x::CompoundPeriod; canonicalize::Bool = false)
+    canonicalize && (x = @__MODULE__().canonicalize(x))
     isempty(x.periods) ? pytimedelta64(Second(0)) : sum(pytimedelta64.(x.periods))
 end
+function pytimedelta64(x::Integer)
+    pyimport("numpy").timedelta64(x)
+end
 export pytimedelta64
 
 function pyconvert_rule_datetime64(::Type{DateTime}, x::Py)
@@ -91,7 +114,9 @@ function pyconvert_rule_timedelta64(::Type{CompoundPeriod}, x::Py)
     units = ("Y", "M", "W", "D", "h", "m", "s", "ms", "us", "ns")
     types = (Year, Month, Week, Day, Hour, Minute, Second, Millisecond, Microsecond, Nanosecond)
     T = types[findfirst(==(unit), units)::Int]
-    pyconvert_return(CompoundPeriod(T(value * count)) |> canonicalize)
+    cp = CompoundPeriod(T(value * count))
+    CANONICALIZE_TIMEDELTA64[] && (cp = @__MODULE__().canonicalize(cp))
+    pyconvert_return(cp)
 end
 
 function pyconvert_rule_timedelta64(::Type{T}, x::Py) where T<:Period

From fac1ef8c97b0672a7d59d93cacd01286b0b6d22d Mon Sep 17 00:00:00 2001
From: hhaensel <helmut.haensel@gmx.com>
Date: Tue, 21 Jan 2025 09:43:35 +0100
Subject: [PATCH 14/22] add tests for timedelta64 canonicalize

---
 test/Numpy.jl | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/test/Numpy.jl b/test/Numpy.jl
index 1d812106..7ab9352f 100644
--- a/test/Numpy.jl
+++ b/test/Numpy.jl
@@ -4,6 +4,7 @@
     CondaPkg.add("numpy")
     
     td = pyimport("numpy").timedelta64
+    get_unit(x) = pyconvert(String, pyimport("numpy").datetime_data(x)[0])
     @testset for x in [
         -1_000_000_000,
         -1_000_000,
@@ -30,7 +31,18 @@
         y2 = pytimedelta64(Unit(x))
         @test pyeq(Bool, y, y2)
         @test pyeq(Bool, y, td(x, "$pyunit"))
+        @test get_unit(y) == "$pyunit"
+        @test get_unit(y2) == "$pyunit"
     end
+    x = pytimedelta64(Second(60))
+    @test get_unit(x) == "s"
+    x = pytimedelta64(Second(60); canonicalize = true)
+    @test get_unit(x) == "m"
+    
+    PythonCall.Convert.CANONICALIZE_TIMEDELTA64[] = true
+    @test pyconvert(Dates.CompoundPeriod, pytimedelta64(Second(60)),).periods[1] isa Minute
+    PythonCall.Convert.CANONICALIZE_TIMEDELTA64[] = false
+    @test pyconvert(Dates.CompoundPeriod, pytimedelta64(Second(60)),).periods[1] isa Second
 end
 
 @testitem "datetime64" begin

From 5abcf1d3dbdc6e7f2d02fff5df9c1cd92ae6c8fe Mon Sep 17 00:00:00 2001
From: hhaensel <helmut.haensel@gmx.com>
Date: Tue, 21 Jan 2025 15:44:59 +0100
Subject: [PATCH 15/22] add CondaPkg and DataFrames as extras in Project.toml

---
 Project.toml     | 5 ++++-
 test/runtests.jl | 2 +-
 2 files changed, 5 insertions(+), 2 deletions(-)

diff --git a/Project.toml b/Project.toml
index 0a86bf7a..25f9956b 100644
--- a/Project.toml
+++ b/Project.toml
@@ -18,6 +18,7 @@ UnsafePointers = "e17b2a0c-0bdf-430a-bd0c-3a23cae4ff39"
 [compat]
 Aqua = "0 - 999"
 CondaPkg = "0.2.23"
+DataFrames = "1.7.0"
 Dates = "1"
 Libdl = "1"
 MacroTools = "0.5"
@@ -33,8 +34,10 @@ julia = "1.6.1"
 
 [extras]
 Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
+CondaPkg = "992eb4ea-22a4-4c89-a5bb-47a3300528ab"
+DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
 Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
 TestItemRunner = "f8b46487-2199-4994-9208-9a1283c18c0a"
 
 [targets]
-test = ["Aqua", "Test", "TestItemRunner"]
+test = ["Aqua", "CondaPkg", "DataFrames", "Test", "TestItemRunner"]
diff --git a/test/runtests.jl b/test/runtests.jl
index 0660c407..b9e874db 100644
--- a/test/runtests.jl
+++ b/test/runtests.jl
@@ -1,3 +1,3 @@
 using TestItemRunner
-using DataFrames
+
 @run_package_tests

From 34f35ce42654045b6b761d63f017dccc8dcb1924 Mon Sep 17 00:00:00 2001
From: hhaensel <helmut.haensel@gmx.com>
Date: Mon, 20 Jan 2025 11:25:28 +0100
Subject: [PATCH 16/22] fix pandas testing

---
 test/Compat.jl |  7 +++++--
 test/Wrap.jl   | 26 ++++++++++++++------------
 2 files changed, 19 insertions(+), 14 deletions(-)

diff --git a/test/Compat.jl b/test/Compat.jl
index 16b3e65a..0b3fc312 100644
--- a/test/Compat.jl
+++ b/test/Compat.jl
@@ -79,11 +79,14 @@ end
 end
 
 @testitem "Tables.jl" begin
+    using CondaPkg
+    CondaPkg.add("pandas")
+    
     @testset "pytable" begin
         x = (x = [1, 2, 3], y = ["a", "b", "c"])
         # pandas
-        # TODO: install pandas and test properly
-        @test_throws PyException pytable(x, :pandas)
+        t = pytable(x, :pandas)
+        @test pyconvert.(Int, Tuple(t.shape)) == (3, 2)
         # columns
         y = pytable(x, :columns)
         @test pyeq(Bool, y, pydict(x = [1, 2, 3], y = ["a", "b", "c"]))
diff --git a/test/Wrap.jl b/test/Wrap.jl
index a1e2a359..03991e2c 100644
--- a/test/Wrap.jl
+++ b/test/Wrap.jl
@@ -439,24 +439,26 @@ end
 
 @testitem "PyPandasDataFrame" begin
     using Tables
+    using DataFrames
+    using CondaPkg
+    CondaPkg.add("pandas")
     @test PyPandasDataFrame isa Type
-    # TODO: figure out how to get pandas into the test environment
-    # for now use some dummy type and take advantage of the fact that the code doesn't actually check it's a real dataframe
-    @pyexec """
-    class DataFrame:
-        def __init__(self, **kw):
-            self.__dict__.update(kw)
-    """ => DataFrame
-    df = DataFrame(shape = (4, 3), columns = pylist(["foo", "bar", "baz"]))
-    x = PyPandasDataFrame(df)
+    x = (x = [1, 2, 3], y = ["a", "b", "c"])
+    py_df = pytable(x, :pandas)
+    @test Tables.istable(PyTable(py_df))
+    df = DataFrame(PyTable(py_df))
+    @test df == DataFrame(x = [1, 2, 3], y = ["a", "b", "c"])
+
+    x = PyPandasDataFrame(py_df)
+    df = DataFrame(x)
+    @test df == DataFrame(x = [1, 2, 3], y = ["a", "b", "c"])
     @test ispy(x)
-    @test Py(x) === df
     @test Tables.istable(x)
     @test Tables.columnaccess(x)
-    @test_throws Exception Tables.columns(x)
+    @test Tables.columns(x)[:x] == [1, 2, 3]
     @test_throws Exception pyconvert(PyPandasDataFrame, 1)
     str = sprint(show, MIME("text/plain"), x)
-    @test occursin(r"4×3 .*PyPandasDataFrame", str)
+    @test occursin(r"3×2 .*PyPandasDataFrame", str)
 end
 
 @testitem "PySet" begin

From 986417358bcec78f93bfaf9fdc9d64e8cf7f7a71 Mon Sep 17 00:00:00 2001
From: hhaensel <helmut.haensel@gmx.com>
Date: Tue, 21 Jan 2025 17:02:13 +0100
Subject: [PATCH 17/22] fix compat with julia < 1.8

---
 src/Convert/Convert.jl | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/src/Convert/Convert.jl b/src/Convert/Convert.jl
index 99ef7c83..e3145d58 100644
--- a/src/Convert/Convert.jl
+++ b/src/Convert/Convert.jl
@@ -49,6 +49,12 @@ using Dates: Year, Month, Day, Hour, Minute, Week, Period, CompoundPeriod, canon
 
 import ..Core: pyconvert
 
+# patch conversion to Period types for julia <= 1.7
+@static if VERSION < v"1.8.0-"
+    Base.convert(::Type{T}, x::CompoundPeriod) where T<:Period =
+        isconcretetype(T) ? sum(T, x.periods; init = zero(T)) : throw(MethodError(convert,(T,x)))
+end
+
 include("pyconvert.jl")
 include("rules.jl")
 include("ctypes.jl")

From 3148bae9cca01a6d580c8ffc70b9d1ec49ea6f3a Mon Sep 17 00:00:00 2001
From: hhaensel <helmut.haensel@gmx.com>
Date: Tue, 21 Jan 2025 17:52:20 +0100
Subject: [PATCH 18/22] specialize Base.convert(::<:Period, CompoundPeriod) for
 julia < 1.8

---
 src/Convert/Convert.jl | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/src/Convert/Convert.jl b/src/Convert/Convert.jl
index e3145d58..8e97c55d 100644
--- a/src/Convert/Convert.jl
+++ b/src/Convert/Convert.jl
@@ -51,8 +51,9 @@ import ..Core: pyconvert
 
 # patch conversion to Period types for julia <= 1.7
 @static if VERSION < v"1.8.0-"
-    Base.convert(::Type{T}, x::CompoundPeriod) where T<:Period =
-        isconcretetype(T) ? sum(T, x.periods; init = zero(T)) : throw(MethodError(convert,(T,x)))
+    for T in (:Year, :Month, :Week, :Day, :Hour, :Minute, :Second, :Millisecond, :Microsecond, :Nanosecond)
+        @eval Base.convert(::Type{$T}, x::CompoundPeriod) = sum($T, x.periods; init = zero($T))
+    end
 end
 
 include("pyconvert.jl")

From 94b47df3a8c4d4acd92fce7e4e669df680b57e32 Mon Sep 17 00:00:00 2001
From: hhaensel <helmut.haensel@gmx.com>
Date: Tue, 21 Jan 2025 18:20:17 +0100
Subject: [PATCH 19/22] change CondaPkg environment

---
 Project.toml       | 14 --------------
 test/CondaPkg.toml |  2 ++
 test/Project.toml  |  6 ++++++
 test/Wrap.jl       |  2 +-
 4 files changed, 9 insertions(+), 15 deletions(-)
 create mode 100644 test/CondaPkg.toml
 create mode 100644 test/Project.toml

diff --git a/Project.toml b/Project.toml
index 25f9956b..a1bf45e8 100644
--- a/Project.toml
+++ b/Project.toml
@@ -16,9 +16,7 @@ Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
 UnsafePointers = "e17b2a0c-0bdf-430a-bd0c-3a23cae4ff39"
 
 [compat]
-Aqua = "0 - 999"
 CondaPkg = "0.2.23"
-DataFrames = "1.7.0"
 Dates = "1"
 Libdl = "1"
 MacroTools = "0.5"
@@ -27,17 +25,5 @@ Pkg = "1"
 Requires = "1"
 Serialization = "1"
 Tables = "1"
-Test = "1"
-TestItemRunner = "0 - 999"
 UnsafePointers = "1"
 julia = "1.6.1"
-
-[extras]
-Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
-CondaPkg = "992eb4ea-22a4-4c89-a5bb-47a3300528ab"
-DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
-Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
-TestItemRunner = "f8b46487-2199-4994-9208-9a1283c18c0a"
-
-[targets]
-test = ["Aqua", "CondaPkg", "DataFrames", "Test", "TestItemRunner"]
diff --git a/test/CondaPkg.toml b/test/CondaPkg.toml
new file mode 100644
index 00000000..8248a602
--- /dev/null
+++ b/test/CondaPkg.toml
@@ -0,0 +1,2 @@
+[deps]
+pandas = ""
diff --git a/test/Project.toml b/test/Project.toml
new file mode 100644
index 00000000..0a1205ee
--- /dev/null
+++ b/test/Project.toml
@@ -0,0 +1,6 @@
+[deps]
+CondaPkg = "992eb4ea-22a4-4c89-a5bb-47a3300528ab"
+DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
+Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
+PythonCall = "6099a3de-0909-46bc-b1f4-468b9a2dfc0d"
+TestItemRunner = "f8b46487-2199-4994-9208-9a1283c18c0a"
diff --git a/test/Wrap.jl b/test/Wrap.jl
index 03991e2c..03deb967 100644
--- a/test/Wrap.jl
+++ b/test/Wrap.jl
@@ -441,7 +441,7 @@ end
     using Tables
     using DataFrames
     using CondaPkg
-    CondaPkg.add("pandas")
+    # CondaPkg.add("pandas")
     @test PyPandasDataFrame isa Type
     x = (x = [1, 2, 3], y = ["a", "b", "c"])
     py_df = pytable(x, :pandas)

From f66f5264c79ff43d9869fd7eefc7bf3b5cbb14c7 Mon Sep 17 00:00:00 2001
From: hhaensel <helmut.haensel@gmx.com>
Date: Tue, 21 Jan 2025 19:55:52 +0100
Subject: [PATCH 20/22] fix test/Project.toml, adapt runtests.jl

---
 CondaPkg.toml     |  9 ---------
 test/Project.toml | 14 +++++++++++++-
 test/runtests.jl  |  5 +++--
 3 files changed, 16 insertions(+), 12 deletions(-)
 delete mode 100644 CondaPkg.toml

diff --git a/CondaPkg.toml b/CondaPkg.toml
deleted file mode 100644
index 7198e26e..00000000
--- a/CondaPkg.toml
+++ /dev/null
@@ -1,9 +0,0 @@
-[deps.libstdcxx-ng]
-version = "<=julia"
-
-[deps.openssl]
-version = "<=julia"
-
-[deps.python]
-build = "**cpython**"
-version = ">=3.8,<4"
diff --git a/test/Project.toml b/test/Project.toml
index 0a1205ee..83f8d875 100644
--- a/test/Project.toml
+++ b/test/Project.toml
@@ -1,6 +1,18 @@
 [deps]
+Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
 CondaPkg = "992eb4ea-22a4-4c89-a5bb-47a3300528ab"
 DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
 Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
-PythonCall = "6099a3de-0909-46bc-b1f4-468b9a2dfc0d"
+Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a"
+Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
+Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
+Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
 TestItemRunner = "f8b46487-2199-4994-9208-9a1283c18c0a"
+
+[compat]
+Aqua = "0 - 999"
+CondaPkg = "0.2.23"
+DataFrames = "1.7.0"
+Dates = "1"
+Test = "1"
+TestItemRunner = "0 - 999"
diff --git a/test/runtests.jl b/test/runtests.jl
index b9e874db..986d591c 100644
--- a/test/runtests.jl
+++ b/test/runtests.jl
@@ -1,3 +1,4 @@
 using TestItemRunner
-
-@run_package_tests
+using CondaPkg
+CondaPkg.add("pandas")
+@run_package_tests verbose=true

From e9277ef1000126d14d783ead8ba8a877c98be3a7 Mon Sep 17 00:00:00 2001
From: hhaensel <helmut.haensel@gmx.com>
Date: Wed, 19 Feb 2025 17:10:31 +0100
Subject: [PATCH 21/22] change order of args of pytimedelta; add docstrings for
 pytimedelta, pytimedelta64, pydatetime64

---
 src/Convert/numpy.jl | 62 ++++++++++++++++++++++++++++++++++++++++++++
 src/Core/builtins.jl | 14 +++++++---
 2 files changed, 73 insertions(+), 3 deletions(-)

diff --git a/src/Convert/numpy.jl b/src/Convert/numpy.jl
index 1013290f..ce558484 100644
--- a/src/Convert/numpy.jl
+++ b/src/Convert/numpy.jl
@@ -29,6 +29,22 @@ const NUMPY_SIMPLE_TYPES = [
     ("complex128", ComplexF64),
 ]
 
+"""
+    pydatetime64([year, month, day, hour, minute, second, millisecond, microsecond, nanosecond, weeks])
+    pydatetime64(; [year, month, day, hour, minute, second, millisecond, microsecond, nanosecond, weeks])
+
+Create a `numpy.pydatetime64` object from the given time arguments.
+Arguments can be supplied either as keyword arguments or positional arguments in the above order.
+
+Examples:
+```julia
+pydatetime64(2025, 1, 2, 3, 4, 5, 6, 7, 8)
+# Python: np.datetime64('2025-01-02T03:04:05.006007008')
+
+pydatetime64(year = 2025, month = 5, day = 20, nanosecond = 1)
+# Python: np.datetime64('2025-05-20T00:00:00.000000001')
+```
+"""
 function pydatetime64(
     _year::Integer=0, _month::Integer=1, _day::Integer=1, _hour::Integer=0, _minute::Integer=0,_second::Integer=0, _millisecond::Integer=0, _microsecond::Integer=0, _nanosecond::Integer=0;
     year::Integer=_year, month::Integer=_month, day::Integer=_day, hour::Integer=_hour, minute::Integer=_minute, second::Integer=_second,
@@ -48,6 +64,30 @@ function pydatetime64(x::Union{Date, DateTime})
 end
 export pydatetime64
 
+"""
+    pytimedelta64([years, months, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, weeks]; canonicalize=false)
+    pytimedelta64(; [years, months, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, weeks], canonicalize=false)
+
+Create a `numpy.timedelta64` object from the given time arguments.
+Arguments can be supplied either as keyword arguments or positional arguments in the above order.
+`years` and `months` cannot be mixed with other units.
+
+Parameters:
+- `canonicalize=false`: If `false`, the unit of the result will be the unit of the least unit that was specified, 
+    if `true`, the result will be converted to least possible unit, e.g. 60s will be converted to 1m.
+
+Examples:
+```julia
+pytimedelta64(1, 2)
+# Python: np.timedelta64(14,'M')
+
+pytimedelta64(years = 1, months = 12; canonicalize = true)
+# Python: np.timedelta64(2,'Y')
+
+pytimedelta64(hours = 3, minutes = 60)
+# Python: np.timedelta64(240,'m')
+```
+"""
 function pytimedelta64(
     _years::Union{Nothing,Integer}=nothing, _months::Union{Nothing,Integer}=nothing,
     _days::Union{Nothing,Integer}=nothing, _hours::Union{Nothing,Integer}=nothing,
@@ -83,6 +123,11 @@ function pytimedelta64(
         pytimedelta64(cp; canonicalize)
     end
 end
+"""
+    pytimedelta64(x::Union{Period, CompoundPeriod}; canonicalize=false)
+
+Convert a Julia `Period` or `CompoundPeriod` to a `numpy.timedelta64` object.
+"""
 function pytimedelta64(@nospecialize(x::T); canonicalize::Bool = false) where T <: Period
     canonicalize && return pytimedelta64(@__MODULE__().canonicalize(x))
 
@@ -94,6 +139,23 @@ function pytimedelta64(x::CompoundPeriod; canonicalize::Bool = false)
     canonicalize && (x = @__MODULE__().canonicalize(x))
     isempty(x.periods) ? pytimedelta64(Second(0)) : sum(pytimedelta64.(x.periods))
 end
+"""
+    pytimedelta64(x::Integer)
+
+Create a dimensionless `numpy.timedelta64` object. If added to another `numpy.timedelta64` object, it will be interpreted as being of the same unit.
+
+Examples:
+```julia
+pytimedelta64(2)
+# Python: np.timedelta64(2)
+
+pytimedelta64(Hour(1)) + pytimedelta64(2)
+# Python: np.timedelta64(3,'h')
+
+pytimedelta64(2) + pytimedelta64(Hour(1)) + pytimedelta64(Minute(1))
+# Python: np.timedelta64(181,'m')
+```
+"""
 function pytimedelta64(x::Integer)
     pyimport("numpy").timedelta64(x)
 end
diff --git a/src/Core/builtins.jl b/src/Core/builtins.jl
index 4559428a..24fa9401 100644
--- a/src/Core/builtins.jl
+++ b/src/Core/builtins.jl
@@ -1167,16 +1167,24 @@ end
 pydatetime(x::Date) = pydatetime(year(x), month(x), day(x))
 export pydatetime
 
+"""
+    pytimedelta([days, hours, minutes, seconds, milliseconds, microseconds, weeks])
+
+Construct a Python `timedelta` object. Arguments can be supplied either as keyword arguments or positional arguments in the above order.
+
+Note that we chose a decreasing order of the positional arguments (except `week`, which goes last) other than the Python function `datetime.timedelta()`.
+This way the functions `pytimedelta()` and `pytimedelta64()` have a similar signature.
+"""
 function pytimedelta(
-    _days::Int=0, _seconds::Int=0, _microseconds::Int=0, _milliseconds::Int=0, _minutes::Int=0, _hours::Int=0, _weeks::Int=0;
-    days::Int=_days, seconds::Int=_seconds, microseconds::Int=_microseconds, milliseconds::Int=_milliseconds, minutes::Int=_minutes, hours::Int=_hours, weeks::Int=_weeks
+    _days::Int=0, _hours::Int=0, _minutes::Int=0, _seconds::Int=0, _milliseconds::Int=0, _microseconds::Int=0,  _weeks::Int=0;
+    days::Int=_days, hours::Int=_hours, minutes::Int=_minutes, seconds::Int=_seconds, milliseconds::Int=_milliseconds, microseconds::Int=_microseconds, weeks::Int=_weeks
 )
     pyimport("datetime").timedelta(days, seconds, microseconds, milliseconds, minutes, hours, weeks)
 end
 function pytimedelta(@nospecialize(x::T)) where T <: Period
     T <: Union{Week, Day, Hour, Minute, Second, Millisecond, Microsecond} || 
         error("Unsupported Period type: ", "Year, Month and Nanosecond are not supported, consider using pytimedelta64 instead.")
-    args = T .== (Day, Second, Microsecond, Millisecond, Minute, Hour, Week)
+    args = T .== (Day, Hour, Minute, Second, Millisecond, Microsecond, Week)
     pytimedelta(x.value .* args...)
 end
 function pytimedelta(x::CompoundPeriod)

From d93e99a30d0255cc4d0999a5955c6c5a19532452 Mon Sep 17 00:00:00 2001
From: hhaensel <helmut.haensel@gmx.com>
Date: Wed, 19 Feb 2025 17:10:58 +0100
Subject: [PATCH 22/22] remove `@py`method for python-style imports

---
 src/PyMacro/PyMacro.jl | 12 ------------
 1 file changed, 12 deletions(-)

diff --git a/src/PyMacro/PyMacro.jl b/src/PyMacro/PyMacro.jl
index c48ed8c5..dff5c4ba 100644
--- a/src/PyMacro/PyMacro.jl
+++ b/src/PyMacro/PyMacro.jl
@@ -899,18 +899,6 @@ macro py(ex)
     esc(py_macro(ex, __module__, __source__))
 end
 
-macro py(keyword, modulename, ex)
-    keyword == :from || return :( nothing )
-
-    d = Dict(isa(a.args[1], Symbol) ? a.args[1] => a.args[1] : a.args[1].args[1] => a.args[2]  for a in ex.args)
-    vars = Expr(:tuple, values(d)...)
-    imports = Tuple(keys(d))
-
-    esc(quote
-        $vars = pyimport($(string(modulename)) => $(string.(imports)))
-    end)
-end
-
 export @py
 
 end