Weekday parse/format (replacement) (#21857)

* parsing capability for iso week year

* remove outdated test
This commit is contained in:
Carlo Capocasa
2023-05-21 20:12:05 +02:00
committed by GitHub
parent 28a116a477
commit 9c2d2773ec
3 changed files with 134 additions and 79 deletions

View File

@@ -62,6 +62,8 @@
| `Monday -> Mon`
`dddd` Full string for the day of the week. | `Saturday -> Saturday`
| `Monday -> Monday`
`GG` The last two digits of the Iso Week-Year | `30/12/2012 -> 13`
`GGGG` The Iso week-calendar year padded to four digits | `30/12/2012 -> 2013`
`h` The hours in one digit if possible. Ranging from 1-12. | `5pm -> 5`
| `2am -> 2`
`hh` The hours in two digits always. If the hour is one digit, 0 is prepended. | `5pm -> 05`
@@ -104,6 +106,10 @@
| `24 AD -> 24`
| `24 BC -> -23`
| `12345 AD -> 12345`
`V` The Iso Week-Number as one or two digits | `3/2/2012 -> 5`
| `1/4/2012 -> 13`
`VV` The Iso Week-Number as two digits always. 0 is prepended if one digit. | `3/2/2012 -> 05`
| `1/4/2012 -> 13`
`z` Displays the timezone offset from UTC. | `UTC+7 -> +7`
| `UTC-5 -> -5`
`zz` Same as above but with leading 0. | `UTC+7 -> +07`
@@ -1507,6 +1513,33 @@ proc getClockStr*(dt = now()): string {.rtl, extern: "nt$1", tags: [TimeEffect].
result = intToStr(dt.hour, 2) & ':' & intToStr(dt.minute, 2) &
':' & intToStr(dt.second, 2)
#
# Iso week
#
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) + initDuration(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)
#
# TimeFormat
#
@@ -1537,6 +1570,9 @@ type
year: Option[int]
month: Option[int]
monthday: Option[int]
isoyear: Option[int]
yearweek: Option[int]
weekday: Option[WeekDay]
utcOffset: Option[int]
# '0' as default for these work fine
@@ -1551,6 +1587,7 @@ type
FormatPattern {.pure.} = enum
d, dd, ddd, dddd
GG, GGGG
h, hh, H, HH
m, mm, M, MM, MMM, MMMM
s, ss
@@ -1560,6 +1597,7 @@ type
YYYY
uuuu
UUUU
V, VV
z, zz, zzz, zzzz
ZZZ, ZZZZ
g
@@ -1688,6 +1726,8 @@ proc stringToPattern(str: string): FormatPattern =
of "dd": result = dd
of "ddd": result = ddd
of "dddd": result = dddd
of "GG": result = GG
of "GGGG": result = GGGG
of "h": result = h
of "hh": result = hh
of "H": result = H
@@ -1710,6 +1750,8 @@ proc stringToPattern(str: string): FormatPattern =
of "YYYY": result = YYYY
of "uuuu": result = uuuu
of "UUUU": result = UUUU
of "V": result = V
of "VV": result = VV
of "z": result = z
of "zz": result = zz
of "zzz": result = zzz
@@ -1759,6 +1801,10 @@ proc formatPattern(dt: DateTime, pattern: FormatPattern, result: var string,
result.add loc.ddd[dt.weekday]
of dddd:
result.add loc.dddd[dt.weekday]
of GG:
result.add (dt.getIsoWeekAndYear.isoyear.int mod 100).intToStr(2)
of GGGG:
result.add $dt.getIsoWeekAndYear.isoyear
of h:
result.add(
if dt.hour == 0: "12"
@@ -1822,6 +1868,10 @@ proc formatPattern(dt: DateTime, pattern: FormatPattern, result: var string,
result.add '+' & $year
of UUUU:
result.add $dt.year
of V:
result.add $dt.getIsoWeekAndYear.isoweek
of VV:
result.add dt.getIsoWeekAndYear.isoweek.intToStr(2)
of z, zz, zzz, zzzz, ZZZ, ZZZZ:
if dt.timezone != nil and dt.timezone.name == "Etc/UTC":
result.add 'Z'
@@ -1876,18 +1926,30 @@ proc parsePattern(input: string, pattern: FormatPattern, i: var int,
result = monthday in MonthdayRange
of ddd:
result = false
for v in loc.ddd:
for d, v in loc.ddd:
if input.substr(i, i+v.len-1).cmpIgnoreCase(v) == 0:
parsed.weekday = some(d.WeekDay)
result = true
i.inc v.len
break
of dddd:
result = false
for v in loc.dddd:
for d, v in loc.dddd:
if input.substr(i, i+v.len-1).cmpIgnoreCase(v) == 0:
parsed.weekday = some(d.WeekDay)
result = true
i.inc v.len
break
of GG:
# Assumes current century
var isoyear = takeInt(2..2)
var thisCen = now().year div 100
parsed.isoyear = some(thisCen*100 + isoyear)
result = isoyear > 0
of GGGG:
let isoyear = takeInt(1..high(int))
parsed.isoyear = some(isoyear)
result = isoyear > 0
of h, H:
parsed.hour = takeInt(1..2)
result = parsed.hour in HourRange
@@ -1978,6 +2040,14 @@ proc parsePattern(input: string, pattern: FormatPattern, i: var int,
parsed.year = some(year)
of UUUU:
parsed.year = some(takeInt(1..high(int), allowSign = true))
of V:
let yearweek = takeInt(1..2)
parsed.yearweek = some(yearweek)
result = yearweek in IsoWeekRange
of VV:
let yearweek = takeInt(2..2)
parsed.yearweek = some(yearweek)
result = yearweek in IsoWeekRange
of z, zz, zzz, zzzz, ZZZ, ZZZZ:
case input[i]
of '+', '-':
@@ -2079,6 +2149,38 @@ proc toDateTime(p: ParsedTime, zone: Timezone, f: TimeFormat,
result = (dateTime(year, month, monthday, hour, minute, second, nanosecond, utc()).toTime +
initDuration(seconds = p.utcOffset.get())).inZone(zone)
proc toDateTimeByWeek(p: ParsedTime, zone: Timezone, f: TimeFormat,
input: string): DateTime =
var isoyear = p.isoyear.get(0)
var yearweek = p.yearweek.get(1)
var weekday = p.weekday.get(dMon)
if p.amPm != apUnknown:
raiseParseException(f, input, "Parsing iso weekyear dates does not support am/pm")
if p.year.isSome:
raiseParseException(f, input, "Use iso-year GG or GGGG as year with iso week number")
if p.month.isSome:
raiseParseException(f, input, "Use either iso week number V or VV or month")
if p.monthday.isSome:
raiseParseException(f, input, "Use weekday ddd or dddd as day with with iso week number")
if p.isoyear.isNone:
raiseParseException(f, input, "Need iso-year with week number")
let hour = p.hour
let minute = p.minute
let second = p.second
let nanosecond = p.nanosecond
if p.utcOffset.isNone:
result = initDateTime(weekday, yearweek.IsoWeekRange, isoyear.IsoYear, hour, minute, second, nanosecond, zone)
else:
result = (initDateTime(weekday, yearweek.IsoWeekRange, isoyear.IsoYear, hour, minute, second, nanosecond, zone).toTime +
initDuration(seconds = p.utcOffset.get())).inZone(zone)
proc format*(dt: DateTime, f: TimeFormat,
loc: DateTimeLocale = DefaultLocale): string {.raises: [].} =
## Format `dt` using the format specified by `f`.
@@ -2184,7 +2286,12 @@ proc parse*(input: string, f: TimeFormat, zone: Timezone = local(),
raiseParseException(f, input,
"Parsing ended but there was still patterns remaining")
result = toDateTime(parsed, zone, f, input)
if parsed.yearweek.isSome:
result = toDateTimeByWeek(parsed, zone, f, input)
elif parsed.isoyear.isSome:
raiseParseException(f, input, "Iso year GG or GGGG require iso week V or VV")
else:
result = toDateTime(parsed, zone, f, input)
proc parse*(input, f: string, tz: Timezone = local(),
loc: DateTimeLocale = DefaultLocale): DateTime {.parseFormatRaises.} =
@@ -2645,33 +2752,6 @@ 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

@@ -742,3 +742,27 @@ block: # ttimes
doAssert getWeeksInIsoYear(2014.IsoYear) == 52
doAssert getWeeksInIsoYear(2015.IsoYear) == 53
doAssert getWeeksInIsoYear(2016.IsoYear) == 52
block: # parse and generate iso years
# short calendar week with text
parseTest("KW 23 2023", "'KW' VV GGGG",
"2023-06-05T00:00:00Z", 155)
parseTest("KW 5 2023", "'KW' V GGGG",
"2023-01-30T00:00:00Z", 29)
parseTest("KW 05 23 Saturday", "'KW' V GG dddd",
"2023-02-04T00:00:00Z", 34)
parseTest("KW 53 20 Fri", "'KW' VV GG ddd",
"2021-01-01T00:00:00Z", 0)
parseTestExcp("KW 23", "'KW' VV") # no year
parseTestExcp("KW 23", "'KW' V") # no year
parseTestExcp("KW 23", "'KW' GG") # no week
parseTestExcp("KW 2023", "'KW' GGGG") # no week
var dt = initDateTime(5, mJan, 2023, 0, 0, 0, utc())
check dt.format("V") == "1"
check dt.format("VV") == "01"
check dt.format("GG") == "23"
check dt.format("GGGG") == "2023"
check dt.format("dddd 'KW'V GGGG") == "Thursday KW1 2023"

View File

@@ -1,49 +0,0 @@
discard """
matrix: "-d:NimMajor=1 -d:NimMinor=0 -d:NimPatch=100"
"""
{.warning[UnusedImport]: off.}
import std/[
# Core:
bitops, typetraits, lenientops, macros, volatile,
# Algorithms:
algorithm, sequtils,
# Collections:
critbits, deques, heapqueue, intsets, lists, options, sets,
sharedlist, tables,
# Strings:
editdistance, wordwrap, parseutils, ropes,
pegs, strformat, strmisc, strscans, strtabs,
strutils, unicode, unidecode,
# Generic operator system services:
os, streams,
# Math libraries:
complex, math, mersenne, random, rationals, stats, sums,
# Internet protocols:
httpcore, mimetypes, uri,
# Parsers:
htmlparser, json, lexbase, parsecfg, parsecsv, parsesql, parsexml,
# XML processing:
xmltree, xmlparser,
# Generators:
htmlgen,
# Hashing:
base64, hashes,
# Miscellaneous:
colors, sugar, varints,
]
doAssert NimVersion == "1.0.100"