forked from numba/numba
-
Notifications
You must be signed in to change notification settings - Fork 0
/
npdatetime.py
198 lines (178 loc) · 6.38 KB
/
npdatetime.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
"""
Helper functions for numpy.timedelta64 and numpy.datetime64.
For now, multiples-of-units (for example timedeltas expressed in tens
of seconds) are not supported.
"""
import numpy as np
# Numpy 1.6 has broken datetime64 support
NPDATETIME_SUPPORTED = not np.__version__.startswith('1.6.')
DATETIME_UNITS = {
'Y': 0, # Years
'M': 1, # Months
'W': 2, # Weeks
# Yes, there's a gap here
'D': 4, # Days
'h': 5, # Hours
'm': 6, # Minutes
's': 7, # Seconds
'ms': 8, # Milliseconds
'us': 9, # Microseconds
'ns': 10, # Nanoseconds
'ps': 11, # Picoseconds
'fs': 12, # Femtoseconds
'as': 13, # Attoseconds
'': 14, # "generic", i.e. unit-less
}
# Numpy's special "Not a Time" value (should be equal to -2**63)
if NPDATETIME_SUPPORTED:
NAT = np.timedelta64('nat').astype(np.int64)
# NOTE: numpy has several inconsistent functions for timedelta casting:
# - can_cast_timedelta64_{metadata,units}() disallows "safe" casting
# to and from generic units
# - cast_timedelta_to_timedelta() allows casting from (but not to)
# generic units
# - compute_datetime_metadata_greatest_common_divisor() allows casting from
# generic units (used for promotion)
def can_cast_timedelta_units(src, dest):
# Mimick numpy's "safe" casting and promotion
# `dest` must be more precise than `src` and they must be compatible
# for conversion.
src = DATETIME_UNITS[src]
dest = DATETIME_UNITS[dest]
if src == dest:
return True
if src == 14:
return True
if src > dest:
return False
if dest == 14:
# unit-less timedelta64 is not compatible with anything else
return False
if src <= 1 and dest > 1:
# Cannot convert between months or years and other units
return False
return True
# Exact conversion factors from one unit to the immediately more precise one
_factors = {
0: (1, 12), # Years -> Months
2: (4, 7), # Weeks -> Days
4: (5, 24), # Days -> Hours
5: (6, 60), # Hours -> Minutes
6: (7, 60), # Minutes -> Seconds
7: (8, 1000),
8: (9, 1000),
9: (10, 1000),
10: (11, 1000),
11: (12, 1000),
12: (13, 1000),
}
def _get_conversion_multiplier(big_unit_code, small_unit_code):
"""
Return an integer multiplier allowing to convert from *big_unit_code*
to *small_unit_code*.
None is returned if the conversion is not possible through a
simple integer multiplication.
"""
# Mimicks get_datetime_units_factor() in numpy's datetime.c,
# with a twist to allow no-op conversion from generic units.
if big_unit_code == 14:
return 1
c = big_unit_code
factor = 1
while c < small_unit_code:
try:
c, mult = _factors[c]
except KeyError:
# No possible conversion
return None
factor *= mult
if c == small_unit_code:
return factor
else:
return None
def get_timedelta_conversion_factor(src_unit, dest_unit):
"""
Return an integer multiplier allowing to convert from timedeltas
of *src_unit* to *dest_unit*.
"""
return _get_conversion_multiplier(DATETIME_UNITS[src_unit],
DATETIME_UNITS[dest_unit])
def get_datetime_timedelta_conversion(datetime_unit, timedelta_unit):
"""
Compute a possible conversion for combining *datetime_unit* and
*timedelta_unit* (presumably for adding or subtracting).
Return (result unit, integer datetime multiplier, integer timedelta multiplier).
RuntimeError is raised if the combination is impossible.
"""
# XXX now unused (I don't know where / how Numpy uses this)
dt_unit_code = DATETIME_UNITS[datetime_unit]
td_unit_code = DATETIME_UNITS[timedelta_unit]
if td_unit_code == 14 or dt_unit_code == 14:
return datetime_unit, 1, 1
if td_unit_code < 2 and dt_unit_code >= 2:
# Cannot combine Y or M timedelta64 with a finer-grained datetime64
raise RuntimeError("cannot combine datetime64(%r) and timedelta64(%r)"
% (datetime_unit, timedelta_unit))
dt_factor, td_factor = 1, 1
# If years or months, the datetime unit is first scaled to weeks or days,
# then conversion continues below. This is the same algorithm as used
# in Numpy's get_datetime_conversion_factor() (src/multiarray/datetime.c):
# """Conversions between years/months and other units use
# the factor averaged over the 400 year leap year cycle."""
if dt_unit_code == 0:
if td_unit_code >= 4:
dt_factor = 97 + 400 * 365
td_factor = 400
dt_unit_code = 4
elif td_unit_code == 2:
dt_factor = 97 + 400 * 365
td_factor = 400 * 7
dt_unit_code = 2
elif dt_unit_code == 1:
if td_unit_code >= 4:
dt_factor = 97 + 400 * 365
td_factor = 400 * 12
dt_unit_code = 4
elif td_unit_code == 2:
dt_factor = 97 + 400 * 365
td_factor = 400 * 12 * 7
dt_unit_code = 2
if td_unit_code >= dt_unit_code:
factor = _get_conversion_multiplier(dt_unit_code, td_unit_code)
assert factor is not None, (dt_unit_code, td_unit_code)
return timedelta_unit, dt_factor * factor, td_factor
else:
factor = _get_conversion_multiplier(td_unit_code, dt_unit_code)
assert factor is not None, (dt_unit_code, td_unit_code)
return datetime_unit, dt_factor, td_factor * factor
def combine_datetime_timedelta_units(datetime_unit, timedelta_unit):
"""
Return the unit result of combining *datetime_unit* with *timedelta_unit*
(e.g. by adding or subtracting). None is returned if combining
those units is forbidden.
"""
dt_unit_code = DATETIME_UNITS[datetime_unit]
td_unit_code = DATETIME_UNITS[timedelta_unit]
if dt_unit_code == 14:
return timedelta_unit
elif td_unit_code == 14:
return datetime_unit
if td_unit_code < 2 and dt_unit_code >= 2:
return None
if dt_unit_code > td_unit_code:
return datetime_unit
else:
return timedelta_unit
def get_best_unit(unit_a, unit_b):
"""
Get the best (i.e. finer-grained) of two units.
"""
a = DATETIME_UNITS[unit_a]
b = DATETIME_UNITS[unit_b]
if a == 14:
return unit_b
if b == 14:
return unit_a
if b > a:
return unit_b
return unit_a