Skip to content

Commit 771e36c

Browse files
mroeschkejreback
authored andcommitted
BUG: tz aware Timestamp field accessors returns local values (pandas-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)
1 parent 8bde21a commit 771e36c

File tree

4 files changed

+117
-78
lines changed

4 files changed

+117
-78
lines changed

doc/source/whatsnew/v0.20.0.txt

+1
Original file line numberDiff line numberDiff line change
@@ -796,6 +796,7 @@ Bug Fixes
796796
~~~~~~~~~
797797

798798
- Bug in ``Timestamp.replace`` now raises ``TypeError`` when incorrect argument names are given; previously this raised ``ValueError`` (:issue:`15240`)
799+
- Bug in ``Timestamp`` returning UTC based time/date attributes when a timezone was provided (:issue:`13303`)
799800
- Bug in ``Index`` power operations with reversed operands (:issue:`14973`)
800801
- Bug in ``TimedeltaIndex`` addition where overflow was being allowed without error (:issue:`14816`)
801802
- Bug in ``TimedeltaIndex`` raising a ``ValueError`` when boolean indexing with ``loc`` (:issue:`14946`)

pandas/_libs/tslib.pyx

+8-2
Original file line numberDiff line numberDiff line change
@@ -1233,16 +1233,22 @@ cdef class _Timestamp(datetime):
12331233
return datetime.__sub__(self, other)
12341234

12351235
cpdef _get_field(self, field):
1236-
out = get_date_field(np.array([self.value], dtype=np.int64), field)
1236+
val = self.value
1237+
if self.tz is not None and not _is_utc(self.tz):
1238+
val = tz_convert_single(self.value, 'UTC', self.tz)
1239+
out = get_date_field(np.array([val], dtype=np.int64), field)
12371240
return int(out[0])
12381241

12391242
cpdef _get_start_end_field(self, field):
12401243
month_kw = self.freq.kwds.get(
12411244
'startingMonth', self.freq.kwds.get(
12421245
'month', 12)) if self.freq else 12
12431246
freqstr = self.freqstr if self.freq else None
1247+
val = self.value
1248+
if self.tz is not None and not _is_utc(self.tz):
1249+
val = tz_convert_single(self.value, 'UTC', self.tz)
12441250
out = get_start_end_field(
1245-
np.array([self.value], dtype=np.int64), field, freqstr, month_kw)
1251+
np.array([val], dtype=np.int64), field, freqstr, month_kw)
12461252
return out[0]
12471253

12481254
property _repr_base:

pandas/tests/indexes/datetimes/test_misc.py

+82-76
Original file line numberDiff line numberDiff line change
@@ -172,82 +172,88 @@ def test_normalize(self):
172172
class TestDatetime64(tm.TestCase):
173173

