Skip to content

Commit 1162f9a

Browse files
committed
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
1 parent 2dbf2a6 commit 1162f9a

File tree

8 files changed

+233
-31
lines changed

8 files changed

+233
-31
lines changed

doc/source/api.rst

+6
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,8 @@ These can be accessed like ``Series.dt.<property>``.
582582
Series.dt.round
583583
Series.dt.floor
584584
Series.dt.ceil
585+
Series.dt.month_name
586+
Series.dt.day_name
585587

586588
**Timedelta Properties**
587589

@@ -1751,6 +1753,8 @@ Time-specific operations
17511753
DatetimeIndex.round
17521754
DatetimeIndex.floor
17531755
DatetimeIndex.ceil
1756+
DatetimeIndex.month_name
1757+
DatetimeIndex.day_name
17541758

17551759
Conversion
17561760
~~~~~~~~~~
@@ -1947,6 +1951,7 @@ Methods
19471951
Timestamp.combine
19481952
Timestamp.ctime
19491953
Timestamp.date
1954+
Timestamp.day_name
19501955
Timestamp.dst
19511956
Timestamp.floor
19521957
Timestamp.freq
@@ -1956,6 +1961,7 @@ Methods
19561961
Timestamp.isocalendar
19571962
Timestamp.isoformat
19581963
Timestamp.isoweekday
1964+
Timestamp.month_name
19591965
Timestamp.normalize
19601966
Timestamp.now
19611967
Timestamp.replace

doc/source/whatsnew/v0.22.0.txt

+3
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ Other Enhancements
2828
- :class:`pandas.io.formats.style.Styler` now has method ``hide_index()`` to determine whether the index will be rendered in ouptut (:issue:`14194`)
2929
- :class:`pandas.io.formats.style.Styler` now has method ``hide_columns()`` to determine whether columns will be hidden in output (:issue:`14194`)
3030
- Improved wording of ``ValueError`` raised in :func:`to_datetime` when ``unit=`` is passed with a non-convertible value (:issue:`14350`)
31+
- :meth:`Timestamp.month_name`, :meth:`DatetimeIndex.month_name`, and :meth:`Series.dt.month_name` are now available (:issue:`12805`)
32+
- :meth:`Timestamp.day_name` and :meth:`DatetimeIndex.day_name` are now available to return day names with a specified locale (:issue:`12806`)
33+
-
3134

3235
.. _whatsnew_0220.api_breaking:
3336

pandas/_libs/tslib.pyx

