From d97f43b2a16351aeeaf4e993f7014b5e803e2b4d Mon Sep 17 00:00:00 2001 From: Matt Roeschke Date: Mon, 6 Nov 2017 22:05:24 -0800 Subject: [PATCH] Add month names 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 Rebase and add depreciation message Deprecate weekday_name, create localizing function in ccalendar.pyx clean up imports Address user comments Try to address ci issues Rebase and adjust tests Move some imports to top of file, remove dup implimention of day_name in fields Fix failing test allow path to accept weekday_name as well address review address review missed files fix typo and call internal method Fix lint modify pytest param for appvoyer fix more appvoyer tests Remove weekday_name availability --- doc/source/api.rst | 9 ++- doc/source/whatsnew/v0.23.0.txt | 4 +- pandas/_libs/tslibs/ccalendar.pyx | 29 ++++++++ pandas/_libs/tslibs/fields.pyx | 35 +++++++--- pandas/_libs/tslibs/nattype.pyx | 49 +++++++++---- pandas/_libs/tslibs/timestamps.pyx | 58 ++++++++++++++-- pandas/core/indexes/datetimes.py | 59 +++++++++++++++- pandas/tests/indexes/datetimes/test_misc.py | 69 ++++++++++++++----- .../tests/scalar/timestamp/test_timestamp.py | 35 ++++++++-- pandas/tests/series/test_datetime_values.py | 44 +++++++++++- 10 files changed, 330 insertions(+), 61 deletions(-) diff --git a/doc/source/api.rst b/doc/source/api.rst index a5e26bc948a70..dba7f6526f22a 100644 --- a/doc/source/api.rst +++ b/doc/source/api.rst @@ -551,7 +551,6 @@ These can be accessed like ``Series.dt.``. Series.dt.weekofyear Series.dt.dayofweek Series.dt.weekday - Series.dt.weekday_name Series.dt.dayofyear Series.dt.quarter Series.dt.is_month_start @@ -581,6 +580,8 @@ These can be accessed like ``Series.dt.``. Series.dt.round Series.dt.floor Series.dt.ceil + Series.dt.month_name + Series.dt.day_name **Timedelta Properties** @@ -1723,7 +1724,6 @@ Time/Date Components DatetimeIndex.week DatetimeIndex.dayofweek DatetimeIndex.weekday - DatetimeIndex.weekday_name DatetimeIndex.quarter DatetimeIndex.tz DatetimeIndex.freq @@ -1759,6 +1759,8 @@ Time-specific operations DatetimeIndex.round DatetimeIndex.floor DatetimeIndex.ceil + DatetimeIndex.month_name + DatetimeIndex.day_name Conversion ~~~~~~~~~~ @@ -1940,7 +1942,6 @@ Properties Timestamp.tzinfo Timestamp.value Timestamp.week - Timestamp.weekday_name Timestamp.weekofyear Timestamp.year @@ -1954,6 +1955,7 @@ Methods Timestamp.combine Timestamp.ctime Timestamp.date + Timestamp.day_name Timestamp.dst Timestamp.floor Timestamp.freq @@ -1963,6 +1965,7 @@ Methods Timestamp.isocalendar Timestamp.isoformat Timestamp.isoweekday + Timestamp.month_name Timestamp.normalize Timestamp.now Timestamp.replace diff --git a/doc/source/whatsnew/v0.23.0.txt b/doc/source/whatsnew/v0.23.0.txt index 7a19f87051746..9fbec736052ab 100644 --- a/doc/source/whatsnew/v0.23.0.txt +++ b/doc/source/whatsnew/v0.23.0.txt @@ -338,7 +338,8 @@ Other Enhancements - For subclassed ``DataFrames``, :func:`DataFrame.apply` will now preserve the ``Series`` subclass (if defined) when passing the data to the applied function (:issue:`19822`) - :func:`DataFrame.from_dict` now accepts a ``columns`` argument that can be used to specify the column names when ``orient='index'`` is used (:issue:`18529`) - Added option ``display.html.use_mathjax`` so `MathJax `_ can be disabled when rendering tables in ``Jupyter`` notebooks (:issue:`19856`, :issue:`19824`) - +- :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_0230.api_breaking: @@ -677,6 +678,7 @@ Deprecations - The ``broadcast`` parameter of ``.apply()`` is deprecated in favor of ``result_type='broadcast'`` (:issue:`18577`) - The ``reduce`` parameter of ``.apply()`` is deprecated in favor of ``result_type='reduce'`` (:issue:`18577`) - The ``order`` parameter of :func:`factorize` is deprecated and will be removed in a future release (:issue:`19727`) +- :attr:`Timestamp.weekday_name`, :attr:`DatetimeIndex.weekday_name`, and :attr:`Series.dt.weekday_name` are deprecated in favor of :meth:`Timestamp.day_name`, :meth:`DatetimeIndex.day_name`, and :meth:`Series.dt.day_name` (:issue:`12806`) .. _whatsnew_0230.prior_deprecations: diff --git a/pandas/_libs/tslibs/ccalendar.pyx b/pandas/_libs/tslibs/ccalendar.pyx index 9bd315b43ea9e..0901d474d044c 100644 --- a/pandas/_libs/tslibs/ccalendar.pyx +++ b/pandas/_libs/tslibs/ccalendar.pyx @@ -12,6 +12,8 @@ cimport numpy as cnp from numpy cimport int64_t, int32_t cnp.import_array() +from locale import LC_TIME +from strptime import LocaleTime # ---------------------------------------------------------------------- # Constants @@ -35,11 +37,18 @@ cdef int32_t* _month_offset = [ # Canonical location for other modules to find name constants MONTHS = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'] +# The first blank line is consistent with calendar.month_name in the calendar +# standard library +MONTHS_FULL = ['', 'January', 'February', 'March', 'April', 'May', 'June', + 'July', 'August', 'September', 'October', 'November', + 'December'] MONTH_NUMBERS = {name: num for num, name in enumerate(MONTHS)} MONTH_ALIASES = {(num + 1): name for num, name in enumerate(MONTHS)} MONTH_TO_CAL_NUM = {name: num + 1 for num, name in enumerate(MONTHS)} DAYS = ['MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT', 'SUN'] +DAYS_FULL = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', + 'Saturday', 'Sunday'] int_to_weekday = {num: name for num, name in enumerate(DAYS)} weekday_to_int = {int_to_weekday[key]: key for key in int_to_weekday} @@ -199,3 +208,23 @@ cpdef int32_t get_day_of_year(int year, int month, int day) nogil: day_of_year = mo_off + day return day_of_year + + +cpdef get_locale_names(object name_type, object locale=None): + """Returns an array of localized day or month names + + Parameters + ---------- + name_type : string, attribute of LocaleTime() in which to return localized + names + locale : string + + Returns + ------- + list of locale names + + """ + from pandas.util.testing import set_locale + + with set_locale(locale, LC_TIME): + return getattr(LocaleTime(), name_type) diff --git a/pandas/_libs/tslibs/fields.pyx b/pandas/_libs/tslibs/fields.pyx index 7a4b9775bd56e..ccf67e765e079 100644 --- a/pandas/_libs/tslibs/fields.pyx +++ b/pandas/_libs/tslibs/fields.pyx @@ -13,7 +13,7 @@ cimport numpy as cnp from numpy cimport ndarray, int64_t, int32_t, int8_t cnp.import_array() - +from ccalendar import get_locale_names, MONTHS_FULL, DAYS_FULL from ccalendar cimport (get_days_in_month, is_leapyear, dayofweek, get_week_of_year, get_day_of_year) from np_datetime cimport (pandas_datetimestruct, pandas_timedeltastruct, @@ -85,26 +85,27 @@ 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 locale=None): """ Given a int64-based datetime index, return array of strings of date name based on requested field (e.g. weekday_name) """ cdef: Py_ssize_t i, count = 0 - ndarray[object] out + ndarray[object] out, names pandas_datetimestruct dts int dow - _dayname = np.array( - ['Monday', 'Tuesday', 'Wednesday', 'Thursday', - 'Friday', 'Saturday', 'Sunday'], - dtype=np.object_) - count = len(dtindex) out = np.empty(count, dtype=object) - if field == 'weekday_name': + if field == 'day_name' or field == 'weekday_name': + if locale is None: + names = np.array(DAYS_FULL, dtype=np.object_) + else: + names = np.array(get_locale_names('f_weekday', locale), + dtype=np.object_) for i in range(count): if dtindex[i] == NPY_NAT: out[i] = np.nan @@ -112,7 +113,21 @@ 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] + out[i] = names[dow].capitalize() + return out + elif field == 'month_name': + if locale is None: + names = np.array(MONTHS_FULL, dtype=np.object_) + else: + names = np.array(get_locale_names('f_month', locale), + 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] = names[dts.month].capitalize() return out raise ValueError("Field %s not supported" % field) diff --git a/pandas/_libs/tslibs/nattype.pyx b/pandas/_libs/tslibs/nattype.pyx index 9f4ef4e515058..be76b55fa169b 100644 --- a/pandas/_libs/tslibs/nattype.pyx +++ b/pandas/_libs/tslibs/nattype.pyx @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- # cython: profile=False -import warnings from cpython cimport ( PyFloat_Check, PyComplex_Check, @@ -39,24 +38,19 @@ _nat_scalar_rules[Py_GE] = False # ---------------------------------------------------------------------- -def _make_nan_func(func_name, cls): +def _make_nan_func(func_name, doc): def f(*args, **kwargs): return np.nan f.__name__ = func_name - f.__doc__ = getattr(cls, func_name).__doc__ + f.__doc__ = doc return f -def _make_nat_func(func_name, cls): +def _make_nat_func(func_name, doc): def f(*args, **kwargs): return NaT - f.__name__ = func_name - if isinstance(cls, str): - # passed the literal docstring directly - f.__doc__ = cls - else: - f.__doc__ = getattr(cls, func_name).__doc__ + f.__doc__ = doc return f @@ -318,11 +312,40 @@ class NaTType(_NaT): # These are the ones that can get their docstrings from datetime. # nan methods - weekday = _make_nan_func('weekday', datetime) - isoweekday = _make_nan_func('isoweekday', datetime) + weekday = _make_nan_func('weekday', datetime.weekday.__doc__) + isoweekday = _make_nan_func('isoweekday', datetime.isoweekday.__doc__) + month_name = _make_nan_func('month_name', # noqa:E128 + """ + Return the month name of the Timestamp with specified locale. + + Parameters + ---------- + locale : string, default None (English locale) + locale determining the language in which to return the month name + + Returns + ------- + month_name : string + + .. versionadded:: 0.23.0 + """) + day_name = _make_nan_func('day_name', # noqa:E128 + """ + Return the day name of the Timestamp with specified locale. + Parameters + ---------- + locale : string, default None (English locale) + locale determining the language in which to return the day name + + Returns + ------- + day_name : string + + .. versionadded:: 0.23.0 + """) # _nat_methods - date = _make_nat_func('date', datetime) + date = _make_nat_func('date', datetime.date.__doc__) utctimetuple = _make_error_func('utctimetuple', datetime) timetz = _make_error_func('timetz', datetime) diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx index 421f781483290..5bb53cf20b478 100644 --- a/pandas/_libs/tslibs/timestamps.pyx +++ b/pandas/_libs/tslibs/timestamps.pyx @@ -24,7 +24,7 @@ cimport ccalendar from conversion import tz_localize_to_utc, date_normalize from conversion cimport (tz_convert_single, _TSObject, convert_to_tsobject, convert_datetime_to_tsobject) -from fields import get_date_field, get_start_end_field +from fields import get_start_end_field, get_date_name_field from nattype import NaT from nattype cimport NPY_NAT from np_datetime import OutOfBoundsDatetime @@ -352,6 +352,16 @@ cdef class _Timestamp(datetime): field, freqstr, month_kw) return out[0] + cpdef _get_date_name_field(self, object field, object locale): + cdef: + int64_t val + ndarray out + + val = self._maybe_convert_value_to_local() + out = get_date_name_field(np.array([val], dtype=np.int64), + field, locale=locale) + return out[0] + @property def _repr_base(self): return '{date} {time}'.format(date=self._date_repr, @@ -714,12 +724,50 @@ class Timestamp(_Timestamp): def dayofweek(self): return self.weekday() + def day_name(self, locale=None): + """ + Return the day name of the Timestamp with specified locale. + + Parameters + ---------- + locale : string, default None (English locale) + locale determining the language in which to return the day name + + Returns + ------- + day_name : string + + .. versionadded:: 0.23.0 + """ + return self._get_date_name_field('day_name', locale) + + def month_name(self, locale=None): + """ + Return the month name of the Timestamp with specified locale. + + Parameters + ---------- + locale : string, default None (English locale) + locale determining the language in which to return the month name + + Returns + ------- + month_name : string + + .. versionadded:: 0.23.0 + """ + return self._get_date_name_field('month_name', locale) + @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()] + """ + .. deprecated:: 0.23.0 + Use ``Timestamp.day_name()`` instead + """ + warnings.warn("`weekday_name` is deprecated and will be removed in a " + "future version. Use `day_name` instead", + DeprecationWarning) + return self.day_name() @property def dayofyear(self): diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py index c9b446b97e956..6b97ee90cd93c 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -231,7 +231,6 @@ class DatetimeIndex(DatelikeOps, TimelikeOps, DatetimeIndexOpsMixin, week dayofweek weekday - weekday_name quarter tz freq @@ -260,6 +259,8 @@ class DatetimeIndex(DatelikeOps, TimelikeOps, DatetimeIndexOpsMixin, to_pydatetime to_series to_frame + month_name + day_name Notes ----- @@ -318,7 +319,7 @@ def _add_comparison_methods(cls): _datetimelike_methods = ['to_period', 'tz_localize', 'tz_convert', 'normalize', 'strftime', 'round', 'floor', - 'ceil'] + 'ceil', 'month_name', 'day_name'] _is_numeric_dtype = False _infer_as_myclass = True @@ -1713,7 +1714,7 @@ def freq(self, value): weekday_name = _field_accessor( 'weekday_name', 'weekday_name', - "The name of day in a week (ex: Friday)\n\n.. versionadded:: 0.18.1") + "The name of day in a week (ex: Friday)\n\n.. deprecated:: 0.23.0") dayofyear = _field_accessor('dayofyear', 'doy', "The ordinal day of the year") @@ -2097,6 +2098,58 @@ def to_julian_date(self): self.nanosecond / 3600.0 / 1e+9 ) / 24.0) + def month_name(self, locale=None): + """ + Return the month names of the DateTimeIndex with specified locale. + + Parameters + ---------- + 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 + + .. versionadded:: 0.23.0 + """ + 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', + locale=locale) + result = self._maybe_mask_results(result) + return Index(result, name=self.name) + + def day_name(self, locale=None): + """ + Return the day names of the DateTimeIndex with specified locale. + + Parameters + ---------- + 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 + + .. versionadded:: 0.23.0 + """ + 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', + locale=locale) + result = self._maybe_mask_results(result) + return Index(result, name=self.name) + DatetimeIndex._add_comparison_methods() DatetimeIndex._add_numeric_methods_disabled() diff --git a/pandas/tests/indexes/datetimes/test_misc.py b/pandas/tests/indexes/datetimes/test_misc.py index 2013b5e6cd6dd..a65b80efc7911 100644 --- a/pandas/tests/indexes/datetimes/test_misc.py +++ b/pandas/tests/indexes/datetimes/test_misc.py @@ -1,3 +1,6 @@ +import locale +import calendar + import pytest import numpy as np @@ -87,7 +90,6 @@ def test_range_edges(self): class TestDatetime64(object): def test_datetimeindex_accessors(self): - dti_naive = DatetimeIndex(freq='D', start=datetime(1998, 1, 1), periods=365) # GH 13303 @@ -134,23 +136,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 @@ -256,6 +241,54 @@ def test_datetimeindex_accessors(self): assert dates.weekofyear.tolist() == expected assert [d.weekofyear for d in dates] == expected + # GH 12806 + @pytest.mark.parametrize('time_locale', [ + None] if tm.get_locales() is None else [None] + tm.get_locales()) + def test_datetime_name_accessors(self, time_locale): + # Test Monday -> Sunday and January -> December, in that sequence + if time_locale is None: + # If the time_locale is None, day-name and month_name should + # return the english attributes + expected_days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', + 'Friday', 'Saturday', 'Sunday'] + expected_months = ['January', 'February', 'March', 'April', 'May', + 'June', 'July', 'August', 'September', + 'October', 'November', 'December'] + else: + with tm.set_locale(time_locale, locale.LC_TIME): + expected_days = calendar.day_name[:] + expected_months = calendar.month_name[1:] + + # 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), + expected_days, + english_days): + name = name.capitalize() + assert dti.weekday_name[day] == eng_name + assert dti.day_name(locale=time_locale)[day] == name + ts = Timestamp(datetime(2016, 4, day)) + assert ts.weekday_name == eng_name + assert ts.day_name(locale=time_locale) == name + dti = dti.append(DatetimeIndex([pd.NaT])) + assert np.isnan(dti.day_name(locale=time_locale)[-1]) + ts = Timestamp(pd.NaT) + assert np.isnan(ts.day_name(locale=time_locale)) + + # GH 12805 + dti = DatetimeIndex(freq='M', start='2012', end='2013') + result = dti.month_name(locale=time_locale) + expected = Index([month.capitalize() for month in expected_months]) + tm.assert_index_equal(result, expected) + for date, expected in zip(dti, expected_months): + result = date.month_name(locale=time_locale) + assert result == expected.capitalize() + dti = dti.append(DatetimeIndex([pd.NaT])) + assert np.isnan(dti.month_name(locale=time_locale)[-1]) + def test_nanosecond_field(self): dti = DatetimeIndex(np.arange(10)) diff --git a/pandas/tests/scalar/timestamp/test_timestamp.py b/pandas/tests/scalar/timestamp/test_timestamp.py index 504a76f259e55..0acf7acb19c0d 100644 --- a/pandas/tests/scalar/timestamp/test_timestamp.py +++ b/pandas/tests/scalar/timestamp/test_timestamp.py @@ -4,6 +4,7 @@ import pytest import dateutil import calendar +import locale import numpy as np from dateutil.tz import tzutc @@ -21,7 +22,7 @@ from pandas.errors import OutOfBoundsDatetime from pandas.compat import long, PY3 from pandas.compat.numpy import np_datetime64_compat -from pandas import Timestamp, Period, Timedelta +from pandas import Timestamp, Period, Timedelta, NaT class TestTimestampProperties(object): @@ -95,13 +96,33 @@ 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.parametrize('data', + [Timestamp('2017-08-28 23:00:00'), + Timestamp('2017-08-28 23:00:00', tz='EST')]) + @pytest.mark.parametrize('time_locale', [ + None] if tm.get_locales() is None else [None] + tm.get_locales()) + def test_names(self, data, time_locale): # GH 17354 - assert data.weekday_name == expected + # Test .weekday_name, .day_name(), .month_name + with tm.assert_produces_warning(DeprecationWarning, + check_stacklevel=False): + assert data.weekday_name == 'Monday' + if time_locale is None: + expected_day = 'Monday' + expected_month = 'August' + else: + with tm.set_locale(time_locale, locale.LC_TIME): + expected_day = calendar.day_name[0].capitalize() + expected_month = calendar.month_name[8].capitalize() + + assert data.day_name(time_locale) == expected_day + assert data.month_name(time_locale) == expected_month + + # Test NaT + nan_ts = Timestamp(NaT) + assert np.isnan(nan_ts.day_name(time_locale)) + assert np.isnan(nan_ts.month_name(time_locale)) @pytest.mark.parametrize('tz', [None, 'UTC', 'US/Eastern', 'Asia/Tokyo']) def test_is_leap_year(self, tz): diff --git a/pandas/tests/series/test_datetime_values.py b/pandas/tests/series/test_datetime_values.py index 93c8ebc5f05df..3abc0f724db25 100644 --- a/pandas/tests/series/test_datetime_values.py +++ b/pandas/tests/series/test_datetime_values.py @@ -1,6 +1,8 @@ # coding=utf-8 # pylint: disable-msg=E1101,W0612 +import locale +import calendar import pytest from datetime import datetime, date @@ -32,7 +34,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', 'day_name', 'month_name'] ok_for_td = TimedeltaIndex._datetimelike_ops ok_for_td_methods = ['components', 'to_pytimedelta', 'total_seconds', 'round', 'floor', 'ceil'] @@ -274,6 +276,46 @@ def test_dt_accessor_no_new_attributes(self): "You cannot add any new attribute"): s.dt.xlabel = "a" + @pytest.mark.parametrize('time_locale', [ + None] if tm.get_locales() is None else [None] + tm.get_locales()) + def test_dt_accessor_datetime_name_accessors(self, time_locale): + # Test Monday -> Sunday and January -> December, in that sequence + if time_locale is None: + # If the time_locale is None, day-name and month_name should + # return the english attributes + expected_days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', + 'Friday', 'Saturday', 'Sunday'] + expected_months = ['January', 'February', 'March', 'April', 'May', + 'June', 'July', 'August', 'September', + 'October', 'November', 'December'] + else: + with tm.set_locale(time_locale, locale.LC_TIME): + expected_days = calendar.day_name[:] + expected_months = calendar.month_name[1:] + + s = Series(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), + expected_days, + english_days): + name = name.capitalize() + assert s.dt.weekday_name[day] == eng_name + assert s.dt.day_name(locale=time_locale)[day] == name + s = s.append(Series([pd.NaT])) + assert np.isnan(s.dt.day_name(locale=time_locale).iloc[-1]) + + s = Series(DatetimeIndex(freq='M', start='2012', end='2013')) + result = s.dt.month_name(locale=time_locale) + expected = Series([month.capitalize() for month in expected_months]) + tm.assert_series_equal(result, expected) + for s_date, expected in zip(s, expected_months): + result = s_date.month_name(locale=time_locale) + assert result == expected.capitalize() + s = s.append(Series([pd.NaT])) + assert np.isnan(s.dt.month_name(locale=time_locale).iloc[-1]) + def test_strftime(self): # GH 10086 s = Series(date_range('20130101', periods=5))