174174
def test_datetimeindex_accessors(self):
175-
dti = DatetimeIndex(freq='D', start=datetime(1998, 1, 1), periods=365)
176-
177-
self.assertEqual(dti.year[0], 1998)
178-
self.assertEqual(dti.month[0], 1)
179-
self.assertEqual(dti.day[0], 1)
180-
self.assertEqual(dti.hour[0], 0)
181-
self.assertEqual(dti.minute[0], 0)
182-
self.assertEqual(dti.second[0], 0)
183-
self.assertEqual(dti.microsecond[0], 0)
184-
self.assertEqual(dti.dayofweek[0], 3)
185-
186-
self.assertEqual(dti.dayofyear[0], 1)
187-
self.assertEqual(dti.dayofyear[120], 121)
188-
189-
self.assertEqual(dti.weekofyear[0], 1)
190-
self.assertEqual(dti.weekofyear[120], 18)
191-
192-
self.assertEqual(dti.quarter[0], 1)
193-
self.assertEqual(dti.quarter[120], 2)
194-
195-
self.assertEqual(dti.days_in_month[0], 31)
196-
self.assertEqual(dti.days_in_month[90], 30)
197-
198-
self.assertEqual(dti.is_month_start[0], True)
199-
self.assertEqual(dti.is_month_start[1], False)
200-
self.assertEqual(dti.is_month_start[31], True)
201-
self.assertEqual(dti.is_quarter_start[0], True)
202-
self.assertEqual(dti.is_quarter_start[90], True)
203-
self.assertEqual(dti.is_year_start[0], True)
204-
self.assertEqual(dti.is_year_start[364], False)
205-
self.assertEqual(dti.is_month_end[0], False)
206-
self.assertEqual(dti.is_month_end[30], True)
207-
self.assertEqual(dti.is_month_end[31], False)
208-
self.assertEqual(dti.is_month_end[364], True)
209-
self.assertEqual(dti.is_quarter_end[0], False)
210-
self.assertEqual(dti.is_quarter_end[30], False)
211-
self.assertEqual(dti.is_quarter_end[89], True)
212-
self.assertEqual(dti.is_quarter_end[364], True)
213-
self.assertEqual(dti.is_year_end[0], False)
214-
self.assertEqual(dti.is_year_end[364], True)
215-
216-
# GH 11128
217-
self.assertEqual(dti.weekday_name[4], u'Monday')
218-
self.assertEqual(dti.weekday_name[5], u'Tuesday')
219-
self.assertEqual(dti.weekday_name[6], u'Wednesday')
220-
self.assertEqual(dti.weekday_name[7], u'Thursday')
221-
self.assertEqual(dti.weekday_name[8], u'Friday')
222-
self.assertEqual(dti.weekday_name[9], u'Saturday')
223-
self.assertEqual(dti.weekday_name[10], u'Sunday')
224-
225-
self.assertEqual(Timestamp('2016-04-04').weekday_name, u'Monday')
226-
self.assertEqual(Timestamp('2016-04-05').weekday_name, u'Tuesday')
227-
self.assertEqual(Timestamp('2016-04-06').weekday_name, u'Wednesday')
228-
self.assertEqual(Timestamp('2016-04-07').weekday_name, u'Thursday')
229-
self.assertEqual(Timestamp('2016-04-08').weekday_name, u'Friday')
230-
self.assertEqual(Timestamp('2016-04-09').weekday_name, u'Saturday')
231-
self.assertEqual(Timestamp('2016-04-10').weekday_name, u'Sunday')
232-
233-
self.assertEqual(len(dti.year), 365)
234-
self.assertEqual(len(dti.month), 365)
235-
self.assertEqual(len(dti.day), 365)
236-
self.assertEqual(len(dti.hour), 365)
237-
self.assertEqual(len(dti.minute), 365)
238-
self.assertEqual(len(dti.second), 365)
239-
self.assertEqual(len(dti.microsecond), 365)
240-
self.assertEqual(len(dti.dayofweek), 365)
241-
self.assertEqual(len(dti.dayofyear), 365)
242-
self.assertEqual(len(dti.weekofyear), 365)
243-
self.assertEqual(len(dti.quarter), 365)
244-
self.assertEqual(len(dti.is_month_start), 365)
245-
self.assertEqual(len(dti.is_month_end), 365)
246-
self.assertEqual(len(dti.is_quarter_start), 365)
247-
self.assertEqual(len(dti.is_quarter_end), 365)
248-
self.assertEqual(len(dti.is_year_start), 365)
249-
self.assertEqual(len(dti.is_year_end), 365)
250-
self.assertEqual(len(dti.weekday_name), 365)
175+
dti_naive = DatetimeIndex(freq='D', start=datetime(1998, 1, 1),
176+
periods=365)
177+
# GH 13303
178+
dti_tz = DatetimeIndex(freq='D', start=datetime(1998, 1, 1),
179+
periods=365, tz='US/Eastern')
180+
for dti in [dti_naive, dti_tz]:
181+
182+
self.assertEqual(dti.year[0], 1998)
183+
self.assertEqual(dti.month[0], 1)
184+
self.assertEqual(dti.day[0], 1)
185+
self.assertEqual(dti.hour[0], 0)
186+
self.assertEqual(dti.minute[0], 0)
187+
self.assertEqual(dti.second[0], 0)
188+
self.assertEqual(dti.microsecond[0], 0)
189+
self.assertEqual(dti.dayofweek[0], 3)
190+
191+
self.assertEqual(dti.dayofyear[0], 1)
192+
self.assertEqual(dti.dayofyear[120], 121)
193+
194+
self.assertEqual(dti.weekofyear[0], 1)
195+
self.assertEqual(dti.weekofyear[120], 18)
196+
197+
self.assertEqual(dti.quarter[0], 1)
198+
self.assertEqual(dti.quarter[120], 2)
199+
200+
self.assertEqual(dti.days_in_month[0], 31)
201+
self.assertEqual(dti.days_in_month[90], 30)
202+
203+
self.assertEqual(dti.is_month_start[0], True)
204+
self.assertEqual(dti.is_month_start[1], False)
205+
self.assertEqual(dti.is_month_start[31], True)
206+
self.assertEqual(dti.is_quarter_start[0], True)
207+
self.assertEqual(dti.is_quarter_start[90], True)
208+
self.assertEqual(dti.is_year_start[0], True)
209+
self.assertEqual(dti.is_year_start[364], False)
210+
self.assertEqual(dti.is_month_end[0], False)
211+
self.assertEqual(dti.is_month_end[30], True)
212+
self.assertEqual(dti.is_month_end[31], False)
213+
self.assertEqual(dti.is_month_end[364], True)
214+
self.assertEqual(dti.is_quarter_end[0], False)
215+
self.assertEqual(dti.is_quarter_end[30], False)
216+
self.assertEqual(dti.is_quarter_end[89], True)
217+
self.assertEqual(dti.is_quarter_end[364], True)
218+
self.assertEqual(dti.is_year_end[0], False)
219+
self.assertEqual(dti.is_year_end[364], True)
220+
221+
# GH 11128
222+
self.assertEqual(dti.weekday_name[4], u'Monday')
223+
self.assertEqual(dti.weekday_name[5], u'Tuesday')
224+
self.assertEqual(dti.weekday_name[6], u'Wednesday')
225+
self.assertEqual(dti.weekday_name[7], u'Thursday')
226+
self.assertEqual(dti.weekday_name[8], u'Friday')
227+
self.assertEqual(dti.weekday_name[9], u'Saturday')
228+
self.assertEqual(dti.weekday_name[10], u'Sunday')
229+
230+
self.assertEqual(Timestamp('2016-04-04').weekday_name, u'Monday')
231+
self.assertEqual(Timestamp('2016-04-05').weekday_name, u'Tuesday')
232+
self.assertEqual(Timestamp('2016-04-06').weekday_name,
233+
u'Wednesday')
234+
self.assertEqual(Timestamp('2016-04-07').weekday_name, u'Thursday')
235+
self.assertEqual(Timestamp('2016-04-08').weekday_name, u'Friday')
236+
self.assertEqual(Timestamp('2016-04-09').weekday_name, u'Saturday')
237+
self.assertEqual(Timestamp('2016-04-10').weekday_name, u'Sunday')
238+
239+
self.assertEqual(len(dti.year), 365)
240+
self.assertEqual(len(dti.month), 365)
241+
self.assertEqual(len(dti.day), 365)
242+
self.assertEqual(len(dti.hour), 365)
243+
self.assertEqual(len(dti.minute), 365)
244+
self.assertEqual(len(dti.second), 365)
245+
self.assertEqual(len(dti.microsecond), 365)
246+
self.assertEqual(len(dti.dayofweek), 365)
247+
self.assertEqual(len(dti.dayofyear), 365)
248+
self.assertEqual(len(dti.weekofyear), 365)
249+
self.assertEqual(len(dti.quarter), 365)
250+
self.assertEqual(len(dti.is_month_start), 365)
251+
self.assertEqual(len(dti.is_month_end), 365)
252+
self.assertEqual(len(dti.is_quarter_start), 365)
253+
self.assertEqual(len(dti.is_quarter_end), 365)
254+
self.assertEqual(len(dti.is_year_start), 365)
255+
self.assertEqual(len(dti.is_year_end), 365)
256+
self.assertEqual(len(dti.weekday_name), 365)
251257

