Don't use parseutils.parseInt in the times module (#10028)
This commit is contained in:
parent
ca672ec62e
commit
2539363851
2 changed files with 78 additions and 39 deletions
|
|
@ -122,12 +122,8 @@
|
||||||
only for years in the range 1..9999).
|
only for years in the range 1..9999).
|
||||||
]##
|
]##
|
||||||
|
|
||||||
|
|
||||||
{.push debugger:off.} # the user does not want to trace a part
|
|
||||||
# of the standard library!
|
|
||||||
|
|
||||||
import
|
import
|
||||||
strutils, parseutils, algorithm, math, options, strformat
|
strutils, algorithm, math, options, strformat
|
||||||
|
|
||||||
include "system/inclrtl"
|
include "system/inclrtl"
|
||||||
|
|
||||||
|
|
@ -304,7 +300,6 @@ const
|
||||||
secondsInMin = 60
|
secondsInMin = 60
|
||||||
secondsInHour = 60*60
|
secondsInHour = 60*60
|
||||||
secondsInDay = 60*60*24
|
secondsInDay = 60*60*24
|
||||||
minutesInHour = 60
|
|
||||||
rateDiff = 10000000'i64 # 100 nsecs
|
rateDiff = 10000000'i64 # 100 nsecs
|
||||||
# The number of hectonanoseconds between 1601/01/01 (windows epoch)
|
# The number of hectonanoseconds between 1601/01/01 (windows epoch)
|
||||||
# and 1970/01/01 (unix epoch).
|
# and 1970/01/01 (unix epoch).
|
||||||
|
|
@ -1602,6 +1597,12 @@ type
|
||||||
## be encoded as ``@[Lit.byte, 3.byte, 'f'.byte, 'o'.byte, 'o'.byte]``.
|
## be encoded as ``@[Lit.byte, 3.byte, 'f'.byte, 'o'.byte, 'o'.byte]``.
|
||||||
formatStr: string
|
formatStr: string
|
||||||
|
|
||||||
|
TimeParseError* = object of ValueError ## \
|
||||||
|
## Raised when parsing input using a ``TimeFormat`` fails.
|
||||||
|
|
||||||
|
TimeFormatParseError* = object of ValueError ## \
|
||||||
|
## Raised when parsing a ``TimeFormat`` string fails.
|
||||||
|
|
||||||
const FormatLiterals = { ' ', '-', '/', ':', '(', ')', '[', ']', ',' }
|
const FormatLiterals = { ' ', '-', '/', ':', '(', ')', '[', ']', ',' }
|
||||||
|
|
||||||
proc `$`*(f: TimeFormat): string =
|
proc `$`*(f: TimeFormat): string =
|
||||||
|
|
@ -1612,9 +1613,34 @@ proc `$`*(f: TimeFormat): string =
|
||||||
f.formatStr
|
f.formatStr
|
||||||
|
|
||||||
proc raiseParseException(f: TimeFormat, input: string, msg: string) =
|
proc raiseParseException(f: TimeFormat, input: string, msg: string) =
|
||||||
raise newException(ValueError,
|
raise newException(TimeParseError,
|
||||||
&"Failed to parse '{input}' with format '{f}'. {msg}")
|
&"Failed to parse '{input}' with format '{f}'. {msg}")
|
||||||
|
|
||||||
|
proc parseInt(s: string, b: var int, start = 0, maxLen = int.high,
|
||||||
|
allowSign = false): int =
|
||||||
|
var sign = -1
|
||||||
|
var i = start
|
||||||
|
let stop = start + min(s.high - start + 1, maxLen) - 1
|
||||||
|
if allowSign and i <= stop:
|
||||||
|
if s[i] == '+':
|
||||||
|
inc(i)
|
||||||
|
elif s[i] == '-':
|
||||||
|
inc(i)
|
||||||
|
sign = 1
|
||||||
|
if i <= stop and s[i] in {'0'..'9'}:
|
||||||
|
b = 0
|
||||||
|
while i <= stop and s[i] in {'0'..'9'}:
|
||||||
|
let c = ord(s[i]) - ord('0')
|
||||||
|
if b >= (low(int) + c) div 10:
|
||||||
|
b = b * 10 - c
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
inc(i)
|
||||||
|
if sign == -1 and b == low(int):
|
||||||
|
return 0
|
||||||
|
b = b * sign
|
||||||
|
result = i - start
|
||||||
|
|
||||||
iterator tokens(f: string): tuple[kind: FormatTokenKind, token: string] =
|
iterator tokens(f: string): tuple[kind: FormatTokenKind, token: string] =
|
||||||
var i = 0
|
var i = 0
|
||||||
var currToken = ""
|
var currToken = ""
|
||||||
|
|
@ -1639,7 +1665,7 @@ iterator tokens(f: string): tuple[kind: FormatTokenKind, token: string] =
|
||||||
i.inc
|
i.inc
|
||||||
|
|
||||||
if i > f.high:
|
if i > f.high:
|
||||||
raise newException(ValueError,
|
raise newException(TimeFormatParseError,
|
||||||
&"Unclosed ' in time format string. " &
|
&"Unclosed ' in time format string. " &
|
||||||
"For a literal ', use ''.")
|
"For a literal ', use ''.")
|
||||||
i.inc
|
i.inc
|
||||||
|
|
@ -1696,7 +1722,8 @@ proc stringToPattern(str: string): FormatPattern =
|
||||||
of "zzz": result = zzz
|
of "zzz": result = zzz
|
||||||
of "zzzz": result = zzzz
|
of "zzzz": result = zzzz
|
||||||
of "g": result = g
|
of "g": result = g
|
||||||
else: raise newException(ValueError, &"'{str}' is not a valid pattern")
|
else: raise newException(TimeFormatParseError,
|
||||||
|
&"'{str}' is not a valid pattern")
|
||||||
|
|
||||||
proc initTimeFormat*(format: string): TimeFormat =
|
proc initTimeFormat*(format: string): TimeFormat =
|
||||||
## Construct a new time format for parsing & formatting time types.
|
## Construct a new time format for parsing & formatting time types.
|
||||||
|
|
@ -1715,7 +1742,7 @@ proc initTimeFormat*(format: string): TimeFormat =
|
||||||
else:
|
else:
|
||||||
result.patterns.add(FormatPattern.Lit.byte)
|
result.patterns.add(FormatPattern.Lit.byte)
|
||||||
if token.len > 255:
|
if token.len > 255:
|
||||||
raise newException(ValueError,
|
raise newException(TimeFormatParseError,
|
||||||
"Format literal is to long:" & token)
|
"Format literal is to long:" & token)
|
||||||
result.patterns.add(token.len.byte)
|
result.patterns.add(token.len.byte)
|
||||||
for c in token:
|
for c in token:
|
||||||
|
|
@ -1833,15 +1860,10 @@ proc formatPattern(dt: DateTime, pattern: FormatPattern, result: var string) =
|
||||||
|
|
||||||
proc parsePattern(input: string, pattern: FormatPattern, i: var int,
|
proc parsePattern(input: string, pattern: FormatPattern, i: var int,
|
||||||
parsed: var ParsedTime): bool =
|
parsed: var ParsedTime): bool =
|
||||||
template takeInt(allowedWidth: Slice[int]): int =
|
template takeInt(allowedWidth: Slice[int], allowSign = false): int =
|
||||||
var sv: int
|
var sv: int
|
||||||
let max = i + allowedWidth.b - 1
|
var pd = parseInt(input, sv, i, allowedWidth.b, allowSign)
|
||||||
var pd =
|
if pd < allowedWidth.a:
|
||||||
if max > input.high:
|
|
||||||
parseInt(input, sv, i)
|
|
||||||
else:
|
|
||||||
parseInt(input[i..max], sv)
|
|
||||||
if pd notin allowedWidth:
|
|
||||||
return false
|
return false
|
||||||
i.inc pd
|
i.inc pd
|
||||||
sv
|
sv
|
||||||
|
|
@ -1853,11 +1875,13 @@ proc parsePattern(input: string, pattern: FormatPattern, i: var int,
|
||||||
|
|
||||||
case pattern
|
case pattern
|
||||||
of d:
|
of d:
|
||||||
parsed.monthday = some(takeInt(1..2))
|
let monthday = takeInt(1..2)
|
||||||
result = parsed.monthday.get() in MonthdayRange
|
parsed.monthday = some(monthday)
|
||||||
|
result = monthday in MonthdayRange
|
||||||
of dd:
|
of dd:
|
||||||
parsed.monthday = some(takeInt(2..2))
|
let monthday = takeInt(2..2)
|
||||||
result = parsed.monthday.get() in MonthdayRange
|
parsed.monthday = some(monthday)
|
||||||
|
result = monthday in MonthdayRange
|
||||||
of ddd:
|
of ddd:
|
||||||
result = input.substr(i, i+2).toLowerAscii() in [
|
result = input.substr(i, i+2).toLowerAscii() in [
|
||||||
"sun", "mon", "tue", "wed", "thu", "fri", "sat"]
|
"sun", "mon", "tue", "wed", "thu", "fri", "sat"]
|
||||||
|
|
@ -1993,7 +2017,7 @@ proc parsePattern(input: string, pattern: FormatPattern, i: var int,
|
||||||
of yyyy:
|
of yyyy:
|
||||||
let year =
|
let year =
|
||||||
if input[i] in { '+', '-' }:
|
if input[i] in { '+', '-' }:
|
||||||
takeInt(4..high(int))
|
takeInt(4..high(int), allowSign = true)
|
||||||
else:
|
else:
|
||||||
takeInt(4..4)
|
takeInt(4..4)
|
||||||
result = year > 0
|
result = year > 0
|
||||||
|
|
@ -2005,12 +2029,12 @@ proc parsePattern(input: string, pattern: FormatPattern, i: var int,
|
||||||
of uuuu:
|
of uuuu:
|
||||||
let year =
|
let year =
|
||||||
if input[i] in { '+', '-' }:
|
if input[i] in { '+', '-' }:
|
||||||
takeInt(4..high(int))
|
takeInt(4..high(int), allowSign = true)
|
||||||
else:
|
else:
|
||||||
takeInt(4..4)
|
takeInt(4..4)
|
||||||
parsed.year = some(year)
|
parsed.year = some(year)
|
||||||
of UUUU:
|
of UUUU:
|
||||||
parsed.year = some(takeInt(1..high(int)))
|
parsed.year = some(takeInt(1..high(int), allowSign = true))
|
||||||
of z, zz, zzz, zzzz:
|
of z, zz, zzz, zzzz:
|
||||||
case input[i]
|
case input[i]
|
||||||
of '+', '-':
|
of '+', '-':
|
||||||
|
|
@ -2055,9 +2079,8 @@ proc parsePattern(input: string, pattern: FormatPattern, i: var int,
|
||||||
else:
|
else:
|
||||||
result = false
|
result = false
|
||||||
of y, yyy, yyyyy:
|
of y, yyy, yyyyy:
|
||||||
raise newException(ValueError,
|
raiseAssert "Pattern is invalid for parsing: " & $pattern
|
||||||
&"The pattern '{pattern}' is only valid for formatting")
|
of Lit: doAssert false, "Can't happen"
|
||||||
of Lit: assert false # Can't happen
|
|
||||||
|
|
||||||
proc toDateTime(p: ParsedTime, zone: Timezone, f: TimeFormat,
|
proc toDateTime(p: ParsedTime, zone: Timezone, f: TimeFormat,
|
||||||
input: string): DateTime =
|
input: string): DateTime =
|
||||||
|
|
@ -2147,7 +2170,8 @@ proc format*(dt: DateTime, f: TimeFormat): string {.raises: [].} =
|
||||||
formatPattern(dt, f.patterns[idx].FormatPattern, result = result)
|
formatPattern(dt, f.patterns[idx].FormatPattern, result = result)
|
||||||
idx.inc
|
idx.inc
|
||||||
|
|
||||||
proc format*(dt: DateTime, f: string): string =
|
proc format*(dt: DateTime, f: string): string
|
||||||
|
{.raises: [TimeFormatParseError].} =
|
||||||
## Shorthand for constructing a ``TimeFormat`` and using it to format ``dt``.
|
## Shorthand for constructing a ``TimeFormat`` and using it to format ``dt``.
|
||||||
##
|
##
|
||||||
## See `Parsing and formatting dates`_ for documentation of the
|
## See `Parsing and formatting dates`_ for documentation of the
|
||||||
|
|
@ -2163,7 +2187,8 @@ proc format*(dt: DateTime, f: static[string]): string {.raises: [].} =
|
||||||
const f2 = initTimeFormat(f)
|
const f2 = initTimeFormat(f)
|
||||||
result = dt.format(f2)
|
result = dt.format(f2)
|
||||||
|
|
||||||
proc format*(time: Time, f: string, zone: Timezone = local()): string {.tags: [].} =
|
proc format*(time: Time, f: string, zone: Timezone = local()): string
|
||||||
|
{.raises: [TimeFormatParseError].} =
|
||||||
## Shorthand for constructing a ``TimeFormat`` and using it to format
|
## Shorthand for constructing a ``TimeFormat`` and using it to format
|
||||||
## ``time``. Will use the timezone specified by ``zone``.
|
## ``time``. Will use the timezone specified by ``zone``.
|
||||||
##
|
##
|
||||||
|
|
@ -2175,13 +2200,14 @@ proc format*(time: Time, f: string, zone: Timezone = local()): string {.tags: []
|
||||||
doAssert format(tm, "yyyy-MM-dd'T'HH:mm:ss", utc()) == "1970-01-01T00:00:00"
|
doAssert format(tm, "yyyy-MM-dd'T'HH:mm:ss", utc()) == "1970-01-01T00:00:00"
|
||||||
time.inZone(zone).format(f)
|
time.inZone(zone).format(f)
|
||||||
|
|
||||||
proc format*(time: Time, f: static[string],
|
proc format*(time: Time, f: static[string], zone: Timezone = local()): string
|
||||||
zone: Timezone = local()): string {.tags: [].} =
|
{.raises: [].} =
|
||||||
## Overload that validates ``f`` at compile time.
|
## Overload that validates ``f`` at compile time.
|
||||||
const f2 = initTimeFormat(f)
|
const f2 = initTimeFormat(f)
|
||||||
result = time.inZone(zone).format(f2)
|
result = time.inZone(zone).format(f2)
|
||||||
|
|
||||||
proc parse*(input: string, f: TimeFormat, zone: Timezone = local()): DateTime =
|
proc parse*(input: string, f: TimeFormat, zone: Timezone = local()): DateTime
|
||||||
|
{.raises: [TimeParseError, Defect].} =
|
||||||
## Parses ``input`` as a ``DateTime`` using the format specified by ``f``.
|
## Parses ``input`` as a ``DateTime`` using the format specified by ``f``.
|
||||||
## If no UTC offset was parsed, then ``input`` is assumed to be specified in
|
## If no UTC offset was parsed, then ``input`` is assumed to be specified in
|
||||||
## the ``zone`` timezone. If a UTC offset was parsed, the result will be
|
## the ``zone`` timezone. If a UTC offset was parsed, the result will be
|
||||||
|
|
@ -2221,7 +2247,8 @@ proc parse*(input: string, f: TimeFormat, zone: Timezone = local()): DateTime =
|
||||||
|
|
||||||
result = toDateTime(parsed, zone, f, input)
|
result = toDateTime(parsed, zone, f, input)
|
||||||
|
|
||||||
proc parse*(input, f: string, tz: Timezone = local()): DateTime =
|
proc parse*(input, f: string, tz: Timezone = local()): DateTime
|
||||||
|
{.raises: [TimeParseError, TimeFormatParseError, Defect].} =
|
||||||
## Shorthand for constructing a ``TimeFormat`` and using it to parse
|
## Shorthand for constructing a ``TimeFormat`` and using it to parse
|
||||||
## ``input`` as a ``DateTime``.
|
## ``input`` as a ``DateTime``.
|
||||||
##
|
##
|
||||||
|
|
@ -2233,12 +2260,14 @@ proc parse*(input, f: string, tz: Timezone = local()): DateTime =
|
||||||
let dtFormat = initTimeFormat(f)
|
let dtFormat = initTimeFormat(f)
|
||||||
result = input.parse(dtFormat, tz)
|
result = input.parse(dtFormat, tz)
|
||||||
|
|
||||||
proc parse*(input: string, f: static[string], zone: Timezone = local()): DateTime =
|
proc parse*(input: string, f: static[string], zone: Timezone = local()): DateTime
|
||||||
|
{.raises: [TimeParseError, Defect].} =
|
||||||
## Overload that validates ``f`` at compile time.
|
## Overload that validates ``f`` at compile time.
|
||||||
const f2 = initTimeFormat(f)
|
const f2 = initTimeFormat(f)
|
||||||
result = input.parse(f2, zone)
|
result = input.parse(f2, zone)
|
||||||
|
|
||||||
proc parseTime*(input, f: string, zone: Timezone): Time =
|
proc parseTime*(input, f: string, zone: Timezone): Time
|
||||||
|
{.raises: [TimeParseError, TimeFormatParseError, Defect].} =
|
||||||
## Shorthand for constructing a ``TimeFormat`` and using it to parse
|
## Shorthand for constructing a ``TimeFormat`` and using it to parse
|
||||||
## ``input`` as a ``DateTime``, then converting it a ``Time``.
|
## ``input`` as a ``DateTime``, then converting it a ``Time``.
|
||||||
##
|
##
|
||||||
|
|
@ -2249,7 +2278,8 @@ proc parseTime*(input, f: string, zone: Timezone): Time =
|
||||||
doAssert parseTime(tStr, "yyyy-MM-dd'T'HH:mm:sszzz", utc()) == fromUnix(0)
|
doAssert parseTime(tStr, "yyyy-MM-dd'T'HH:mm:sszzz", utc()) == fromUnix(0)
|
||||||
parse(input, f, zone).toTime()
|
parse(input, f, zone).toTime()
|
||||||
|
|
||||||
proc parseTime*(input: string, f: static[string], zone: Timezone): Time =
|
proc parseTime*(input: string, f: static[string], zone: Timezone): Time
|
||||||
|
{.raises: [TimeParseError, Defect].} =
|
||||||
## Overload that validates ``format`` at compile time.
|
## Overload that validates ``format`` at compile time.
|
||||||
const f2 = initTimeFormat(f)
|
const f2 = initTimeFormat(f)
|
||||||
result = input.parse(f2, zone).toTime()
|
result = input.parse(f2, zone).toTime()
|
||||||
|
|
@ -2275,8 +2305,6 @@ proc `$`*(time: Time): string {.tags: [], raises: [], benign.} =
|
||||||
doAssert $tm == "1970-01-01T00:00:00" & format(dt, "zzz")
|
doAssert $tm == "1970-01-01T00:00:00" & format(dt, "zzz")
|
||||||
$time.local
|
$time.local
|
||||||
|
|
||||||
{.pop.}
|
|
||||||
|
|
||||||
proc countLeapYears*(yearSpan: int): int =
|
proc countLeapYears*(yearSpan: int): int =
|
||||||
## Returns the number of leap years spanned by a given number of years.
|
## Returns the number of leap years spanned by a given number of years.
|
||||||
##
|
##
|
||||||
|
|
|
||||||
|
|
@ -245,6 +245,17 @@ suite "ttimes":
|
||||||
parseTestExcp("12345", "uuuu")
|
parseTestExcp("12345", "uuuu")
|
||||||
parseTestExcp("-1 BC", "UUUU g")
|
parseTestExcp("-1 BC", "UUUU g")
|
||||||
|
|
||||||
|
test "incorrect inputs: invalid sign":
|
||||||
|
parseTestExcp("+1", "YYYY")
|
||||||
|
parseTestExcp("+1", "dd")
|
||||||
|
parseTestExcp("+1", "MM")
|
||||||
|
parseTestExcp("+1", "hh")
|
||||||
|
parseTestExcp("+1", "mm")
|
||||||
|
parseTestExcp("+1", "ss")
|
||||||
|
|
||||||
|
test "_ as a seperator":
|
||||||
|
discard parse("2000_01_01", "YYYY'_'MM'_'dd")
|
||||||
|
|
||||||
test "dynamic timezone":
|
test "dynamic timezone":
|
||||||
let tz = staticTz(seconds = -9000)
|
let tz = staticTz(seconds = -9000)
|
||||||
let dt = initDateTime(1, mJan, 2000, 12, 00, 00, tz)
|
let dt = initDateTime(1, mJan, 2000, 12, 00, 00, tz)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue