Skip to content

Commit

Permalink
BUG: tz aware Timestamp field accessors returns local values (pandas-…
Browse files Browse the repository at this point in the history
…dev#13303)

closes pandas-dev#13303

Previously, calling a date/time attribute with Timestamp that's tz
aware (e.g. `Timestamp('...', tz='...').dayofyear`) would return the
attribute in UTC instead of the local tz.

Author: Matt Roeschke <[email protected]>

Closes pandas-dev#15740 from mroeschke/fix_13303 and squashes the following commits:

b78b333 [Matt Roeschke] BUG: tz aware Timestamp field accessors returns local values (pandas-dev#13303)
  • Loading branch information
mroeschke authored and jreback committed Mar 20, 2017
1 parent 8bde21a commit 771e36c
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 78 deletions.
1 change: 1 addition & 0 deletions doc/source/whatsnew/v0.20.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -796,6 +796,7 @@ Bug Fixes
~~~~~~~~~

- Bug in ``Timestamp.replace`` now raises ``TypeError`` when incorrect argument names are given; previously this raised ``ValueError`` (:issue:`15240`)
- Bug in ``Timestamp`` returning UTC based time/date attributes when a timezone was provided (:issue:`13303`)
- Bug in ``Index`` power operations with reversed operands (:issue:`14973`)
- Bug in ``TimedeltaIndex`` addition where overflow was being allowed without error (:issue:`14816`)
- Bug in ``TimedeltaIndex`` raising a ``ValueError`` when boolean indexing with ``loc`` (:issue:`14946`)
Expand Down
10 changes: 8 additions & 2 deletions pandas/_libs/tslib.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -1233,16 +1233,22 @@ cdef class _Timestamp(datetime):
return datetime.__sub__(self, other)

cpdef _get_field(self, field):
out = get_date_field(np.array([self.value], dtype=np.int64), field)
val = self.value
if self.tz is not None and not _is_utc(self.tz):
val = tz_convert_single(self.value, 'UTC', self.tz)
out = get_date_field(np.array([val], dtype=np.int64), field)
return int(out[0])

cpdef _get_start_end_field(self, field):
month_kw = self.freq.kwds.get(
'startingMonth', self.freq.kwds.get(
'month', 12)) if self.freq else 12
freqstr = self.freqstr if self.freq else None
val = self.value
if self.tz is not None and not _is_utc(self.tz):
val = tz_convert_single(self.value, 'UTC', self.tz)
out = get_start_end_field(
np.array([self.value], dtype=np.int64), field, freqstr, month_kw)
np.array([val], dtype=np.int64), field, freqstr, month_kw)
return out[0]

property _repr_base:
Expand Down
158 changes: 82 additions & 76 deletions pandas/tests/indexes/datetimes/test_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,82 +172,88 @@ def test_normalize(self):
class TestDatetime64(tm.TestCase):

def test_datetimeindex_accessors(self):
dti = DatetimeIndex(freq='D', start=datetime(1998, 1, 1), periods=365)

self.assertEqual(dti.year[0], 1998)
self.assertEqual(dti.month[0], 1)
self.assertEqual(dti.day[0], 1)
self.assertEqual(dti.hour[0], 0)
self.assertEqual(dti.minute[0], 0)
self.assertEqual(dti.second[0], 0)
self.assertEqual(dti.microsecond[0], 0)
self.assertEqual(dti.dayofweek[0], 3)

self.assertEqual(dti.dayofyear[0], 1)
self.assertEqual(dti.dayofyear[120], 121)

self.assertEqual(dti.weekofyear[0], 1)
self.assertEqual(dti.weekofyear[120], 18)

self.assertEqual(dti.quarter[0], 1)
self.assertEqual(dti.quarter[120], 2)

self.assertEqual(dti.days_in_month[0], 31)
self.assertEqual(dti.days_in_month[90], 30)

self.assertEqual(dti.is_month_start[0], True)
self.assertEqual(dti.is_month_start[1], False)
self.assertEqual(dti.is_month_start[31], True)
self.assertEqual(dti.is_quarter_start[0], True)
self.assertEqual(dti.is_quarter_start[90], True)
self.assertEqual(dti.is_year_start[0], True)
self.assertEqual(dti.is_year_start[364], False)
self.assertEqual(dti.is_month_end[0], False)
self.assertEqual(dti.is_month_end[30], True)
self.assertEqual(dti.is_month_end[31], False)
self.assertEqual(dti.is_month_end[364], True)
self.assertEqual(dti.is_quarter_end[0], False)
self.assertEqual(dti.is_quarter_end[30], False)
self.assertEqual(dti.is_quarter_end[89], True)
self.assertEqual(dti.is_quarter_end[364], True)
self.assertEqual(dti.is_year_end[0], False)
self.assertEqual(dti.is_year_end[364], True)

# GH 11128
self.assertEqual(dti.weekday_name[4], u'Monday')
self.assertEqual(dti.weekday_name[5], u'Tuesday')
self.assertEqual(dti.weekday_name[6], u'Wednesday')
self.assertEqual(dti.weekday_name[7], u'Thursday')
self.assertEqual(dti.weekday_name[8], u'Friday')
self.assertEqual(dti.weekday_name[9], u'Saturday')
self.assertEqual(dti.weekday_name[10], u'Sunday')

self.assertEqual(Timestamp('2016-04-04').weekday_name, u'Monday')
self.assertEqual(Timestamp('2016-04-05').weekday_name, u'Tuesday')
self.assertEqual(Timestamp('2016-04-06').weekday_name, u'Wednesday')
self.assertEqual(Timestamp('2016-04-07').weekday_name, u'Thursday')
self.assertEqual(Timestamp('2016-04-08').weekday_name, u'Friday')
self.assertEqual(Timestamp('2016-04-09').weekday_name, u'Saturday')
self.assertEqual(Timestamp('2016-04-10').weekday_name, u'Sunday')

self.assertEqual(len(dti.year), 365)
self.assertEqual(len(dti.month), 365)
self.assertEqual(len(dti.day), 365)
self.assertEqual(len(dti.hour), 365)
self.assertEqual(len(dti.minute), 365)
self.assertEqual(len(dti.second), 365)
self.assertEqual(len(dti.microsecond), 365)
self.assertEqual(len(dti.dayofweek), 365)
self.assertEqual(len(dti.dayofyear), 365)
self.assertEqual(len(dti.weekofyear), 365)
self.assertEqual(len(dti.quarter), 365)
self.assertEqual(len(dti.is_month_start), 365)
self.assertEqual(len(dti.is_month_end), 365)
self.assertEqual(len(dti.is_quarter_start), 365)
self.assertEqual(len(dti.is_quarter_end), 365)
self.assertEqual(len(dti.is_year_start), 365)
self.assertEqual(len(dti.is_year_end), 365)
self.assertEqual(len(dti.weekday_name), 365)
dti_naive = DatetimeIndex(freq='D', start=datetime(1998, 1, 1),
periods=365)
# GH 13303
dti_tz = DatetimeIndex(freq='D', start=datetime(1998, 1, 1),
periods=365, tz='US/Eastern')
for dti in [dti_naive, dti_tz]:

self.assertEqual(dti.year[0], 1998)
self.assertEqual(dti.month[0], 1)
self.assertEqual(dti.day[0], 1)
self.assertEqual(dti.hour[0], 0)
self.assertEqual(dti.minute[0], 0)
self.assertEqual(dti.second[0], 0)
self.assertEqual(dti.microsecond[0], 0)
self.assertEqual(dti.dayofweek[0], 3)

self.assertEqual(dti.dayofyear[0], 1)
self.assertEqual(dti.dayofyear[120], 121)

self.assertEqual(dti.weekofyear[0], 1)
self.assertEqual(dti.weekofyear[120], 18)

self.assertEqual(dti.quarter[0], 1)
self.assertEqual(dti.quarter[120], 2)

self.assertEqual(dti.days_in_month[0], 31)
self.assertEqual(dti.days_in_month[90], 30)

self.assertEqual(dti.is_month_start[0], True)
self.assertEqual(dti.is_month_start[1], False)
self.assertEqual(dti.is_month_start[31], True)
self.assertEqual(dti.is_quarter_start[0], True)
self.assertEqual(dti.is_quarter_start[90], True)
self.assertEqual(dti.is_year_start[0], True)
self.assertEqual(dti.is_year_start[364], False)
self.assertEqual(dti.is_month_end[0], False)
self.assertEqual(dti.is_month_end[30], True)
self.assertEqual(dti.is_month_end[31], False)
self.assertEqual(dti.is_month_end[364], True)
self.assertEqual(dti.is_quarter_end[0], False)
self.assertEqual(dti.is_quarter_end[30], False)
self.assertEqual(dti.is_quarter_end[89], True)
self.assertEqual(dti.is_quarter_end[364], True)
self.assertEqual(dti.is_year_end[0], False)
self.assertEqual(dti.is_year_end[364], True)

# GH 11128
self.assertEqual(dti.weekday_name[4], u'Monday')
self.assertEqual(dti.weekday_name[5], u'Tuesday')
self.assertEqual(dti.weekday_name[6], u'Wednesday')
self.assertEqual(dti.weekday_name[7], u'Thursday')
self.assertEqual(dti.weekday_name[8], u'Friday')
self.assertEqual(dti.weekday_name[9], u'Saturday')
self.assertEqual(dti.weekday_name[10], u'Sunday')

self.assertEqual(Timestamp('2016-04-04').weekday_name, u'Monday')
self.assertEqual(Timestamp('2016-04-05').weekday_name, u'Tuesday')
self.assertEqual(Timestamp('2016-04-06').weekday_name,
u'Wednesday')
self.assertEqual(Timestamp('2016-04-07').weekday_name, u'Thursday')
self.assertEqual(Timestamp('2016-04-08').weekday_name, u'Friday')
self.assertEqual(Timestamp('2016-04-09').weekday_name, u'Saturday')
self.assertEqual(Timestamp('2016-04-10').weekday_name, u'Sunday')

self.assertEqual(len(dti.year), 365)
self.assertEqual(len(dti.month), 365)
self.assertEqual(len(dti.day), 365)
self.assertEqual(len(dti.hour), 365)
self.assertEqual(len(dti.minute), 365)
self.assertEqual(len(dti.second), 365)
self.assertEqual(len(dti.microsecond), 365)
self.assertEqual(len(dti.dayofweek), 365)
self.assertEqual(len(dti.dayofyear), 365)
self.assertEqual(len(dti.weekofyear), 365)
self.assertEqual(len(dti.quarter), 365)
self.assertEqual(len(dti.is_month_start), 365)
self.assertEqual(len(dti.is_month_end), 365)
self.assertEqual(len(dti.is_quarter_start), 365)
self.assertEqual(len(dti.is_quarter_end), 365)
self.assertEqual(len(dti.is_year_start), 365)
self.assertEqual(len(dti.is_year_end), 365)
self.assertEqual(len(dti.weekday_name), 365)

dti = DatetimeIndex(freq='BQ-FEB', start=datetime(1998, 1, 1),
periods=4)
Expand Down
26 changes: 26 additions & 0 deletions pandas/tests/scalar/test_timestamp.py
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,32 @@ def check(value, equal):
check(ts.daysinmonth, 31)
check(ts.daysinmonth, 31)

# GH 13303
ts = Timestamp('2014-12-31 23:59:00-05:00', tz='US/Eastern')
check(ts.year, 2014)
check(ts.month, 12)
check(ts.day, 31)
check(ts.hour, 23)
check(ts.minute, 59)
check(ts.second, 0)
self.assertRaises(AttributeError, lambda: ts.millisecond)
check(ts.microsecond, 0)
check(ts.nanosecond, 0)
check(ts.dayofweek, 2)
check(ts.quarter, 4)
check(ts.dayofyear, 365)
check(ts.week, 1)
check(ts.daysinmonth, 31)

ts = Timestamp('2014-01-01 00:00:00+01:00')
starts = ['is_month_start', 'is_quarter_start', 'is_year_start']
for start in starts:
self.assertTrue(getattr(ts, start))
ts = Timestamp('2014-12-31 23:59:59+01:00')
ends = ['is_month_end', 'is_year_end', 'is_quarter_end']
for end in ends:
self.assertTrue(getattr(ts, end))

def test_nat_fields(self):
# GH 10050
ts = Timestamp('NaT')
Expand Down

0 comments on commit 771e36c

Please sign in to comment.