From 3d7a1d422b0f80a07f260f598591deb03ff86de5 Mon Sep 17 00:00:00 2001 From: rockg Date: Sun, 6 Dec 2015 07:45:19 -0500 Subject: [PATCH] BUG: Parsing offset strings with non-zero minutes was incorrect --- doc/source/whatsnew/v0.18.0.txt | 2 +- pandas/src/datetime/np_datetime_strings.c | 2 +- pandas/tests/test_tseries.py | 35 ----------------------- pandas/tseries/tests/test_holiday.py | 34 ++++++++++++++++++++-- pandas/tseries/tests/test_tslib.py | 32 ++++++++++++++++++++- 5 files changed, 65 insertions(+), 40 deletions(-) diff --git a/doc/source/whatsnew/v0.18.0.txt b/doc/source/whatsnew/v0.18.0.txt index 8fe7a5753f1d1..5d26896ba1e07 100644 --- a/doc/source/whatsnew/v0.18.0.txt +++ b/doc/source/whatsnew/v0.18.0.txt @@ -187,7 +187,7 @@ Bug Fixes - +- Bug in parsing timezone offset strings with non-zero minutes (:issue:`11708`) diff --git a/pandas/src/datetime/np_datetime_strings.c b/pandas/src/datetime/np_datetime_strings.c index f7835971ed0b7..bc4ef1b3c8184 100644 --- a/pandas/src/datetime/np_datetime_strings.c +++ b/pandas/src/datetime/np_datetime_strings.c @@ -869,7 +869,7 @@ parse_iso_8601_datetime(char *str, int len, if (out_local != NULL) { *out_local = 1; // Unlike NumPy, do not change internal value to local time - *out_tzoffset = 60 * offset_hour - offset_minute; + *out_tzoffset = 60 * offset_hour + offset_minute; } } diff --git a/pandas/tests/test_tseries.py b/pandas/tests/test_tseries.py index b94c91f72802a..72318f8073595 100644 --- a/pandas/tests/test_tseries.py +++ b/pandas/tests/test_tseries.py @@ -10,7 +10,6 @@ import pandas._period as period import pandas.algos as algos from pandas.core import common as com -from pandas.tseries.holiday import Holiday, SA, next_monday,USMartinLutherKingJr,USMemorialDay,AbstractHolidayCalendar import datetime from pandas import DateOffset @@ -694,40 +693,6 @@ def test_get_period_field_raises_on_out_of_range(self): def test_get_period_field_array_raises_on_out_of_range(self): self.assertRaises(ValueError, period.get_period_field_arr, -1, np.empty(1), 0) -class TestFederalHolidayCalendar(tm.TestCase): - - # Test for issue 10278 - def test_no_mlk_before_1984(self): - class MLKCalendar(AbstractHolidayCalendar): - rules=[USMartinLutherKingJr] - holidays = MLKCalendar().holidays(start='1984', end='1988').to_pydatetime().tolist() - # Testing to make sure holiday is not incorrectly observed before 1986 - self.assertEqual(holidays, [datetime.datetime(1986, 1, 20, 0, 0), datetime.datetime(1987, 1, 19, 0, 0)]) - - def test_memorial_day(self): - class MemorialDay(AbstractHolidayCalendar): - rules=[USMemorialDay] - holidays = MemorialDay().holidays(start='1971', end='1980').to_pydatetime().tolist() - # Fixes 5/31 error and checked manually against wikipedia - self.assertEqual(holidays, [datetime.datetime(1971, 5, 31, 0, 0), datetime.datetime(1972, 5, 29, 0, 0), - datetime.datetime(1973, 5, 28, 0, 0), datetime.datetime(1974, 5, 27, 0, 0), - datetime.datetime(1975, 5, 26, 0, 0), datetime.datetime(1976, 5, 31, 0, 0), - datetime.datetime(1977, 5, 30, 0, 0), datetime.datetime(1978, 5, 29, 0, 0), - datetime.datetime(1979, 5, 28, 0, 0)]) - - - - -class TestHolidayConflictingArguments(tm.TestCase): - - # GH 10217 - - def test_both_offset_observance_raises(self): - - with self.assertRaises(NotImplementedError) as cm: - h = Holiday("Cyber Monday", month=11, day=1, - offset=[DateOffset(weekday=SA(4))], observance=next_monday) - if __name__ == '__main__': import nose nose.runmodule(argv=[__file__, '-vvs', '-x', '--pdb', '--pdb-failure'], diff --git a/pandas/tseries/tests/test_holiday.py b/pandas/tseries/tests/test_holiday.py index 1da397e768a86..dc07a6d455f86 100644 --- a/pandas/tseries/tests/test_holiday.py +++ b/pandas/tseries/tests/test_holiday.py @@ -7,7 +7,7 @@ USFederalHolidayCalendar, USMemorialDay, USThanksgivingDay, nearest_workday, next_monday_or_tuesday, next_monday, previous_friday, sunday_to_monday, Holiday, DateOffset, - MO, Timestamp, AbstractHolidayCalendar, get_calendar, + MO, SA, Timestamp, AbstractHolidayCalendar, get_calendar, HolidayCalendarFactory, next_workday, previous_workday, before_nearest_workday, EasterMonday, GoodFriday, after_nearest_workday, weekend_to_monday, USLaborDay, @@ -358,7 +358,37 @@ def test_after_nearest_workday(self): self.assertEqual(after_nearest_workday(self.sa), self.mo) self.assertEqual(after_nearest_workday(self.su), self.tu) self.assertEqual(after_nearest_workday(self.fr), self.mo) - + +class TestFederalHolidayCalendar(tm.TestCase): + + # Test for issue 10278 + def test_no_mlk_before_1984(self): + class MLKCalendar(AbstractHolidayCalendar): + rules=[USMartinLutherKingJr] + holidays = MLKCalendar().holidays(start='1984', end='1988').to_pydatetime().tolist() + # Testing to make sure holiday is not incorrectly observed before 1986 + self.assertEqual(holidays, [datetime(1986, 1, 20, 0, 0), datetime(1987, 1, 19, 0, 0)]) + + def test_memorial_day(self): + class MemorialDay(AbstractHolidayCalendar): + rules=[USMemorialDay] + holidays = MemorialDay().holidays(start='1971', end='1980').to_pydatetime().tolist() + # Fixes 5/31 error and checked manually against wikipedia + self.assertEqual(holidays, [datetime(1971, 5, 31, 0, 0), datetime(1972, 5, 29, 0, 0), + datetime(1973, 5, 28, 0, 0), datetime(1974, 5, 27, 0, 0), + datetime(1975, 5, 26, 0, 0), datetime(1976, 5, 31, 0, 0), + datetime(1977, 5, 30, 0, 0), datetime(1978, 5, 29, 0, 0), + datetime(1979, 5, 28, 0, 0)]) + +class TestHolidayConflictingArguments(tm.TestCase): + + # GH 10217 + + def test_both_offset_observance_raises(self): + + with self.assertRaises(NotImplementedError) as cm: + h = Holiday("Cyber Monday", month=11, day=1, + offset=[DateOffset(weekday=SA(4))], observance=next_monday) if __name__ == '__main__': nose.runmodule(argv=[__file__, '-vvs', '-x', '--pdb', '--pdb-failure'], diff --git a/pandas/tseries/tests/test_tslib.py b/pandas/tseries/tests/test_tslib.py index f618b2593597e..4fe136fceb671 100644 --- a/pandas/tseries/tests/test_tslib.py +++ b/pandas/tseries/tests/test_tslib.py @@ -141,6 +141,21 @@ def test_constructor_with_stringoffset(self): 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))) + + # GH11708 + # This should be 2015-11-18 10:00 in UTC -> converted to Asia/Katmandu tz + result = Timestamp("2015-11-18 15:45:00+05:45", tz="Asia/Katmandu") + self.assertEqual(result.value, Timestamp("2015-11-18 10:00").value) + expected_repr = "Timestamp('2015-11-18 15:45:00+0545', tz='Asia/Katmandu')" + self.assertEqual(repr(result), expected_repr) + self.assertEqual(result, eval(repr(result))) + + # This should be 2015-11-18 10:00 in UTC -> converted to Asia/Kolkata tz + result = Timestamp("2015-11-18 15:30:00+05:30", tz="Asia/Kolkata") + self.assertEqual(result.value, Timestamp("2015-11-18 10:00").value) + expected_repr = "Timestamp('2015-11-18 15:30:00+0530', tz='Asia/Kolkata')" + self.assertEqual(repr(result), expected_repr) + self.assertEqual(result, eval(repr(result))) def test_constructor_invalid(self): with tm.assertRaisesRegexp(TypeError, 'Cannot convert input'): @@ -620,6 +635,21 @@ def test_parsers_quarterly_with_freq(self): result, _, _ = tools.parse_time_string(date_str, freq=freq) self.assertEqual(result, exp) + def test_parsers_timezone_minute_offsets_roundtrip(self): + #GH11708 + base = to_datetime("2013-01-01 00:00:00") + dt_strings = [('2013-01-01 05:45+0545', + "Asia/Katmandu", + "Timestamp('2013-01-01 05:45:00+0545', tz='Asia/Katmandu')"), + ('2013-01-01 05:30+0530', + "Asia/Kolkata", + "Timestamp('2013-01-01 05:30:00+0530', tz='Asia/Kolkata')")] + + for dt_string, tz, dt_string_repr in dt_strings: + dt_time = to_datetime(dt_string) + self.assertEqual(base, dt_time) + converted_time = dt_time.tz_localize('UTC').tz_convert(tz) + self.assertEqual(dt_string_repr, repr(converted_time)) class TestArrayToDatetime(tm.TestCase): def test_parsing_valid_dates(self): @@ -721,7 +751,7 @@ def test_parsing_timezone_offsets(self): '01-01-2013 08:00:00+08:00', '2013-01-01T08:00:00.000000000+0800', '2012-12-31T16:00:00.000000000-0800', - '12-31-2012 23:00:00-01:00', + '12-31-2012 23:00:00-01:00' ] expected_output = tslib.array_to_datetime(