From 9ed29b7c9df8f385b307203ff2ad7cb2dfc090bc Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Thu, 10 Jan 2019 13:27:34 -0800 Subject: [PATCH] BUG: do freq validation in DTA.__init__ (#24686) * do freq validation in DTA.__init__ * troubleshoot parquet fail * troubleshoot feather * troubleshoot pyarrow fail --- pandas/core/arrays/datetimelike.py | 2 +- pandas/core/arrays/datetimes.py | 17 +++++++++++++++-- pandas/core/internals/blocks.py | 2 +- pandas/tests/arrays/test_datetimes.py | 10 ++++++++++ 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index cfb697b3c357ab..73e799f9e0a36e 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -606,7 +606,7 @@ def _concat_same_type(cls, to_concat): def copy(self, deep=False): values = self.asi8.copy() - return type(self)(values, dtype=self.dtype, freq=self.freq) + return type(self)._simple_new(values, dtype=self.dtype, freq=self.freq) def _values_for_factorize(self): return self.asi8, iNaT diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index efa1757a989fc8..d2d9fcf954fe34 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -258,6 +258,8 @@ def __init__(self, values, dtype=_NS_DTYPE, freq=None, copy=False): if isinstance(values, (ABCSeries, ABCIndexClass)): values = values._values + inferred_freq = getattr(values, "_freq", None) + if isinstance(values, type(self)): # validation dtz = getattr(dtype, 'tz', None) @@ -322,9 +324,20 @@ def __init__(self, values, dtype=_NS_DTYPE, freq=None, copy=False): self._dtype = dtype self._freq = freq + if inferred_freq is None and freq is not None: + type(self)._validate_frequency(self, freq) + @classmethod - def _simple_new(cls, values, freq=None, dtype=None): - return cls(values, freq=freq, dtype=dtype) + def _simple_new(cls, values, freq=None, dtype=_NS_DTYPE): + assert isinstance(values, np.ndarray) + if values.dtype == 'i8': + values = values.view(_NS_DTYPE) + + result = object.__new__(cls) + result._data = values + result._freq = freq + result._dtype = dtype + return result @classmethod def _from_sequence(cls, data, dtype=None, copy=False, diff --git a/pandas/core/internals/blocks.py b/pandas/core/internals/blocks.py index 8f9bc7b4adee8b..20881972c068a3 100644 --- a/pandas/core/internals/blocks.py +++ b/pandas/core/internals/blocks.py @@ -3084,7 +3084,7 @@ def make_block(values, placement, klass=None, ndim=None, dtype=None, elif klass is DatetimeTZBlock and not is_datetime64tz_dtype(values): # TODO: This is no longer hit internally; does it need to be retained # for e.g. pyarrow? - values = DatetimeArray(values, dtype) + values = DatetimeArray._simple_new(values, dtype=dtype) return klass(values, ndim=ndim, placement=placement) diff --git a/pandas/tests/arrays/test_datetimes.py b/pandas/tests/arrays/test_datetimes.py index 8228ed7652fea7..60caf61782bbf0 100644 --- a/pandas/tests/arrays/test_datetimes.py +++ b/pandas/tests/arrays/test_datetimes.py @@ -16,6 +16,16 @@ class TestDatetimeArrayConstructor(object): + def test_freq_validation(self): + # GH#24623 check that invalid instances cannot be created with the + # public constructor + arr = np.arange(5, dtype=np.int64) * 3600 * 10**9 + + msg = ("Inferred frequency H from passed values does not " + "conform to passed frequency W-SUN") + with pytest.raises(ValueError, match=msg): + DatetimeArray(arr, freq="W") + @pytest.mark.parametrize('meth', [DatetimeArray._from_sequence, sequence_to_dt64ns, pd.to_datetime,