""" Implementation of operations on numpy timedelta64. """ import numpy as np import operator from llvmlite.llvmpy.core import Type, Constant import llvmlite.llvmpy.core as lc from numba.core import types, cgutils from numba.core.imputils import (lower_builtin, lower_constant, impl_ret_untracked, lower_cast) from numba.np import npdatetime_helpers, numpy_support, npyfuncs from numba.extending import overload_method from numba.core.config import IS_32BITS from numba.core.errors import LoweringError # datetime64 and timedelta64 use the same internal representation DATETIME64 = TIMEDELTA64 = Type.int(64) NAT = Constant.int(TIMEDELTA64, npdatetime_helpers.NAT) TIMEDELTA_BINOP_SIG = (types.NPTimedelta,) * 2 def scale_by_constant(builder, val, factor): """ Multiply *val* by the constant *factor*. """ return builder.mul(val, Constant.int(TIMEDELTA64, factor)) def unscale_by_constant(builder, val, factor): """ Divide *val* by the constant *factor*. """ return builder.sdiv(val, Constant.int(TIMEDELTA64, factor)) def add_constant(builder, val, const): """ Add constant *const* to *val*. """ return builder.add(val, Constant.int(TIMEDELTA64, const)) def scale_timedelta(context, builder, val, srcty, destty): """ Scale the timedelta64 *val* from *srcty* to *destty* (both numba.types.NPTimedelta instances) """ factor = npdatetime_helpers.get_timedelta_conversion_factor( srcty.unit, destty.unit) if factor is None: # This can happen when using explicit output in a ufunc. msg = f"cannot convert timedelta64 from {srcty.unit} to {destty.unit}" raise LoweringError(msg) return scale_by_constant(builder, val, factor) def normalize_timedeltas(context, builder, left, right, leftty, rightty): """ Scale either *left* or *right* to the other's unit, in order to have homogeneous units. """ factor = npdatetime_helpers.get_timedelta_conversion_factor( leftty.unit, rightty.unit) if factor is not None: return scale_by_constant(builder, left, factor), right factor = npdatetime_helpers.get_timedelta_conversion_factor( rightty.unit, leftty.unit) if factor is not None: return left, scale_by_constant(builder, right, factor) # Typing should not let this happen, except on == and != operators raise RuntimeError("cannot normalize %r and %r" % (leftty, rightty)) def alloc_timedelta_result(builder, name='ret'): """ Allocate a NaT-initialized datetime64 (or timedelta64) result slot. """ ret = cgutils.alloca_once(builder, TIMEDELTA64, name=name) builder.store(NAT, ret) return ret def alloc_boolean_result(builder, name='ret'): """ Allocate an uninitialized boolean result slot. """ ret = cgutils.alloca_once(builder, Type.int(1), name=name) return ret def is_not_nat(builder, val): """ Return a predicate which is true if *val* is not NaT. """ return builder.icmp(lc.ICMP_NE, val, NAT) def are_not_nat(builder, vals): """ Return a predicate which is true if all of *vals* are not NaT. """ assert len(vals) >= 1 pred = is_not_nat(builder, vals[0]) for val in vals[1:]: pred = builder.and_(pred, is_not_nat(builder, val)) return pred def make_constant_array(vals): consts = [Constant.int(TIMEDELTA64, v) for v in vals] return Constant.array(TIMEDELTA64, consts) normal_year_months = make_constant_array([31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]) leap_year_months = make_constant_array([31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]) normal_year_months_acc = make_constant_array( [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]) leap_year_months_acc = make_constant_array( [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335]) @lower_constant(types.NPDatetime) @lower_constant(types.NPTimedelta) def datetime_constant(context, builder, ty, pyval): return DATETIME64(pyval.astype(np.int64)) # Arithmetic operators on timedelta64 @lower_builtin(operator.pos, types.NPTimedelta) def timedelta_pos_impl(context, builder, sig, args): res = args[0] return impl_ret_untracked(context, builder, sig.return_type, res) @lower_builtin(operator.neg, types.NPTimedelta) def timedelta_neg_impl(context, builder, sig, args): res = builder.neg(args[0]) return impl_ret_untracked(context, builder, sig.return_type, res) @lower_builtin(abs, types.NPTimedelta) def timedelta_abs_impl(context, builder, sig, args): val, = args ret = alloc_timedelta_result(builder) with builder.if_else(cgutils.is_scalar_neg(builder, val)) as (then, otherwise): with then: builder.store(builder.neg(val), ret) with otherwise: builder.store(val, ret) res = builder.load(ret) return impl_ret_untracked(context, builder, sig.return_type, res) def timedelta_sign_impl(context, builder, sig, args): """ np.sign(timedelta64) """ val, = args ret = alloc_timedelta_result(builder) zero = Constant.int(TIMEDELTA64, 0) with builder.if_else(builder.icmp(lc.ICMP_SGT, val, zero) ) as (gt_zero, le_zero): with gt_zero: builder.store(Constant.int(TIMEDELTA64, 1), ret) with le_zero: with builder.if_else(builder.icmp(lc.ICMP_EQ, val, zero) ) as (eq_zero, lt_zero): with eq_zero: builder.store(Constant.int(TIMEDELTA64, 0), ret) with lt_zero: builder.store(Constant.int(TIMEDELTA64, -1), ret) res = builder.load(ret) return impl_ret_untracked(context, builder, sig.return_type, res) @lower_builtin(operator.add, *TIMEDELTA_BINOP_SIG) @lower_builtin(operator.iadd, *TIMEDELTA_BINOP_SIG) def timedelta_add_impl(context, builder, sig, args): [va, vb] = args [ta, tb] = sig.args ret = alloc_timedelta_result(builder) with cgutils.if_likely(builder, are_not_nat(builder, [va, vb])): va = scale_timedelta(context, builder, va, ta, sig.return_type) vb = scale_timedelta(context, builder, vb, tb, sig.return_type) builder.store(builder.add(va, vb), ret) res = builder.load(ret) return impl_ret_untracked(context, builder, sig.return_type, res) @lower_builtin(operator.sub, *TIMEDELTA_BINOP_SIG) @lower_builtin(operator.isub, *TIMEDELTA_BINOP_SIG) def timedelta_sub_impl(context, builder, sig, args): [va, vb] = args [ta, tb] = sig.args ret = alloc_timedelta_result(builder) with cgutils.if_likely(builder, are_not_nat(builder, [va, vb])): va = scale_timedelta(context, builder, va, ta, sig.return_type) vb = scale_timedelta(context, builder, vb, tb, sig.return_type) builder.store(builder.sub(va, vb), ret) res = builder.load(ret) return impl_ret_untracked(context, builder, sig.return_type, res) def _timedelta_times_number(context, builder, td_arg, td_type, number_arg, number_type, return_type): ret = alloc_timedelta_result(builder) with cgutils.if_likely(builder, is_not_nat(builder, td_arg)): if isinstance(number_type, types.Float): val = builder.sitofp(td_arg, number_arg.type) val = builder.fmul(val, number_arg) val = _cast_to_timedelta(context, builder, val) else: val = builder.mul(td_arg, number_arg) # The scaling is required for ufunc np.multiply() with an explicit # output in a different unit. val = scale_timedelta(context, builder, val, td_type, return_type) builder.store(val, ret) return builder.load(ret) @lower_builtin(operator.mul, types.NPTimedelta, types.Integer) @lower_builtin(operator.imul, types.NPTimedelta, types.Integer) @lower_builtin(operator.mul, types.NPTimedelta, types.Float) @lower_builtin(operator.imul, types.NPTimedelta, types.Float) def timedelta_times_number(context, builder, sig, args): res = _timedelta_times_number(context, builder, args[0], sig.args[0], args[1], sig.args[1], sig.return_type) return impl_ret_untracked(context, builder, sig.return_type, res) @lower_builtin(operator.mul, types.Integer, types.NPTimedelta) @lower_builtin(operator.imul, types.Integer, types.NPTimedelta) @lower_builtin(operator.mul, types.Float, types.NPTimedelta) @lower_builtin(operator.imul, types.Float, types.NPTimedelta) def number_times_timedelta(context, builder, sig, args): res = _timedelta_times_number(context, builder, args[1], sig.args[1], args[0], sig.args[0], sig.return_type) return impl_ret_untracked(context, builder, sig.return_type, res) @lower_builtin(operator.truediv, types.NPTimedelta, types.Integer) @lower_builtin(operator.itruediv, types.NPTimedelta, types.Integer) @lower_builtin(operator.floordiv, types.NPTimedelta, types.Integer) @lower_builtin(operator.ifloordiv, types.NPTimedelta, types.Integer) @lower_builtin(operator.truediv, types.NPTimedelta, types.Float) @lower_builtin(operator.itruediv, types.NPTimedelta, types.Float) @lower_builtin(operator.floordiv, types.NPTimedelta, types.Float) @lower_builtin(operator.ifloordiv, types.NPTimedelta, types.Float) def timedelta_over_number(context, builder, sig, args): td_arg, number_arg = args number_type = sig.args[1] ret = alloc_timedelta_result(builder) ok = builder.and_(is_not_nat(builder, td_arg), builder.not_(cgutils.is_scalar_zero_or_nan(builder, number_arg))) with cgutils.if_likely(builder, ok): # Denominator is non-zero, non-NaN if isinstance(number_type, types.Float): val = builder.sitofp(td_arg, number_arg.type) val = builder.fdiv(val, number_arg) val = _cast_to_timedelta(context, builder, val) else: val = builder.sdiv(td_arg, number_arg) # The scaling is required for ufuncs np.*divide() with an explicit # output in a different unit. val = scale_timedelta(context, builder, val, sig.args[0], sig.return_type) builder.store(val, ret) res = builder.load(ret) return impl_ret_untracked(context, builder, sig.return_type, res) @lower_builtin(operator.truediv, *TIMEDELTA_BINOP_SIG) @lower_builtin(operator.itruediv, *TIMEDELTA_BINOP_SIG) def timedelta_over_timedelta(context, builder, sig, args): [va, vb] = args [ta, tb] = sig.args not_nan = are_not_nat(builder, [va, vb]) ll_ret_type = context.get_value_type(sig.return_type) ret = cgutils.alloca_once(builder, ll_ret_type, name='ret') builder.store(Constant.real(ll_ret_type, float('nan')), ret) with cgutils.if_likely(builder, not_nan): va, vb = normalize_timedeltas(context, builder, va, vb, ta, tb) va = builder.sitofp(va, ll_ret_type) vb = builder.sitofp(vb, ll_ret_type) builder.store(builder.fdiv(va, vb), ret) res = builder.load(ret) return impl_ret_untracked(context, builder, sig.return_type, res) if numpy_support.numpy_version >= (1, 16): # np 1.16 added support for: # * np.floor_divide on mm->q # * np.remainder on mm->m @lower_builtin(operator.floordiv, *TIMEDELTA_BINOP_SIG) def timedelta_floor_div_timedelta(context, builder, sig, args): [va, vb] = args [ta, tb] = sig.args ll_ret_type = context.get_value_type(sig.return_type) not_nan = are_not_nat(builder, [va, vb]) ret = cgutils.alloca_once(builder, ll_ret_type, name='ret') zero = Constant.int(ll_ret_type, 0) one = Constant.int(ll_ret_type, 1) builder.store(zero, ret) with cgutils.if_likely(builder, not_nan): va, vb = normalize_timedeltas(context, builder, va, vb, ta, tb) # is the denominator zero or NaT? denom_ok = builder.not_(builder.icmp_signed('==', vb, zero)) with cgutils.if_likely(builder, denom_ok): # is either arg negative? vaneg = builder.icmp_signed('<', va, zero) neg = builder.or_(vaneg, builder.icmp_signed('<', vb, zero)) with builder.if_else(neg) as (then, otherwise): with then: # one or more value negative with builder.if_else(vaneg) as (negthen, negotherwise): with negthen: top = builder.sub(va, one) div = builder.sdiv(top, vb) builder.store(div, ret) with negotherwise: top = builder.add(va, one) div = builder.sdiv(top, vb) builder.store(div, ret) with otherwise: div = builder.sdiv(va, vb) builder.store(div, ret) res = builder.load(ret) return impl_ret_untracked(context, builder, sig.return_type, res) def timedelta_mod_timedelta(context, builder, sig, args): # inspired by https://github.com/numpy/numpy/blob/fe8072a12d65e43bd2e0b0f9ad67ab0108cc54b3/numpy/core/src/umath/loops.c.src#L1424 # alg is basically as `a % b`: # if a or b is NaT return NaT # elseif b is 0 return NaT # else pretend a and b are int and do pythonic int modulus [va, vb] = args [ta, tb] = sig.args not_nan = are_not_nat(builder, [va, vb]) ll_ret_type = context.get_value_type(sig.return_type) ret = alloc_timedelta_result(builder) builder.store(NAT, ret) zero = Constant.int(ll_ret_type, 0) with cgutils.if_likely(builder, not_nan): va, vb = normalize_timedeltas(context, builder, va, vb, ta, tb) # is the denominator zero or NaT? denom_ok = builder.not_(builder.icmp_signed('==', vb, zero)) with cgutils.if_likely(builder, denom_ok): # is either arg negative? vapos = builder.icmp_signed('>', va, zero) vbpos = builder.icmp_signed('>', vb, zero) rem = builder.srem(va, vb) cond = builder.or_(builder.and_(vapos, vbpos), builder.icmp_signed('==', rem, zero)) with builder.if_else(cond) as (then, otherwise): with then: builder.store(rem, ret) with otherwise: builder.store(builder.add(rem, vb), ret) res = builder.load(ret) return impl_ret_untracked(context, builder, sig.return_type, res) # Comparison operators on timedelta64 def _create_timedelta_comparison_impl(ll_op, default_value): def impl(context, builder, sig, args): [va, vb] = args [ta, tb] = sig.args ret = alloc_boolean_result(builder) with builder.if_else(are_not_nat(builder, [va, vb])) as (then, otherwise): with then: try: norm_a, norm_b = normalize_timedeltas( context, builder, va, vb, ta, tb) except RuntimeError: # Cannot normalize units => the values are unequal (except if NaT) builder.store(default_value, ret) else: builder.store(builder.icmp(ll_op, norm_a, norm_b), ret) with otherwise: if numpy_support.numpy_version < (1, 16): # No scaling when comparing NaTs builder.store(builder.icmp(ll_op, va, vb), ret) else: # NumPy >= 1.16 switched to NaT ==/>=/>/ being True if ll_op == lc.ICMP_NE: builder.store(cgutils.true_bit, ret) else: builder.store(cgutils.false_bit, ret) res = builder.load(ret) return impl_ret_untracked(context, builder, sig.return_type, res) return impl def _create_timedelta_ordering_impl(ll_op): def impl(context, builder, sig, args): [va, vb] = args [ta, tb] = sig.args ret = alloc_boolean_result(builder) with builder.if_else(are_not_nat(builder, [va, vb])) as (then, otherwise): with then: norm_a, norm_b = normalize_timedeltas( context, builder, va, vb, ta, tb) builder.store(builder.icmp(ll_op, norm_a, norm_b), ret) with otherwise: if numpy_support.numpy_version < (1, 16): # No scaling when comparing NaT with something else # (i.e. NaT is <= everything else, since it's the smallest # int64 value) builder.store(builder.icmp(ll_op, va, vb), ret) else: # NumPy >= 1.16 switched to NaT >=/>/= 2: return dt_val, src_unit # Need to compute the day ordinal for *dt_val* if src_unit_code == 0: # Years to days year_val = dt_val days_val = year_to_days(builder, year_val) else: # Months to days leap_array = cgutils.global_constant(builder, "leap_year_months_acc", leap_year_months_acc) normal_array = cgutils.global_constant(builder, "normal_year_months_acc", normal_year_months_acc) days = cgutils.alloca_once(builder, TIMEDELTA64) # First compute year number and month number year, month = cgutils.divmod_by_constant(builder, dt_val, 12) # Then deduce the number of days with builder.if_else(is_leap_year(builder, year)) as (then, otherwise): with then: addend = builder.load(cgutils.gep(builder, leap_array, 0, month, inbounds=True)) builder.store(addend, days) with otherwise: addend = builder.load(cgutils.gep(builder, normal_array, 0, month, inbounds=True)) builder.store(addend, days) days_val = year_to_days(builder, year) days_val = builder.add(days_val, builder.load(days)) if dest_unit_code == 2: # Need to scale back to weeks weeks, _ = cgutils.divmod_by_constant(builder, days_val, 7) return weeks, 'W' else: return days_val, 'D' def convert_datetime_for_arith(builder, dt_val, src_unit, dest_unit): """ Convert datetime *dt_val* from *src_unit* to *dest_unit*. """ # First partial conversion to days or weeks, if necessary. dt_val, dt_unit = reduce_datetime_for_unit( builder, dt_val, src_unit, dest_unit) # Then multiply by the remaining constant factor. dt_factor = npdatetime_helpers.get_timedelta_conversion_factor(dt_unit, dest_unit) if dt_factor is None: # This can happen when using explicit output in a ufunc. raise LoweringError("cannot convert datetime64 from %r to %r" % (src_unit, dest_unit)) return scale_by_constant(builder, dt_val, dt_factor) def _datetime_timedelta_arith(ll_op_name): def impl(context, builder, dt_arg, dt_unit, td_arg, td_unit, ret_unit): ret = alloc_timedelta_result(builder) with cgutils.if_likely(builder, are_not_nat(builder, [dt_arg, td_arg])): dt_arg = convert_datetime_for_arith(builder, dt_arg, dt_unit, ret_unit) td_factor = npdatetime_helpers.get_timedelta_conversion_factor( td_unit, ret_unit) td_arg = scale_by_constant(builder, td_arg, td_factor) ret_val = getattr(builder, ll_op_name)(dt_arg, td_arg) builder.store(ret_val, ret) return builder.load(ret) return impl _datetime_plus_timedelta = _datetime_timedelta_arith('add') _datetime_minus_timedelta = _datetime_timedelta_arith('sub') # datetime64 + timedelta64 @lower_builtin(operator.add, types.NPDatetime, types.NPTimedelta) @lower_builtin(operator.iadd, types.NPDatetime, types.NPTimedelta) def datetime_plus_timedelta(context, builder, sig, args): dt_arg, td_arg = args dt_type, td_type = sig.args res = _datetime_plus_timedelta(context, builder, dt_arg, dt_type.unit, td_arg, td_type.unit, sig.return_type.unit) return impl_ret_untracked(context, builder, sig.return_type, res) @lower_builtin(operator.add, types.NPTimedelta, types.NPDatetime) @lower_builtin(operator.iadd, types.NPTimedelta, types.NPDatetime) def timedelta_plus_datetime(context, builder, sig, args): td_arg, dt_arg = args td_type, dt_type = sig.args res = _datetime_plus_timedelta(context, builder, dt_arg, dt_type.unit, td_arg, td_type.unit, sig.return_type.unit) return impl_ret_untracked(context, builder, sig.return_type, res) # datetime64 - timedelta64 @lower_builtin(operator.sub, types.NPDatetime, types.NPTimedelta) @lower_builtin(operator.isub, types.NPDatetime, types.NPTimedelta) def datetime_minus_timedelta(context, builder, sig, args): dt_arg, td_arg = args dt_type, td_type = sig.args res = _datetime_minus_timedelta(context, builder, dt_arg, dt_type.unit, td_arg, td_type.unit, sig.return_type.unit) return impl_ret_untracked(context, builder, sig.return_type, res) # datetime64 - datetime64 @lower_builtin(operator.sub, types.NPDatetime, types.NPDatetime) def datetime_minus_datetime(context, builder, sig, args): va, vb = args ta, tb = sig.args unit_a = ta.unit unit_b = tb.unit ret_unit = sig.return_type.unit ret = alloc_timedelta_result(builder) with cgutils.if_likely(builder, are_not_nat(builder, [va, vb])): va = convert_datetime_for_arith(builder, va, unit_a, ret_unit) vb = convert_datetime_for_arith(builder, vb, unit_b, ret_unit) ret_val = builder.sub(va, vb) builder.store(ret_val, ret) res = builder.load(ret) return impl_ret_untracked(context, builder, sig.return_type, res) # datetime64 comparisons def _create_datetime_comparison_impl(ll_op): def impl(context, builder, sig, args): va, vb = args ta, tb = sig.args unit_a = ta.unit unit_b = tb.unit ret_unit = npdatetime_helpers.get_best_unit(unit_a, unit_b) ret = alloc_boolean_result(builder) with builder.if_else(are_not_nat(builder, [va, vb])) as (then, otherwise): with then: norm_a = convert_datetime_for_arith( builder, va, unit_a, ret_unit) norm_b = convert_datetime_for_arith( builder, vb, unit_b, ret_unit) ret_val = builder.icmp(ll_op, norm_a, norm_b) builder.store(ret_val, ret) with otherwise: if numpy_support.numpy_version < (1, 16): # No scaling when comparing NaTs ret_val = builder.icmp(ll_op, va, vb) else: if ll_op == lc.ICMP_NE: ret_val = cgutils.true_bit else: ret_val = cgutils.false_bit builder.store(ret_val, ret) res = builder.load(ret) return impl_ret_untracked(context, builder, sig.return_type, res) return impl datetime_eq_datetime_impl = _create_datetime_comparison_impl(lc.ICMP_EQ) datetime_ne_datetime_impl = _create_datetime_comparison_impl(lc.ICMP_NE) datetime_lt_datetime_impl = _create_datetime_comparison_impl(lc.ICMP_SLT) datetime_le_datetime_impl = _create_datetime_comparison_impl(lc.ICMP_SLE) datetime_gt_datetime_impl = _create_datetime_comparison_impl(lc.ICMP_SGT) datetime_ge_datetime_impl = _create_datetime_comparison_impl(lc.ICMP_SGE) for op, func in [(operator.eq, datetime_eq_datetime_impl), (operator.ne, datetime_ne_datetime_impl), (operator.lt, datetime_lt_datetime_impl), (operator.le, datetime_le_datetime_impl), (operator.gt, datetime_gt_datetime_impl), (operator.ge, datetime_ge_datetime_impl)]: lower_builtin(op, *[types.NPDatetime]*2)(func) ######################################################################## # datetime/timedelta fmax/fmin maximum/minimum support def _gen_datetime_max_impl(NAT_DOMINATES): def datetime_max_impl(context, builder, sig, args): # note this could be optimizing relying on the actual value of NAT # but as NumPy doesn't rely on this, this seems more resilient in1, in2 = args in1_not_nat = is_not_nat(builder, in1) in2_not_nat = is_not_nat(builder, in2) in1_ge_in2 = builder.icmp(lc.ICMP_SGE, in1, in2) res = builder.select(in1_ge_in2, in1, in2) if NAT_DOMINATES and numpy_support.numpy_version >= (1, 18): # NaT now dominates, like NaN in1, in2 = in2, in1 res = builder.select(in1_not_nat, res, in2) res = builder.select(in2_not_nat, res, in1) return impl_ret_untracked(context, builder, sig.return_type, res) return datetime_max_impl datetime_maximum_impl = _gen_datetime_max_impl(True) datetime_fmax_impl = _gen_datetime_max_impl(False) def _gen_datetime_min_impl(NAT_DOMINATES): def datetime_min_impl(context, builder, sig, args): # note this could be optimizing relying on the actual value of NAT # but as NumPy doesn't rely on this, this seems more resilient in1, in2 = args in1_not_nat = is_not_nat(builder, in1) in2_not_nat = is_not_nat(builder, in2) in1_le_in2 = builder.icmp(lc.ICMP_SLE, in1, in2) res = builder.select(in1_le_in2, in1, in2) if NAT_DOMINATES and numpy_support.numpy_version >= (1, 18): # NaT now dominates, like NaN in1, in2 = in2, in1 res = builder.select(in1_not_nat, res, in2) res = builder.select(in2_not_nat, res, in1) return impl_ret_untracked(context, builder, sig.return_type, res) return datetime_min_impl datetime_minimum_impl = _gen_datetime_min_impl(True) datetime_fmin_impl = _gen_datetime_min_impl(False) def _gen_timedelta_max_impl(NAT_DOMINATES): def timedelta_max_impl(context, builder, sig, args): # note this could be optimizing relying on the actual value of NAT # but as NumPy doesn't rely on this, this seems more resilient in1, in2 = args in1_not_nat = is_not_nat(builder, in1) in2_not_nat = is_not_nat(builder, in2) in1_ge_in2 = builder.icmp(lc.ICMP_SGE, in1, in2) res = builder.select(in1_ge_in2, in1, in2) if NAT_DOMINATES and numpy_support.numpy_version >= (1, 18): # NaT now dominates, like NaN in1, in2 = in2, in1 res = builder.select(in1_not_nat, res, in2) res = builder.select(in2_not_nat, res, in1) return impl_ret_untracked(context, builder, sig.return_type, res) return timedelta_max_impl timedelta_maximum_impl = _gen_timedelta_max_impl(True) timedelta_fmax_impl = _gen_timedelta_max_impl(False) def _gen_timedelta_min_impl(NAT_DOMINATES): def timedelta_min_impl(context, builder, sig, args): # note this could be optimizing relying on the actual value of NAT # but as NumPy doesn't rely on this, this seems more resilient in1, in2 = args in1_not_nat = is_not_nat(builder, in1) in2_not_nat = is_not_nat(builder, in2) in1_le_in2 = builder.icmp(lc.ICMP_SLE, in1, in2) res = builder.select(in1_le_in2, in1, in2) if NAT_DOMINATES and numpy_support.numpy_version >= (1, 18): # NaT now dominates, like NaN in1, in2 = in2, in1 res = builder.select(in1_not_nat, res, in2) res = builder.select(in2_not_nat, res, in1) return impl_ret_untracked(context, builder, sig.return_type, res) return timedelta_min_impl timedelta_minimum_impl = _gen_timedelta_min_impl(True) timedelta_fmin_impl = _gen_timedelta_min_impl(False) def _cast_to_timedelta(context, builder, val): temp = builder.alloca(TIMEDELTA64) val_is_nan = builder.fcmp_unordered('uno', val, val) with builder.if_else(val_is_nan) as ( then, els): with then: # NaN does not guarantee to cast to NAT. # We should store NAT explicitly. builder.store(NAT, temp) with els: builder.store(builder.fptosi(val, TIMEDELTA64), temp) return builder.load(temp) @lower_builtin(np.isnat, types.NPDatetime) @lower_builtin(np.isnat, types.NPTimedelta) def _np_isnat_impl(context, builder, sig, args): return npyfuncs.np_datetime_isnat_impl(context, builder, sig, args) @lower_cast(types.NPDatetime, types.Integer) @lower_cast(types.NPTimedelta, types.Integer) def _cast_npdatetime_int64(context, builder, fromty, toty, val): if toty.bitwidth != 64: # all date time types are 64 bit msg = f"Cannot cast {fromty} to {toty} as {toty} is not 64 bits wide." raise ValueError(msg) return val @overload_method(types.NPTimedelta, '__hash__') @overload_method(types.NPDatetime, '__hash__') def ol_hash_npdatetime(x): if IS_32BITS: def impl(x): x = np.int64(x) if x < 2**31 - 1: # x < LONG_MAX y = np.int32(x) else: hi = (np.int64(x) & 0xffffffff00000000) >> 32 lo = (np.int64(x) & 0x00000000ffffffff) y = np.int32(lo + (1000003) * hi) if y == -1: y = np.int32(-2) return y else: def impl(x): if np.int64(x) == -1: return np.int64(-2) return np.int64(x) return impl