Repository: numba/numba

Issue:
Support __hash__ for numpy.datetime64
## Feature request
The numpy datetime64 type can't be used as a typed Dict key currently as Numba doesn't seem to implement a hash function for it.

E.g.
```
import numpy as np
from numba.typed import Dict

d = Dict()
d[np.datetime64("2000-01-01")] = 1
```

Fails with an error message stating that there is no `__hash__` attribute on datetime64[D]


Discussion:
Fairly sure that this is just not implemented yet. It's a bit tricky but not too hard to deal with, `__hash__` of the date time types is just their `int64` value.

Resolving patch:
diff --git a/numba/core/typing/builtins.py b/numba/core/typing/builtins.py
--- a/numba/core/typing/builtins.py
+++ b/numba/core/typing/builtins.py
@@ -804,6 +804,15 @@ def typer(val):
             elif isinstance(val, (types.Number, types.Boolean)):
                  # Scalar constructor, e.g. np.int32(42)
                  return ty
+            elif isinstance(val, (types.NPDatetime, types.NPTimedelta)):
+                # Constructor cast from datetime-like, e.g.
+                # > np.int64(np.datetime64("2000-01-01"))
+                if ty.bitwidth == 64:
+                    return ty
+                else:
+                    msg = (f"Cannot cast {val} to {ty} as {ty} is not 64 bits "
+                           "wide.")
+                    raise errors.TypingError(msg)
             else:
                 # unsupported
                 msg = f"Casting {val} to {ty} directly is unsupported."
diff --git a/numba/np/npdatetime.py b/numba/np/npdatetime.py
--- a/numba/np/npdatetime.py
+++ b/numba/np/npdatetime.py
@@ -10,8 +10,10 @@
 
 from numba.core import types, cgutils
 from numba.core.imputils import (lower_builtin, lower_constant,
-                                    impl_ret_untracked)
+                                 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
 
 # datetime64 and timedelta64 use the same internal representation
 DATETIME64 = TIMEDELTA64 = Type.int(64)
@@ -812,3 +814,35 @@ def _cast_to_timedelta(context, builder, val):
 @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


Test patch:
diff --git a/numba/tests/test_npdatetime.py b/numba/tests/test_npdatetime.py
--- a/numba/tests/test_npdatetime.py
+++ b/numba/tests/test_npdatetime.py
@@ -75,6 +75,8 @@ def neg_usecase(x):
 def abs_usecase(x):
     return abs(x)
 
+def hash_usecase(x):
+    return hash(x)
 
 def make_add_constant(const):
     def add_constant(x):
@@ -521,6 +523,26 @@ def check(a):
         check(TD('NaT'))
         check(TD('NaT', 'ms'))
 
+    def test_hash(self):
+        f = self.jit(hash_usecase)
+        def check(a):
+            self.assertPreciseEqual(f(a), hash(a))
+        
+        TD_CASES = ((3,), (-4,), (3, 'ms'), (-4, 'ms'), (27, 'D'),
+                    (2, 'D'), (2, 'W'), (2, 'Y'), (3, 'W'),
+                    (365, 'D'), (10000, 'D'), (-10000, 'D'), 
+                    ('NaT',), ('NaT', 'ms'), ('NaT', 'D'), (-1,))
+        DT_CASES = (('2014',), ('2016',), ('2000',), ('2014-02',),
+                    ('2014-03',), ('2014-04',), ('2016-02',), ('2000-12-31',),
+                    ('2014-01-16',), ('2014-01-05',), ('2014-01-07',),
+                    ('2014-01-06',), ('2014-02-02',), ('2014-02-27',),
+                    ('2014-02-16',), ('2014-03-01',), ('2000-01-01T01:02:03.002Z',),
+                    ('2000-01-01T01:02:03Z',), ('NaT',))
+
+        for case, typ in zip(TD_CASES + DT_CASES,
+                             (TD,) * len(TD_CASES) + (DT,) * len(TD_CASES)):
+            check(typ(*case))
+
 
 class TestTimedeltaArithmeticNoPython(TestTimedeltaArithmetic):