Skip to content

Commit d7396eb

Browse files
committed
ENH: implement pandas_timedelta_to_timedeltastruct for other resos
1 parent d5f2e5c commit d7396eb

File tree

3 files changed

+307
-7
lines changed

3 files changed

+307
-7
lines changed

pandas/_libs/tslibs/np_datetime.pyx

+8
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,14 @@ cdef inline void td64_to_tdstruct(int64_t td64,
163163
return
164164

165165

166+
# just exposed for testing at the moment
167+
def py_td64_to_tdstruct(int64_t td64, NPY_DATETIMEUNIT unit):
168+
cdef:
169+
pandas_timedeltastruct tds
170+
pandas_timedelta_to_timedeltastruct(td64, unit, &tds)
171+
return tds # <- returned as a dict to python
172+
173+
166174
cdef inline int64_t pydatetime_to_dt64(datetime val,
167175
npy_datetimestruct *dts):
168176
"""

pandas/_libs/tslibs/src/datetime/np_datetime.c

+230-6
Original file line numberDiff line numberDiff line change
@@ -682,19 +682,23 @@ void pandas_timedelta_to_timedeltastruct(npy_timedelta td,
682682
npy_int64 sfrac;
683683
npy_int64 ifrac;
684684
int sign;
685-
npy_int64 DAY_NS = 86400000000000LL;
685+
npy_int64 PER_DAY;
686+
npy_int64 PER_SEC;
686687

687688
/* Initialize the output to all zeros */
688689
memset(out, 0, sizeof(pandas_timedeltastruct));
689690

690691
switch (base) {
691692
case NPY_FR_ns:
692693

694+
PER_DAY = 86400000000000LL;
695+
PER_SEC = 1000LL * 1000LL * 1000LL;
696+
693697
// put frac in seconds
694-
if (td < 0 && td % (1000LL * 1000LL * 1000LL) != 0)
695-
frac = td / (1000LL * 1000LL * 1000LL) - 1;
698+
if (td < 0 && td % PER_SEC != 0)
699+
frac = td / PER_SEC - 1;
696700
else
697-
frac = td / (1000LL * 1000LL * 1000LL);
701+
frac = td / PER_SEC;
698702

699703
if (frac < 0) {
700704
sign = -1;
@@ -738,12 +742,12 @@ void pandas_timedelta_to_timedeltastruct(npy_timedelta td,
738742
}
739743

740744
sfrac = (out->hrs * 3600LL + out->min * 60LL
741-
+ out->sec) * (1000LL * 1000LL * 1000LL);
745+
+ out->sec) * PER_SEC;
742746

743747
if (sign < 0)
744748
out->days = -out->days;
745749

746-
ifrac = td - (out->days * DAY_NS + sfrac);
750+
ifrac = td - (out->days * PER_DAY + sfrac);
747751

748752
if (ifrac != 0) {
749753
out->ms = ifrac / (1000LL * 1000LL);
@@ -762,11 +766,231 @@ void pandas_timedelta_to_timedeltastruct(npy_timedelta td,
762766
out->nanoseconds = out->ns;
763767
break;
764768

769+
case NPY_FR_us:
770+
771+
PER_DAY = 86400000000LL;
772+
PER_SEC = 1000LL * 1000LL;
773+
774+
// put frac in seconds
775+
if (td < 0 && td % PER_SEC != 0)
776+
frac = td / PER_SEC - 1;
777+
else
778+
frac = td / PER_SEC;
779+
780+
if (frac < 0) {
781+
sign = -1;
782+
783+
// even fraction
784+
if ((-frac % 86400LL) != 0) {
785+
out->days = -frac / 86400LL + 1;
786+
frac += 86400LL * out->days;
787+
} else {
788+
frac = -frac;
789+
}
790+
} else {
791+
sign = 1;
792+
out->days = 0;
793+
}
794+
795+
if (frac >= 86400) {
796+
out->days += frac / 86400LL;
797+
frac -= out->days * 86400LL;
798+
}
799+
800+
if (frac >= 3600) {
801+
out->hrs = frac / 3600LL;
802+
frac -= out->hrs * 3600LL;
803+
} else {
804+
out->hrs = 0;
805+
}
806+
807+
if (frac >= 60) {
808+
out->min = frac / 60LL;
809+
frac -= out->min * 60LL;
810+
} else {
811+
out->min = 0;
812+
}
813+
814+
if (frac >= 0) {
815+
out->sec = frac;
816+
frac -= out->sec;
817+
} else {
818+
out->sec = 0;
819+
}
820+
821+
sfrac = (out->hrs * 3600LL + out->min * 60LL
822+
+ out->sec) * PER_SEC;
823+
824+
if (sign < 0)
825+
out->days = -out->days;
826+
827+
ifrac = td - (out->days * PER_DAY + sfrac);
828+
829+
if (ifrac != 0) {
830+
out->ms = ifrac / 1000LL;
831+
ifrac -= out->ms * 1000LL;
832+
out->us = ifrac / 1L;
833+
ifrac -= out->us * 1L;
834+
out->ns = ifrac;
835+
} else {
836+
out->ms = 0;
837+
out->us = 0;
838+
out->ns = 0;
839+
}
840+
break;
841+
842+
case NPY_FR_ms:
843+
844+
PER_DAY = 86400000LL;
845+
PER_SEC = 1000LL;
846+
847+
// put frac in seconds
848+
if (td < 0 && td % PER_SEC != 0)
849+
frac = td / PER_SEC - 1;
850+
else
851+
frac = td / PER_SEC;
852+
853+
if (frac < 0) {
854+
sign = -1;
855+
856+
// even fraction
857+
if ((-frac % 86400LL) != 0) {
858+
out->days = -frac / 86400LL + 1;
859+
frac += 86400LL * out->days;
860+
} else {
861+
frac = -frac;
862+
}
863+
} else {
864+
sign = 1;
865+
out->days = 0;
866+
}
867+
868+
if (frac >= 86400) {
869+
out->days += frac / 86400LL;
870+
frac -= out->days * 86400LL;
871+
}
872+
873+
if (frac >= 3600) {
874+
out->hrs = frac / 3600LL;
875+
frac -= out->hrs * 3600LL;
876+
} else {
877+
out->hrs = 0;
878+
}
879+
880+
if (frac >= 60) {
881+
out->min = frac / 60LL;
882+
frac -= out->min * 60LL;
883+
} else {
884+
out->min = 0;
885+
}
886+
887+
if (frac >= 0) {
888+
out->sec = frac;
889+
frac -= out->sec;
890+
} else {
891+
out->sec = 0;
892+
}
893+
894+
sfrac = (out->hrs * 3600LL + out->min * 60LL
895+
+ out->sec) * PER_SEC;
896+
897+
if (sign < 0)
898+
out->days = -out->days;
899+
900+
ifrac = td - (out->days * PER_DAY + sfrac);
901+
902+
if (ifrac != 0) {
903+
out->ms = ifrac;
904+
out->us = 0;
905+
out->ns = 0;
906+
} else {
907+
out->ms = 0;
908+
out->us = 0;
909+
out->ns = 0;
910+
}
911+
break;
912+
913+
case NPY_FR_s:
914+
// special case where we can simplify many expressions bc PER_SEC=1
915+
916+
PER_DAY = 86400000LL;
917+
PER_SEC = 1L;
918+
919+
// put frac in seconds
920+
if (td < 0 && td % PER_SEC != 0)
921+
frac = td / PER_SEC - 1;
922+
else
923+
frac = td / PER_SEC;
924+
925+
if (frac < 0) {
926+
sign = -1;
927+
928+
// even fraction
929+
if ((-frac % 86400LL) != 0) {
930+
out->days = -frac / 86400LL + 1;
931+
frac += 86400LL * out->days;
932+
} else {
933+
frac = -frac;
934+
}
935+
} else {
936+
sign = 1;
937+
out->days = 0;
938+
}
939+
940+
if (frac >= 86400) {
941+
out->days += frac / 86400LL;
942+
frac -= out->days * 86400LL;
943+
}
944+
945+
if (frac >= 3600) {
946+
out->hrs = frac / 3600LL;
947+
frac -= out->hrs * 3600LL;
948+
} else {
949+
out->hrs = 0;
950+
}
951+
952+
if (frac >= 60) {
953+
out->min = frac / 60LL;
954+
frac -= out->min * 60LL;
955+
} else {
956+
out->min = 0;
957+
}
958+
959+
if (frac >= 0) {
960+
out->sec = frac;
961+
frac -= out->sec;
962+
} else {
963+
out->sec = 0;
964+
}
965+
966+
sfrac = (out->hrs * 3600LL + out->min * 60LL
967+
+ out->sec) * PER_SEC;
968+
969+
if (sign < 0)
970+
out->days = -out->days;
971+
972+
ifrac = td - (out->days * PER_DAY + sfrac);
973+
974+
if (ifrac != 0) {
975+
out->ms = 0;
976+
out->us = 0;
977+
out->ns = 0;
978+
} else {
979+
out->ms = 0;
980+
out->us = 0;
981+
out->ns = 0;
982+
}
983+
break;
984+
765985
default:
766986
PyErr_SetString(PyExc_RuntimeError,
767987
"NumPy timedelta metadata is corrupted with "
768988
"invalid base unit");
769989
}
990+
991+
out->seconds = out->hrs * 3600 + out->min * 60 + out->sec;
992+
out->microseconds = out->ms * 1000 + out->us;
993+
out->nanoseconds = out->ns;
770994
}
771995

772996

pandas/tests/tslibs/test_np_datetime.py

+69-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import numpy as np
22

3-
from pandas._libs.tslibs.np_datetime import py_get_unit_from_dtype
3+
from pandas._libs.tslibs.np_datetime import (
4+
py_get_unit_from_dtype,
5+
py_td64_to_tdstruct,
6+
)
47

58

69
def test_get_unit_from_dtype():
@@ -35,3 +38,68 @@ def test_get_unit_from_dtype():
3538
assert py_get_unit_from_dtype(np.dtype("m8[ps]")) == 11
3639
assert py_get_unit_from_dtype(np.dtype("m8[fs]")) == 12
3740
assert py_get_unit_from_dtype(np.dtype("m8[as]")) == 13
41+
42+
43+
def test_td64_to_tdstruct():
44+
val = 12454636234 # arbitrary value
45+
46+
res1 = py_td64_to_tdstruct(val, 10) # ns
47+
exp1 = {
48+
"days": 0,
49+
"hrs": 0,
50+
"min": 0,
51+
"sec": 12,
52+
"ms": 454,
53+
"us": 636,
54+
"ns": 234,
55+
"seconds": 12,
56+
"microseconds": 454636,
57+
"nanoseconds": 234,
58+
}
59+
assert res1 == exp1
60+
61+
res2 = py_td64_to_tdstruct(val, 9) # us
62+
exp2 = {
63+
"days": 0,
64+
"hrs": 3,
65+
"min": 27,
66+
"sec": 34,
67+
"ms": 636,
68+
"us": 234,
69+
"ns": 0,
70+
"seconds": 12454,
71+
"microseconds": 636234,
72+
"nanoseconds": 0,
73+
}
74+
assert res2 == exp2
75+
76+
res3 = py_td64_to_tdstruct(val, 8) # ms
77+
exp3 = {
78+
"days": 144,
79+
"hrs": 3,
80+
"min": 37,
81+
"sec": 16,
82+
"ms": 234,
83+
"us": 0,
84+
"ns": 0,
85+
"seconds": 13036,
86+
"microseconds": 234000,
87+
"nanoseconds": 0,
88+
}
89+
assert res3 == exp3
90+
91+
# Note this out of bounds for nanosecond Timedelta
92+
res4 = py_td64_to_tdstruct(val, 7) # s
93+
exp4 = {
94+
"days": 144150,
95+
"hrs": 21,
96+
"min": 10,
97+
"sec": 34,
98+
"ms": 0,
99+
"us": 0,
100+
"ns": 0,
101+
"seconds": 76234,
102+
"microseconds": 0,
103+
"nanoseconds": 0,
104+
}
105+
assert res4 == exp4

0 commit comments

Comments
 (0)