BUG: Further corrections for days_at_time (#1334)

* BUG: Further corrections for days_at_time

- Revert to using DateOffset, as Timedelta doesn't handle offsetting by
  one day over a tz change properly:

    In [12]: pd.Timestamp('2004-04-05', tz='America/Chicago') + pd.Timedelta(days=-1)
    Out[12]: Timestamp('2004-04-03 23:00:00-0600', tz='America/Chicago')

    In [13]: pd.Timestamp('2004-04-05', tz='America/Chicago') + pd.DateOffset(days=-1)
    Out[13]: Timestamp('2004-04-04 00:00:00-0600', tz='America/Chicago')

  By creating a DateOffset using the `days` kwarg, the issue previously
  fixed in bcc867b is addressed.

- To preempt any other pandas issues around day offsets, changes to
  performing these with no timezone, then localizing to the local
  timezone when shifting the time.
- Adds unit test for days_at_time

* STY: Remove unused import
This commit is contained in:
Andrew Daniels
2016-07-19 13:19:40 -04:00
committed by GitHub
parent b364b0fbfc
commit 6cebf05417
2 changed files with 44 additions and 5 deletions
+34
View File
@@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from datetime import time
from os.path import (
abspath,
dirname,
@@ -23,16 +24,19 @@ from collections import namedtuple
import numpy as np
import pandas as pd
from nose_parameterized import parameterized
from pandas import (
read_csv,
Timestamp,
)
from pandas.util.testing import assert_index_equal
from pytz import timezone
from zipline.errors import (
CalendarNameCollision,
InvalidCalendarName,
)
from zipline.utils.calendars.exchange_calendar_nyse import NYSEExchangeCalendar
from zipline.utils.calendars.trading_calendar import days_at_time
from zipline.utils.calendars import(
register_calendar,
deregister_calendar,
@@ -81,6 +85,36 @@ class CalendarRegistrationTestCase(TestCase):
self.assertNotEqual(real_nyse, retr_cal)
class DaysAtTimeTestCase(TestCase):
@parameterized.expand([
# NYSE standard day
(
'2016-07-19', 0, time(9, 31), timezone('US/Eastern'),
'2016-07-19 9:31',
),
# CME standard day
(
'2016-07-19', -1, time(17, 1), timezone('America/Chicago'),
'2016-07-18 17:01',
),
# CME day after DST start
(
'2004-04-05', -1, time(17, 1), timezone('America/Chicago'),
'2004-04-04 17:01'
),
# ICE day after DST start
(
'1990-04-02', -1, time(19, 1), timezone('America/Chicago'),
'1990-04-01 19:01',
),
])
def test_days_at_time(self, day, day_offset, time_offset, tz, expected):
days = pd.DatetimeIndex([pd.Timestamp(day, tz=tz)])
result = days_at_time(days, time_offset, tz, day_offset)[0]
expected = pd.Timestamp(expected, tz=tz).tz_convert('UTC')
self.assertEqual(result, expected)
class ExchangeCalendarTestBase(object):
# Override in subclasses.
+10 -5
View File
@@ -21,7 +21,7 @@ from pandas import (
DataFrame,
date_range,
DatetimeIndex,
Timedelta
DateOffset
)
from pandas.tseries.offsets import CustomBusinessDay
@@ -677,11 +677,16 @@ def days_at_time(days, t, tz, day_offset=0):
day_offset : int
The number of days we want to offset @days by
"""
days = DatetimeIndex(days).tz_localize(None).tz_localize(tz)
days_offset = days + Timedelta(days=day_offset)
# Offset days without tz to avoid timezone issues.
days = DatetimeIndex(days).tz_localize(None)
days_offset = days + DateOffset(days=day_offset)
# Shift all days to the target time in the local timezone, then
# convert to UTC.
return days_offset.shift(
1, freq=Timedelta(hours=t.hour, minutes=t.minute, seconds=t.second)
).tz_convert('UTC')
1, freq=DateOffset(hour=t.hour, minute=t.minute, second=t.second)
).tz_localize(tz).tz_convert('UTC')
def holidays_at_time(calendar, start, end, time, tz):