+51
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,15 @@ from tslibs.np_datetime cimport (check_dts_bounds,
5555
from tslibs.np_datetime import OutOfBoundsDatetime
5656

5757
from .tslibs.parsing import parse_datetime_string
58+
from .tslibs.strptime import LocaleTime
5859

5960
cimport cython
6061

6162
import warnings
6263

64+
import locale
65+
from pandas.util.testing import set_locale
66+
6367
import pytz
6468
UTC = pytz.utc
6569

@@ -534,6 +538,53 @@ class Timestamp(_Timestamp):
534538

535539
return Period(self, freq=freq)
536540

541+
def day_name(self, time_locale=None):
542+
"""
543+
Return the day name of the Timestamp with specified locale.
544+
545+
Parameters
546+
----------
547+
time_locale : string, default None (English locale)
548+
locale determining the language in which to return the day name
549+
550+
Returns
551+
-------
552+
day_name : string
553+
"""
554+
if time_locale is None:
555+
days = {0: 'monday', 1: 'tuesday', 2: 'wednesday',
556+
3: 'thursday', 4: 'friday', 5: 'saturday',
557+
6: 'sunday'}
558+
else:
559+
with set_locale(time_locale, locale.LC_TIME):
560+
locale_time = LocaleTime()
561+
days = dict(enumerate(locale_time.f_weekday))
562+
return days[self.weekday()].capitalize()
563+
564+
def month_name(self, time_locale=None):
565+
"""
566+
Return the month name of the Timestamp with specified locale.
567+
568+
Parameters
569+
----------
570+
time_locale : string, default None (English locale)
571+
locale determining the language in which to return the month name
572+
573+
Returns
574+
-------
575+
month_name : string
576+
"""
577+
if time_locale is None:
578+
months = {1: 'january', 2: 'february', 3: 'march',
579+
4: 'april', 5: 'may', 6: 'june', 7: 'july',
580+
8: 'august', 9: 'september', 10: 'october',
581+
11: 'november', 12: 'december'}
582+
else:
583+
with set_locale(time_locale, locale.LC_TIME):
584+
locale_time = LocaleTime()
585+
months = dict(enumerate(locale_time.f_month))
586+
return months[self.month].capitalize()
587+
537588
@property
538589
def dayofweek(self):
539590
return self.weekday()

pandas/_libs/tslibs/fields.pyx

+44-6
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ from np_datetime cimport (pandas_datetimestruct, pandas_timedeltastruct,
2222
days_per_month_table, is_leapyear, dayofweek)
2323
from nattype cimport NPY_NAT
2424

25+
from pandas._libs.tslibs.strptime import LocaleTime
26+
27+
import locale
28+
from pandas.util.testing import set_locale
2529

2630
def build_field_sarray(ndarray[int64_t] dtindex):
2731
"""
@@ -67,7 +71,8 @@ def build_field_sarray(ndarray[int64_t] dtindex):
6771

6872
@cython.wraparound(False)
6973
@cython.boundscheck(False)
70-
def get_date_name_field(ndarray[int64_t] dtindex, object field):
74+
def get_date_name_field(ndarray[int64_t] dtindex, object field,
75+
object time_locale=None):
7176
"""
7277
Given a int64-based datetime index, return array of strings of date
7378
name based on requested field (e.g. weekday_name)
@@ -77,16 +82,15 @@ def get_date_name_field(ndarray[int64_t] dtindex, object field):
7782
ndarray[object] out
7883
pandas_datetimestruct dts
7984
int dow
80-
81-
_dayname = np.array(
82-
['Monday', 'Tuesday', 'Wednesday', 'Thursday',
83-
'Friday', 'Saturday', 'Sunday'],
84-
dtype=np.object_)
85+
object locale_time = LocaleTime()
8586

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

8990
if field == 'weekday_name':
91+
_dayname = np.array(['Monday', 'Tuesday', 'Wednesday', 'Thursday',
92+
'Friday', 'Saturday', 'Sunday'],
93+
dtype=np.object_)
9094
for i in range(count):
9195
if dtindex[i] == NPY_NAT:
9296
out[i] = np.nan
@@ -95,6 +99,40 @@ def get_date_name_field(ndarray[int64_t] dtindex, object field):
9599
dt64_to_dtstruct(dtindex[i], &dts)
96100
dow = dayofweek(dts.year, dts.month, dts.day)
97101
out[i] = _dayname[dow]
102+
if field == 'day_name':
103+
if time_locale is None:
104+
_dayname = np.array(['monday', 'tuesday', 'wednesday', 'thursday',
105+
'friday', 'saturday', 'sunday'],
106+
dtype=np.object_)
107+
else:
108+
with set_locale(time_locale, locale.LC_TIME):
109+
locale_time = LocaleTime()
110+
_dayname = np.array(locale_time.f_weekday, dtype=np.object_)
111+
for i in range(count):
112+
if dtindex[i] == NPY_NAT:
113+
out[i] = np.nan
114+
continue
115+
116+
dt64_to_dtstruct(dtindex[i], &dts)
117+
dow = dayofweek(dts.year, dts.month, dts.day)
118+
out[i] = _dayname[dow].capitalize()
119+
return out
120+
elif field == 'month_name':
121+
if time_locale is None:
122+
_monthname = np.array(['', 'monday', 'tuesday', 'wednesday',
123+
'thursday', 'friday', 'saturday', 'sunday'],
124+
dtype=np.object_)
125+
else:
126+
with set_locale(time_locale, locale.LC_TIME):
127+
locale_time = LocaleTime()
128+
_monthname = np.array(locale_time.f_month, dtype=np.object_)
129+
for i in range(count):
130+
if dtindex[i] == NPY_NAT:
131+
out[i] = np.nan
132+
continue
133+
134+
dt64_to_dtstruct(dtindex[i], &dts)
135+
out[i] = _monthname[dts.month].capitalize()
98136
return out
99137

100138
raise ValueError("Field %s not supported" % field)

pandas/_libs/tslibs/nattype.pyx

+31-1
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,13 @@ _nat_scalar_rules[Py_GE] = False
4242
def _make_nan_func(func_name, cls):
4343
def f(*args, **kwargs):
4444
return np.nan
45+
4546
f.__name__ = func_name
46-
f.__doc__ = getattr(cls, func_name).__doc__
47+
if isinstance(cls, str):
48+
# passed the literal docstring directly
49+
f.__doc__ = cls
50+
else:
51+
f.__doc__ = getattr(cls, func_name).__doc__
4752
return f
4853

4954

@@ -320,7 +325,32 @@ class NaTType(_NaT):
320325
# nan methods
321326
weekday = _make_nan_func('weekday', datetime)
322327
isoweekday = _make_nan_func('isoweekday', datetime)
328+
month_name = _make_nan_func('month_name', # noqa:E128
329+
"""
330+
Return the month name of the Timestamp with specified locale.
323331
332+
Parameters
333+
----------
334+
time_locale : string, default None (English locale)
335+
locale determining the language in which to return the month name
336+
337+
Returns
338+
-------
339+
month_name : string
340+
""")
341+
day_name = _make_nan_func('day_name', # noqa:E128
342+
"""
343+
Return the day name of the Timestamp with specified locale.
344+
345+
Parameters
346+
----------
347+
time_locale : string, default None (English locale)
348+
locale determining the language in which to return the day name
349+
350+
Returns
351+
-------
352+
day_name : string
353+
""")
324354
# _nat_methods
325355
date = _make_nat_func('date', datetime)
326356

pandas/core/indexes/datetimes.py

+50
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,8 @@ class DatetimeIndex(DatelikeOps, TimelikeOps, DatetimeIndexOpsMixin,
250250
to_pydatetime
251251
to_series
252252
to_frame
253+
month_name
254+
day_name
253255
254256
Notes
255257
-----
@@ -2013,6 +2015,54 @@ def to_julian_date(self):
20132015
self.nanosecond / 3600.0 / 1e+9
20142016
) / 24.0)
20152017

