From 812133b69e262319c6ddf814e7c133e798369043 Mon Sep 17 00:00:00 2001 From: sinhrks Date: Wed, 18 Jun 2014 22:08:58 +0900 Subject: [PATCH] BUG: Timestamp.tz_convert resets nanosecond --- doc/source/v0.14.1.txt | 2 +- pandas/tseries/tests/test_timezones.py | 28 +++++++++---------------- pandas/tseries/tests/test_tslib.py | 38 +++++++++++++++++++++++++++------- pandas/tslib.pyx | 9 ++++++-- 4 files changed, 48 insertions(+), 29 deletions(-) diff --git a/doc/source/v0.14.1.txt b/doc/source/v0.14.1.txt index 197bc9bae5..158fa1561e 100644 --- a/doc/source/v0.14.1.txt +++ b/doc/source/v0.14.1.txt @@ -248,7 +248,7 @@ Bug Fixes - BUG in ``resample`` raises ``ValueError`` when target contains ``NaT`` (:issue:`7227`) - +- Bug in ``Timestamp.tz_convert`` resets ``nanosecond`` info (:issue:`7534`) - Bug in ``Index.astype(float)`` where it would return an ``object`` dtype ``Index`` (:issue:`7464`). diff --git a/pandas/tseries/tests/test_timezones.py b/pandas/tseries/tests/test_timezones.py index b1d8bdd9f8..0fec3e48c6 100644 --- a/pandas/tseries/tests/test_timezones.py +++ b/pandas/tseries/tests/test_timezones.py @@ -1,5 +1,5 @@ # pylint: disable-msg=E1101,W0612 -from datetime import datetime, time, timedelta, tzinfo, date +from datetime import datetime, timedelta, tzinfo, date import sys import os import unittest @@ -8,10 +8,9 @@ import numpy as np import pytz -from pandas import (Index, Series, TimeSeries, DataFrame, isnull, - date_range, Timestamp) +from pandas import (Index, Series, DataFrame, isnull, Timestamp) -from pandas import DatetimeIndex, Int64Index, to_datetime, NaT +from pandas import DatetimeIndex, to_datetime, NaT from pandas import tslib import pandas.core.datetools as datetools @@ -20,17 +19,10 @@ import pandas.tseries.tools as tools from pytz import NonExistentTimeError -from pandas.util.testing import assert_series_equal, assert_almost_equal, assertRaisesRegexp import pandas.util.testing as tm -import pandas.lib as lib -import pandas.core.datetools as dt -from numpy.random import rand from pandas.util.testing import assert_frame_equal -import pandas.compat as compat -from pandas.compat import range, lrange, zip, cPickle as pickle -from pandas.core.datetools import BDay -import pandas.core.common as com +from pandas.compat import lrange, zip from pandas import _np_version_under1p7 @@ -544,13 +536,13 @@ def test_localized_at_time_between_time(self): result = ts_local.at_time(time(10, 0)) expected = ts.at_time(time(10, 0)).tz_localize(self.tzstr('US/Eastern')) - assert_series_equal(result, expected) + tm.assert_series_equal(result, expected) self.assertTrue(self.cmptz(result.index.tz, self.tz('US/Eastern'))) t1, t2 = time(10, 0), time(11, 0) result = ts_local.between_time(t1, t2) expected = ts.between_time(t1, t2).tz_localize(self.tzstr('US/Eastern')) - assert_series_equal(result, expected) + tm.assert_series_equal(result, expected) self.assertTrue(self.cmptz(result.index.tz, self.tz('US/Eastern'))) def test_string_index_alias_tz_aware(self): @@ -631,7 +623,7 @@ def test_frame_no_datetime64_dtype(self): 'datetimes_with_tz' : datetimes_with_tz }) result = df.get_dtype_counts() expected = Series({ 'datetime64[ns]' : 3, 'object' : 1 }) - assert_series_equal(result, expected) + tm.assert_series_equal(result, expected) def test_hongkong_tz_convert(self): # #1673 @@ -863,7 +855,7 @@ def test_series_frame_tz_localize(self): # Can't localize if already tz-aware rng = date_range('1/1/2011', periods=100, freq='H', tz='utc') ts = Series(1, index=rng) - assertRaisesRegexp(TypeError, 'Already tz-aware', ts.tz_localize, 'US/Eastern') + tm.assertRaisesRegexp(TypeError, 'Already tz-aware', ts.tz_localize, 'US/Eastern') def test_series_frame_tz_convert(self): rng = date_range('1/1/2011', periods=200, freq='D', @@ -887,7 +879,7 @@ def test_series_frame_tz_convert(self): # can't convert tz-naive rng = date_range('1/1/2011', periods=200, freq='D') ts = Series(1, index=rng) - assertRaisesRegexp(TypeError, "Cannot convert tz-naive", ts.tz_convert, 'US/Eastern') + tm.assertRaisesRegexp(TypeError, "Cannot convert tz-naive", ts.tz_convert, 'US/Eastern') def test_join_utc_convert(self): rng = date_range('1/1/2011', periods=100, freq='H', tz='utc') @@ -1033,7 +1025,7 @@ def test_arith_utc_convert(self): expected = uts1 + uts2 self.assertEqual(result.index.tz, pytz.UTC) - assert_series_equal(result, expected) + tm.assert_series_equal(result, expected) def test_intersection(self): rng = date_range('1/1/2011', periods=100, freq='H', tz='utc') diff --git a/pandas/tseries/tests/test_tslib.py b/pandas/tseries/tests/test_tslib.py index bf1f7879ba..9499f05a4a 100644 --- a/pandas/tseries/tests/test_tslib.py +++ b/pandas/tseries/tests/test_tslib.py @@ -9,6 +9,7 @@ from pandas.tslib import period_asfreq, period_ordinal from pandas.tseries.index import date_range from pandas.tseries.frequencies import get_freq +import pandas.tseries.offsets as offsets from pandas import _np_version_under1p7 import pandas.util.testing as tm from pandas.util.testing import assert_series_equal @@ -61,7 +62,7 @@ def test_bounds_with_different_units(self): for unit in time_units: self.assertRaises( ValueError, - tslib.Timestamp, + Timestamp, np.datetime64(date_string, dtype='M8[%s]' % unit) ) @@ -72,27 +73,48 @@ def test_bounds_with_different_units(self): for date_string in in_bounds_dates: for unit in time_units: - tslib.Timestamp( + Timestamp( np.datetime64(date_string, dtype='M8[%s]' % unit) ) + def test_tz(self): + t = '2014-02-01 09:00' + ts = Timestamp(t) + local = ts.tz_localize('Asia/Tokyo') + self.assertEqual(local.hour, 9) + self.assertEqual(local, Timestamp(t, tz='Asia/Tokyo')) + conv = local.tz_convert('US/Eastern') + self.assertEqual(conv, + Timestamp('2014-01-31 19:00', tz='US/Eastern')) + self.assertEqual(conv.hour, 19) + + # preserves nanosecond + ts = Timestamp(t) + offsets.Nano(5) + local = ts.tz_localize('Asia/Tokyo') + self.assertEqual(local.hour, 9) + self.assertEqual(local.nanosecond, 5) + conv = local.tz_convert('US/Eastern') + self.assertEqual(conv.nanosecond, 5) + self.assertEqual(conv.hour, 19) + def test_barely_oob_dts(self): one_us = np.timedelta64(1) # By definition we can't go out of bounds in [ns], so we # convert the datetime64s to [us] so we can go out of bounds - min_ts_us = np.datetime64(tslib.Timestamp.min).astype('M8[us]') - max_ts_us = np.datetime64(tslib.Timestamp.max).astype('M8[us]') + min_ts_us = np.datetime64(Timestamp.min).astype('M8[us]') + max_ts_us = np.datetime64(Timestamp.max).astype('M8[us]') # No error for the min/max datetimes - tslib.Timestamp(min_ts_us) - tslib.Timestamp(max_ts_us) + Timestamp(min_ts_us) + Timestamp(max_ts_us) # One us less than the minimum is an error - self.assertRaises(ValueError, tslib.Timestamp, min_ts_us - one_us) + self.assertRaises(ValueError, Timestamp, min_ts_us - one_us) # One us more than the maximum is an error - self.assertRaises(ValueError, tslib.Timestamp, max_ts_us + one_us) + self.assertRaises(ValueError, Timestamp, max_ts_us + one_us) + class TestDatetimeParsingWrappers(tm.TestCase): def test_does_not_convert_mixed_integer(self): diff --git a/pandas/tslib.pyx b/pandas/tslib.pyx index 24b1215b94..679359f1b4 100644 --- a/pandas/tslib.pyx +++ b/pandas/tslib.pyx @@ -341,13 +341,15 @@ class Timestamp(_Timestamp): def is_year_end(self): return self._get_start_end_field('is_year_end') - def tz_localize(self, tz): + def tz_localize(self, tz, infer_dst=False): """ Convert naive Timestamp to local time zone Parameters ---------- tz : pytz.timezone or dateutil.tz.tzfile + infer_dst : boolean, default False + Attempt to infer fall dst-transition hours based on order Returns ------- @@ -355,7 +357,10 @@ class Timestamp(_Timestamp): """ if self.tzinfo is None: # tz naive, localize - return Timestamp(self.to_pydatetime(), tz=tz) + tz = maybe_get_tz(tz) + value = tz_localize_to_utc(np.array([self.value]), tz, + infer_dst=infer_dst)[0] + return Timestamp(value, tz=tz) else: raise Exception('Cannot localize tz-aware Timestamp, use ' 'tz_convert for conversions')