Skip to content

Commit b67f096

Browse files
committed
Fix datetime.fromtimestamp(): check bounds
Issue python#29100: Fix datetime.fromtimestamp() regression introduced in Python 3.6.0: check minimum and maximum years.
1 parent 57fe245 commit b67f096

File tree

3 files changed

+79
-21
lines changed

3 files changed

+79
-21
lines changed

Lib/test/datetimetester.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1989,6 +1989,42 @@ def test_microsecond_rounding(self):
19891989
self.assertEqual(t.second, 0)
19901990
self.assertEqual(t.microsecond, 7812)
19911991

1992+
def test_timestamp_limits(self):
1993+
# minimum timestamp
1994+
min_dt = self.theclass.min.replace(tzinfo=timezone.utc)
1995+
min_ts = min_dt.timestamp()
1996+
# date 0001-01-01 00:00:00+00:00: timestamp=-62135596800
1997+
self.assertEqual(self.theclass.fromtimestamp(min_ts, tz=timezone.utc),
1998+
min_dt)
1999+
2000+
# maximum timestamp: set seconds to zero to avoid rounding issues
2001+
max_dt = self.theclass.max.replace(tzinfo=timezone.utc,
2002+
second=0, microsecond=0)
2003+
max_ts = max_dt.timestamp()
2004+
# date 9999-12-31 23:59:00+00:00: timestamp 253402300740
2005+
self.assertEqual(self.theclass.fromtimestamp(max_ts, tz=timezone.utc),
2006+
max_dt)
2007+
2008+
# number of seconds greater than 1 year: make sure that the new date
2009+
# is not valid in datetime.datetime limits
2010+
delta = 3600 * 24 * 400
2011+
2012+
# too small
2013+
ts = min_ts - delta
2014+
# converting a Python int to C time_t can raise a OverflowError,
2015+
# especially on 32-bit platforms.
2016+
with self.assertRaises((ValueError, OverflowError)):
2017+
self.theclass.fromtimestamp(ts)
2018+
with self.assertRaises((ValueError, OverflowError)):
2019+
self.theclass.utcfromtimestamp(ts)
2020+
2021+
# too big
2022+
ts = max_dt.timestamp() + delta
2023+
with self.assertRaises((ValueError, OverflowError)):
2024+
self.theclass.fromtimestamp(ts)
2025+
with self.assertRaises((ValueError, OverflowError)):
2026+
self.theclass.utcfromtimestamp(ts)
2027+
19922028
def test_insane_fromtimestamp(self):
19932029
# It's possible that some platform maps time_t to double,
19942030
# and that this test will fail there. This test should

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ Extension Modules
5757
Library
5858
-------
5959

60+
- Issue #29100: Fix datetime.fromtimestamp() regression introduced in Python
61+
3.6.0: check minimum and maximum years.
62+
6063
- Issue #29519: Fix weakref spewing exceptions during interpreter shutdown
6164
when used with a rare combination of multiprocessing and custom codecs.
6265

Modules/_datetimemodule.c

Lines changed: 40 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ static PyTypeObject PyDateTime_TimeType;
120120
static PyTypeObject PyDateTime_TZInfoType;
121121
static PyTypeObject PyDateTime_TimeZoneType;
122122

