Skip to content

Commit ada49e1

Browse files
committed
Merge pull request #11774 from rockg/minute-tz
BUG: Parsing offset strings with non-zero minutes was incorrect
2 parents 6ca564b + 3d7a1d4 commit ada49e1

File tree

5 files changed

+65
-40
lines changed

5 files changed

+65
-40
lines changed

doc/source/whatsnew/v0.18.0.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ Bug Fixes
187187

188188

189189

190-
190+
- Bug in parsing timezone offset strings with non-zero minutes (:issue:`11708`)
191191

192192

193193

pandas/src/datetime/np_datetime_strings.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -869,7 +869,7 @@ parse_iso_8601_datetime(char *str, int len,
869869
if (out_local != NULL) {
870870
*out_local = 1;
871871
// Unlike NumPy, do not change internal value to local time
872-
*out_tzoffset = 60 * offset_hour - offset_minute;
872+
*out_tzoffset = 60 * offset_hour + offset_minute;
873873
}
874874
}
875875

pandas/tests/test_tseries.py

-35
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
import pandas._period as period
1111
import pandas.algos as algos
1212
from pandas.core import common as com
13-
from pandas.tseries.holiday import Holiday, SA, next_monday,USMartinLutherKingJr,USMemorialDay,AbstractHolidayCalendar
1413
import datetime
1514
from pandas import DateOffset
1615

@@ -694,40 +693,6 @@ def test_get_period_field_raises_on_out_of_range(self):
694693
def test_get_period_field_array_raises_on_out_of_range(self):
695694
self.assertRaises(ValueError, period.get_period_field_arr, -1, np.empty(1), 0)
696695

