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

create functions instead of attributes

Fix white space

Modify tests

Lint
  • Loading branch information
mroeschke committed Nov 22, 2017
1 parent 2dbf2a6 commit 1162f9a
Show file tree
Hide file tree
Showing 8 changed files with 233 additions and 31 deletions.
6 changes: 6 additions & 0 deletions doc/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,8 @@ These can be accessed like ``Series.dt.<property>``.
Series.dt.round
Series.dt.floor
Series.dt.ceil
Series.dt.month_name
Series.dt.day_name

**Timedelta Properties**

Expand Down Expand Up @@ -1751,6 +1753,8 @@ Time-specific operations
DatetimeIndex.round
DatetimeIndex.floor
DatetimeIndex.ceil
DatetimeIndex.month_name
DatetimeIndex.day_name

Conversion
~~~~~~~~~~
Expand Down Expand Up @@ -1947,6 +1951,7 @@ Methods
Timestamp.combine
Timestamp.ctime
Timestamp.date
Timestamp.day_name
Timestamp.dst
Timestamp.floor
Timestamp.freq
Expand All @@ -1956,6 +1961,7 @@ Methods
Timestamp.isocalendar
Timestamp.isoformat
Timestamp.isoweekday
Timestamp.month_name
Timestamp.normalize
Timestamp.now
Timestamp.replace
Expand Down
3 changes: 3 additions & 0 deletions doc/source/whatsnew/v0.22.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ Other Enhancements
- :class:`pandas.io.formats.style.Styler` now has method ``hide_index()`` to determine whether the index will be rendered in ouptut (:issue:`14194`)
- :class:`pandas.io.formats.style.Styler` now has method ``hide_columns()`` to determine whether columns will be hidden in output (:issue:`14194`)
- Improved wording of ``ValueError`` raised in :func:`to_datetime` when ``unit=`` is passed with a non-convertible value (:issue:`14350`)
- :meth:`Timestamp.month_name`, :meth:`DatetimeIndex.month_name`, and :meth:`Series.dt.month_name` are now available (:issue:`12805`)
- :meth:`Timestamp.day_name` and :meth:`DatetimeIndex.day_name` are now available to return day names with a specified locale (:issue:`12806`)
-

.. _whatsnew_0220.api_breaking:

Expand Down
51 changes: 51 additions & 0 deletions pandas/_libs/tslib.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,15 @@ from tslibs.np_datetime cimport (check_dts_bounds,
from tslibs.np_datetime import OutOfBoundsDatetime

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

cimport cython

import warnings

import locale
from pandas.util.testing import set_locale

import pytz
UTC = pytz.utc

Expand Down Expand Up @@ -534,6 +538,53 @@ class Timestamp(_Timestamp):

return Period(self, freq=freq)

def day_name(self, time_locale=None):
"""
Return the day name of the Timestamp with specified locale.
Parameters
----------
time_locale : string, default None (English locale)
locale determining the language in which to return the day name
Returns
-------
day_name : string
"""
if time_locale is None:
days = {0: 'monday', 1: 'tuesday', 2: 'wednesday',
3: 'thursday', 4: 'friday', 5: 'saturday',
6: 'sunday'}
else:
with set_locale(time_locale, locale.LC_TIME):
locale_time = LocaleTime()
days = dict(enumerate(locale_time.f_weekday))
return days[self.weekday()].capitalize()

def month_name(self, time_locale=None):
"""
Return the month name of the Timestamp with specified locale.
Parameters
----------
time_locale : string, default None (English locale)
locale determining the language in which to return the month name
Returns
-------
month_name : string
"""
if time_locale is None:
months = {1: 'january', 2: 'february', 3: 'march',
4: 'april', 5: 'may', 6: 'june', 7: 'july',
8: 'august', 9: 'september', 10: 'october',
11: 'november', 12: 'december'}
else:
with set_locale(time_locale, locale.LC_TIME):
locale_time = LocaleTime()
months = dict(enumerate(locale_time.f_month))
return months[self.month].capitalize()

@property
def dayofweek(self):
return self.weekday()
Expand Down
50 changes: 44 additions & 6 deletions pandas/_libs/tslibs/fields.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ from np_datetime cimport (pandas_datetimestruct, pandas_timedeltastruct,
days_per_month_table, is_leapyear, dayofweek)
from nattype cimport NPY_NAT

from pandas._libs.tslibs.strptime import LocaleTime

import locale
from pandas.util.testing import set_locale

def build_field_sarray(ndarray[int64_t] dtindex):
"""
Expand Down Expand Up @@ -67,7 +71,8 @@ def build_field_sarray(ndarray[int64_t] dtindex):

@cython.wraparound(False)
@cython.boundscheck(False)
def get_date_name_field(ndarray[int64_t] dtindex, object field):
def get_date_name_field(ndarray[int64_t] dtindex, object field,
object time_locale=None):
"""
Given a int64-based datetime index, return array of strings of date
name based on requested field (e.g. weekday_name)
Expand All @@ -77,16 +82,15 @@ 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(['Monday', 'Tuesday', 'Wednesday', 'Thursday',
'Friday', 'Saturday', 'Sunday'],
dtype=np.object_)
for i in range(count):
if dtindex[i] == NPY_NAT:
out[i] = np.nan
Expand All @@ -95,6 +99,40 @@ def get_date_name_field(ndarray[int64_t] dtindex, object field):
dt64_to_dtstruct(dtindex[i], &dts)
dow = dayofweek(dts.year, dts.month, dts.day)
out[i] = _dayname[dow]
if field == 'day_name':
if time_locale is None:
_dayname = np.array(['monday', 'tuesday', 'wednesday', 'thursday',
'friday', 'saturday', 'sunday'],
dtype=np.object_)
else:
with set_locale(time_locale, locale.LC_TIME):
locale_time = LocaleTime()
_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].capitalize()
return out
elif field == 'month_name':
if time_locale is None:
_monthname = np.array(['', 'monday', 'tuesday', 'wednesday',
'thursday', 'friday', 'saturday', 'sunday'],
dtype=np.object_)
else:
with set_locale(time_locale, locale.LC_TIME):
locale_time = LocaleTime()
_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
32 changes: 31 additions & 1 deletion pandas/_libs/tslibs/nattype.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,13 @@ _nat_scalar_rules[Py_GE] = False
def _make_nan_func(func_name, cls):
def f(*args, **kwargs):
return np.nan

