From 67323069fb5e8f1de3000d7606d26172a2e18a9f Mon Sep 17 00:00:00 2001 From: sinhrks Date: Sun, 3 Aug 2014 17:51:54 +0900 Subject: [PATCH] BUG: Timestamp cannot parse nanosecond from string --- doc/source/v0.15.0.txt | 5 +- pandas/src/datetime.pxd | 15 +- pandas/src/datetime/np_datetime_strings.c | 29 +-- pandas/src/datetime/np_datetime_strings.h | 7 +- pandas/tseries/tests/test_timeseries.py | 25 ++- pandas/tseries/tests/test_tslib.py | 216 +++++++++++++++++++--- pandas/tslib.pyx | 79 ++++---- 7 files changed, 282 insertions(+), 94 deletions(-) diff --git a/doc/source/v0.15.0.txt b/doc/source/v0.15.0.txt index 148cf85d0b5ab..7029438c13d67 100644 --- a/doc/source/v0.15.0.txt +++ b/doc/source/v0.15.0.txt @@ -166,6 +166,8 @@ previously results in ``Exception`` or ``TypeError`` (:issue:`7812`) - ``DataFrame.tz_localize`` and ``DataFrame.tz_convert`` now accepts an optional ``level`` argument for localizing a specific level of a MultiIndex (:issue:`7846`) +- ``Timestamp.__repr__`` displays ``dateutil.tz.tzoffset`` info (:issue:`7907`) + .. _whatsnew_0150.dt: .dt accessor @@ -443,7 +445,8 @@ Bug Fixes - Bug in ``Series.str.cat`` with an index which was filtered as to not include the first item (:issue:`7857`) - +- Bug in ``Timestamp`` cannot parse ``nanosecond`` from string (:issue:`7878`) +- Bug in ``Timestamp`` with string offset and ``tz`` results incorrect (:issue:`7833`) - Bug in ``tslib.tz_convert`` and ``tslib.tz_convert_single`` may return different results (:issue:`7798`) - Bug in ``DatetimeIndex.intersection`` of non-overlapping timestamps with tz raises ``IndexError`` (:issue:`7880`) diff --git a/pandas/src/datetime.pxd b/pandas/src/datetime.pxd index abd3bc3333adb..0896965162698 100644 --- a/pandas/src/datetime.pxd +++ b/pandas/src/datetime.pxd @@ -109,7 +109,8 @@ cdef extern from "datetime/np_datetime_strings.h": int parse_iso_8601_datetime(char *str, int len, PANDAS_DATETIMEUNIT unit, NPY_CASTING casting, pandas_datetimestruct *out, - npy_bool *out_local, PANDAS_DATETIMEUNIT *out_bestunit, + int *out_local, int *out_tzoffset, + PANDAS_DATETIMEUNIT *out_bestunit, npy_bool *out_special) int make_iso_8601_datetime(pandas_datetimestruct *dts, char *outstr, int outlen, @@ -123,7 +124,8 @@ cdef extern from "datetime/np_datetime_strings.h": -cdef inline _string_to_dts(object val, pandas_datetimestruct* dts): +cdef inline _string_to_dts(object val, pandas_datetimestruct* dts, + int* out_local, int* out_tzoffset): cdef int result cdef char *tmp @@ -131,21 +133,22 @@ cdef inline _string_to_dts(object val, pandas_datetimestruct* dts): val = PyUnicode_AsASCIIString(val); tmp = val - result = _cstring_to_dts(tmp, len(val), dts) + result = _cstring_to_dts(tmp, len(val), dts, out_local, out_tzoffset) if result == -1: raise ValueError('Unable to parse %s' % str(val)) cdef inline int _cstring_to_dts(char *val, int length, - pandas_datetimestruct* dts): + pandas_datetimestruct* dts, + int* out_local, int* out_tzoffset): cdef: - npy_bool islocal, special + npy_bool special PANDAS_DATETIMEUNIT out_bestunit int result result = parse_iso_8601_datetime(val, length, PANDAS_FR_ns, NPY_UNSAFE_CASTING, - dts, &islocal, &out_bestunit, &special) + dts, out_local, out_tzoffset, &out_bestunit, &special) return result diff --git a/pandas/src/datetime/np_datetime_strings.c b/pandas/src/datetime/np_datetime_strings.c index 9c78e995f4fe3..3f09de851e231 100644 --- a/pandas/src/datetime/np_datetime_strings.c +++ b/pandas/src/datetime/np_datetime_strings.c @@ -363,7 +363,9 @@ convert_datetimestruct_local_to_utc(pandas_datetimestruct *out_dts_utc, * to be cast to the 'unit' parameter. * * 'out' gets filled with the parsed date-time. - * 'out_local' gets set to 1 if the parsed time was in local time, + * 'out_local' gets whether returned value contains timezone. 0 for UTC, 1 for local time. + * 'out_tzoffset' gets set to timezone offset by minutes + * if the parsed time was in local time, * to 0 otherwise. The values 'now' and 'today' don't get counted * as local, and neither do UTC +/-#### timezone offsets, because * they aren't using the computer's local timezone offset. @@ -381,7 +383,8 @@ parse_iso_8601_datetime(char *str, int len, PANDAS_DATETIMEUNIT unit, NPY_CASTING casting, pandas_datetimestruct *out, - npy_bool *out_local, + int *out_local, + int *out_tzoffset, PANDAS_DATETIMEUNIT *out_bestunit, npy_bool *out_special) { @@ -778,19 +781,6 @@ parse_iso_8601_datetime(char *str, int len, if (sublen == 0) { // Unlike NumPy, treating no time zone as naive goto finish; - -/* - if (convert_datetimestruct_local_to_utc(out, out) < 0) { - goto error; - } - - // Since neither "Z" nor a time-zone was specified, it's local - if (out_local != NULL) { - *out_local = 1; - } - - goto finish; -*/ } /* UTC specifier */ @@ -816,9 +806,6 @@ parse_iso_8601_datetime(char *str, int len, * Since "local" means local with respect to the current * machine, we say this is non-local. */ - if (out_local != NULL) { - *out_local = 0; - } if (*substr == '-') { offset_neg = 1; @@ -872,7 +859,11 @@ parse_iso_8601_datetime(char *str, int len, offset_hour = -offset_hour; offset_minute = -offset_minute; } - add_minutes_to_datetimestruct(out, -60 * offset_hour - offset_minute); + if (out_local != NULL) { + *out_local = 1; + // Unlike NumPy, do not change internal value to local time + *out_tzoffset = 60 * offset_hour - offset_minute; + } } /* Skip trailing whitespace */ diff --git a/pandas/src/datetime/np_datetime_strings.h b/pandas/src/datetime/np_datetime_strings.h index 9a2488fefaf56..0d9a0944310fb 100644 --- a/pandas/src/datetime/np_datetime_strings.h +++ b/pandas/src/datetime/np_datetime_strings.h @@ -27,7 +27,9 @@ * to be cast to the 'unit' parameter. * * 'out' gets filled with the parsed date-time. - * 'out_local' gets set to 1 if the parsed time was in local time, + * 'out_local' gets whether returned value contains timezone. 0 for UTC, 1 for local time. + * 'out_tzoffset' gets set to timezone offset by minutes + * if the parsed time was in local time, * to 0 otherwise. The values 'now' and 'today' don't get counted * as local, and neither do UTC +/-#### timezone offsets, because * they aren't using the computer's local timezone offset. @@ -45,7 +47,8 @@ parse_iso_8601_datetime(char *str, int len, PANDAS_DATETIMEUNIT unit, NPY_CASTING casting, pandas_datetimestruct *out, - npy_bool *out_local, + int *out_local, + int *out_tzoffset, PANDAS_DATETIMEUNIT *out_bestunit, npy_bool *out_special); diff --git a/pandas/tseries/tests/test_timeseries.py b/pandas/tseries/tests/test_timeseries.py index e651370be7d6d..f94910d9dec89 100644 --- a/pandas/tseries/tests/test_timeseries.py +++ b/pandas/tseries/tests/test_timeseries.py @@ -2173,10 +2173,31 @@ def test_constructor_coverage(self): def test_constructor_datetime64_tzformat(self): # GH 6572 tm._skip_if_no_pytz() + import pytz + # ISO 8601 format results in pytz.FixedOffset + for freq in ['AS', 'W-SUN']: + idx = date_range('2013-01-01T00:00:00-05:00', '2016-01-01T23:59:59-05:00', freq=freq) + expected = date_range('2013-01-01T00:00:00', '2016-01-01T23:59:59', + freq=freq, tz=pytz.FixedOffset(-300)) + tm.assert_index_equal(idx, expected) + # Unable to use `US/Eastern` because of DST + expected_i8 = date_range('2013-01-01T00:00:00', '2016-01-01T23:59:59', + freq=freq, tz='America/Lima') + self.assert_numpy_array_equal(idx.asi8, expected_i8.asi8) + + idx = date_range('2013-01-01T00:00:00+09:00', '2016-01-01T23:59:59+09:00', freq=freq) + expected = date_range('2013-01-01T00:00:00', '2016-01-01T23:59:59', + freq=freq, tz=pytz.FixedOffset(540)) + tm.assert_index_equal(idx, expected) + expected_i8 = date_range('2013-01-01T00:00:00', '2016-01-01T23:59:59', + freq=freq, tz='Asia/Tokyo') + self.assert_numpy_array_equal(idx.asi8, expected_i8.asi8) + tm._skip_if_no_dateutil() from dateutil.tz import tzoffset + # Non ISO 8601 format results in dateutil.tz.tzoffset for freq in ['AS', 'W-SUN']: - idx = date_range('2013-01-01T00:00:00-05:00', '2016-01-01T23:59:59-05:00', freq=freq) + idx = date_range('2013/1/1 0:00:00-5:00', '2016/1/1 23:59:59-5:00', freq=freq) expected = date_range('2013-01-01T00:00:00', '2016-01-01T23:59:59', freq=freq, tz=tzoffset(None, -18000)) tm.assert_index_equal(idx, expected) @@ -2185,7 +2206,7 @@ def test_constructor_datetime64_tzformat(self): freq=freq, tz='America/Lima') self.assert_numpy_array_equal(idx.asi8, expected_i8.asi8) - idx = date_range('2013-01-01T00:00:00+09:00', '2016-01-01T23:59:59+09:00', freq=freq) + idx = date_range('2013/1/1 0:00:00+9:00', '2016/1/1 23:59:59+09:00', freq=freq) expected = date_range('2013-01-01T00:00:00', '2016-01-01T23:59:59', freq=freq, tz=tzoffset(None, 32400)) tm.assert_index_equal(idx, expected) diff --git a/pandas/tseries/tests/test_tslib.py b/pandas/tseries/tests/test_tslib.py index 563ab74ad975a..a700a617b0dee 100644 --- a/pandas/tseries/tests/test_tslib.py +++ b/pandas/tseries/tests/test_tslib.py @@ -15,40 +15,180 @@ from pandas.util.testing import assert_series_equal class TestTimestamp(tm.TestCase): + + def test_constructor(self): + base_str = '2014-07-01 09:00' + base_dt = datetime.datetime(2014, 7, 1, 9) + base_expected = 1404205200000000000 + + # confirm base representation is correct + import calendar + self.assertEqual(calendar.timegm(base_dt.timetuple()) * 1000000000, base_expected) + + tests = [(base_str, base_dt, base_expected), + ('2014-07-01 10:00', datetime.datetime(2014, 7, 1, 10), + base_expected + 3600 * 1000000000), + ('2014-07-01 09:00:00.000008000', + datetime.datetime(2014, 7, 1, 9, 0, 0, 8), base_expected + 8000), + ('2014-07-01 09:00:00.000000005', + Timestamp('2014-07-01 09:00:00.000000005'), base_expected + 5)] + + tm._skip_if_no_pytz() + tm._skip_if_no_dateutil() + import pytz + import dateutil + timezones = [(None, 0), ('UTC', 0), (pytz.utc, 0), + ('Asia/Tokyo', 9), ('US/Eastern', -4), ('dateutil/US/Pacific', -7), + (pytz.FixedOffset(-180), -3), (dateutil.tz.tzoffset(None, 18000), 5)] + + for date_str, date, expected in tests: + for result in [Timestamp(date_str), Timestamp(date)]: + # only with timestring + self.assertEqual(result.value, expected) + self.assertEqual(tslib.pydt_to_i8(result), expected) + + # re-creation shouldn't affect to internal value + result = Timestamp(result) + self.assertEqual(result.value, expected) + self.assertEqual(tslib.pydt_to_i8(result), expected) + + # with timezone + for tz, offset in timezones: + for result in [Timestamp(date_str, tz=tz), Timestamp(date, tz=tz)]: + expected_tz = expected - offset * 3600 * 1000000000 + self.assertEqual(result.value, expected_tz) + self.assertEqual(tslib.pydt_to_i8(result), expected_tz) + + # should preserve tz + result = Timestamp(result) + self.assertEqual(result.value, expected_tz) + self.assertEqual(tslib.pydt_to_i8(result), expected_tz) + + # should convert to UTC + result = Timestamp(result, tz='UTC') + expected_utc = expected - offset * 3600 * 1000000000 + self.assertEqual(result.value, expected_utc) + self.assertEqual(tslib.pydt_to_i8(result), expected_utc) + + def test_constructor_with_stringoffset(self): + # GH 7833 + base_str = '2014-07-01 11:00:00+02:00' + base_dt = datetime.datetime(2014, 7, 1, 9) + base_expected = 1404205200000000000 + + # confirm base representation is correct + import calendar + self.assertEqual(calendar.timegm(base_dt.timetuple()) * 1000000000, base_expected) + + tests = [(base_str, base_expected), + ('2014-07-01 12:00:00+02:00', base_expected + 3600 * 1000000000), + ('2014-07-01 11:00:00.000008000+02:00', base_expected + 8000), + ('2014-07-01 11:00:00.000000005+02:00', base_expected + 5)] + + tm._skip_if_no_pytz() + tm._skip_if_no_dateutil() + import pytz + import dateutil + timezones = [(None, 0), ('UTC', 0), (pytz.utc, 0), + ('Asia/Tokyo', 9), ('US/Eastern', -4), + ('dateutil/US/Pacific', -7), + (pytz.FixedOffset(-180), -3), (dateutil.tz.tzoffset(None, 18000), 5)] + + for date_str, expected in tests: + for result in [Timestamp(date_str)]: + # only with timestring + self.assertEqual(result.value, expected) + self.assertEqual(tslib.pydt_to_i8(result), expected) + + # re-creation shouldn't affect to internal value + result = Timestamp(result) + self.assertEqual(result.value, expected) + self.assertEqual(tslib.pydt_to_i8(result), expected) + + # with timezone + for tz, offset in timezones: + result = Timestamp(date_str, tz=tz) + expected_tz = expected + self.assertEqual(result.value, expected_tz) + self.assertEqual(tslib.pydt_to_i8(result), expected_tz) + + # should preserve tz + result = Timestamp(result) + self.assertEqual(result.value, expected_tz) + self.assertEqual(tslib.pydt_to_i8(result), expected_tz) + + # should convert to UTC + result = Timestamp(result, tz='UTC') + expected_utc = expected + self.assertEqual(result.value, expected_utc) + self.assertEqual(tslib.pydt_to_i8(result), expected_utc) + + # This should be 2013-11-01 05:00 in UTC -> converted to Chicago tz + result = Timestamp('2013-11-01 00:00:00-0500', tz='America/Chicago') + self.assertEqual(result.value, Timestamp('2013-11-01 05:00').value) + expected_repr = "Timestamp('2013-11-01 00:00:00-0500', tz='America/Chicago')" + self.assertEqual(repr(result), expected_repr) + self.assertEqual(result, eval(repr(result))) + + # This should be 2013-11-01 05:00 in UTC -> converted to Tokyo tz (+09:00) + result = Timestamp('2013-11-01 00:00:00-0500', tz='Asia/Tokyo') + self.assertEqual(result.value, Timestamp('2013-11-01 05:00').value) + expected_repr = "Timestamp('2013-11-01 14:00:00+0900', tz='Asia/Tokyo')" + self.assertEqual(repr(result), expected_repr) + self.assertEqual(result, eval(repr(result))) + def test_repr(self): - date = '2014-03-07' - tz = 'US/Eastern' - freq = 'M' - - date_only = Timestamp(date) - self.assertIn(date, repr(date_only)) - self.assertNotIn(tz, repr(date_only)) - self.assertNotIn(freq, repr(date_only)) - self.assertEqual(date_only, eval(repr(date_only))) - - date_tz = Timestamp(date, tz=tz) - self.assertIn(date, repr(date_tz)) - self.assertIn(tz, repr(date_tz)) - self.assertNotIn(freq, repr(date_tz)) - self.assertEqual(date_tz, eval(repr(date_tz))) - - date_freq = Timestamp(date, offset=freq) - self.assertIn(date, repr(date_freq)) - self.assertNotIn(tz, repr(date_freq)) - self.assertIn(freq, repr(date_freq)) - self.assertEqual(date_freq, eval(repr(date_freq))) - - date_tz_freq = Timestamp(date, tz=tz, offset=freq) - self.assertIn(date, repr(date_tz_freq)) - self.assertIn(tz, repr(date_tz_freq)) - self.assertIn(freq, repr(date_tz_freq)) - self.assertEqual(date_tz_freq, eval(repr(date_tz_freq))) + dates = ['2014-03-07', '2014-01-01 09:00', '2014-01-01 00:00:00.000000001'] + timezones = ['UTC', 'Asia/Tokyo', 'US/Eastern', 'dateutil/US/Pacific'] + if _np_version_under1p7: + freqs = ['D', 'M', 'S'] + else: + freqs = ['D', 'M', 'S', 'N'] + + for date in dates: + for tz in timezones: + for freq in freqs: + # avoid to match with timezone name + freq_repr = "'{0}'".format(freq) + if tz.startswith('dateutil'): + tz_repr = tz.replace('dateutil', '') + else: + tz_repr = tz + + date_only = Timestamp(date) + self.assertIn(date, repr(date_only)) + self.assertNotIn(tz_repr, repr(date_only)) + self.assertNotIn(freq_repr, repr(date_only)) + self.assertEqual(date_only, eval(repr(date_only))) + + date_tz = Timestamp(date, tz=tz) + self.assertIn(date, repr(date_tz)) + self.assertIn(tz_repr, repr(date_tz)) + self.assertNotIn(freq_repr, repr(date_tz)) + self.assertEqual(date_tz, eval(repr(date_tz))) + + date_freq = Timestamp(date, offset=freq) + self.assertIn(date, repr(date_freq)) + self.assertNotIn(tz_repr, repr(date_freq)) + self.assertIn(freq_repr, repr(date_freq)) + self.assertEqual(date_freq, eval(repr(date_freq))) + + date_tz_freq = Timestamp(date, tz=tz, offset=freq) + self.assertIn(date, repr(date_tz_freq)) + self.assertIn(tz_repr, repr(date_tz_freq)) + self.assertIn(freq_repr, repr(date_tz_freq)) + self.assertEqual(date_tz_freq, eval(repr(date_tz_freq))) # this can cause the tz field to be populated, but it's redundant to information in the datestring + tm._skip_if_no_pytz() + import pytz date_with_utc_offset = Timestamp('2014-03-13 00:00:00-0400', tz=None) self.assertIn('2014-03-13 00:00:00-0400', repr(date_with_utc_offset)) self.assertNotIn('tzoffset', repr(date_with_utc_offset)) - self.assertEqual(date_with_utc_offset, eval(repr(date_with_utc_offset))) + self.assertIn('pytz.FixedOffset(-240)', repr(date_with_utc_offset)) + expr = repr(date_with_utc_offset).replace("'pytz.FixedOffset(-240)'", + 'pytz.FixedOffset(-240)') + self.assertEqual(date_with_utc_offset, eval(expr)) def test_bounds_with_different_units(self): out_of_bounds_dates = ( @@ -314,8 +454,24 @@ def test_timedelta_ms_arithmetic(self): self.assert_ns_timedelta(time, -123000000) def test_nanosecond_string_parsing(self): - self.timestamp = Timestamp('2013-05-01 07:15:45.123456789') - self.assertEqual(self.timestamp.value, 1367392545123456000) + ts = Timestamp('2013-05-01 07:15:45.123456789') + # GH 7878 + expected_repr = '2013-05-01 07:15:45.123456789' + expected_value = 1367392545123456789 + self.assertEqual(ts.value, expected_value) + self.assertIn(expected_repr, repr(ts)) + + ts = Timestamp('2013-05-01 07:15:45.123456789+09:00', tz='Asia/Tokyo') + self.assertEqual(ts.value, expected_value - 9 * 3600 * 1000000000) + self.assertIn(expected_repr, repr(ts)) + + ts = Timestamp('2013-05-01 07:15:45.123456789', tz='UTC') + self.assertEqual(ts.value, expected_value) + self.assertIn(expected_repr, repr(ts)) + + ts = Timestamp('2013-05-01 07:15:45.123456789', tz='US/Eastern') + self.assertEqual(ts.value, expected_value + 4 * 3600 * 1000000000) + self.assertIn(expected_repr, repr(ts)) def test_nanosecond_timestamp(self): # GH 7610 diff --git a/pandas/tslib.pyx b/pandas/tslib.pyx index 3b1a969e17a18..7084184b7d423 100644 --- a/pandas/tslib.pyx +++ b/pandas/tslib.pyx @@ -217,12 +217,6 @@ class Timestamp(_Timestamp): cdef _TSObject ts cdef _Timestamp ts_base - if util.is_string_object(ts_input): - try: - ts_input = parse_date(ts_input) - except Exception: - pass - ts = convert_to_tsobject(ts_input, tz, unit) if ts.value == NPY_NAT: @@ -263,7 +257,7 @@ class Timestamp(_Timestamp): except: pass - tz = ", tz='{0}'".format(zone) if zone is not None and not isinstance(zone, tzoffset) else "" + tz = ", tz='{0}'".format(zone) if zone is not None else "" offset = ", offset='{0}'".format(self.offset.freqstr) if self.offset is not None else "" return "Timestamp('{stamp}'{tz}{offset})".format(stamp=stamp, tz=tz, offset=offset) @@ -926,12 +920,41 @@ cdef convert_to_tsobject(object ts, object tz, object unit): cdef: _TSObject obj bint utc_convert = 1 + int out_local = 0, out_tzoffset = 0 if tz is not None: tz = maybe_get_tz(tz) obj = _TSObject() + if util.is_string_object(ts): + if ts in _nat_strings: + ts = NaT + else: + try: + _string_to_dts(ts, &obj.dts, &out_local, &out_tzoffset) + obj.value = pandas_datetimestruct_to_datetime(PANDAS_FR_ns, &obj.dts) + _check_dts_bounds(&obj.dts) + if out_local == 1: + obj.tzinfo = pytz.FixedOffset(out_tzoffset) + obj.value = tz_convert_single(obj.value, obj.tzinfo, 'UTC') + if tz is None: + _check_dts_bounds(&obj.dts) + return obj + else: + # Keep the converter same as PyDateTime's + ts = Timestamp(obj.value, tz=obj.tzinfo) + else: + ts = obj.value + if tz is not None: + # shift for _localize_tso + ts = tz_convert_single(ts, tz, 'UTC') + except ValueError: + try: + ts = parse_datetime_string(ts) + except Exception: + raise ValueError + if ts is None or ts is NaT or ts is np_NaT: obj.value = NPY_NAT elif is_datetime64_object(ts): @@ -954,12 +977,6 @@ cdef convert_to_tsobject(object ts, object tz, object unit): ts = cast_from_unit(ts,unit) obj.value = ts pandas_datetime_to_datetimestruct(ts, PANDAS_FR_ns, &obj.dts) - elif util.is_string_object(ts): - if ts in _nat_strings: - obj.value = NPY_NAT - else: - _string_to_dts(ts, &obj.dts) - obj.value = pandas_datetimestruct_to_datetime(PANDAS_FR_ns, &obj.dts) elif PyDateTime_Check(ts): if tz is not None: # sort of a temporary hack @@ -970,6 +987,10 @@ cdef convert_to_tsobject(object ts, object tz, object unit): obj.value = _pydatetime_to_dts(ts, &obj.dts) obj.tzinfo = ts.tzinfo else: #tzoffset + try: + tz = ts.astimezone(tz).tzinfo + except: + pass obj.value = _pydatetime_to_dts(ts, &obj.dts) ts_offset = _get_utcoffset(ts.tzinfo, ts) obj.value -= _delta_to_nanoseconds(ts_offset) @@ -979,10 +1000,7 @@ cdef convert_to_tsobject(object ts, object tz, object unit): PANDAS_FR_ns, &obj.dts) obj.tzinfo = tz elif not _is_utc(tz): - try: - ts = tz.localize(ts) - except AttributeError: - ts = ts.replace(tzinfo=tz) + ts = _localize_pydatetime(ts, tz) obj.value = _pydatetime_to_dts(ts, &obj.dts) obj.tzinfo = ts.tzinfo else: @@ -1071,14 +1089,11 @@ def _localize_pydatetime(object dt, object tz): return dt.tz_localize(tz) elif tz == 'UTC' or tz is UTC: return UTC.localize(dt) - - elif _treat_tz_as_pytz(tz): - # datetime.replace may return incorrect result in pytz + try: + # datetime.replace with pytz may be incorrect result return tz.localize(dt) - elif _treat_tz_as_dateutil(tz): + except AttributeError: return dt.replace(tzinfo=tz) - else: - raise ValueError(type(tz), tz) def get_timezone(tz): @@ -1239,6 +1254,7 @@ def array_to_datetime(ndarray[object] values, raise_=False, dayfirst=False, bint utc_convert = bool(utc), seen_integer=0, seen_datetime=0 _TSObject _ts int64_t m = cast_from_unit(None,unit) + int out_local = 0, out_tzoffset = 0 try: result = np.empty(n, dtype='M8[ns]') @@ -1321,9 +1337,12 @@ def array_to_datetime(ndarray[object] values, raise_=False, dayfirst=False, iresult[i] = iNaT continue - _string_to_dts(val, &dts) - iresult[i] = pandas_datetimestruct_to_datetime(PANDAS_FR_ns, - &dts) + _string_to_dts(val, &dts, &out_local, &out_tzoffset) + value = pandas_datetimestruct_to_datetime(PANDAS_FR_ns, &dts) + if out_local == 1: + tz = pytz.FixedOffset(out_tzoffset) + value = tz_convert_single(value, tz, 'UTC') + iresult[i] = value _check_dts_bounds(&dts) except ValueError: try: @@ -2867,14 +2886,6 @@ cdef inline int64_t _normalized_stamp(pandas_datetimestruct *dts): return pandas_datetimestruct_to_datetime(PANDAS_FR_ns, dts) -cdef inline void m8_populate_tsobject(int64_t stamp, _TSObject tso, object tz): - tso.value = stamp - pandas_datetime_to_datetimestruct(tso.value, PANDAS_FR_ns, &tso.dts) - - if tz is not None: - _localize_tso(tso, tz) - - def dates_normalized(ndarray[int64_t] stamps, tz=None): cdef: Py_ssize_t i, n = len(stamps)