|
3 | 3 | import sys
|
4 | 4 | import os
|
5 | 5 | import unittest
|
| 6 | +import itertools |
6 | 7 | import nose
|
7 | 8 |
|
8 | 9 | import numpy as np
|
|
12 | 13 | date_range, Timestamp)
|
13 | 14 |
|
14 | 15 | from pandas import DatetimeIndex, Int64Index, to_datetime
|
| 16 | +from pandas import tslib |
15 | 17 |
|
16 | 18 | from pandas.core.daterange import DateRange
|
17 | 19 | import pandas.core.datetools as datetools
|
@@ -39,11 +41,22 @@ def _skip_if_no_pytz():
|
39 | 41 | except ImportError:
|
40 | 42 | raise nose.SkipTest("pytz not installed")
|
41 | 43 |
|
| 44 | +def _skip_if_no_dateutil(): |
| 45 | + try: |
| 46 | + import dateutil |
| 47 | + except ImportError: |
| 48 | + raise nose.SkipTest |
| 49 | + |
42 | 50 | try:
|
43 | 51 | import pytz
|
44 | 52 | except ImportError:
|
45 | 53 | pass
|
46 | 54 |
|
| 55 | +try: |
| 56 | + import dateutil |
| 57 | +except ImportError: |
| 58 | + pass |
| 59 | + |
47 | 60 |
|
48 | 61 | class FixedOffset(tzinfo):
|
49 | 62 | """Fixed offset in minutes east from UTC."""
|
@@ -958,6 +971,201 @@ def test_tzaware_offset(self):
|
958 | 971 | offset = dates + offsets.Hour(5)
|
959 | 972 | self.assertEqual(dates[0] + offsets.Hour(5), offset[0])
|
960 | 973 |
|
| 974 | +class TestPytzDateutilTimeZones(unittest.TestCase): |
| 975 | + _multiprocess_can_split_ = True |
| 976 | + FINANCIAL_TIMEZONE_NAMES = ( |
| 977 | + 'Africa/Johannesburg', |
| 978 | + 'America/New_York', 'America/Chicago', 'America/Los_Angeles', |
| 979 | + 'Asia/Bangkok', 'Asia/Hong_Kong', 'Asia/Shanghai', 'Asia/Tokyo', |
| 980 | + 'Australia/Sydney', |
| 981 | + 'Europe/Berlin', 'Europe/London', 'Europe/Zurich', |
| 982 | + 'GMT', 'UTC', |
| 983 | + ) |
| 984 | + |
| 985 | + def setUp(self): |
| 986 | + _skip_if_no_pytz() |
| 987 | + _skip_if_no_dateutil() |
| 988 | + |
| 989 | + def _gen_financial_timezone_pairs(self): |
| 990 | + for pair in itertools.permutations(self.FINANCIAL_TIMEZONE_NAMES, 2): |
| 991 | + yield pair |
| 992 | + |
| 993 | + def _assert_two_values_same_attributes(self, a, b, attrs): |
| 994 | + for attr in attrs: |
| 995 | + tm.assert_attr_equal(attr, a, b) |
| 996 | + |
| 997 | + def _assert_two_timestamp_values_same(self, a, b): |
| 998 | + self._assert_two_values_same_attributes(a, b, \ |
| 999 | + ('year', 'month', 'day', 'hour', 'minute', 'second', 'microsecond', 'nanosecond')) |
| 1000 | + |
| 1001 | + def _assert_two_datetime_values_same(self, a, b): |
| 1002 | + self._assert_two_values_same_attributes(a, b, \ |
| 1003 | + ('year', 'month', 'day', 'hour', 'minute', 'second', 'microsecond')) |
| 1004 | + |
| 1005 | + def _clear_tslib_cache(self): |
| 1006 | + tslib.trans_cache = {} |
| 1007 | + tslib.utc_offset_cache = {} |
| 1008 | + |
| 1009 | + def test_timestamp_tz_as_str(self): |
| 1010 | + """TestPytzDateutilTimeZones: Single date with default time zone, pytz and dateutil.""" |
| 1011 | + ts = Timestamp('3/11/2012 04:00', tz='US/Eastern') |
| 1012 | + exp_pytz = Timestamp('3/11/2012 04:00', tz=pytz.timezone('US/Eastern')) |
| 1013 | + exp_du = Timestamp('3/11/2012 04:00', tz=dateutil.tz.gettz('US/Eastern')) |
| 1014 | + self.assertEquals(ts, exp_pytz) |
| 1015 | + self._assert_two_timestamp_values_same(ts, exp_pytz) |
| 1016 | + self.assertEquals(ts, exp_du) |
| 1017 | + self._assert_two_timestamp_values_same(ts, exp_du) |
| 1018 | + |
| 1019 | + def test_timestamp_tz_conversion(self): |
| 1020 | + """TestPytzDateutilTimeZones: Single date time zone conversion with pytz and dateutil.""" |
| 1021 | + ts_base = Timestamp('3/11/2012 04:00', tz='US/Eastern') |
| 1022 | + ts_pytz = ts_base.astimezone(pytz.timezone('Europe/Moscow')) |
| 1023 | + ts_du = ts_base.astimezone(dateutil.tz.gettz('Europe/Moscow')) |
| 1024 | + self._assert_two_timestamp_values_same(ts_pytz, ts_du) |
| 1025 | + |
| 1026 | + def test_eastern_london_large_year_range_jan_june(self): |
| 1027 | + """TestPytzDateutilTimeZones: Matches Eastern->London->Eastern Jan and Jun 1st for 1970-2049.""" |
| 1028 | + for yr, mo in itertools.product(range(1970, 2050), (1, 6)): |
| 1029 | + # US->Europe |
| 1030 | + ts_base = Timestamp(datetime(yr, mo, 1, 12, 0), tz=pytz.timezone('US/Eastern')) |
| 1031 | + ts_pytz = ts_base.astimezone(pytz.timezone('Europe/London')) |
| 1032 | + ts_du = ts_base.astimezone(dateutil.tz.gettz('Europe/London')) |
| 1033 | + self._assert_two_timestamp_values_same(ts_pytz, ts_du) |
| 1034 | + # Europe->US |
| 1035 | + ts_base = Timestamp(datetime(yr, mo, 1, 12, 0), tz=pytz.timezone('Europe/London')) |
| 1036 | + ts_pytz = ts_base.astimezone(pytz.timezone('US/Eastern')) |
| 1037 | + ts_du = ts_base.astimezone(dateutil.tz.gettz('US/Eastern')) |
| 1038 | + self._assert_two_timestamp_values_same(ts_pytz, ts_du) |
| 1039 | + |
| 1040 | + def test_eastern_london_every_day_2012_2013(self): |
| 1041 | + """TestPytzDateutilTimeZones: Matches for Eastern->London->Eastern daily for two years (one a leap year).""" |
| 1042 | + # 2012 is a leap year |
| 1043 | + for yr, mo, dy in itertools.product((2012, 2013), range(1, 13), range(1, 32)): |
| 1044 | + # US->Europe |
| 1045 | + try: |
| 1046 | + ts_base = Timestamp(datetime(yr, mo, dy, 12, 0), tz=pytz.timezone('US/Eastern')) |
| 1047 | + except ValueError: |
| 1048 | + continue |
| 1049 | + ts_pytz = ts_base.astimezone(pytz.timezone('Europe/London')) |
| 1050 | + ts_du = ts_base.astimezone(dateutil.tz.gettz('Europe/London')) |
| 1051 | + self._assert_two_timestamp_values_same(ts_pytz, ts_du) |
| 1052 | + # Europe->US |
| 1053 | + ts_base = Timestamp(datetime(yr, mo, dy, 12, 0), tz=pytz.timezone('Europe/London')) |
| 1054 | + ts_pytz = ts_base.astimezone(pytz.timezone('US/Eastern')) |
| 1055 | + ts_du = ts_base.astimezone(dateutil.tz.gettz('US/Eastern')) |
| 1056 | + self._assert_two_timestamp_values_same(ts_pytz, ts_du) |
| 1057 | + |
| 1058 | + def test_common_financial_timezones(self): |
| 1059 | + """TestPytzDateutilTimeZones: Permutations of time zones for major financial centres, midday, first day of each month, 2013.""" |
| 1060 | + self._clear_tslib_cache() |
| 1061 | + for mo in range(1, 12): |
| 1062 | + for tz_from, tz_to in self._gen_financial_timezone_pairs(): |
| 1063 | + ts_base = Timestamp(datetime(2013, mo, 1, 12, 0), tz=tz_from) |
| 1064 | + ts_pytz = ts_base.astimezone(pytz.timezone(tz_to)) |
| 1065 | + ts_du = ts_base.astimezone(dateutil.tz.gettz(tz_to)) |
| 1066 | + self._assert_two_timestamp_values_same(ts_pytz, ts_du) |
| 1067 | + |
| 1068 | + def test_common_financial_timezones_dateutil_loaded_first(self): |
| 1069 | + """TestPytzDateutilTimeZones: Permutations of time zones for major financial centres, midday, first day of each month, 2013. dateutil timezones loaded first""" |
| 1070 | + self._clear_tslib_cache() |
| 1071 | + for mo in range(1, 12): |
| 1072 | + for tz_from, tz_to in self._gen_financial_timezone_pairs(): |
| 1073 | + ts_base = Timestamp(datetime(2013, mo, 1, 12, 0), tz=tz_from) |
| 1074 | + ts_du = ts_base.astimezone(dateutil.tz.gettz(tz_to)) |
| 1075 | + ts_pytz = ts_base.astimezone(pytz.timezone(tz_to)) |
| 1076 | + self._assert_two_timestamp_values_same(ts_pytz, ts_du) |
| 1077 | + |
| 1078 | + def test_conflict_dst_start_US_Eastern(self): |
| 1079 | + """TestPytzDateutilTimeZones: Demonstrate that libraries disagree about start of DST, US/Eastern 2012.""" |
| 1080 | + # tstamp 2012-03-11 02:00:00 |
| 1081 | + # pytz: 2012-03-11 02:00:00-05:00 UTC offset -1 day, 19:00:00 UTC time: 07:00:00 |
| 1082 | + #dateutil: 2012-03-11 02:00:00-04:00 UTC offset -1 day, 20:00:00 UTC time: 06:00:00 |
| 1083 | + tstamp = datetime(2012, 3, 11, 2, 0) |
| 1084 | + tz_name = 'US/Eastern' |
| 1085 | + ts_pytz = pytz.timezone(tz_name).localize(tstamp) |
| 1086 | + ts_du = tstamp.replace(tzinfo=dateutil.tz.gettz(tz_name)) |
| 1087 | + self.assertEqual(str(ts_pytz), '2012-03-11 02:00:00-05:00') |
| 1088 | + self.assertEqual(str(ts_du), '2012-03-11 02:00:00-04:00') |
| 1089 | + self._assert_two_datetime_values_same(ts_pytz, ts_du) |
| 1090 | + self.assertNotEqual(ts_pytz.utcoffset(), ts_du.utcoffset()) |
| 1091 | + self.assertNotEqual( |
| 1092 | + str(ts_pytz.astimezone(pytz.timezone('UTC'))), |
| 1093 | + str(ts_du.astimezone(dateutil.tz.tzutc())), |
| 1094 | + ) |
| 1095 | + |
| 1096 | + def test_conflict_dst_start_UK(self): |
| 1097 | + """TestPytzDateutilTimeZones: Demonstrate that libraries disagree about start of DST, Europe/London 2013.""" |
| 1098 | + # tstamp 2013-03-31 01:00:00 |
| 1099 | + # pytz: 2013-03-31 01:00:00+00:00 UTC offset 0:00:00 UTC time: 01:00:00 |
| 1100 | + #dateutil: 2013-03-31 01:00:00+01:00 UTC offset 1:00:00 UTC time: 00:00:00 |
| 1101 | + tstamp = datetime(2013, 3, 31, 1, 0) |
| 1102 | + tz_name = 'Europe/London' |
| 1103 | + ts_pytz = pytz.timezone(tz_name).localize(tstamp) |
| 1104 | + ts_du = tstamp.replace(tzinfo=dateutil.tz.gettz(tz_name)) |
| 1105 | + self.assertEqual(str(ts_pytz), '2013-03-31 01:00:00+00:00') |
| 1106 | + self.assertEqual(str(ts_du), '2013-03-31 01:00:00+01:00') |
| 1107 | + self._assert_two_datetime_values_same(ts_pytz, ts_du) |
| 1108 | + self.assertNotEqual(ts_pytz.utcoffset(), ts_du.utcoffset()) |
| 1109 | + self.assertNotEqual( |
| 1110 | + str(ts_pytz.astimezone(pytz.timezone('UTC'))), |
| 1111 | + str(ts_du.astimezone(dateutil.tz.tzutc())), |
| 1112 | + ) |
| 1113 | + |
| 1114 | + def test_date_range_us_pacific_weekly(self): |
| 1115 | + """TestPytzDateutilTimeZones: Test a date_range weekly US/Pacific through 2012.""" |
| 1116 | + range_pytz = date_range('2012-01-01 12:00', periods=52, freq='W', tz=pytz.timezone('US/Pacific')) |
| 1117 | + range_du = date_range('2012-01-01 12:00', periods=52, freq='W', tz=dateutil.tz.gettz('US/Pacific')) |
| 1118 | + for a, b in zip(range_pytz, range_du): |
| 1119 | + self.assertEquals(a, b) |
| 1120 | + |
| 1121 | + def test_series_us_eastern(self): |
| 1122 | + """TestPytzDateutilTimeZones: Test a Series with a timestamp index, US/Eastern Time across start DST 2012.""" |
| 1123 | + rng = date_range('3/9/2012 12:00', periods=5, freq='D') |
| 1124 | + ts = Series(np.random.randn(len(rng)), rng) |
| 1125 | + # Localize to UTC and convert to Eastern time with default timezone library |
| 1126 | + ts_utc = ts.tz_localize('UTC') |
| 1127 | + ser_std = ts_utc.tz_convert('US/Eastern') |
| 1128 | + # Convert to Eastern time specifically with pytz |
| 1129 | + ser_pytz = ts_utc.tz_convert(pytz.timezone('US/Eastern')) |
| 1130 | + # Now with dateutil |
| 1131 | + ser_du = ts_utc.tz_convert(dateutil.tz.gettz('US/Eastern')) |
| 1132 | + # Check the indicies, firstly Timestamps |
| 1133 | + for s, p, d in zip(ser_std.index, ser_pytz.index, ser_du.index): |
| 1134 | + self.assertEquals(s, p) |
| 1135 | + self.assertEquals(s, d) |
| 1136 | + self.assertEquals(p, d) |
| 1137 | + # assert_series_equal(ser_pytz, ser_du) fails as ser_pytz.tz != ser_du.tz |
| 1138 | + self.assertTrue(np.array_equal(ser_du.index.asi8, ser_pytz.index.asi8)) |
| 1139 | + self.assertNotEqual(ser_pytz.index.tz, ser_du.index.tz) |
| 1140 | + |
| 1141 | + def test_series_subtract_pytz_dateutil(self): |
| 1142 | + """TestPytzDateutilTimeZones: Create two series of Timestamps 15:00 US/Pacific from pytz and 12:00 US/Eastern from dateutil and subtract them.""" |
| 1143 | + dr_pytz = date_range('2012-06-15 12:00', periods=5, freq='D').tz_localize(pytz.timezone('US/Pacific')) |
| 1144 | + dr_du = date_range('2012-06-15 15:00', periods=5, freq='D').tz_localize(dateutil.tz.gettz('US/Eastern')) |
| 1145 | + ts_pytz = Series(dr_pytz, range(5)) |
| 1146 | + ts_du = Series(dr_du, range(5)) |
| 1147 | + diff = ts_pytz - ts_du |
| 1148 | + # Should be 0 hours apart |
| 1149 | + exp = Series(np.zeros((5,), dtype='m8[ns]'), range(5)) |
| 1150 | + self.assertEquals(diff.dtype, np.dtype('m8[ns]')) |
| 1151 | + tm.assert_series_equal(diff, exp) |
| 1152 | + # Check reverse |
| 1153 | + diff = ts_du - ts_pytz |
| 1154 | + self.assertEquals(diff.dtype, np.dtype('m8[ns]')) |
| 1155 | + tm.assert_series_equal(diff, exp) |
| 1156 | + |
| 1157 | + def test_common_financial_timezones_timedelta_zero(self): |
| 1158 | + """TestPytzDateutilTimeZones: Time zones for major financial centres in pytz and dateutil subtract to zero.""" |
| 1159 | + self._clear_tslib_cache() |
| 1160 | + for mo in range(1, 12): |
| 1161 | + for tz_from, tz_to in self._gen_financial_timezone_pairs(): |
| 1162 | + ts_base = Timestamp(datetime(2013, mo, 1, 12, 0), tz=tz_from) |
| 1163 | + ts_pytz = ts_base.astimezone(pytz.timezone(tz_to)) |
| 1164 | + ts_du = ts_base.astimezone(dateutil.tz.gettz(tz_to)) |
| 1165 | + diff = ts_pytz - ts_du |
| 1166 | + self.assertTrue(isinstance(diff, timedelta)) |
| 1167 | + self.assertEqual(diff, timedelta(0), 'From: %s to: %s' % (tz_from, tz_to)) |
| 1168 | + |
961 | 1169 | if __name__ == '__main__':
|
962 | 1170 | nose.runmodule(argv=[__file__, '-vvs', '-x', '--pdb', '--pdb-failure'],
|
963 | 1171 | exit=False)
|
0 commit comments