11from __future__ import annotations
22
3+ import re
34import warnings
45from typing import TYPE_CHECKING , TypedDict , overload
56
@@ -174,11 +175,13 @@ def default(self, o: object) -> Any:
174175 return str (o )
175176 if np .isscalar (o ):
176177 out : Any
177- if hasattr (o , "dtype" ) and o .dtype .kind == "M " and hasattr (o , "view" ):
178+ if hasattr (o , "dtype" ) and o .dtype .kind in "Mm " and hasattr (o , "view" ):
178179 # https://github.com/zarr-developers/zarr-python/issues/2119
179180 # `.item()` on a datetime type might or might not return an
180181 # integer, depending on the value.
181182 # Explicitly cast to an int first, and then grab .item()
183+ if np .isnat (o ):
184+ return "NaT"
182185 out = o .view ("i8" ).item ()
183186 else :
184187 # convert numpy scalar to python type, and pass
@@ -440,12 +443,25 @@ def update_attributes(self, attributes: dict[str, JSON]) -> Self:
440443FLOAT = np .float16 | np .float32 | np .float64
441444COMPLEX_DTYPE = Literal ["complex64" , "complex128" ]
442445COMPLEX = np .complex64 | np .complex128
446+ DATETIME_DTYPE = Literal ["datetime64" ]
447+ DATETIME = np .datetime64
448+ TIMEDELTA_DTYPE = Literal ["timedelta64" ]
449+ TIMEDELTA = np .timedelta64
443450STRING_DTYPE = Literal ["string" ]
444451STRING = np .str_
445452BYTES_DTYPE = Literal ["bytes" ]
446453BYTES = np .bytes_
447454
448- ALL_DTYPES = BOOL_DTYPE | INTEGER_DTYPE | FLOAT_DTYPE | COMPLEX_DTYPE | STRING_DTYPE | BYTES_DTYPE
455+ ALL_DTYPES = (
456+ BOOL_DTYPE
457+ | INTEGER_DTYPE
458+ | FLOAT_DTYPE
459+ | COMPLEX_DTYPE
460+ | DATETIME_DTYPE
461+ | TIMEDELTA_DTYPE
462+ | STRING_DTYPE
463+ | BYTES_DTYPE
464+ )
449465
450466
451467@overload
@@ -490,6 +506,20 @@ def parse_fill_value(
490506) -> BYTES : ...
491507
492508
509+ @overload
510+ def parse_fill_value (
511+ fill_value : complex | str | bytes | np .generic | Sequence [Any ] | bool ,
512+ dtype : DATETIME_DTYPE ,
513+ ) -> DATETIME : ...
514+
515+
516+ @overload
517+ def parse_fill_value (
518+ fill_value : complex | str | bytes | np .generic | Sequence [Any ] | bool ,
519+ dtype : TIMEDELTA_DTYPE ,
520+ ) -> TIMEDELTA : ...
521+
522+
493523def parse_fill_value (
494524 fill_value : Any ,
495525 dtype : ALL_DTYPES ,
@@ -551,12 +581,24 @@ def parse_fill_value(
551581 # fill_value != casted_value below.
552582 with warnings .catch_warnings ():
553583 warnings .filterwarnings ("ignore" , category = DeprecationWarning )
554- casted_value = np .dtype (np_dtype ).type (fill_value )
584+ if np .dtype (np_dtype ).kind in "Mm" :
585+ # datetime64 values have an associated precision
586+ match = re .search (r"\[(.*?)\]" , np .dtype (np_dtype ).str )
587+ if match :
588+ precision = match .group (1 )
589+ else :
590+ precision = "s"
591+ casted_value = np .dtype (np_dtype ).type (fill_value , precision )
592+ else :
593+ casted_value = np .dtype (np_dtype ).type (fill_value )
555594 except (ValueError , OverflowError , TypeError ) as e :
556595 raise ValueError (f"fill value { fill_value !r} is not valid for dtype { data_type } " ) from e
557596 # Check if the value is still representable by the dtype
558- if (fill_value == "NaN" and np .isnan (casted_value )) or (
559- fill_value in ["Infinity" , "-Infinity" ] and not np .isfinite (casted_value )
597+ if (
598+ (fill_value == "NaN" and np .isnan (casted_value ))
599+ or (fill_value in ["Infinity" , "-Infinity" ] and not np .isfinite (casted_value ))
600+ or (fill_value == "NaT" and np .isnat (casted_value ))
601+ or (np .dtype (np_dtype ).kind in "Mm" and np .isnat (casted_value ) and np .isnat (fill_value ))
560602 ):
561603 pass
562604 elif np_dtype .kind == "f" :
@@ -576,7 +618,6 @@ def parse_fill_value(
576618 else :
577619 if fill_value != casted_value :
578620 raise ValueError (f"fill value { fill_value !r} is not valid for dtype { data_type } " )
579-
580621 return casted_value
581622
582623
@@ -585,9 +626,17 @@ def default_fill_value(dtype: DataType) -> str | bytes | np.generic:
585626 return ""
586627 elif dtype == DataType .bytes :
587628 return b""
629+ np_dtype = dtype .to_numpy ()
630+ np_dtype = cast (np .dtype [Any ], np_dtype )
631+ if np_dtype .kind in "Mm" :
632+ # datetime64 values have an associated precision
633+ match = re .search (r"\[(.*?)\]" , np_dtype .str )
634+ if match :
635+ precision = match .group (1 )
636+ else :
637+ precision = "s"
638+ return np_dtype .type ("nat" , precision ) # type: ignore[misc,call-arg]
588639 else :
589- np_dtype = dtype .to_numpy ()
590- np_dtype = cast (np .dtype [Any ], np_dtype )
591640 return np_dtype .type (0 ) # type: ignore[misc]
592641
593642
@@ -610,6 +659,24 @@ class DataType(Enum):
610659 float64 = "float64"
611660 complex64 = "complex64"
612661 complex128 = "complex128"
662+ datetime64ns = ("datetime[ns]" ,)
663+ datetime64ms = ("datetime[ms]" ,)
664+ datetime64s = ("datetime[s]" ,)
665+ datetime64m = ("datetime[m]" ,)
666+ datetime64h = ("datetime[h]" ,)
667+ datetime64D = ("datetime[D]" ,)
668+ datetime64W = ("datetime[W]" ,)
669+ datetime64M = ("datetime[M]" ,)
670+ datetime64Y = ("datetime[Y]" ,)
671+ timedelta64ns = ("deltatime[ns]" ,)
672+ timedelta64ms = ("deltatime[ms]" ,)
673+ timedelta64s = ("deltatime[s]" ,)
674+ timedelta64m = ("deltatime[m]" ,)
675+ timedelta64h = ("deltatime[h]" ,)
676+ timedelta64D = ("deltatime[D]" ,)
677+ timedelta64W = ("deltatime[W]" ,)
678+ timedelta64M = ("deltatime[M]" ,)
679+ timedelta64Y = ("deltatime[Y]" ,)
613680 string = "string"
614681 bytes = "bytes"
615682
@@ -630,6 +697,24 @@ def byte_count(self) -> int | None:
630697 DataType .float64 : 8 ,
631698 DataType .complex64 : 8 ,
632699 DataType .complex128 : 16 ,
700+ DataType .datetime64ns : 8 ,
701+ DataType .datetime64ms : 8 ,
702+ DataType .datetime64s : 8 ,
703+ DataType .datetime64m : 8 ,
704+ DataType .datetime64h : 8 ,
705+ DataType .datetime64D : 8 ,
706+ DataType .datetime64W : 8 ,
707+ DataType .datetime64M : 8 ,
708+ DataType .datetime64Y : 8 ,
709+ DataType .timedelta64ns : 8 ,
710+ DataType .timedelta64ms : 8 ,
711+ DataType .timedelta64s : 8 ,
712+ DataType .timedelta64m : 8 ,
713+ DataType .timedelta64h : 8 ,
714+ DataType .timedelta64D : 8 ,
715+ DataType .timedelta64W : 8 ,
716+ DataType .timedelta64M : 8 ,
717+ DataType .timedelta64Y : 8 ,
633718 }
634719 try :
635720 return data_type_byte_counts [self ]
@@ -657,6 +742,24 @@ def to_numpy_shortname(self) -> str:
657742 DataType .float64 : "f8" ,
658743 DataType .complex64 : "c8" ,
659744 DataType .complex128 : "c16" ,
745+ DataType .datetime64ns : "M8[ns]" ,
746+ DataType .datetime64ms : "M8[ms]" ,
747+ DataType .datetime64s : "M8[s]" ,
748+ DataType .datetime64m : "M8[m]" ,
749+ DataType .datetime64h : "M8[h]" ,
750+ DataType .datetime64D : "M8[D]" ,
751+ DataType .datetime64W : "M8[W]" ,
752+ DataType .datetime64M : "M8[M]" ,
753+ DataType .datetime64Y : "M8[Y]" ,
754+ DataType .timedelta64ns : "m8[ns]" ,
755+ DataType .timedelta64ms : "m8[ms]" ,
756+ DataType .timedelta64s : "m8[s]" ,
757+ DataType .timedelta64m : "m8[m]" ,
758+ DataType .timedelta64h : "m8[h]" ,
759+ DataType .timedelta64D : "m8[D]" ,
760+ DataType .timedelta64W : "m8[W]" ,
761+ DataType .timedelta64M : "m8[M]" ,
762+ DataType .timedelta64Y : "m8[Y]" ,
660763 }
661764 return data_type_to_numpy [self ]
662765
@@ -700,6 +803,24 @@ def from_numpy(cls, dtype: np.dtype[Any]) -> DataType:
700803 "<f8" : "float64" ,
701804 "<c8" : "complex64" ,
702805 "<c16" : "complex128" ,
806+ "<M8[ns]" : "datetime64ns" ,
807+ "<M8[ms]" : "datetime64ms" ,
808+ "<M8[s]" : "datetime64s" ,
809+ "<M8[m]" : "datetime64m" ,
810+ "<M8[h]" : "datetime64h" ,
811+ "<M8[D]" : "datetime64D" ,
812+ "<M8[W]" : "datetime64W" ,
813+ "<M8[M]" : "datetime64M" ,
814+ "<M8[Y]" : "datetime64Y" ,
815+ "<m8[ns]" : "timedelta64ns" ,
816+ "<m8[ms]" : "timedelta64ms" ,
817+ "<m8[s]" : "timedelta64s" ,
818+ "<m8[m]" : "timedelta64m" ,
819+ "<m8[h]" : "timedelta64h" ,
820+ "<m8[D]" : "timedelta64D" ,
821+ "<m8[W]" : "timedelta64W" ,
822+ "<m8[M]" : "timedelta64M" ,
823+ "<m8[Y]" : "timedelta64Y" ,
703824 }
704825 return DataType [dtype_to_data_type [dtype .str ]]
705826
0 commit comments