2018+
def month_name(self, time_locale=None):
2019+
"""
2020+
Return the month names of the DateTimeIndex with specified locale.
2021+
2022+
Parameters
2023+
----------
2024+
time_locale : string, default None (English locale)
2025+
locale determining the language in which to return the month name
2026+
2027+
Returns
2028+
-------
2029+
month_names : Index
2030+
Index of month names
2031+
"""
2032+
values = self.asi8
2033+
if self.tz is not None:
2034+
if self.tz is not utc:
2035+
values = self._local_timestamps()
2036+
2037+
result = fields.get_date_name_field(values, 'month_name',
2038+
time_locale=time_locale)
2039+
result = self._maybe_mask_results(result)
2040+
return Index(result, name=self.name)
2041+
2042+
def day_name(self, time_locale=None):
2043+
"""
2044+
Return the day names of the DateTimeIndex with specified locale.
2045+
2046+
Parameters
2047+
----------
2048+
time_locale : string, default None (English locale)
2049+
locale determining the language in which to return the day name
2050+
2051+
Returns
2052+
-------
2053+
month_names : Index
2054+
Index of day names
2055+
"""
2056+
values = self.asi8
2057+
if self.tz is not None:
2058+
if self.tz is not utc:
2059+
values = self._local_timestamps()
2060+
2061+
result = fields.get_date_name_field(values, 'day_name',
2062+
time_locale=time_locale)
2063+
result = self._maybe_mask_results(result)
2064+
return Index(result, name=self.name)
2065+
20162066

