Merge pull request #5024 from flyx/timesimprovement
Improvements to times.nim
This commit is contained in:
commit
c629d6fef1
2 changed files with 89 additions and 42 deletions
|
|
@ -144,8 +144,9 @@ type
|
||||||
yearday*: range[0..365] ## The number of days since January 1,
|
yearday*: range[0..365] ## The number of days since January 1,
|
||||||
## in the range 0 to 365.
|
## in the range 0 to 365.
|
||||||
## Always 0 if the target is JS.
|
## Always 0 if the target is JS.
|
||||||
isDST*: bool ## Determines whether DST is in effect. Always
|
isDST*: bool ## Determines whether DST is in effect.
|
||||||
## ``False`` if time is UTC.
|
## Semantically, this adds another negative hour
|
||||||
|
## offset to the time in addition to the timezone.
|
||||||
timezone*: int ## The offset of the (non-DST) timezone in seconds
|
timezone*: int ## The offset of the (non-DST) timezone in seconds
|
||||||
## west of UTC. Note that the sign of this number
|
## west of UTC. Note that the sign of this number
|
||||||
## is the opposite of the one in a formatted
|
## is the opposite of the one in a formatted
|
||||||
|
|
@ -278,16 +279,20 @@ proc `+`*(ti1, ti2: TimeInterval): TimeInterval =
|
||||||
carryO = `div`(ti1.months + ti2.months, 12)
|
carryO = `div`(ti1.months + ti2.months, 12)
|
||||||
result.years = carryO + ti1.years + ti2.years
|
result.years = carryO + ti1.years + ti2.years
|
||||||
|
|
||||||
|
proc `-`*(ti: TimeInterval): TimeInterval =
|
||||||
|
result = TimeInterval(
|
||||||
|
milliseconds: -ti.milliseconds,
|
||||||
|
seconds: -ti.seconds,
|
||||||
|
minutes: -ti.minutes,
|
||||||
|
hours: -ti.hours,
|
||||||
|
days: -ti.days,
|
||||||
|
months: -ti.months,
|
||||||
|
years: -ti.years
|
||||||
|
)
|
||||||
|
|
||||||
proc `-`*(ti1, ti2: TimeInterval): TimeInterval =
|
proc `-`*(ti1, ti2: TimeInterval): TimeInterval =
|
||||||
## Subtracts TimeInterval ``ti1`` from ``ti2``.
|
## Subtracts TimeInterval ``ti1`` from ``ti2``.
|
||||||
result = ti1
|
result = ti1 + (-ti2)
|
||||||
result.milliseconds -= ti2.milliseconds
|
|
||||||
result.seconds -= ti2.seconds
|
|
||||||
result.minutes -= ti2.minutes
|
|
||||||
result.hours -= ti2.hours
|
|
||||||
result.days -= ti2.days
|
|
||||||
result.months -= ti2.months
|
|
||||||
result.years -= ti2.years
|
|
||||||
|
|
||||||
proc isLeapYear*(year: int): bool =
|
proc isLeapYear*(year: int): bool =
|
||||||
## returns true if ``year`` is a leap year
|
## returns true if ``year`` is a leap year
|
||||||
|
|
@ -363,16 +368,9 @@ proc `-`*(a: TimeInfo, interval: TimeInterval): TimeInfo =
|
||||||
##
|
##
|
||||||
## **Note:** This has been only briefly tested, it is inaccurate especially
|
## **Note:** This has been only briefly tested, it is inaccurate especially
|
||||||
## when you subtract so much that you reach the Julian calendar.
|
## when you subtract so much that you reach the Julian calendar.
|
||||||
let t = toSeconds(toTime(a))
|
let
|
||||||
var intval: TimeInterval
|
t = toSeconds(toTime(a))
|
||||||
intval.milliseconds = - interval.milliseconds
|
secs = toSeconds(a, -interval)
|
||||||
intval.seconds = - interval.seconds
|
|
||||||
intval.minutes = - interval.minutes
|
|
||||||
intval.hours = - interval.hours
|
|
||||||
intval.days = - interval.days
|
|
||||||
intval.months = - interval.months
|
|
||||||
intval.years = - interval.years
|
|
||||||
let secs = toSeconds(a, intval)
|
|
||||||
if a.timezone == 0:
|
if a.timezone == 0:
|
||||||
result = getGMTime(fromSeconds(t + secs))
|
result = getGMTime(fromSeconds(t + secs))
|
||||||
else:
|
else:
|
||||||
|
|
@ -824,28 +822,32 @@ proc formatToken(info: TimeInfo, token: string, buf: var string) =
|
||||||
if fyear.len != 5: fyear = repeat('0', 5-fyear.len()) & fyear
|
if fyear.len != 5: fyear = repeat('0', 5-fyear.len()) & fyear
|
||||||
buf.add(fyear)
|
buf.add(fyear)
|
||||||
of "z":
|
of "z":
|
||||||
let hours = abs(info.timezone) div secondsInHour
|
let
|
||||||
if info.timezone <= 0: buf.add('+')
|
nonDstTz = info.timezone - int(info.isDst) * secondsInHour
|
||||||
|
hours = abs(nonDstTz) div secondsInHour
|
||||||
|
if nonDstTz <= 0: buf.add('+')
|
||||||
else: buf.add('-')
|
else: buf.add('-')
|
||||||
buf.add($hours)
|
buf.add($hours)
|
||||||
of "zz":
|
of "zz":
|
||||||
let hours = abs(info.timezone) div secondsInHour
|
let
|
||||||
if info.timezone <= 0: buf.add('+')
|
nonDstTz = info.timezone - int(info.isDst) * secondsInHour
|
||||||
|
hours = abs(nonDstTz) div secondsInHour
|
||||||
|
if nonDstTz <= 0: buf.add('+')
|
||||||
else: buf.add('-')
|
else: buf.add('-')
|
||||||
if hours < 10: buf.add('0')
|
if hours < 10: buf.add('0')
|
||||||
buf.add($hours)
|
buf.add($hours)
|
||||||
of "zzz":
|
of "zzz":
|
||||||
let
|
let
|
||||||
hours = abs(info.timezone) div secondsInHour
|
nonDstTz = info.timezone - int(info.isDst) * secondsInHour
|
||||||
minutes = (abs(info.timezone) div secondsInMin) mod minutesInHour
|
hours = abs(nonDstTz) div secondsInHour
|
||||||
if info.timezone <= 0: buf.add('+')
|
minutes = (abs(nonDstTz) div secondsInMin) mod minutesInHour
|
||||||
|
if nonDstTz <= 0: buf.add('+')
|
||||||
else: buf.add('-')
|
else: buf.add('-')
|
||||||
if hours < 10: buf.add('0')
|
if hours < 10: buf.add('0')
|
||||||
buf.add($hours)
|
buf.add($hours)
|
||||||
buf.add(':')
|
buf.add(':')
|
||||||
if minutes < 10: buf.add('0')
|
if minutes < 10: buf.add('0')
|
||||||
buf.add($minutes)
|
buf.add($minutes)
|
||||||
|
|
||||||
of "":
|
of "":
|
||||||
discard
|
discard
|
||||||
else:
|
else:
|
||||||
|
|
@ -1000,7 +1002,6 @@ proc parseToken(info: var TimeInfo; token, value: string; j: var int) =
|
||||||
of "M":
|
of "M":
|
||||||
var pd = parseInt(value[j..j+1], sv)
|
var pd = parseInt(value[j..j+1], sv)
|
||||||
info.month = Month(sv-1)
|
info.month = Month(sv-1)
|
||||||
info.monthday = sv
|
|
||||||
j += pd
|
j += pd
|
||||||
of "MM":
|
of "MM":
|
||||||
var month = value[j..j+1].parseInt()
|
var month = value[j..j+1].parseInt()
|
||||||
|
|
@ -1089,27 +1090,42 @@ proc parseToken(info: var TimeInfo; token, value: string; j: var int) =
|
||||||
info.year = value[j..j+3].parseInt()
|
info.year = value[j..j+3].parseInt()
|
||||||
j += 4
|
j += 4
|
||||||
of "z":
|
of "z":
|
||||||
|
info.isDST = false
|
||||||
if value[j] == '+':
|
if value[j] == '+':
|
||||||
info.timezone = 0 - parseInt($value[j+1]) * secondsInHour
|
info.timezone = 0 - parseInt($value[j+1]) * secondsInHour
|
||||||
elif value[j] == '-':
|
elif value[j] == '-':
|
||||||
info.timezone = parseInt($value[j+1]) * secondsInHour
|
info.timezone = parseInt($value[j+1]) * secondsInHour
|
||||||
|
elif value[j] == 'Z':
|
||||||
|
info.timezone = 0
|
||||||
|
j += 1
|
||||||
|
return
|
||||||
else:
|
else:
|
||||||
raise newException(ValueError,
|
raise newException(ValueError,
|
||||||
"Couldn't parse timezone offset (z), got: " & value[j])
|
"Couldn't parse timezone offset (z), got: " & value[j])
|
||||||
j += 2
|
j += 2
|
||||||
of "zz":
|
of "zz":
|
||||||
|
info.isDST = false
|
||||||
if value[j] == '+':
|
if value[j] == '+':
|
||||||
info.timezone = 0 - value[j+1..j+2].parseInt() * secondsInHour
|
info.timezone = 0 - value[j+1..j+2].parseInt() * secondsInHour
|
||||||
elif value[j] == '-':
|
elif value[j] == '-':
|
||||||
info.timezone = value[j+1..j+2].parseInt() * secondsInHour
|
info.timezone = value[j+1..j+2].parseInt() * secondsInHour
|
||||||
|
elif value[j] == 'Z':
|
||||||
|
info.timezone = 0
|
||||||
|
j += 1
|
||||||
|
return
|
||||||
else:
|
else:
|
||||||
raise newException(ValueError,
|
raise newException(ValueError,
|
||||||
"Couldn't parse timezone offset (zz), got: " & value[j])
|
"Couldn't parse timezone offset (zz), got: " & value[j])
|
||||||
j += 3
|
j += 3
|
||||||
of "zzz":
|
of "zzz":
|
||||||
|
info.isDST = false
|
||||||
var factor = 0
|
var factor = 0
|
||||||
if value[j] == '+': factor = -1
|
if value[j] == '+': factor = -1
|
||||||
elif value[j] == '-': factor = 1
|
elif value[j] == '-': factor = 1
|
||||||
|
elif value[j] == 'Z':
|
||||||
|
info.timezone = 0
|
||||||
|
j += 1
|
||||||
|
return
|
||||||
else:
|
else:
|
||||||
raise newException(ValueError,
|
raise newException(ValueError,
|
||||||
"Couldn't parse timezone offset (zzz), got: " & value[j])
|
"Couldn't parse timezone offset (zzz), got: " & value[j])
|
||||||
|
|
@ -1122,8 +1138,11 @@ proc parseToken(info: var TimeInfo; token, value: string; j: var int) =
|
||||||
j += token.len
|
j += token.len
|
||||||
|
|
||||||
proc parse*(value, layout: string): TimeInfo =
|
proc parse*(value, layout: string): TimeInfo =
|
||||||
## This function parses a date/time string using the standard format identifiers (below)
|
## This function parses a date/time string using the standard format
|
||||||
## The function defaults information not provided in the format string from the running program (timezone, month, year, etc)
|
## identifiers as listed below. The function defaults information not provided
|
||||||
|
## in the format string from the running program (timezone, month, year, etc).
|
||||||
|
## Daylight saving time is only set if no timezone is given and the given date
|
||||||
|
## lies within the DST period of the current locale.
|
||||||
##
|
##
|
||||||
## ========== ================================================================================= ================================================
|
## ========== ================================================================================= ================================================
|
||||||
## Specifier Description Example
|
## Specifier Description Example
|
||||||
|
|
@ -1148,7 +1167,7 @@ proc parse*(value, layout: string): TimeInfo =
|
||||||
## tt Same as above, but ``AM`` and ``PM`` instead of ``A`` and ``P`` respectively.
|
## tt Same as above, but ``AM`` and ``PM`` instead of ``A`` and ``P`` respectively.
|
||||||
## yy Displays the year to two digits. ``2012 -> 12``
|
## yy Displays the year to two digits. ``2012 -> 12``
|
||||||
## yyyy Displays the year to four digits. ``2012 -> 2012``
|
## yyyy Displays the year to four digits. ``2012 -> 2012``
|
||||||
## z Displays the timezone offset from UTC. ``GMT+7 -> +7``, ``GMT-5 -> -5``
|
## z Displays the timezone offset from UTC. ``Z`` is parsed as ``+0`` ``GMT+7 -> +7``, ``GMT-5 -> -5``
|
||||||
## zz Same as above but with leading 0. ``GMT+7 -> +07``, ``GMT-5 -> -05``
|
## zz Same as above but with leading 0. ``GMT+7 -> +07``, ``GMT-5 -> -05``
|
||||||
## zzz Same as above but with ``:mm`` where *mm* represents minutes. ``GMT+7 -> +07:00``, ``GMT-5 -> -05:00``
|
## zzz Same as above but with ``:mm`` where *mm* represents minutes. ``GMT+7 -> +07:00``, ``GMT-5 -> -05:00``
|
||||||
## ========== ================================================================================= ================================================
|
## ========== ================================================================================= ================================================
|
||||||
|
|
@ -1166,6 +1185,8 @@ proc parse*(value, layout: string): TimeInfo =
|
||||||
info.hour = 0
|
info.hour = 0
|
||||||
info.minute = 0
|
info.minute = 0
|
||||||
info.second = 0
|
info.second = 0
|
||||||
|
info.isDST = true # using this is flag for checking whether a timezone has \
|
||||||
|
# been read (because DST is always false when a tz is parsed)
|
||||||
while true:
|
while true:
|
||||||
case layout[i]
|
case layout[i]
|
||||||
of ' ', '-', '/', ':', '\'', '\0', '(', ')', '[', ']', ',':
|
of ' ', '-', '/', ':', '\'', '\0', '(', ')', '[', ']', ',':
|
||||||
|
|
@ -1195,16 +1216,17 @@ proc parse*(value, layout: string): TimeInfo =
|
||||||
parseToken(info, token, value, j)
|
parseToken(info, token, value, j)
|
||||||
token = ""
|
token = ""
|
||||||
|
|
||||||
# We are going to process the date to find out if we are in DST, because the
|
if info.isDST:
|
||||||
# default based on the current time may be wrong. Calling getLocalTime will
|
# means that no timezone has been parsed. In this case, we need to check
|
||||||
# set this correctly, but the actual time may be offset from when we called
|
# whether the date is within DST of the local time.
|
||||||
# toTime with a possibly incorrect DST setting, so we are only going to take
|
let tmp = getLocalTime(toTime(info))
|
||||||
# the isDST from this result.
|
# correctly set isDST so that the following step works on the correct time
|
||||||
let correctDST = getLocalTime(toTime(info))
|
info.isDST = tmp.isDST
|
||||||
info.isDST = correctDST.isDST
|
|
||||||
|
|
||||||
# Now we process it again with the correct isDST to correct things like
|
# Correct weekday and yearday; transform timestamp to local time.
|
||||||
# weekday and yearday.
|
# There currently is no way of returning this with the original (parsed)
|
||||||
|
# timezone while also setting weekday and yearday (we are depending on stdlib
|
||||||
|
# to provide this calculation).
|
||||||
return getLocalTime(toTime(info))
|
return getLocalTime(toTime(info))
|
||||||
|
|
||||||
# Leap year calculations are adapted from:
|
# Leap year calculations are adapted from:
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,10 @@ parseTest("2006-01-12T15:04:05Z-07:00", "yyyy-MM-dd'T'HH:mm:ss'Z'zzz",
|
||||||
# RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"
|
# RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"
|
||||||
parseTest("2006-01-12T15:04:05.999999999Z-07:00",
|
parseTest("2006-01-12T15:04:05.999999999Z-07:00",
|
||||||
"yyyy-MM-ddTHH:mm:ss.999999999Zzzz", "2006-01-12T22:04:05+00:00", 11)
|
"yyyy-MM-ddTHH:mm:ss.999999999Zzzz", "2006-01-12T22:04:05+00:00", 11)
|
||||||
|
for tzFormat in ["z", "zz", "zzz"]:
|
||||||
|
# formatting timezone as 'Z' for UTC
|
||||||
|
parseTest("2001-01-12T22:04:05Z", "yyyy-MM-dd'T'HH:mm:ss" & tzFormat,
|
||||||
|
"2001-01-12T22:04:05+00:00", 11)
|
||||||
# Kitchen = "3:04PM"
|
# Kitchen = "3:04PM"
|
||||||
parseTestTimeOnly("3:04PM", "h:mmtt", "15:04:00")
|
parseTestTimeOnly("3:04PM", "h:mmtt", "15:04:00")
|
||||||
#when not defined(testing):
|
#when not defined(testing):
|
||||||
|
|
@ -201,4 +205,25 @@ for tz in [
|
||||||
let ti = TimeInfo(monthday: 1, timezone: tz[0])
|
let ti = TimeInfo(monthday: 1, timezone: tz[0])
|
||||||
doAssert ti.format("z") == tz[1]
|
doAssert ti.format("z") == tz[1]
|
||||||
doAssert ti.format("zz") == tz[2]
|
doAssert ti.format("zz") == tz[2]
|
||||||
doAssert ti.format("zzz") == tz[3]
|
doAssert ti.format("zzz") == tz[3]
|
||||||
|
|
||||||
|
block dstTest:
|
||||||
|
let nonDst = TimeInfo(year: 2015, month: mJan, monthday: 01, yearday: 0,
|
||||||
|
weekday: dThu, hour: 00, minute: 00, second: 00, isDST: false, timezone: 0)
|
||||||
|
var dst = nonDst
|
||||||
|
dst.isDst = true
|
||||||
|
# note that both isDST == true and isDST == false are valid here because
|
||||||
|
# DST is in effect on January 1st in some southern parts of Australia.
|
||||||
|
|
||||||
|
doAssert nonDst.toTime() - dst.toTime() == 3600
|
||||||
|
doAssert nonDst.format("z") == "+0"
|
||||||
|
doAssert dst.format("z") == "+1"
|
||||||
|
|
||||||
|
# parsing will set isDST in relation to the local time. We take a date in
|
||||||
|
# January and one in July to maximize the probability to hit one date with DST
|
||||||
|
# and one without on the local machine. However, this is not guaranteed.
|
||||||
|
let
|
||||||
|
parsedJan = parse("2016-01-05 04:00:00+01:00", "yyyy-MM-dd HH:mm:sszzz")
|
||||||
|
parsedJul = parse("2016-07-01 04:00:00+01:00", "yyyy-MM-dd HH:mm:sszzz")
|
||||||
|
doAssert toTime(parsedJan) == fromSeconds(1451962800)
|
||||||
|
doAssert toTime(parsedJul) == fromSeconds(1467342000)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue