Skip to content

Commit 0e58964

Browse files
jbrockmendeljreback
authored andcommitted
Implement get_day_of_year, tests (#19555)
1 parent b210bd3 commit 0e58964

File tree

9 files changed

+67
-38
lines changed

9 files changed

+67
-38
lines changed

pandas/_libs/tslibs/ccalendar.pxd

+1
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ cdef int dayofweek(int y, int m, int m) nogil
1010
cdef bint is_leapyear(int64_t year) nogil
1111
cpdef int32_t get_days_in_month(int year, Py_ssize_t month) nogil
1212
cpdef int32_t get_week_of_year(int year, int month, int day) nogil
13+
cpdef int32_t get_day_of_year(int year, int month, int day) nogil

pandas/_libs/tslibs/ccalendar.pyx

+36-7
Original file line numberDiff line numberDiff line change
@@ -142,17 +142,13 @@ cpdef int32_t get_week_of_year(int year, int month, int day) nogil:
142142
Assumes the inputs describe a valid date.
143143
"""
144144
cdef:
145-
bint isleap, isleap_prev
146-
int32_t mo_off
145+
bint isleap
147146
int32_t doy, dow
148147
int woy
149148

150149
isleap = is_leapyear(year)
151-
isleap_prev = is_leapyear(year - 1)
152-
153-
mo_off = _month_offset[isleap * 13 + month - 1]
154150

155-
doy = mo_off + day
151+
doy = get_day_of_year(year, month, day)
156152
dow = dayofweek(year, month, day)
157153

158154
# estimate
@@ -162,7 +158,7 @@ cpdef int32_t get_week_of_year(int year, int month, int day) nogil:
162158

163159
# verify
164160
if woy < 0:
165-
if (woy > -2) or (woy == -2 and isleap_prev):
161+
if (woy > -2) or (woy == -2 and is_leapyear(year - 1)):
166162
woy = 53
167163
else:
168164
woy = 52
@@ -171,3 +167,36 @@ cpdef int32_t get_week_of_year(int year, int month, int day) nogil:
171167
woy = 1
172168

173169
return woy
170+
171+
172+
@cython.wraparound(False)
173+
@cython.boundscheck(False)
174+
cpdef int32_t get_day_of_year(int year, int month, int day) nogil:
175+
"""Return the ordinal day-of-year for the given day.
176+
177+
Parameters
178+
----------
179+
year : int
180+
month : int
181+
day : int
182+
183+
Returns
184+
-------
185+
day_of_year : int32_t
186+
187+
Notes
188+
-----
189+
Assumes the inputs describe a valid date.
190+
"""
191+
cdef:
192+
bint isleap
193+
int32_t mo_off
194+
int32_t doy, dow
195+
int woy
196+
197+
isleap = is_leapyear(year)
198+
199+
mo_off = _month_offset[isleap * 13 + month - 1]
200+
201+
day_of_year = mo_off + day
202+
return day_of_year

pandas/_libs/tslibs/fields.pyx

+2-11
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ cnp.import_array()
1515

1616

1717
from ccalendar cimport (get_days_in_month, is_leapyear, dayofweek,
18-
get_week_of_year)
18+
get_week_of_year, get_day_of_year)
1919
from np_datetime cimport (pandas_datetimestruct, pandas_timedeltastruct,
2020
dt64_to_dtstruct, td64_to_tdstruct)
2121
from nattype cimport NPY_NAT
@@ -374,15 +374,7 @@ def get_date_field(ndarray[int64_t] dtindex, object field):
374374
cdef:
375375
Py_ssize_t i, count = 0
376376
ndarray[int32_t] out
377-
ndarray[int32_t, ndim=2] _month_offset
378-
int isleap, isleap_prev
379377
pandas_datetimestruct dts
380-
int mo_off, doy, dow
381-
382-
_month_offset = np.array(
383-
[[0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365],
384-
[0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366]],
385-
dtype=np.int32 )
386378

387379
count = len(dtindex)
388380
out = np.empty(count, dtype='i4')
@@ -482,8 +474,7 @@ def get_date_field(ndarray[int64_t] dtindex, object field):
482474
continue
483475

484476
dt64_to_dtstruct(dtindex[i], &dts)
485-
isleap = is_leapyear(dts.year)
486-
out[i] = _month_offset[isleap, dts.month -1] + dts.day
477+
out[i] = get_day_of_year(dts.year, dts.month, dts.day)
487478
return out
488479

489480
elif field == 'dow':

pandas/_libs/tslibs/period.pyx

+3-2
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ from cpython.datetime cimport PyDateTime_Check, PyDateTime_IMPORT
2222
PyDateTime_IMPORT
2323

2424
from np_datetime cimport (pandas_datetimestruct, dtstruct_to_dt64,
25-
dt64_to_dtstruct, is_leapyear)
25+
dt64_to_dtstruct)
2626

2727
cimport util
2828
from util cimport is_period_object, is_string_object, INT32_MIN
@@ -34,11 +34,12 @@ from timezones cimport is_utc, is_tzlocal, get_utcoffset, get_dst_info
3434
from timedeltas cimport delta_to_nanoseconds
3535

3636
from ccalendar import MONTH_NUMBERS
37+
from ccalendar cimport is_leapyear
3738
from frequencies cimport (get_freq_code, get_base_alias,
3839
get_to_timestamp_base, get_freq_str,
3940
get_rule_month)
4041
from parsing import parse_time_string, NAT_SENTINEL
41-
from resolution import resolution, Resolution
42+
from resolution import Resolution
4243
from nattype import nat_strings, NaT, iNaT
4344
from nattype cimport _nat_scalar_rules, NPY_NAT
4445

pandas/_libs/tslibs/timestamps.pyx

+4-16
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,7 @@ from nattype import NaT
2929
from nattype cimport NPY_NAT
3030
from np_datetime import OutOfBoundsDatetime
3131
from np_datetime cimport (reverse_ops, cmp_scalar, check_dts_bounds,
32-
pandas_datetimestruct, dt64_to_dtstruct,
33-
is_leapyear)
32+
pandas_datetimestruct, dt64_to_dtstruct)
3433
from timedeltas import Timedelta
3534
from timedeltas cimport delta_to_nanoseconds
3635
from timezones cimport (
@@ -291,14 +290,6 @@ cdef class _Timestamp(datetime):
291290
val = tz_convert_single(self.value, 'UTC', self.tz)
292291
return val
293292

294-
cpdef int _get_field(self, field):
295-
cdef:
296-
int64_t val
297-
ndarray[int32_t] out
298-
val = self._maybe_convert_value_to_local()
299-
out = get_date_field(np.array([val], dtype=np.int64), field)
300-
return int(out[0])
301-
302293
cpdef bint _get_start_end_field(self, str field):
303294
cdef:
304295
int64_t val
@@ -695,14 +686,11 @@ class Timestamp(_Timestamp):
695686

696687
@property
697688
def dayofyear(self):
698-
return self._get_field('doy')
689+
return ccalendar.get_day_of_year(self.year, self.month, self.day)
699690

700691
@property
701692
def week(self):
702-
if self.freq is None:
703-
# fastpath for non-business
704-
return ccalendar.get_week_of_year(self.year, self.month, self.day)
705-
return self._get_field('woy')
693+
return ccalendar.get_week_of_year(self.year, self.month, self.day)
706694

707695
weekofyear = week
708696

@@ -764,7 +752,7 @@ class Timestamp(_Timestamp):
764752

765753
@property
766754
def is_leap_year(self):
767-
return bool(is_leapyear(self.year))
755+
return bool(ccalendar.is_leapyear(self.year))
768756

769757
def tz_localize(self, tz, ambiguous='raise', errors='raise'):
770758
"""

pandas/core/indexes/datetimes.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
from pandas._libs import (lib, index as libindex, tslib as libts,
5656
join as libjoin, Timestamp)
5757
from pandas._libs.tslibs import (timezones, conversion, fields, parsing,
58-
period as libperiod)
58+
resolution as libresolution)
5959

