Add Week-Of-Year Implementation to Times Module (#17223)

* initial

* more tests

* Apply suggestions from code review

idiomatize

Co-authored-by: Timothee Cour <timothee.cour2@gmail.com>

* test iron age dates

* add examples

* fix typo

* consistent param mention

* add since pragrams

* add changelog

* Update lib/pure/times.nim

Co-authored-by: Timothee Cour <timothee.cour2@gmail.com>

* fix examples

* fix negative years

* add getWeeksInYear tests

* add back fix dropped by rebase

* week-year tuple api

* add changelog

* fix doc tags

* add docstrings

* fix typos

Co-authored-by: konsumlamm <44230978+konsumlamm@users.noreply.github.com>

Co-authored-by: Timothee Cour <timothee.cour2@gmail.com>
Co-authored-by: konsumlamm <44230978+konsumlamm@users.noreply.github.com>
This commit is contained in:
Carlo Capocasa
2022-01-03 09:11:23 +01:00
committed by GitHub
parent bbd5086bc3
commit e49d52eb61
3 changed files with 183 additions and 0 deletions

View File

@@ -27,6 +27,12 @@
- Sends `ehlo` first. If the mail server does not understand, it sends `helo` as a fallback.
- Added `IsoWeekRange`, a range type to represent the number of weeks in an ISO week-based year.
- Added `IsoYear`, a distinct int type to prevent bugs from confusing the week-based year and the regular year.
- Added `initDateTime` in `times` to create a datetime from a weekday, and ISO 8601 week number and week-based year.
- Added `getIsoWeekAndYear` in `times` to get an ISO week number along with the corresponding ISO week-based year from a datetime.
- Added `getIsoWeeksInYear` in `times` to return the number of weeks in an ISO week-based year.
## Language changes
- Pragma macros on type definitions can now return `nnkTypeSection` nodes as well as `nnkTypeDef`,

View File

@@ -288,6 +288,13 @@ type
YeardayRange* = range[0..365]
NanosecondRange* = range[0..999_999_999]
IsoWeekRange* = range[1 .. 53]
## An ISO 8601 calendar week number.
IsoYear* = distinct int
## An ISO 8601 calendar year number.
##
## .. warning:: The ISO week-based year can correspond to the following or previous year from 29 December to January 3.
Time* = object ## Represents a point in time.
seconds: int64
nanosecond: NanosecondRange
@@ -529,6 +536,54 @@ proc getDaysInYear*(year: int): int =
doAssert getDaysInYear(2001) == 365
result = 365 + (if isLeapYear(year): 1 else: 0)
proc `==`*(a, b: IsoYear): bool {.borrow.}
proc `$`*(p: IsoYear): string {.borrow.}
proc getWeeksInIsoYear*(y: IsoYear): IsoWeekRange {.since: (1, 5).} =
## Returns the number of weeks in the specified ISO 8601 week-based year, which can be
## either 53 or 52.
runnableExamples:
assert getWeeksInIsoYear(IsoYear(2000)) == 52
assert getWeeksInIsoYear(IsoYear(2001)) == 53
var y = int(y)
# support negative years
y = if y < 0: 400 + y mod 400 else: y
# source: https://webspace.science.uu.nl/~gent0113/calendar/isocalendar.htm
let p = (y + (y div 4) - (y div 100) + (y div 400)) mod 7
let y1 = y - 1
let p1 = (y1 + (y1 div 4) - (y1 div 100) + (y1 div 400)) mod 7
if p == 4 or p1 == 3: 53 else: 52
proc getIsoWeekAndYear*(dt: DateTime):
tuple[isoweek: IsoWeekRange, isoyear: IsoYear] {.since: (1, 5).} =
## Returns the ISO 8601 week and year.
##
## .. warning:: The ISO week-based year can correspond to the following or previous year from 29 December to January 3.
runnableExamples:
assert getIsoWeekAndYear(initDateTime(21, mApr, 2018, 00, 00, 00)) == (isoweek: 16.IsoWeekRange, isoyear: 2018.IsoYear)
block:
let (w, y) = getIsoWeekAndYear(initDateTime(30, mDec, 2019, 00, 00, 00))
assert w == 01.IsoWeekRange
assert y == 2020.IsoYear
assert getIsoWeekAndYear(initDateTime(13, mSep, 2020, 00, 00, 00)) == (isoweek: 37.IsoWeekRange, isoyear: 2020.IsoYear)
block:
let (w, y) = getIsoWeekAndYear(initDateTime(2, mJan, 2021, 00, 00, 00))
assert w.int > 52
assert w.int < 54
assert y.int mod 100 == 20
# source: https://webspace.science.uu.nl/~gent0113/calendar/isocalendar.htm
var w = (dt.yearday.int - dt.weekday.int + 10) div 7
if w < 1:
(isoweek: getWeeksInIsoYear(IsoYear(dt.year - 1)), isoyear: IsoYear(dt.year - 1))
elif (w > getWeeksInIsoYear(IsoYear(dt.year))):
(isoweek: IsoWeekRange(1), isoyear: IsoYear(dt.year + 1))
else:
(isoweek: IsoWeekRange(w), isoyear: IsoYear(dt.year))
proc stringifyUnit(value: int | int64, unit: TimeUnit): string =
## Stringify time unit with it's name, lowercased
let strUnit = $unit
@@ -2578,6 +2633,33 @@ proc `+=`*(t: var Time, b: TimeInterval) =
proc `-=`*(t: var Time, b: TimeInterval) =
t = t - b
#
# Day of year
#
proc initDateTime*(weekday: WeekDay, isoweek: IsoWeekRange, isoyear: IsoYear,
hour: HourRange, minute: MinuteRange, second: SecondRange,
nanosecond: NanosecondRange,
zone: Timezone = local()): DateTime {.since: (1, 5).} =
## Create a new `DateTime <#DateTime>`_ from a weekday and an ISO 8601 week number and year
## in the specified timezone.
##
## .. warning:: The ISO week-based year can correspond to the following or previous year from 29 December to January 3.
runnableExamples:
assert initDateTime(21, mApr, 2018, 00, 00, 00) == initDateTime(dSat, 16, 2018.IsoYear, 00, 00, 00)
assert initDateTime(30, mDec, 2019, 00, 00, 00) == initDateTime(dMon, 01, 2020.IsoYear, 00, 00, 00)
assert initDateTime(13, mSep, 2020, 00, 00, 00) == initDateTime(dSun, 37, 2020.IsoYear, 00, 00, 00)
assert initDateTime(2, mJan, 2021, 00, 00, 00) == initDateTime(dSat, 53, 2020.IsoYear, 00, 00, 00)
# source https://webspace.science.uu.nl/~gent0113/calendar/isocalendar.htm
let d = isoweek * 7 + weekday.int - initDateTime(4, mJan, isoyear.int, 00, 00, 00).weekday.int - 4
initDateTime(1, mJan, isoyear.int, hour, minute, second, nanosecond, zone) + initTimeInterval(days=d)
proc initDateTime*(weekday: WeekDay, isoweek: IsoWeekRange, isoyear: IsoYear,
hour: HourRange, minute: MinuteRange, second: SecondRange,
zone: Timezone = local()): DateTime {.since: (1, 5).} =
initDateTime(weekday, isoweek, isoyear, hour, minute, second, 0, zone)
#
# Other
#

View File

@@ -646,3 +646,98 @@ block: # ttimes
doAssert initDuration(milliseconds = 500).inMilliseconds == 500
doAssert initDuration(milliseconds = -500).inMilliseconds == -500
doAssert initDuration(nanoseconds = -999999999).inMilliseconds == -999
block: # getIsoWeekAndYear
doAssert getIsoWeekAndYear(initDateTime(04, mNov, 2019, 00, 00, 00)) == (isoweek: 45.IsoWeekRange, isoyear: 2019.IsoYear)
doAssert initDateTime(dMon, 45, 2019.IsoYear, 00, 00, 00) == initDateTime(04, mNov, 2019, 00, 00, 00)
doAssert getIsoWeekAndYear(initDateTime(28, mDec, 2019, 00, 00, 00)) == (isoweek: 52.IsoWeekRange, isoyear: 2019.IsoYear)
doAssert initDateTime(dSat, 52, 2019.IsoYear, 00, 00, 00) == initDateTime(28, mDec, 2019, 00, 00, 00)
doAssert getIsoWeekAndYear(initDateTime(29, mDec, 2019, 00, 00, 00)) == (isoweek: 52.IsoWeekRange, isoyear: 2019.IsoYear)
doAssert initDateTime(dSun, 52, 2019.IsoYear, 00, 00, 00) == initDateTime(29, mDec, 2019, 00, 00, 00)
doAssert getIsoWeekAndYear(initDateTime(30, mDec, 2019, 00, 00, 00)) == (isoweek: 01.IsoWeekRange, isoyear: 2020.IsoYear)
doAssert initDateTime(dMon, 01, 2020.IsoYear, 00, 00, 00) == initDateTime(30, mDec, 2019, 00, 00, 00)
doAssert getIsoWeekAndYear(initDateTime(31, mDec, 2019, 00, 00, 00)) == (isoweek: 01.IsoWeekRange, isoyear: 2020.IsoYear)
doAssert initDateTime(dTue, 01, 2020.IsoYear, 00, 00, 00) == initDateTime(31, mDec, 2019, 00, 00, 00)
doAssert getIsoWeekAndYear(initDateTime(01, mJan, 2020, 00, 00, 00)) == (isoweek: 01.IsoWeekRange, isoyear: 2020.IsoYear)
doAssert initDateTime(dWed, 01, 2020.IsoYear, 00, 00, 00) == initDateTime(01, mJan, 2020, 00, 00, 00)
doAssert getIsoWeekAndYear(initDateTime(02, mJan, 2020, 00, 00, 00)) == (isoweek: 01.IsoWeekRange, isoyear: 2020.IsoYear)
doAssert initDateTime(dThu, 01, 2020.IsoYear, 00, 00, 00) == initDateTime(02, mJan, 2020, 00, 00, 00)
doAssert getIsoWeekAndYear(initDateTime(05, mApr, 2020, 00, 00, 00)) == (isoweek: 14.IsoWeekRange, isoyear: 2020.IsoYear)
doAssert initDateTime(dSun, 14, 2020.IsoYear, 00, 00, 00) == initDateTime(05, mApr, 2020, 00, 00, 00)
doAssert getIsoWeekAndYear(initDateTime(06, mApr, 2020, 00, 00, 00)) == (isoweek: 15.IsoWeekRange, isoyear: 2020.IsoYear)
doAssert initDateTime(dMon, 15, 2020.IsoYear, 00, 00, 00) == initDateTime(06, mApr, 2020, 00, 00, 00)
doAssert getIsoWeekAndYear(initDateTime(10, mApr, 2020, 00, 00, 00)) == (isoweek: 15.IsoWeekRange, isoyear: 2020.IsoYear)
doAssert initDateTime(dFri, 15, 2020.IsoYear, 00, 00, 00) == initDateTime(10, mApr, 2020, 00, 00, 00)
doAssert getIsoWeekAndYear(initDateTime(12, mApr, 2020, 00, 00, 00)) == (isoweek: 15.IsoWeekRange, isoyear: 2020.IsoYear)
doAssert initDateTime(dSun, 15, 2020.IsoYear, 00, 00, 00) == initDateTime(12, mApr, 2020, 00, 00, 00)
doAssert getIsoWeekAndYear(initDateTime(13, mApr, 2020, 00, 00, 00)) == (isoweek: 16.IsoWeekRange, isoyear: 2020.IsoYear)
doAssert initDateTime(dMon, 16, 2020.IsoYear, 00, 00, 00) == initDateTime(13, mApr, 2020, 00, 00, 00)
doAssert getIsoWeekAndYear(initDateTime(15, mApr, 2020, 00, 00, 00)) == (isoweek: 16.IsoWeekRange, isoyear: 2020.IsoYear)
doAssert initDateTime(dThu, 16, 2020.IsoYear, 00, 00, 00) == initDateTime(16, mApr, 2020, 00, 00, 00)
doAssert getIsoWeekAndYear(initDateTime(17, mJul, 2020, 00, 00, 00)) == (isoweek: 29.IsoWeekRange, isoyear: 2020.IsoYear)
doAssert initDateTime(dFri, 29, 2020.IsoYear, 00, 00, 00) == initDateTime(17, mJul, 2020, 00, 00, 00)
doAssert getIsoWeekAndYear(initDateTime(19, mJul, 2020, 00, 00, 00)) == (isoweek: 29.IsoWeekRange, isoyear: 2020.IsoYear)
doAssert initDateTime(dSun, 29, 2020.IsoYear, 00, 00, 00) == initDateTime(19, mJul, 2020, 00, 00, 00)
doAssert getIsoWeekAndYear(initDateTime(20, mJul, 2020, 00, 00, 00)) == (isoweek: 30.IsoWeekRange, isoyear: 2020.IsoYear)
doAssert initDateTime(dMon, 30, 2020.IsoYear, 00, 00, 00) == initDateTime(20, mJul, 2020, 00, 00, 00)
doAssert getIsoWeekAndYear(initDateTime(23, mJul, 2020, 00, 00, 00)) == (isoweek: 30.IsoWeekRange, isoyear: 2020.IsoYear)
doAssert initDateTime(dThu, 30, 2020.IsoYear, 00, 00, 00) == initDateTime(23, mJul, 2020, 00, 00, 00)
doAssert getIsoWeekAndYear(initDateTime(31, mDec, 2020, 00, 00, 00)) == (isoweek: 53.IsoWeekRange, isoyear: 2020.IsoYear)
doAssert initDateTime(dThu, 53, 2020.IsoYear, 00, 00, 00) == initDateTime(31, mDec, 2020, 00, 00, 00)
doAssert getIsoWeekAndYear(initDateTime(01, mJan, 2021, 00, 00, 00)) == (isoweek: 53.IsoWeekRange, isoyear: 2020.IsoYear)
doAssert initDateTime(dFri, 53, 2020.IsoYear, 00, 00, 00) == initDateTime(01, mJan, 2021, 00, 00, 00)
doAssert getIsoWeekAndYear(initDateTime(02, mJan, 2021, 00, 00, 00)) == (isoweek: 53.IsoWeekRange, isoyear: 2020.IsoYear)
doAssert initDateTime(dSat, 53, 2020.IsoYear, 00, 00, 00) == initDateTime(02, mJan, 2021, 00, 00, 00)
doAssert getIsoWeekAndYear(initDateTime(03, mJan, 2021, 00, 00, 00)) == (isoweek: 53.IsoWeekRange, isoyear: 2020.IsoYear)
doAssert initDateTime(dSun, 53, 2020.IsoYear, 00, 00, 00) == initDateTime(03, mJan, 2021, 00, 00, 00)
doAssert getIsoWeekAndYear(initDateTime(04, mJan, 2021, 00, 00, 00)) == (isoweek: 01.IsoWeekRange, isoyear: 2021.IsoYear)
doAssert initDateTime(dMon, 01, 2021.IsoYear, 00, 00, 00) == initDateTime(04, mJan, 2021, 00, 00, 00)
doAssert getIsoWeekAndYear(initDateTime(01, mFeb, 2021, 00, 00, 00)) == (isoweek: 05.IsoWeekRange, isoyear: 2021.IsoYear)
doAssert initDateTime(dMon, 05, 2021.IsoYear, 00, 00, 00) == initDateTime(01, mFeb, 2021, 00, 00, 00)
doAssert getIsoWeekAndYear(initDateTime(01, mFeb, 2021, 01, 02, 03, 400_000_000, staticTz(hours=1))) == (isoweek: 05.IsoWeekRange, isoyear: 2021.IsoYear)
doAssert initDateTime(dMon, 05, 2021.IsoYear, 01, 02, 03, 400_000_000, staticTz(hours=1)) == initDateTime(01, mFeb, 2021, 01, 02, 03, 400_000_000, staticTz(hours=1))
doAssert getIsoWeekAndYear(initDateTime(01, mApr, +0001, 00, 00, 00)) == (isoweek: 13.IsoWeekRange, isoyear: 0001.IsoYear)
doAssert initDateTime(dSun, 13, 0001.IsoYear, 00, 00, 00) == initDateTime(01, mApr, 0001, 00, 00, 00)
doAssert getIsoWeekAndYear(initDateTime(01, mApr, +0000, 00, 00, 00)) == (isoweek: 13.IsoWeekRange, isoyear: 0000.IsoYear)
doAssert initDateTime(dSat, 13, 0000.IsoYear, 00, 00, 00) == initDateTime(01, mApr, 0000, 00, 00, 00)
doAssert getIsoWeekAndYear(initDateTime(01, mApr, -0001, 00, 00, 00)) == (isoweek: 13.IsoWeekRange, isoyear: (-0001).IsoYear)
doAssert initDateTime(dThu, 13, (-0001).IsoYear, 00, 00, 00) == initDateTime(01, mApr, -0001, 00, 00, 00)
doAssert getIsoWeekAndYear(initDateTime(01, mApr, -0002, 00, 00, 00)) == (isoweek: 14.IsoWeekRange, isoyear: (-0002).IsoYear)
doAssert initDateTime(dWed, 14, (-0002).IsoYear, 00, 00, 00) == initDateTime(01, mApr, -0002, 00, 00, 00)
doAssert getIsoWeekAndYear(initDateTime(01, mApr, -0753, 00, 00, 00)) == (isoweek: 14.IsoWeekRange, isoyear: (-0753).IsoYear)
doAssert initDateTime(dMon, 14, (-0753).IsoYear, 00, 00, 00) == initDateTime(01, mApr, -0753, 00, 00, 00)
block: # getWeeksInIsoYear
doAssert getWeeksInIsoYear((-0014).IsoYear) == 52
doAssert getWeeksInIsoYear((-0013).IsoYear) == 53
doAssert getWeeksInIsoYear((-0012).IsoYear) == 52
doAssert getWeeksInIsoYear((-0009).IsoYear) == 52
doAssert getWeeksInIsoYear((-0008).IsoYear) == 53
doAssert getWeeksInIsoYear((-0007).IsoYear) == 52
doAssert getWeeksInIsoYear((-0003).IsoYear) == 52
doAssert getWeeksInIsoYear((-0002).IsoYear) == 53
doAssert getWeeksInIsoYear((-0001).IsoYear) == 52
doAssert getWeeksInIsoYear(0003.IsoYear) == 52
doAssert getWeeksInIsoYear(0004.IsoYear) == 53
doAssert getWeeksInIsoYear(0005.IsoYear) == 52
doAssert getWeeksInIsoYear(1653.IsoYear) == 52
doAssert getWeeksInIsoYear(1654.IsoYear) == 53
doAssert getWeeksInIsoYear(1655.IsoYear) == 52
doAssert getWeeksInIsoYear(1997.IsoYear) == 52
doAssert getWeeksInIsoYear(1998.IsoYear) == 53
doAssert getWeeksInIsoYear(1999.IsoYear) == 52
doAssert getWeeksInIsoYear(2008.IsoYear) == 52
doAssert getWeeksInIsoYear(2009.IsoYear) == 53
doAssert getWeeksInIsoYear(2010.IsoYear) == 52
doAssert getWeeksInIsoYear(2014.IsoYear) == 52
doAssert getWeeksInIsoYear(2015.IsoYear) == 53
doAssert getWeeksInIsoYear(2016.IsoYear) == 52