252258
dti = DatetimeIndex(freq='BQ-FEB', start=datetime(1998, 1, 1),
253259
periods=4)

pandas/tests/scalar/test_timestamp.py

+26
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,32 @@ def check(value, equal):
550550
check(ts.daysinmonth, 31)
551551
check(ts.daysinmonth, 31)
552552

553+
# GH 13303
554+
ts = Timestamp('2014-12-31 23:59:00-05:00', tz='US/Eastern')
555+
check(ts.year, 2014)
556+
check(ts.month, 12)
557+
check(ts.day, 31)
558+
check(ts.hour, 23)
559+
check(ts.minute, 59)
560+
check(ts.second, 0)
561+
self.assertRaises(AttributeError, lambda: ts.millisecond)
562+
check(ts.microsecond, 0)
563+
check(ts.nanosecond, 0)
564+
check(ts.dayofweek, 2)
565+
check(ts.quarter, 4)
566+
check(ts.dayofyear, 365)
567+
check(ts.week, 1)
568+
check(ts.daysinmonth, 31)
569+
570+
ts = Timestamp('2014-01-01 00:00:00+01:00')
571+
starts = ['is_month_start', 'is_quarter_start', 'is_year_start']
572+
for start in starts:
573+
self.assertTrue(getattr(ts, start))
574+
ts = Timestamp('2014-12-31 23:59:59+01:00')
575+
ends = ['is_month_end', 'is_year_end', 'is_quarter_end']
576+
for end in ends:
577+
self.assertTrue(getattr(ts, end))
578+
553579
def test_nat_fields(self):
554580
# GH 10050
555581
ts = Timestamp('NaT')

0 commit comments

Comments
 (0)