Skip to content

Commit

Permalink
Add month names
Browse files Browse the repository at this point in the history
Fix localiztion of months

Add series test and whatsnew notes

Modify test and lint

Add Timestamp rst

Address comments

Lint, move whatsnew, remove hardcoded Monday, check that get_locale is None
  • Loading branch information
mroeschke committed Nov 11, 2017
1 parent 3493aba commit c4837dc
Show file tree
Hide file tree
Showing 9 changed files with 77 additions and 37 deletions.
3 changes: 3 additions & 0 deletions doc/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,7 @@ These can be accessed like ``Series.dt.<property>``.
Series.dt.time
Series.dt.year
Series.dt.month
Series.dt.month_name
Series.dt.day
Series.dt.hour
Series.dt.minute
Expand Down Expand Up @@ -1537,6 +1538,7 @@ Time/Date Components

DatetimeIndex.year
DatetimeIndex.month
DatetimeIndex.month_name
DatetimeIndex.day
DatetimeIndex.hour
DatetimeIndex.minute
Expand Down Expand Up @@ -1757,6 +1759,7 @@ Properties
Timestamp.microsecond
Timestamp.min
Timestamp.month
Timestamp.month_name
Timestamp.nanosecond
Timestamp.quarter
Timestamp.resolution
Expand Down
3 changes: 2 additions & 1 deletion doc/source/whatsnew/v0.22.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Other Enhancements

- Better support for :func:`Dataframe.style.to_excel` output with the ``xlsxwriter`` engine. (:issue:`16149`)
- :func:`pandas.tseries.frequencies.to_offset` now accepts leading '+' signs e.g. '+1h'. (:issue:`18171`)
- :attr:`Timestamp.month_name`, :attr:`DatetimeIndex.month_name`, and :attr:`Series.dt.month_name` are now available (:issue:`12805`)
-

.. _whatsnew_0220.api_breaking:
Expand Down Expand Up @@ -91,7 +92,7 @@ Bug Fixes
Conversion
^^^^^^^^^^

-
- Bug in :attr:`Timestamp.weekday_name` and :attr:`DatetimeIndex.weekday_name` not returning locale aware values (:issue:`12806`)
-
-

Expand Down
14 changes: 10 additions & 4 deletions pandas/_libs/tslib.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ from khash cimport (
kh_resize_int64, kh_get_int64)

from .tslibs.parsing import parse_datetime_string
from .tslibs.strptime import LocaleTime

cimport cython

Expand Down Expand Up @@ -533,10 +534,15 @@ class Timestamp(_Timestamp):

@property
def weekday_name(self):
cdef dict wdays = {0: 'Monday', 1: 'Tuesday', 2: 'Wednesday',
3: 'Thursday', 4: 'Friday', 5: 'Saturday',
6: 'Sunday'}
return wdays[self.weekday()]
cdef object locale_time = LocaleTime()
cdef dict wdays = dict(enumerate(locale_time.f_weekday))
return wdays[self.weekday()].capitalize()

@property
def month_name(self):
cdef object locale_time = LocaleTime()
cdef dict months = dict(enumerate(locale_time.f_month))
return months[self.month].capitalize()

@property
def dayofyear(self):
Expand Down
21 changes: 15 additions & 6 deletions pandas/_libs/tslibs/fields.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ cimport util

cdef int64_t NPY_NAT = util.get_nat()

from pandas._libs.tslibs.strptime import LocaleTime


