summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--numpy/core/src/multiarray/datetime.c22
-rw-r--r--numpy/core/tests/test_datetime.py37
2 files changed, 58 insertions, 1 deletions
diff --git a/numpy/core/src/multiarray/datetime.c b/numpy/core/src/multiarray/datetime.c
index b24bc0356..e0064c017 100644
--- a/numpy/core/src/multiarray/datetime.c
+++ b/numpy/core/src/multiarray/datetime.c
@@ -13,6 +13,7 @@
#include <Python.h>
#include "numpy/arrayobject.h"
+#include "numpyos.h"
#include "npy_config.h"
#include "npy_pycompat.h"
@@ -723,12 +724,21 @@ parse_datetime_extended_unit_from_string(char const *str, Py_ssize_t len,
{
char const *substr = str, *substrend = NULL;
int den = 1;
+ npy_longlong true_meta_val;
/* First comes an optional integer multiplier */
out_meta->num = (int)strtol_const(substr, &substrend, 10);
if (substr == substrend) {
out_meta->num = 1;
}
+ else {
+ // check for 32-bit integer overflow
+ char *endptr = NULL;
+ true_meta_val = NumPyOS_strtoll(substr, &endptr, 10);
+ if (true_meta_val > INT_MAX || true_meta_val < 0) {
+ goto bad_input;
+ }
+ }
substr = substrend;
/* Next comes the unit itself, followed by either '/' or the string end */
@@ -3776,7 +3786,17 @@ time_to_time_resolve_descriptors(
meta2 = get_datetime_metadata_from_dtype(loop_descrs[1]);
assert(meta2 != NULL);
- if (meta1->base == meta2->base && meta1->num == meta2->num) {
+ if ((meta1->base == meta2->base && meta1->num == meta2->num) ||
+ // handle some common metric prefix conversions
+ // 1000 fold conversions
+ ((meta2->base >= 7) && (meta1->base - meta2->base == 1)
+ && ((meta1->num / meta2->num) == 1000)) ||
+ // 10^6 fold conversions
+ ((meta2->base >= 7) && (meta1->base - meta2->base == 2)
+ && ((meta1->num / meta2->num) == 1000000)) ||
+ // 10^9 fold conversions
+ ((meta2->base >= 7) && (meta1->base - meta2->base == 3)
+ && ((meta1->num / meta2->num) == 1000000000))) {
if (byteorder_may_allow_view) {
return NPY_NO_CASTING | byteorder_may_allow_view;
}
diff --git a/numpy/core/tests/test_datetime.py b/numpy/core/tests/test_datetime.py
index 5a490646e..69eba7ba0 100644
--- a/numpy/core/tests/test_datetime.py
+++ b/numpy/core/tests/test_datetime.py
@@ -63,6 +63,7 @@ class TestDateTime:
assert_raises(TypeError, np.dtype, 'm7')
assert_raises(TypeError, np.dtype, 'M16')
assert_raises(TypeError, np.dtype, 'm16')
+ assert_raises(TypeError, np.dtype, 'M8[3000000000ps]')
def test_datetime_casting_rules(self):
# Cannot cast safely/same_kind between timedelta and datetime
@@ -137,6 +138,42 @@ class TestDateTime:
assert_(not np.can_cast('M8[h]', 'M8', casting='same_kind'))
assert_(not np.can_cast('M8[h]', 'M8', casting='safe'))
+ def test_datetime_prefix_conversions(self):
+ # regression tests related to gh-19631;
+ # test metric prefixes from seconds down to
+ # attoseconds for bidirectional conversions
+ smaller_units = ['M8[7000ms]',
+ 'M8[2000us]',
+ 'M8[1000ns]',
+ 'M8[5000ns]',
+ 'M8[2000ps]',
+ 'M8[9000fs]',
+ 'M8[1000as]',
+ 'M8[2000000ps]',
+ 'M8[1000000as]',
+ 'M8[2000000000ps]',
+ 'M8[1000000000as]']
+ larger_units = ['M8[7s]',
+ 'M8[2ms]',
+ 'M8[us]',
+ 'M8[5us]',
+ 'M8[2ns]',
+ 'M8[9ps]',
+ 'M8[1fs]',
+ 'M8[2us]',
+ 'M8[1ps]',
+ 'M8[2ms]',
+ 'M8[1ns]']
+ for larger_unit, smaller_unit in zip(larger_units, smaller_units):
+ assert np.can_cast(larger_unit, smaller_unit, casting='safe')
+ assert np.can_cast(smaller_unit, larger_unit, casting='safe')
+
+ @pytest.mark.parametrize("unit", [
+ "s", "ms", "us", "ns", "ps", "fs", "as"])
+ def test_prohibit_negative_datetime(self, unit):
+ with assert_raises(TypeError):
+ np.array([1], dtype=f"M8[-1{unit}]")
+
def test_compare_generic_nat(self):
# regression tests for gh-6452
assert_(np.datetime64('NaT') !=