123+
static int check_tzinfo_subclass(PyObject *p);
124+
123125
_Py_IDENTIFIER(as_integer_ratio);
124126
_Py_IDENTIFIER(fromutc);
125127
_Py_IDENTIFIER(isoformat);
@@ -400,8 +402,7 @@ check_date_args(int year, int month, int day)
400402
{
401403

402404
if (year < MINYEAR || year > MAXYEAR) {
403-
PyErr_SetString(PyExc_ValueError,
404-
"year is out of range");
405+
PyErr_Format(PyExc_ValueError, "year %i is out of range", year);
405406
return -1;
406407
}
407408
if (month < 1 || month > 12) {
@@ -672,6 +673,10 @@ new_date_ex(int year, int month, int day, PyTypeObject *type)
672673
{
673674
PyDateTime_Date *self;
674675

676+
if (check_date_args(year, month, day) < 0) {
677+
return NULL;
678+
}
679+
675680
self = (PyDateTime_Date *) (type->tp_alloc(type, 0));
676681
if (self != NULL)
677682
set_date_fields(self, year, month, day);
@@ -689,6 +694,16 @@ new_datetime_ex2(int year, int month, int day, int hour, int minute,
689694
PyDateTime_DateTime *self;
690695
char aware = tzinfo != Py_None;
691696

697+
if (check_date_args(year, month, day) < 0) {
698+
return NULL;
699+
}
700+
if (check_time_args(hour, minute, second, usecond, fold) < 0) {
701+
return NULL;
702+
}
703+
if (check_tzinfo_subclass(tzinfo) < 0) {
704+
return NULL;
705+
}
706+
692707
self = (PyDateTime_DateTime *) (type->tp_alloc(type, aware));
693708
if (self != NULL) {
694709
self->hastzinfo = aware;
@@ -726,6 +741,13 @@ new_time_ex2(int hour, int minute, int second, int usecond,
726741
PyDateTime_Time *self;
727742
char aware = tzinfo != Py_None;
728743

744+
if (check_time_args(hour, minute, second, usecond, fold) < 0) {
745+
return NULL;
746+
}
747+
if (check_tzinfo_subclass(tzinfo) < 0) {
748+
return NULL;
749+
}
750+
729751
self = (PyDateTime_Time *) (type->tp_alloc(type, aware));
730752
if (self != NULL) {
731753
self->hastzinfo = aware;
@@ -2500,8 +2522,6 @@ date_new(PyTypeObject *type, PyObject *args, PyObject *kw)
25002522

25012523
if (PyArg_ParseTupleAndKeywords(args, kw, "iii", date_kws,
25022524
&year, &month, &day)) {
2503-
if (check_date_args(year, month, day) < 0)
2504-
return NULL;
25052525
self = new_date_ex(year, month, day, type);
25062526
}
25072527
return self;
@@ -3586,10 +3606,6 @@ time_new(PyTypeObject *type, PyObject *args, PyObject *kw)
35863606
if (PyArg_ParseTupleAndKeywords(args, kw, "|iiiiO$i", time_kws,
35873607
&hour, &minute, &second, &usecond,
35883608
&tzinfo, &fold)) {
3589-
if (check_time_args(hour, minute, second, usecond, fold) < 0)
3590-
return NULL;
3591-
if (check_tzinfo_subclass(tzinfo) < 0)
3592-
return NULL;
35933609
self = new_time_ex2(hour, minute, second, usecond, tzinfo, fold,
35943610
type);
35953611
}
@@ -4176,12 +4192,6 @@ datetime_new(PyTypeObject *type, PyObject *args, PyObject *kw)
41764192
if (PyArg_ParseTupleAndKeywords(args, kw, "iii|iiiiO$i", datetime_kws,
41774193
&year, &month, &day, &hour, &minute,
41784194
&second, &usecond, &tzinfo, &fold)) {
4179-
if (check_date_args(year, month, day) < 0)
4180-
return NULL;
4181-
if (check_time_args(hour, minute, second, usecond, fold) < 0)
4182-
return NULL;
4183-
if (check_tzinfo_subclass(tzinfo) < 0)
4184-
return NULL;
41854195
self = new_datetime_ex2(year, month, day,
41864196
hour, minute, second, usecond,
41874197
tzinfo, fold, type);
@@ -4203,7 +4213,15 @@ static long long
42034213
utc_to_seconds(int year, int month, int day,
42044214
int hour, int minute, int second)
42054215
{
4206-
long long ordinal = ymd_to_ord(year, month, day);
4216+
long long ordinal;
4217+
4218+
/* ymd_to_ord() doesn't support year <= 0 */
4219+
if (year < MINYEAR || year > MAXYEAR) {
4220+
PyErr_Format(PyExc_ValueError, "year %i is out of range", year);
4221+
return -1;
4222+
}
4223+
4224+
ordinal = ymd_to_ord(year, month, day);
42074225
return ((ordinal * 24 + hour) * 60 + minute) * 60 + second;
42084226
}
42094227

@@ -4219,7 +4237,6 @@ local(long long u)
42194237
"timestamp out of range for platform time_t");
42204238
return -1;
42214239
}
4222-
/* XXX: add bounds checking */
42234240
if (_PyTime_localtime(t, &local_time) != 0)
42244241
return -1;
42254242
return utc_to_seconds(local_time.tm_year + 1900,
@@ -4257,6 +4274,7 @@ datetime_from_timet_and_us(PyObject *cls, TM_FUNC f, time_t timet, int us,
42574274
*/
42584275
second = Py_MIN(59, tm.tm_sec);
42594276

4277+
/* local timezone requires to compute fold */
42604278
if (tzinfo == Py_None && f == _PyTime_localtime) {
42614279
long long probe_seconds, result_seconds, transition;
42624280

@@ -4516,12 +4534,13 @@ add_datetime_timedelta(PyDateTime_DateTime *date, PyDateTime_Delta *delta,
45164534

45174535
assert(factor == 1 || factor == -1);
45184536
if (normalize_datetime(&year, &month, &day,
4519-
&hour, &minute, &second, &microsecond) < 0)
4537+
&hour, &minute, &second, &microsecond) < 0) {
45204538
return NULL;
4521-
else
4522-
return new_datetime(year, month, day,
4523-
hour, minute, second, microsecond,
4524-
HASTZINFO(date) ? date->tzinfo : Py_None, 0);
4539+
}
4540+
4541+
return new_datetime(year, month, day,
4542+
hour, minute, second, microsecond,
4543+
HASTZINFO(date) ? date->tzinfo : Py_None, 0);
45254544
}
45264545

45274546
static PyObject *

0 commit comments

Comments
 (0)