697-
class TestFederalHolidayCalendar(tm.TestCase):
698-
699-
# Test for issue 10278
700-
def test_no_mlk_before_1984(self):
701-
class MLKCalendar(AbstractHolidayCalendar):
702-
rules=[USMartinLutherKingJr]
703-
holidays = MLKCalendar().holidays(start='1984', end='1988').to_pydatetime().tolist()
704-
# Testing to make sure holiday is not incorrectly observed before 1986
705-
self.assertEqual(holidays, [datetime.datetime(1986, 1, 20, 0, 0), datetime.datetime(1987, 1, 19, 0, 0)])
706-
707-
def test_memorial_day(self):
708-
class MemorialDay(AbstractHolidayCalendar):
709-
rules=[USMemorialDay]
710-
holidays = MemorialDay().holidays(start='1971', end='1980').to_pydatetime().tolist()
711-
# Fixes 5/31 error and checked manually against wikipedia
712-
self.assertEqual(holidays, [datetime.datetime(1971, 5, 31, 0, 0), datetime.datetime(1972, 5, 29, 0, 0),
713-
datetime.datetime(1973, 5, 28, 0, 0), datetime.datetime(1974, 5, 27, 0, 0),
714-
datetime.datetime(1975, 5, 26, 0, 0), datetime.datetime(1976, 5, 31, 0, 0),
715-
datetime.datetime(1977, 5, 30, 0, 0), datetime.datetime(1978, 5, 29, 0, 0),
716-
datetime.datetime(1979, 5, 28, 0, 0)])
717-
718-
719-
720-
721-
class TestHolidayConflictingArguments(tm.TestCase):
722-
723-
# GH 10217
724-
725-
def test_both_offset_observance_raises(self):
726-
727-
with self.assertRaises(NotImplementedError) as cm:
728-
h = Holiday("Cyber Monday", month=11, day=1,
729-
offset=[DateOffset(weekday=SA(4))], observance=next_monday)
730-
731696
if __name__ == '__main__':
732697
import nose
733698
nose.runmodule(argv=[__file__, '-vvs', '-x', '--pdb', '--pdb-failure'],

pandas/tseries/tests/test_holiday.py

+32-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
USFederalHolidayCalendar, USMemorialDay, USThanksgivingDay,
88
nearest_workday, next_monday_or_tuesday, next_monday,
99
previous_friday, sunday_to_monday, Holiday, DateOffset,
10-
MO, Timestamp, AbstractHolidayCalendar, get_calendar,
10+
MO, SA, Timestamp, AbstractHolidayCalendar, get_calendar,
1111
HolidayCalendarFactory, next_workday, previous_workday,
1212
before_nearest_workday, EasterMonday, GoodFriday,
1313
after_nearest_workday, weekend_to_monday, USLaborDay,
@@ -358,7 +358,37 @@ def test_after_nearest_workday(self):
358358
self.assertEqual(after_nearest_workday(self.sa), self.mo)
359359
self.assertEqual(after_nearest_workday(self.su), self.tu)
360360
self.assertEqual(after_nearest_workday(self.fr), self.mo)
361-
361+
362+
class TestFederalHolidayCalendar(tm.TestCase):
363+
364+
# Test for issue 10278
365+
def test_no_mlk_before_1984(self):
366+
class MLKCalendar(AbstractHolidayCalendar):
367+
rules=[USMartinLutherKingJr]
368+
holidays = MLKCalendar().holidays(start='1984', end='1988').to_pydatetime().tolist()
369+
# Testing to make sure holiday is not incorrectly observed before 1986
370+
self.assertEqual(holidays, [datetime(1986, 1, 20, 0, 0), datetime(1987, 1, 19, 0, 0)])
371+
372+
def test_memorial_day(self):
373+
class MemorialDay(AbstractHolidayCalendar):
374+
rules=[USMemorialDay]
375+
holidays = MemorialDay().holidays(start='1971', end='1980').to_pydatetime().tolist()
376+
# Fixes 5/31 error and checked manually against wikipedia
377+
self.assertEqual(holidays, [datetime(1971, 5, 31, 0, 0), datetime(1972, 5, 29, 0, 0),
378+
datetime(1973, 5, 28, 0, 0), datetime(1974, 5, 27, 0, 0),
379+
datetime(1975, 5, 26, 0, 0), datetime(1976, 5, 31, 0, 0),
380+
datetime(1977, 5, 30, 0, 0), datetime(1978, 5, 29, 0, 0),
381+
datetime(1979, 5, 28, 0, 0)])
382+
383+
class TestHolidayConflictingArguments(tm.TestCase):
384+
385+
# GH 10217
386+
387+
def test_both_offset_observance_raises(self):
388+
389+
with self.assertRaises(NotImplementedError) as cm:
390+
h = Holiday("Cyber Monday", month=11, day=1,
391+
offset=[DateOffset(weekday=SA(4))], observance=next_monday)
362392

363393
if __name__ == '__main__':
364394
nose.runmodule(argv=[__file__, '-vvs', '-x', '--pdb', '--pdb-failure'],

pandas/tseries/tests/test_tslib.py

+31-1
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,21 @@ def test_constructor_with_stringoffset(self):
141141
expected_repr = "Timestamp('2013-11-01 14:00:00+0900', tz='Asia/Tokyo')"
142142
self.assertEqual(repr(result), expected_repr)
143143
self.assertEqual(result, eval(repr(result)))
144+
145+
# GH11708
146+
# This should be 2015-11-18 10:00 in UTC -> converted to Asia/Katmandu tz
147+
result = Timestamp("2015-11-18 15:45:00+05:45", tz="Asia/Katmandu")
148+
self.assertEqual(result.value, Timestamp("2015-11-18 10:00").value)
149+
expected_repr = "Timestamp('2015-11-18 15:45:00+0545', tz='Asia/Katmandu')"
150+
self.assertEqual(repr(result), expected_repr)
151+
self.assertEqual(result, eval(repr(result)))
152+
153+
# This should be 2015-11-18 10:00 in UTC -> converted to Asia/Kolkata tz
154+
result = Timestamp("2015-11-18 15:30:00+05:30", tz="Asia/Kolkata")
155+
self.assertEqual(result.value, Timestamp("2015-11-18 10:00").value)
156+
expected_repr = "Timestamp('2015-11-18 15:30:00+0530', tz='Asia/Kolkata')"
157+
self.assertEqual(repr(result), expected_repr)
158+
self.assertEqual(result, eval(repr(result)))
144159

145160
def test_constructor_invalid(self):
146161
with tm.assertRaisesRegexp(TypeError, 'Cannot convert input'):
@@ -620,6 +635,21 @@ def test_parsers_quarterly_with_freq(self):
620635
result, _, _ = tools.parse_time_string(date_str, freq=freq)
621636
self.assertEqual(result, exp)
622637

638+
def test_parsers_timezone_minute_offsets_roundtrip(self):
639+
#GH11708
640+
base = to_datetime("2013-01-01 00:00:00")
641+
dt_strings = [('2013-01-01 05:45+0545',
642+
"Asia/Katmandu",
643+
"Timestamp('2013-01-01 05:45:00+0545', tz='Asia/Katmandu')"),
644+
('2013-01-01 05:30+0530',
645+
"Asia/Kolkata",
646+
"Timestamp('2013-01-01 05:30:00+0530', tz='Asia/Kolkata')")]
647+
648+
for dt_string, tz, dt_string_repr in dt_strings:
649+
dt_time = to_datetime(dt_string)
650+
self.assertEqual(base, dt_time)
651+
converted_time = dt_time.tz_localize('UTC').tz_convert(tz)
652+
self.assertEqual(dt_string_repr, repr(converted_time))
623653

624654
class TestArrayToDatetime(tm.TestCase):
625655
def test_parsing_valid_dates(self):
@@ -721,7 +751,7 @@ def test_parsing_timezone_offsets(self):
721751
'01-01-2013 08:00:00+08:00',
722752
'2013-01-01T08:00:00.000000000+0800',
723753
'2012-12-31T16:00:00.000000000-0800',
724-
'12-31-2012 23:00:00-01:00',
754+
'12-31-2012 23:00:00-01:00'
725755
]
726756

727757
expected_output = tslib.array_to_datetime(

0 commit comments

Comments
 (0)