def build_field_sarray(ndarray[int64_t] dtindex):
"""
Expand Down Expand Up @@ -83,24 +85,31 @@ def get_date_name_field(ndarray[int64_t] dtindex, object field):
ndarray[object] out
pandas_datetimestruct dts
int dow

_dayname = np.array(
['Monday', 'Tuesday', 'Wednesday', 'Thursday',
'Friday', 'Saturday', 'Sunday'],
dtype=np.object_)
object locale_time = LocaleTime()

count = len(dtindex)
out = np.empty(count, dtype=object)

if field == 'weekday_name':
_dayname = np.array(locale_time.f_weekday, dtype=np.object_)
for i in range(count):
if dtindex[i] == NPY_NAT:
out[i] = np.nan
continue

dt64_to_dtstruct(dtindex[i], &dts)
dow = dayofweek(dts.year, dts.month, dts.day)
out[i] = _dayname[dow]
out[i] = _dayname[dow].capitalize()
return out
elif field == 'month_name':
_monthname = np.array(locale_time.f_month, dtype=np.object_)
for i in range(count):
if dtindex[i] == NPY_NAT:
out[i] = np.nan
continue

dt64_to_dtstruct(dtindex[i], &dts)
out[i] = _monthname[dts.month].capitalize()
return out

raise ValueError("Field %s not supported" % field)
Expand Down
1 change: 1 addition & 0 deletions pandas/_libs/tslibs/nattype.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@ class NaTType(_NaT):
daysinmonth = property(fget=lambda self: np.nan)
dayofweek = property(fget=lambda self: np.nan)
weekday_name = property(fget=lambda self: np.nan)
month_name = property(fget=lambda self: np.nan)

# inject Timedelta properties
days = property(fget=lambda self: np.nan)
Expand Down
7 changes: 6 additions & 1 deletion pandas/core/indexes/datetimes.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ def _join_i8_wrapper(joinf, **kwargs):
_bool_ops = ['is_month_start', 'is_month_end',
'is_quarter_start', 'is_quarter_end', 'is_year_start',
'is_year_end', 'is_leap_year']
_object_ops = ['weekday_name', 'freq', 'tz']
_object_ops = ['weekday_name', 'month_name', 'freq', 'tz']
_field_ops = ['year', 'month', 'day', 'hour', 'minute', 'second',
'weekofyear', 'week', 'weekday', 'dayofweek',
'dayofyear', 'quarter', 'days_in_month',
Expand Down Expand Up @@ -1581,6 +1581,11 @@ def _set_freq(self, value):
'weekday_name',
"The name of day in a week (ex: Friday)\n\n.. versionadded:: 0.18.1")

month_name = _field_accessor(
'month_name',
'month_name',
"The name of month in a year (ex: May)\n\n.. versionadded:: 0.21.1")

dayofyear = _field_accessor('dayofyear', 'doy',
"The ordinal day of the year")
quarter = _field_accessor('quarter', 'q', "The quarter of the date")
Expand Down
46 changes: 28 additions & 18 deletions pandas/tests/indexes/datetimes/test_misc.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import locale
import calendar

import pytest

import numpy as np
Expand Down Expand Up @@ -173,7 +176,6 @@ def test_normalize(self):
class TestDatetime64(object):

def test_datetimeindex_accessors(self):

dti_naive = DatetimeIndex(freq='D', start=datetime(1998, 1, 1),
periods=365)
# GH 13303
Expand Down Expand Up @@ -220,23 +222,6 @@ def test_datetimeindex_accessors(self):
assert not dti.is_year_end[0]
assert dti.is_year_end[364]

# GH 11128
assert dti.weekday_name[4] == u'Monday'
assert dti.weekday_name[5] == u'Tuesday'
assert dti.weekday_name[6] == u'Wednesday'
assert dti.weekday_name[7] == u'Thursday'
assert dti.weekday_name[8] == u'Friday'
assert dti.weekday_name[9] == u'Saturday'
assert dti.weekday_name[10] == u'Sunday'

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

assert len(dti.year) == 365
assert len(dti.month) == 365
assert len(dti.day) == 365
Expand Down Expand Up @@ -342,6 +327,31 @@ def test_datetimeindex_accessors(self):
assert dates.weekofyear.tolist() == expected
assert [d.weekofyear for d in dates] == expected

# GH 12806
@pytest.mark.skipif(tm.get_locales() is None or len(tm.get_locales()) == 0,
reason='No available locales')
@pytest.mark.parametrize('time_locale', tm.get_locales())
def test_datetime_name_accessors(self, time_locale):
with tm.set_locale(time_locale, locale.LC_TIME):
# GH 11128
dti = DatetimeIndex(freq='D', start=datetime(1998, 1, 1),
periods=365)
for day, name in zip(range(4, 11), calendar.day_name):
# Test Monday -> Sunday
assert dti.weekday_name[day] == name.capitalize()
date = datetime(2016, 4, day)
assert Timestamp(date).weekday_name == name.capitalize()

# GH 12805
dti = DatetimeIndex(freq='M', start='2012', end='2013')
# Test January -> December
result = dti.month_name
expected = Index([month.capitalize()
for month in calendar.month_name[1:]])
tm.assert_index_equal(result, expected)
for date, result in zip(dti, calendar.month_name[1:]):
assert date.month_name == result.capitalize()

def test_nanosecond_field(self):
dti = DatetimeIndex(np.arange(10))

Expand Down
17 changes: 11 additions & 6 deletions pandas/tests/scalar/test_timestamp.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
""" test the scalar Timestamp """

import sys
import locale
import pytz
import pytest
import dateutil
Expand Down Expand Up @@ -597,13 +598,17 @@ def check(value, equal):
for end in ends:
assert getattr(ts, end)

@pytest.mark.parametrize('data, expected',
[(Timestamp('2017-08-28 23:00:00'), 'Monday'),
(Timestamp('2017-08-28 23:00:00', tz='EST'),
'Monday')])
def test_weekday_name(self, data, expected):
# GH 12806
@pytest.mark.skipif(tm.get_locales() is None or len(tm.get_locales()) == 0,
reason='No available locales')
@pytest.mark.parametrize('data',
[Timestamp('2017-08-28 23:00:00'),
Timestamp('2017-08-28 23:00:00', tz='EST')])
@pytest.mark.parametrize('time_locale', tm.get_locales())
def test_weekday_name(self, data, time_locale):
# GH 17354
assert data.weekday_name == expected
with tm.set_locale(time_locale, locale.LC_TIME):
assert data.weekday_name == calendar.day_name[0].capitalize()

def test_pprint(self):
# GH12622
Expand Down
2 changes: 1 addition & 1 deletion pandas/tests/series/test_datetime_values.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def test_dt_namespace_accessor(self):
ok_for_dt = DatetimeIndex._datetimelike_ops
ok_for_dt_methods = ['to_period', 'to_pydatetime', 'tz_localize',
'tz_convert', 'normalize', 'strftime', 'round',
'floor', 'ceil', 'weekday_name']
'floor', 'ceil', 'weekday_name', 'month_name']
ok_for_td = TimedeltaIndex._datetimelike_ops
ok_for_td_methods = ['components', 'to_pytimedelta', 'total_seconds',
'round', 'floor', 'ceil']
Expand Down

0 comments on commit c4837dc

Please sign in to comment.