20172067
DatetimeIndex._add_numeric_methods_disabled()
20182068
DatetimeIndex._add_logical_methods_disabled()

pandas/tests/indexes/datetimes/test_misc.py

+34-18
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import locale
2+
import calendar
3+
14
import pytest
25

36
import numpy as np
@@ -173,7 +176,6 @@ def test_normalize(self):
173176
class TestDatetime64(object):
174177

175178
def test_datetimeindex_accessors(self):
176-
177179
dti_naive = DatetimeIndex(freq='D', start=datetime(1998, 1, 1),
178180
periods=365)
179181
# GH 13303
@@ -220,23 +222,6 @@ def test_datetimeindex_accessors(self):
220222
assert not dti.is_year_end[0]
221223
assert dti.is_year_end[364]
222224

223-
# GH 11128
224-
assert dti.weekday_name[4] == u'Monday'
225-
assert dti.weekday_name[5] == u'Tuesday'
226-
assert dti.weekday_name[6] == u'Wednesday'
227-
assert dti.weekday_name[7] == u'Thursday'
228-
assert dti.weekday_name[8] == u'Friday'
229-
assert dti.weekday_name[9] == u'Saturday'
230-
assert dti.weekday_name[10] == u'Sunday'
231-
232-
assert Timestamp('2016-04-04').weekday_name == u'Monday'
233-
assert Timestamp('2016-04-05').weekday_name == u'Tuesday'
234-
assert Timestamp('2016-04-06').weekday_name == u'Wednesday'
235-
assert Timestamp('2016-04-07').weekday_name == u'Thursday'
236-
assert Timestamp('2016-04-08').weekday_name == u'Friday'
237-
assert Timestamp('2016-04-09').weekday_name == u'Saturday'
238-
assert Timestamp('2016-04-10').weekday_name == u'Sunday'
239-
240225
assert len(dti.year) == 365
241226
assert len(dti.month) == 365
242227
assert len(dti.day) == 365
@@ -342,6 +327,37 @@ def test_datetimeindex_accessors(self):
342327
assert dates.weekofyear.tolist() == expected
343328
assert [d.weekofyear for d in dates] == expected
344329

330+
# GH 12806
331+
@pytest.mark.skipif(not tm.get_locales(), reason='No available locales')
332+
@pytest.mark.parametrize('time_locale', tm.get_locales())
333+
def test_datetime_name_accessors(self, time_locale):
334+
with tm.set_locale(time_locale, locale.LC_TIME):
335+
# GH 11128
336+
dti = DatetimeIndex(freq='D', start=datetime(1998, 1, 1),
337+
periods=365)
338+
english_days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday',
339+
'Friday', 'Saturday', 'Sunday']
340+
for day, name, eng_name in zip(range(4, 11),
341+
calendar.day_name,
342+
english_days):
343+
# Test Monday -> Sunday
344+
assert dti.weekday_name[day] == eng_name
345+
assert dti.day_name(time_locale=time_locale) == name
346+
ts = Timestamp(datetime(2016, 4, day))
347+
assert ts.weekday_name == eng_name
348+
assert ts.day_name(time_locale=time_locale) == name
349+
350+
# GH 12805
351+
dti = DatetimeIndex(freq='M', start='2012', end='2013')
352+
# Test January -> December
353+
result = dti.month_name
354+
expected = Index([month.capitalize()
355+
for month in calendar.month_name[1:]])
356+
tm.assert_index_equal(result, expected)
357+
for date, expected in zip(dti, calendar.month_name[1:]):
358+
result = date.month_name(time_locale=time_locale)
359+
assert result == expected.capitalize()
360+
345361
def test_nanosecond_field(self):
346362
dti = DatetimeIndex(np.arange(10))
347363

0 commit comments

Comments
 (0)