Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit88c0937

Browse files
pgansslevstinner
authored andcommitted
bpo-36004: Add date.fromisocalendar (GH-11888)
This commit implements the first version of date.fromisocalendar, theinverse function for date.isocalendar.
1 parenta86e064 commit88c0937

File tree

6 files changed

+209
-0
lines changed

6 files changed

+209
-0
lines changed

‎Doc/library/datetime.rst‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,13 @@ Other constructors, all class methods:
458458
..versionadded::3.7
459459

460460

461+
..classmethod::date.fromisocalendar(year, week, day)
462+
463+
Return a:class:`date` corresponding to the ISO calendar date specified by
464+
year, week and day. This is the inverse of the function:meth:`date.isocalendar`.
465+
466+
..versionadded::3.8
467+
461468

462469
Class attributes:
463470

@@ -854,6 +861,16 @@ Other constructors, all class methods:
854861

855862
..versionadded::3.7
856863

864+
865+
..classmethod::datetime.fromisocalendar(year, week, day)
866+
867+
Return a:class:`datetime` corresponding to the ISO calendar date specified
868+
by year, week and day. The non-date components of the datetime are populated
869+
with their normal default values. This is the inverse of the function
870+
:meth:`datetime.isocalendar`.
871+
872+
..versionadded::3.8
873+
857874
..classmethod::datetime.strptime(date_string, format)
858875

859876
Return a:class:`.datetime` corresponding to *date_string*, parsed according to

‎Doc/whatsnew/3.8.rst‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,16 @@ where the DLL is stored (if a full or partial path is used to load the initial
244244
DLL) and paths added by:func:`~os.add_dll_directory`.
245245

246246

247+
datetime
248+
--------
249+
250+
Added new alternate constructors:meth:`datetime.date.fromisocalendar` and
251+
:meth:`datetime.datetime.fromisocalendar`, which construct:class:`date` and
252+
:class:`datetime` objects respectively from ISO year, week number and weekday;
253+
these are the inverse of each class's ``isocalendar`` method.
254+
(Contributed by Paul Ganssle in:issue:`36004`.)
255+
256+
247257
gettext
248258
-------
249259

‎Lib/datetime.py‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -884,6 +884,40 @@ def fromisoformat(cls, date_string):
884884
exceptException:
885885
raiseValueError(f'Invalid isoformat string:{date_string!r}')
886886

887+
@classmethod
888+
deffromisocalendar(cls,year,week,day):
889+
"""Construct a date from the ISO year, week number and weekday.
890+
891+
This is the inverse of the date.isocalendar() function"""
892+
# Year is bounded this way because 9999-12-31 is (9999, 52, 5)
893+
ifnotMINYEAR<=year<=MAXYEAR:
894+
raiseValueError(f"Year is out of range:{year}")
895+
896+
ifnot0<week<53:
897+
out_of_range=True
898+
899+
ifweek==53:
900+
# ISO years have 53 weeks in them on years starting with a
901+
# Thursday and leap years starting on a Wednesday
902+
first_weekday=_ymd2ord(year,1,1)%7
903+
if (first_weekday==4or (first_weekday==3and
904+
_is_leap(year))):
905+
out_of_range=False
906+
907+
ifout_of_range:
908+
raiseValueError(f"Invalid week:{week}")
909+
910+
ifnot0<day<8:
911+
raiseValueError(f"Invalid weekday:{day} (range is [1, 7])")
912+
913+
# Now compute the offset from (Y, 1, 1) in days:
914+
day_offset= (week-1)*7+ (day-1)
915+
916+
# Calculate the ordinal day for monday, week 1
917+
day_1=_isoweek1monday(year)
918+
ord_day=day_1+day_offset
919+
920+
returncls(*_ord2ymd(ord_day))
887921

888922
# Conversions to string
889923

@@ -2141,6 +2175,7 @@ def _isoweek1monday(year):
21412175
week1monday+=7
21422176
returnweek1monday
21432177

2178+
21442179
classtimezone(tzinfo):
21452180
__slots__='_offset','_name'
21462181

‎Lib/test/datetimetester.py‎

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1795,6 +1795,82 @@ def test_fromisoformat_fails_typeerror(self):
17951795
withself.assertRaises(TypeError):
17961796
self.theclass.fromisoformat(bad_type)
17971797

