Skip to content

Commit e162033

Browse files
committed
Add function to get_iso_calendar
- This function reproduces what `datetime.date.isocalendar` returns - Refactor get_week_of_year to use get_iso_calendar internally
1 parent a44ac34 commit e162033

File tree

3 files changed

+67
-12
lines changed

3 files changed

+67
-12
lines changed

pandas/_libs/tslibs/ccalendar.pxd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ cdef int dayofweek(int y, int m, int d) nogil
77
cdef bint is_leapyear(int64_t year) nogil
88
cpdef int32_t get_days_in_month(int year, Py_ssize_t month) nogil
99
cpdef int32_t get_week_of_year(int year, int month, int day) nogil
10+
cpdef (int32_t, int32_t, int32_t) get_iso_calendar(int year, int month, int day) nogil
1011
cpdef int32_t get_day_of_year(int year, int month, int day) nogil

pandas/_libs/tslibs/ccalendar.pyx

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -150,33 +150,63 @@ cpdef int32_t get_week_of_year(int year, int month, int day) nogil:
150150
-------
151151
week_of_year : int32_t
152152
153+
Notes
154+
-----
155+
Assumes the inputs describe a valid date.
156+
"""
157+
return get_iso_calendar(year, month, day)[1]
158+
159+
160+
@cython.wraparound(False)
161+
@cython.boundscheck(False)
162+
cpdef (int32_t, int32_t, int32_t) get_iso_calendar(int year, int month, int day) nogil:
163+
"""
164+
Return the year, week, and day of year corresponding to ISO 8601
165+
166+
Parameters
167+
----------
168+
year : int
169+
month : int
170+
day : int
171+
172+
Returns
173+
-------
174+
(year : int32_t, week : int32_t, day : int32_t)
175+
153176
Notes
154177
-----
155178
Assumes the inputs describe a valid date.
156179
"""
157180
cdef:
158181
int32_t doy, dow
159-
int woy
182+
int32_t iso_year, iso_week
160183

161184
doy = get_day_of_year(year, month, day)
162185
dow = dayofweek(year, month, day)
163186

164187
# estimate
165-
woy = (doy - 1) - dow + 3
166-
if woy >= 0:
167-
woy = woy // 7 + 1
188+
iso_week = (doy - 1) - dow + 3
189+
if iso_week >= 0:
190+
iso_week = iso_week // 7 + 1
168191

169192
# verify
170-
if woy < 0:
171-
if (woy > -2) or (woy == -2 and is_leapyear(year - 1)):
172-
woy = 53
193+
if iso_week < 0:
194+
if (iso_week > -2) or (iso_week == -2 and is_leapyear(year - 1)):
195+
iso_week = 53
173196
else:
174-
woy = 52
175-
elif woy == 53:
197+
iso_week = 52
198+
elif iso_week == 53:
176199
if 31 - day + dow < 3:
177-
woy = 1
200+
iso_week = 1
201+
202+
iso_year = year
203+
if iso_week == 1 and doy > 7:
204+
iso_year += 1
205+
206+
elif iso_week >= 52 and doy < 7:
207+
iso_year -= 1
178208

179-
return woy
209+
return iso_year, iso_week, dow + 1
180210

181211

182212
@cython.wraparound(False)

pandas/tests/tslibs/test_ccalendar.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from datetime import datetime
1+
from datetime import date, datetime
22

33
import numpy as np
44
import pytest
@@ -25,3 +25,27 @@ def test_get_day_of_year_dt():
2525

2626
expected = (dt - dt.replace(month=1, day=1)).days + 1
2727
assert result == expected
28+
29+
30+
@pytest.mark.parametrize(
31+
"input_date_tuple, expected_iso_tuple",
32+
[
33+
[(2020, 1, 1), (2020, 1, 3)],
34+
[(2019, 12, 31), (2020, 1, 2)],
35+
[(2019, 12, 30), (2020, 1, 1)],
36+
[(2009, 12, 31), (2009, 53, 4)],
37+
[(2010, 1, 1), (2009, 53, 5)],
38+
[(2010, 1, 3), (2009, 53, 7)],
39+
[(2010, 1, 4), (2010, 1, 1)],
40+
[(2006, 1, 1), (2005, 52, 7)],
41+
[(2005, 12, 31), (2005, 52, 6)],
42+
[(2008, 12, 28), (2008, 52, 7)],
43+
[(2008, 12, 29), (2009, 1, 1)],
44+
],
45+
)
46+
def test_dt_correct_iso_8601_year_week_and_day(input_date_tuple, expected_iso_tuple):
47+
assert (
48+
ccalendar.get_iso_calendar(*input_date_tuple)
49+
== date(*input_date_tuple).isocalendar()
50+
)
51+
assert ccalendar.get_iso_calendar(*input_date_tuple) == expected_iso_tuple

0 commit comments

Comments
 (0)