f.__name__ = func_name
f.__doc__ = getattr(cls, func_name).__doc__
if isinstance(cls, str):
# passed the literal docstring directly
f.__doc__ = cls
else:
f.__doc__ = getattr(cls, func_name).__doc__
return f


Expand Down Expand Up @@ -320,7 +325,32 @@ class NaTType(_NaT):
# nan methods
weekday = _make_nan_func('weekday', datetime)
isoweekday = _make_nan_func('isoweekday', datetime)
month_name = _make_nan_func('month_name', # noqa:E128
"""
Return the month name of the Timestamp with specified locale.
Parameters
----------
time_locale : string, default None (English locale)
locale determining the language in which to return the month name
Returns
-------
month_name : string
""")
day_name = _make_nan_func('day_name', # noqa:E128
"""
Return the day name of the Timestamp with specified locale.
Parameters
----------
time_locale : string, default None (English locale)
locale determining the language in which to return the day name
Returns
-------
day_name : string
""")
# _nat_methods
date = _make_nat_func('date', datetime)

Expand Down
50 changes: 50 additions & 0 deletions pandas/core/indexes/datetimes.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,8 @@ class DatetimeIndex(DatelikeOps, TimelikeOps, DatetimeIndexOpsMixin,
to_pydatetime
to_series
to_frame
month_name
day_name
Notes
-----
Expand Down Expand Up @@ -2013,6 +2015,54 @@ def to_julian_date(self):
self.nanosecond / 3600.0 / 1e+9
) / 24.0)

def month_name(self, time_locale=None):
"""
Return the month names of the DateTimeIndex with specified locale.
Parameters
----------
time_locale : string, default None (English locale)
locale determining the language in which to return the month name
Returns
-------
month_names : Index
Index of month names
"""
values = self.asi8
if self.tz is not None:
if self.tz is not utc:
values = self._local_timestamps()

result = fields.get_date_name_field(values, 'month_name',
time_locale=time_locale)
result = self._maybe_mask_results(result)
return Index(result, name=self.name)

def day_name(self, time_locale=None):
"""
Return the day names of the DateTimeIndex with specified locale.
Parameters
----------
time_locale : string, default None (English locale)
locale determining the language in which to return the day name
Returns
-------
month_names : Index
Index of day names
"""
values = self.asi8
if self.tz is not None:
if self.tz is not utc:
values = self._local_timestamps()

result = fields.get_date_name_field(values, 'day_name',
time_locale=time_locale)
result = self._maybe_mask_results(result)
return Index(result, name=self.name)


DatetimeIndex._add_numeric_methods_disabled()
DatetimeIndex._add_logical_methods_disabled()
Expand Down
52 changes: 34 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,37 @@ def test_datetimeindex_accessors(self):
assert dates.weekofyear.tolist() == expected
assert [d.weekofyear for d in dates] == expected

# GH 12806
@pytest.mark.skipif(not tm.get_locales(), 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)
english_days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday',
'Friday', 'Saturday', 'Sunday']
for day, name, eng_name in zip(range(4, 11),
calendar.day_name,
english_days):
# Test Monday -> Sunday
assert dti.weekday_name[day] == eng_name
assert dti.day_name(time_locale=time_locale) == name
ts = Timestamp(datetime(2016, 4, day))
assert ts.weekday_name == eng_name
assert ts.day_name(time_locale=time_locale) == name

# 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, expected in zip(dti, calendar.month_name[1:]):
result = date.month_name(time_locale=time_locale)
assert result == expected.capitalize()

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

Expand Down
Loading

0 comments on commit 1162f9a

Please sign in to comment.