6060
# -------- some conversion wrapper functions
6161

@@ -1795,7 +1795,7 @@ def is_normalized(self):
17951795

17961796
@cache_readonly
17971797
def _resolution(self):
1798-
return libperiod.resolution(self.asi8, self.tz)
1798+
return libresolution.resolution(self.asi8, self.tz)
17991799

18001800
def insert(self, loc, item):
18011801
"""

pandas/tests/tslibs/__init__.py

Whitespace-only changes.

pandas/tests/tslibs/test_ccalendar.py

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# -*- coding: utf-8 -*-
2+
from datetime import datetime
3+
4+
import numpy as np
5+
6+
from pandas._libs.tslibs import ccalendar
7+
8+
9+
def test_get_day_of_year():
10+
assert ccalendar.get_day_of_year(2001, 3, 1) == 60
11+
assert ccalendar.get_day_of_year(2004, 3, 1) == 61
12+
assert ccalendar.get_day_of_year(1907, 12, 31) == 365
13+
assert ccalendar.get_day_of_year(2004, 12, 31) == 366
14+
15+
dt = datetime.fromordinal(1 + np.random.randint(365 * 4000))
16+
result = ccalendar.get_day_of_year(dt.year, dt.month, dt.day)
17+
expected = (dt - dt.replace(month=1, day=1)).days + 1
18+
assert result == expected

setup.py

+1
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,7 @@ def pxd(name):
515515
'pyxfile': '_libs/tslibs/period',
516516
'pxdfiles': ['_libs/src/util',
517517
'_libs/missing',
518+
'_libs/tslibs/ccalendar',
518519
'_libs/tslibs/timedeltas',
519520
'_libs/tslibs/timezones',
520521
'_libs/tslibs/nattype'],

0 commit comments

Comments
 (0)