1798+
deftest_fromisocalendar(self):
1799+
# For each test case, assert that fromisocalendar is the
1800+
# inverse of the isocalendar function
1801+
dates= [
1802+
(2016,4,3),
1803+
(2005,1,2),# (2004, 53, 7)
1804+
(2008,12,30),# (2009, 1, 2)
1805+
(2010,1,2),# (2009, 53, 6)
1806+
(2009,12,31),# (2009, 53, 4)
1807+
(1900,1,1),# Unusual non-leap year (year % 100 == 0)
1808+
(1900,12,31),
1809+
(2000,1,1),# Unusual leap year (year % 400 == 0)
1810+
(2000,12,31),
1811+
(2004,1,1),# Leap year
1812+
(2004,12,31),
1813+
(1,1,1),
1814+
(9999,12,31),
1815+
(MINYEAR,1,1),
1816+
(MAXYEAR,12,31),
1817+
]
1818+
1819+
fordatecompsindates:
1820+
withself.subTest(datecomps=datecomps):
1821+
dobj=self.theclass(*datecomps)
1822+
isocal=dobj.isocalendar()
1823+
1824+
d_roundtrip=self.theclass.fromisocalendar(*isocal)
1825+
1826+
self.assertEqual(dobj,d_roundtrip)
1827+
1828+
deftest_fromisocalendar_value_errors(self):
1829+
isocals= [
1830+
(2019,0,1),
1831+
(2019,-1,1),
1832+
(2019,54,1),
1833+
(2019,1,0),
1834+
(2019,1,-1),
1835+
(2019,1,8),
1836+
(2019,53,1),
1837+
(10000,1,1),
1838+
(0,1,1),
1839+
(9999999,1,1),
1840+
(2<<32,1,1),
1841+
(2019,2<<32,1),
1842+
(2019,1,2<<32),
1843+
]
1844+
1845+
forisocalinisocals:
1846+
withself.subTest(isocal=isocal):
1847+
withself.assertRaises(ValueError):
1848+
self.theclass.fromisocalendar(*isocal)
1849+
1850+
deftest_fromisocalendar_type_errors(self):
1851+
err_txformers= [
1852+
str,
1853+
float,
1854+
lambdax:None,
1855+
]
1856+
1857+
# Take a valid base tuple and transform it to contain one argument
1858+
# with the wrong type. Repeat this for each argument, e.g.
1859+
# [("2019", 1, 1), (2019, "1", 1), (2019, 1, "1"), ...]
1860+
isocals= []
1861+
base= (2019,1,1)
1862+
foriinrange(3):
1863+
fortxformerinerr_txformers:
1864+
err_val=list(base)
1865+
err_val[i]=txformer(err_val[i])
1866+
isocals.append(tuple(err_val))
1867+
1868+
forisocalinisocals:
1869+
withself.subTest(isocal=isocal):
1870+
withself.assertRaises(TypeError):
1871+
self.theclass.fromisocalendar(*isocal)
1872+
1873+
17981874
#############################################################################
17991875
# datetime tests
18001876

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Added new alternate constructors:meth:`datetime.date.fromisocalendar` and
2+
:meth:`datetime.datetime.fromisocalendar`, which construct date objects from
3+
ISO year, week number and weekday; these are the inverse of each class's
4+
``isocalendar`` method. Patch by Paul Ganssle.

‎Modules/_datetimemodule.c‎

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3003,6 +3003,67 @@ date_fromisoformat(PyObject *cls, PyObject *dtstr)
30033003
returnNULL;
30043004
}
30053005

3006+
3007+
staticPyObject*
3008+
date_fromisocalendar(PyObject*cls,PyObject*args,PyObject*kw)
3009+
{
3010+
staticchar*keywords[]= {
3011+
"year","week","day",NULL
3012+
};
3013+
3014+
intyear,week,day;
3015+
if (PyArg_ParseTupleAndKeywords(args,kw,"iii:fromisocalendar",
3016+
keywords,
3017+
&year,&week,&day)==0) {
3018+
if (PyErr_ExceptionMatches(PyExc_OverflowError)) {
3019+
PyErr_Format(PyExc_ValueError,
3020+
"ISO calendar component out of range");
3021+
3022+
}
3023+
returnNULL;
3024+
}
3025+
3026+
// Year is bounded to 0 < year < 10000 because 9999-12-31 is (9999, 52, 5)
3027+
if (year<MINYEAR||year>MAXYEAR) {
3028+
PyErr_Format(PyExc_ValueError,"Year is out of range: %d",year);
3029+
returnNULL;
3030+
}
3031+
3032+
if (week <=0||week >=53) {
3033+
intout_of_range=1;
3034+
if (week==53) {
3035+
// ISO years have 53 weeks in it on years starting with a Thursday
3036+
// and on leap years starting on Wednesday
3037+
intfirst_weekday=weekday(year,1,1);
3038+
if (first_weekday==3|| (first_weekday==2&&is_leap(year))) {
3039+
out_of_range=0;
3040+
}
3041+
}
3042+
3043+
if (out_of_range) {
3044+
PyErr_Format(PyExc_ValueError,"Invalid week: %d",week);
3045+
returnNULL;
3046+
}
3047+
}
3048+
3049+
if (day <=0||day >=8) {
3050+
PyErr_Format(PyExc_ValueError,"Invalid day: %d (range is [1, 7])",
3051+
day);
3052+
returnNULL;
3053+
}
3054+
3055+
// Convert (Y, W, D) to (Y, M, D) in-place
3056+
intday_1=iso_week1_monday(year);
3057+
3058+
intmonth=week;
3059+
intday_offset= (month-1)*7+day-1;
3060+
3061+
ord_to_ymd(day_1+day_offset,&year,&month,&day);
3062+
3063+
returnnew_date_subclass_ex(year,month,day,cls);
3064+
}
3065+
3066+
30063067
/*
30073068
* Date arithmetic.
30083069
*/
@@ -3296,6 +3357,12 @@ static PyMethodDef date_methods[] = {
32963357
METH_CLASS,
32973358
PyDoc_STR("str -> Construct a date from the output of date.isoformat()")},
32983359

3360+
{"fromisocalendar", (PyCFunction)(void(*)(void))date_fromisocalendar,
3361+
METH_VARARGS |METH_KEYWORDS |METH_CLASS,
3362+
PyDoc_STR("int, int, int -> Construct a date from the ISO year, week "
3363+
"number and weekday.\n\n"
3364+
"This is the inverse of the date.isocalendar() function")},
3365+
32993366
{"today", (PyCFunction)date_today,METH_NOARGS |METH_CLASS,
33003367
PyDoc_STR("Current date or datetime: same as "
33013368
"self.__class__.fromtimestamp(time.time()).")},

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2026 Movatter.jp