Skip to content

Commit 13ff4a0

Browse files
committed
Fix parsing not raising an error for week 53 for ordinary years
1 parent 2946bd6 commit 13ff4a0

File tree

7 files changed

+75
-8
lines changed

7 files changed

+75
-8
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
- Fixed the `weeks` property for negative `Period` instances.
88
- Fixed `start_of()` methods not setting microseconds to 0.
99
- Fixed errors on some systems when retrieving timezone from clock files.
10-
- Fix parsing of partial time.
10+
- Fixed parsing of partial time.
11+
- Fixed parsing not raising an error for week 53 for ordinary years.
1112

1213

1314
## [2.0.1] - 2018-05-10

pendulum/_extensions/_helpers.c

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,17 +93,25 @@ const int DAY_OF_WEEK_TABLE[12] = {
9393

9494
/* ------------------------------------------------------------------------- */
9595

96+
int _p(int y) {
97+
return y + y/4 - y/100 + y /400;
98+
}
99+
96100
int _is_leap(int year) {
97101
return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
98102
}
99103

104+
int _is_long_year(int year) {
105+
return (p(year) % 7 == 4) || (p(year - 1) % 7 == 3);
106+
}
107+
100108
int _week_day(int year, int month, int day) {
101109
int y;
102110
int w;
103111

104112
y = year - (month < 3);
105113

106-
w = (y + y/4 - y/100 + y /400 + DAY_OF_WEEK_TABLE[month - 1] + day) % 7;
114+
w = (_p(y) + DAY_OF_WEEK_TABLE[month - 1] + day) % 7;
107115

108116
if (!w) {
109117
w = 7;
@@ -329,6 +337,22 @@ PyObject* is_leap(PyObject *self, PyObject *args) {
329337
return leap;
330338
}
331339

340+
PyObject* is_long_year(PyObject *self, PyObject *args) {
341+
PyObject *is_long;
342+
int year;
343+
344+
if (!PyArg_ParseTuple(args, "i", &year)) {
345+
PyErr_SetString(
346+
PyExc_ValueError, "Invalid parameters"
347+
);
348+
return NULL;
349+
}
350+
351+
is_long = PyBool_FromLong(_is_long_year(year));
352+
353+
return is_long;
354+
}
355+
332356
PyObject* week_day(PyObject *self, PyObject *args) {
333357
PyObject *wd;
334358
int year;
@@ -820,6 +844,12 @@ static PyMethodDef helpers_methods[] = {
820844
METH_VARARGS,
821845
PyDoc_STR("Checks if a year is a leap year.")
822846
},
847+
{
848+
"is_long_year",
849+
(PyCFunction) is_long_year,
850+
METH_VARARGS,
851+
PyDoc_STR("Checks if a year is a long year.")
852+
},
823853
{
824854
"week_day",
825855
(PyCFunction) week_day,

pendulum/_extensions/helpers.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,13 @@ def is_leap(year):
5151
return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
5252

5353

54+
def is_long_year(year):
55+
def p(y):
56+
return y + y//4 - y//100 + y//400
57+
58+
return p(year) % 7 == 4 or p(year - 1) % 7 == 3
59+
60+
5461
def week_day(year, month, day):
5562
if month < 3:
5663
year -= 1

pendulum/helpers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@
1111
try:
1212
from ._extensions._helpers import (
1313
local_time, precise_diff,
14-
is_leap, week_day, days_in_year,
14+
is_leap, is_long_year, week_day, days_in_year,
1515
timestamp
1616
)
1717
except ImportError:
1818
from ._extensions.helpers import (
1919
local_time, precise_diff,
20-
is_leap, week_day, days_in_year,
20+
is_leap, is_long_year, week_day, days_in_year,
2121
timestamp
2222
)
2323

pendulum/parsing/_iso8601.c

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,11 @@ const char PARSER_ERRORS[17][80] = {
132132

133133
/* ------------------------------------------------------------------------- */
134134

135+
136+
int p(int y) {
137+
return y + y/4 - y/100 + y/400;
138+
}
139+
135140
int is_leap(int year) {
136141
return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
137142
}
@@ -142,7 +147,7 @@ int week_day(int year, int month, int day) {
142147

143148
y = year - (month < 3);
144149

145-
w = (y + y/4 - y/100 + y /400 + DAY_OF_WEEK_TABLE[month - 1] + day) % 7;
150+
w = (p(y) + DAY_OF_WEEK_TABLE[month - 1] + day) % 7;
146151

147152
if (!w) {
148153
w = 7;
@@ -159,6 +164,10 @@ int days_in_year(int year) {
159164
return DAYS_PER_N_YEAR;
160165
}
161166

167+
int is_long_year(int year) {
168+
return (p(year) % 7 == 4) || (p(year - 1) % 7 == 3);
169+
}
170+
162171

163172
/* ------------------------ Custom Types ------------------------------- */
164173

@@ -576,7 +585,7 @@ Parsed* _parse_iso8601_datetime(char *str, Parsed *parsed) {
576585
}
577586

578587
// Checks
579-
if (week > 53) {
588+
if (week > 53 || week > 52 && !is_long_year(parsed->year)) {
580589
parsed->error = PARSER_INVALID_WEEK_NUMBER;
581590

582591
return NULL;

pendulum/parsing/iso8601.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
HOURS_PER_DAY, MINUTES_PER_HOUR, SECONDS_PER_MINUTE,
88
MONTHS_OFFSETS
99
)
10-
from ..helpers import week_day, days_in_year, is_leap
10+
from ..helpers import week_day, days_in_year, is_leap, is_long_year
1111
from ..tz.timezone import FixedTimezone
1212
from ..duration import Duration
1313
from .exceptions import ParserError
@@ -409,7 +409,7 @@ def _get_iso_8601_week(year, week, weekday):
409409
year = int(year)
410410
week = int(week)
411411

412-
if week > 53:
412+
if week > 53 or week > 52 and not is_long_year(year):
413413
raise ParserError('Invalid week for week date')
414414

415415
if weekday > 7:

tests/parsing/test_parsing.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,20 @@ def test_iso8601_week_number():
315315
assert 0 == parsed.microsecond
316316
assert parsed.tzinfo is None
317317

318+
# Long Year
319+
text = '2015W53'
320+
321+
parsed = parse(text)
322+
323+
assert 2015 == parsed.year
324+
assert 12 == parsed.month
325+
assert 28 == parsed.day
326+
assert 0 == parsed.hour
327+
assert 0 == parsed.minute
328+
assert 0 == parsed.second
329+
assert 0 == parsed.microsecond
330+
assert parsed.tzinfo is None
331+
318332
text = '2012-W05-5'
319333

320334
parsed = parse(text)
@@ -632,6 +646,12 @@ def test_invalid():
632646
with pytest.raises(ParserError):
633647
parse(text)
634648

649+
# W53 in normal year (not long)
650+
text = '2017W53'
651+
652+
with pytest.raises(ParserError):
653+
parse(text)
654+
635655

636656
def test_exif_edge_case():
637657
text = '2016:12:26 15:45:28'

0 commit comments

Comments
 (0)