mirror of
https://github.com/nim-lang/Nim.git
synced 2026-02-17 00:24:16 +00:00
New implementations of times.parse & times.format (#8094)
This commit is contained in:
committed by
Andreas Rumpf
parent
c6671776a1
commit
3b310e91cd
@@ -683,8 +683,8 @@ when isMainModule:
|
||||
# works:
|
||||
import times
|
||||
|
||||
var nullTime: DateTime
|
||||
check &"{nullTime:yyyy-mm-dd}", "0000-00-00"
|
||||
var dt = initDateTime(01, mJan, 2000, 00, 00, 00)
|
||||
check &"{dt:yyyy-MM-dd}", "2000-01-01"
|
||||
|
||||
var tm = fromUnix(0)
|
||||
discard &"{tm}"
|
||||
|
||||
1374
lib/pure/times.nim
1374
lib/pure/times.nim
File diff suppressed because it is too large
Load Diff
@@ -36,8 +36,8 @@ let utcPlus2 = Timezone(zoneInfoFromUtc: staticZoneInfoFromUtc, zoneInfoFromTz:
|
||||
block timezoneTests:
|
||||
let dt = initDateTime(01, mJan, 2017, 12, 00, 00, utcPlus2)
|
||||
doAssert $dt == "2017-01-01T12:00:00+02:00"
|
||||
doAssert $dt.utc == "2017-01-01T10:00:00+00:00"
|
||||
doAssert $dt.utc == "2017-01-01T10:00:00Z"
|
||||
doAssert $dt.utc.inZone(utcPlus2) == $dt
|
||||
|
||||
doAssert $initDateTime(01, mJan, 1911, 12, 00, 00, utc()) == "1911-01-01T12:00:00+00:00"
|
||||
doAssert $initDateTime(01, mJan, 0023, 12, 00, 00, utc()) == "0023-01-01T12:00:00+00:00"
|
||||
doAssert $initDateTime(01, mJan, 1911, 12, 00, 00, utc()) == "1911-01-01T12:00:00Z"
|
||||
doAssert $initDateTime(01, mJan, 0023, 12, 00, 00, utc()) == "0023-01-01T12:00:00Z"
|
||||
@@ -8,6 +8,24 @@ discard """
|
||||
import
|
||||
times, os, strutils, unittest
|
||||
|
||||
proc staticTz(hours, minutes, seconds: int = 0): Timezone {.noSideEffect.} =
|
||||
let offset = hours * 3600 + minutes * 60 + seconds
|
||||
|
||||
proc zoneInfoFromTz(adjTime: Time): ZonedTime {.locks: 0.} =
|
||||
result.isDst = false
|
||||
result.utcOffset = offset
|
||||
result.adjTime = adjTime
|
||||
|
||||
proc zoneInfoFromUtc(time: Time): ZonedTime {.locks: 0.}=
|
||||
result.isDst = false
|
||||
result.utcOffset = offset
|
||||
result.adjTime = fromUnix(time.toUnix - offset)
|
||||
|
||||
result.name = ""
|
||||
result.zoneInfoFromTz = zoneInfoFromTz
|
||||
result.zoneInfoFromUtc = zoneInfoFromUtc
|
||||
|
||||
|
||||
# $ date --date='@2147483647'
|
||||
# Tue 19 Jan 03:14:07 GMT 2038
|
||||
|
||||
@@ -19,25 +37,10 @@ proc checkFormat(t: DateTime, format, expected: string) =
|
||||
echo "actual : ", actual
|
||||
doAssert false
|
||||
|
||||
let t = fromUnix(2147483647).utc
|
||||
t.checkFormat("ddd dd MMM hh:mm:ss yyyy", "Tue 19 Jan 03:14:07 2038")
|
||||
t.checkFormat("ddd ddMMMhh:mm:ssyyyy", "Tue 19Jan03:14:072038")
|
||||
t.checkFormat("d dd ddd dddd h hh H HH m mm M MM MMM MMMM s" &
|
||||
" ss t tt y yy yyy yyyy yyyyy z zz zzz",
|
||||
"19 19 Tue Tuesday 3 03 3 03 14 14 1 01 Jan January 7 07 A AM 8 38 038 2038 02038 +0 +00 +00:00")
|
||||
|
||||
t.checkFormat("yyyyMMddhhmmss", "20380119031407")
|
||||
|
||||
# issue 7620
|
||||
let t7620_am = parse("4/15/2017 12:01:02 AM +0", "M/d/yyyy' 'h:mm:ss' 'tt' 'z", utc())
|
||||
t7620_am.checkFormat("M/d/yyyy' 'h:mm:ss' 'tt' 'z", "4/15/2017 12:01:02 AM +0")
|
||||
let t7620_pm = parse("4/15/2017 12:01:02 PM +0", "M/d/yyyy' 'h:mm:ss' 'tt' 'z", utc())
|
||||
t7620_pm.checkFormat("M/d/yyyy' 'h:mm:ss' 'tt' 'z", "4/15/2017 12:01:02 PM +0")
|
||||
|
||||
let t2 = fromUnix(160070789).utc # Mon 27 Jan 16:06:29 GMT 1975
|
||||
let t2 = fromUnix(160070789).utc() # Mon 27 Jan 16:06:29 GMT 1975
|
||||
t2.checkFormat("d dd ddd dddd h hh H HH m mm M MM MMM MMMM s" &
|
||||
" ss t tt y yy yyy yyyy yyyyy z zz zzz",
|
||||
"27 27 Mon Monday 4 04 16 16 6 06 1 01 Jan January 29 29 P PM 5 75 975 1975 01975 +0 +00 +00:00")
|
||||
"27 27 Mon Monday 4 04 16 16 6 06 1 01 Jan January 29 29 P PM 5 75 975 1975 01975 Z Z Z")
|
||||
|
||||
var t4 = fromUnix(876124714).utc # Mon 6 Oct 08:58:34 BST 1997
|
||||
t4.checkFormat("M MM MMM MMMM", "10 10 Oct October")
|
||||
@@ -83,16 +86,16 @@ let seqB: seq[Time] = @[]
|
||||
doAssert seqA == seqB
|
||||
|
||||
for tz in [
|
||||
(0, "+0", "+00", "+00:00"), # UTC
|
||||
(-3600, "+1", "+01", "+01:00"), # CET
|
||||
(-39600, "+11", "+11", "+11:00"), # two digits
|
||||
(-1800, "+0", "+00", "+00:30"), # half an hour
|
||||
(7200, "-2", "-02", "-02:00"), # positive
|
||||
(38700, "-10", "-10", "-10:45")]: # positive with three quaters hour
|
||||
let ti = DateTime(month: mJan, monthday: 1, utcOffset: tz[0])
|
||||
doAssert ti.format("z") == tz[1]
|
||||
doAssert ti.format("zz") == tz[2]
|
||||
doAssert ti.format("zzz") == tz[3]
|
||||
(staticTz(seconds = 0), "+0", "+00", "+00:00"), # UTC
|
||||
(staticTz(seconds = -3600), "+1", "+01", "+01:00"), # CET
|
||||
(staticTz(seconds = -39600), "+11", "+11", "+11:00"), # two digits
|
||||
(staticTz(seconds = -1800), "+0", "+00", "+00:30"), # half an hour
|
||||
(staticTz(seconds = 7200), "-2", "-02", "-02:00"), # positive
|
||||
(staticTz(seconds = 38700), "-10", "-10", "-10:45")]: # positive with three quaters hour
|
||||
let dt = initDateTime(1, mJan, 2000, 00, 00, 00, tz[0])
|
||||
doAssert dt.format("z") == tz[1]
|
||||
doAssert dt.format("zz") == tz[2]
|
||||
doAssert dt.format("zzz") == tz[3]
|
||||
|
||||
block countLeapYears:
|
||||
# 1920, 2004 and 2020 are leap years, and should be counted starting at the following year
|
||||
@@ -112,11 +115,9 @@ template parseTest(s, f, sExpected: string, ydExpected: int) =
|
||||
let
|
||||
parsed = s.parse(f, utc())
|
||||
parsedStr = $parsed
|
||||
if parsedStr != sExpected:
|
||||
echo "GOT ", parsedStr, " EXPECTED ", sExpected, " FORMAT ", f
|
||||
check parsedStr == sExpected
|
||||
if parsed.yearday != ydExpected:
|
||||
echo s
|
||||
echo parsed.repr
|
||||
echo parsed.yearday, " exp: ", ydExpected
|
||||
check(parsed.yearday == ydExpected)
|
||||
|
||||
template parseTestExcp(s, f: string) =
|
||||
@@ -130,51 +131,43 @@ template parseTestTimeOnly(s, f, sExpected: string) =
|
||||
# explicit timezone offsets in all tests.
|
||||
template runTimezoneTests() =
|
||||
parseTest("Tuesday at 09:04am on Dec 15, 2015 +0",
|
||||
"dddd at hh:mmtt on MMM d, yyyy z", "2015-12-15T09:04:00+00:00", 348)
|
||||
"dddd 'at' hh:mmtt 'on' MMM d, yyyy z", "2015-12-15T09:04:00Z", 348)
|
||||
# ANSIC = "Mon Jan _2 15:04:05 2006"
|
||||
parseTest("Thu Jan 12 15:04:05 2006 +0", "ddd MMM dd HH:mm:ss yyyy z",
|
||||
"2006-01-12T15:04:05+00:00", 11)
|
||||
"2006-01-12T15:04:05Z", 11)
|
||||
# UnixDate = "Mon Jan _2 15:04:05 MST 2006"
|
||||
parseTest("Thu Jan 12 15:04:05 2006 +0", "ddd MMM dd HH:mm:ss yyyy z",
|
||||
"2006-01-12T15:04:05+00:00", 11)
|
||||
"2006-01-12T15:04:05Z", 11)
|
||||
# RubyDate = "Mon Jan 02 15:04:05 -0700 2006"
|
||||
parseTest("Mon Feb 29 15:04:05 -07:00 2016 +0", "ddd MMM dd HH:mm:ss zzz yyyy z",
|
||||
"2016-02-29T15:04:05+00:00", 59) # leap day
|
||||
"2016-02-29T15:04:05Z", 59) # leap day
|
||||
# RFC822 = "02 Jan 06 15:04 MST"
|
||||
parseTest("12 Jan 16 15:04 +0", "dd MMM yy HH:mm z",
|
||||
"2016-01-12T15:04:00+00:00", 11)
|
||||
"2016-01-12T15:04:00Z", 11)
|
||||
# RFC822Z = "02 Jan 06 15:04 -0700" # RFC822 with numeric zone
|
||||
parseTest("01 Mar 16 15:04 -07:00", "dd MMM yy HH:mm zzz",
|
||||
"2016-03-01T22:04:00+00:00", 60) # day after february in leap year
|
||||
"2016-03-01T22:04:00Z", 60) # day after february in leap year
|
||||
# RFC850 = "Monday, 02-Jan-06 15:04:05 MST"
|
||||
parseTest("Monday, 12-Jan-06 15:04:05 +0", "dddd, dd-MMM-yy HH:mm:ss z",
|
||||
"2006-01-12T15:04:05+00:00", 11)
|
||||
"2006-01-12T15:04:05Z", 11)
|
||||
# RFC1123 = "Mon, 02 Jan 2006 15:04:05 MST"
|
||||
parseTest("Sun, 01 Mar 2015 15:04:05 +0", "ddd, dd MMM yyyy HH:mm:ss z",
|
||||
"2015-03-01T15:04:05+00:00", 59) # day after february in non-leap year
|
||||
"2015-03-01T15:04:05Z", 59) # day after february in non-leap year
|
||||
# RFC1123Z = "Mon, 02 Jan 2006 15:04:05 -0700" # RFC1123 with numeric zone
|
||||
parseTest("Thu, 12 Jan 2006 15:04:05 -07:00", "ddd, dd MMM yyyy HH:mm:ss zzz",
|
||||
"2006-01-12T22:04:05+00:00", 11)
|
||||
"2006-01-12T22:04:05Z", 11)
|
||||
# RFC3339 = "2006-01-02T15:04:05Z07:00"
|
||||
parseTest("2006-01-12T15:04:05Z-07:00", "yyyy-MM-ddTHH:mm:ssZzzz",
|
||||
"2006-01-12T22:04:05+00:00", 11)
|
||||
parseTest("2006-01-12T15:04:05Z-07:00", "yyyy-MM-dd'T'HH:mm:ss'Z'zzz",
|
||||
"2006-01-12T22:04:05+00:00", 11)
|
||||
"2006-01-12T22:04:05Z", 11)
|
||||
# RFC3339Nano = "2006-01-02T15:04:05.999999999Z07: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-dd'T'HH:mm:ss'.999999999Z'zzz", "2006-01-12T22:04:05Z", 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)
|
||||
"2001-01-12T22:04:05Z", 11)
|
||||
# Kitchen = "3:04PM"
|
||||
parseTestTimeOnly("3:04PM", "h:mmtt", "15:04:00")
|
||||
#when not defined(testing):
|
||||
# echo "Kitchen: " & $s.parse(f)
|
||||
# var ti = timeToTimeInfo(getTime())
|
||||
# echo "Todays date after decoding: ", ti
|
||||
# var tint = timeToTimeInterval(getTime())
|
||||
# echo "Todays date after decoding to interval: ", tint
|
||||
|
||||
# Bug with parse not setting DST properly if the current local DST differs from
|
||||
# the date being parsed. Need to test parse dates both in and out of DST. We
|
||||
@@ -195,8 +188,8 @@ template runTimezoneTests() =
|
||||
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).toUnix == 1451962800
|
||||
doAssert toTime(parsedJul).toUnix == 1467342000
|
||||
check toTime(parsedJan).toUnix == 1451962800
|
||||
check toTime(parsedJul).toUnix == 1467342000
|
||||
|
||||
suite "ttimes":
|
||||
|
||||
@@ -256,7 +249,7 @@ suite "ttimes":
|
||||
check $(dt + initDuration(days = 1)) == "2017-03-26T13:00:00+02:00"
|
||||
|
||||
test "datetime before epoch":
|
||||
check $fromUnix(-2147483648).utc == "1901-12-13T20:45:52+00:00"
|
||||
check $fromUnix(-2147483648).utc == "1901-12-13T20:45:52Z"
|
||||
|
||||
test "adding/subtracting time across dst":
|
||||
putenv("TZ", "Europe/Stockholm")
|
||||
@@ -319,6 +312,15 @@ suite "ttimes":
|
||||
test "incorrect inputs: timezone (zzz) 3":
|
||||
parseTestExcp("2018-02-19 16:30:00 +01:0", "yyyy-MM-dd hh:mm:ss zzz")
|
||||
|
||||
test "incorrect inputs: year (yyyy/uuuu)":
|
||||
parseTestExcp("-0001", "yyyy")
|
||||
parseTestExcp("-0001", "YYYY")
|
||||
parseTestExcp("1", "yyyy")
|
||||
parseTestExcp("12345", "yyyy")
|
||||
parseTestExcp("1", "uuuu")
|
||||
parseTestExcp("12345", "uuuu")
|
||||
parseTestExcp("-1 BC", "UUUU g")
|
||||
|
||||
test "dynamic timezone":
|
||||
proc staticOffset(offset: int): Timezone =
|
||||
proc zoneInfoFromTz(adjTime: Time): ZonedTime =
|
||||
@@ -340,7 +342,7 @@ suite "ttimes":
|
||||
check dt.utcOffset == -9000
|
||||
check dt.isDst == false
|
||||
check $dt == "2000-01-01T12:00:00+02:30"
|
||||
check $dt.utc == "2000-01-01T09:30:00+00:00"
|
||||
check $dt.utc == "2000-01-01T09:30:00Z"
|
||||
check $dt.utc.inZone(tz) == $dt
|
||||
|
||||
test "isLeapYear":
|
||||
@@ -351,12 +353,12 @@ suite "ttimes":
|
||||
|
||||
test "subtract months":
|
||||
var dt = initDateTime(1, mFeb, 2017, 00, 00, 00, utc())
|
||||
check $(dt - initTimeInterval(months = 1)) == "2017-01-01T00:00:00+00:00"
|
||||
check $(dt - initTimeInterval(months = 1)) == "2017-01-01T00:00:00Z"
|
||||
dt = initDateTime(15, mMar, 2017, 00, 00, 00, utc())
|
||||
check $(dt - initTimeInterval(months = 1)) == "2017-02-15T00:00:00+00:00"
|
||||
check $(dt - initTimeInterval(months = 1)) == "2017-02-15T00:00:00Z"
|
||||
dt = initDateTime(31, mMar, 2017, 00, 00, 00, utc())
|
||||
# This happens due to monthday overflow. It's consistent with Phobos.
|
||||
check $(dt - initTimeInterval(months = 1)) == "2017-03-03T00:00:00+00:00"
|
||||
check $(dt - initTimeInterval(months = 1)) == "2017-03-03T00:00:00Z"
|
||||
|
||||
test "duration":
|
||||
let d = initDuration
|
||||
@@ -384,11 +386,11 @@ suite "ttimes":
|
||||
discard initDateTime(1, mJan, -35_000, 12, 00, 00)
|
||||
discard initDateTime(1, mJan, 35_000, 12, 00, 00)
|
||||
# with duration/timeinterval
|
||||
let dt = initDateTime(1, mJan, 35_000, 12, 00, 00, utc()) +
|
||||
let dt = initDateTime(1, mJan, -35_000, 12, 00, 00, utc()) +
|
||||
initDuration(seconds = 1)
|
||||
check dt.second == 1
|
||||
let dt2 = dt + 35_001.years
|
||||
check $dt2 == "0001-01-01T12:00:01+00:00"
|
||||
check $dt2 == "0001-01-01T12:00:01Z"
|
||||
|
||||
test "compare datetimes":
|
||||
var dt1 = now()
|
||||
@@ -426,4 +428,90 @@ suite "ttimes":
|
||||
check (-1).fromWinTime.nanosecond == convert(Seconds, Nanoseconds, 1) - 100
|
||||
check -1.fromWinTime.toWinTime == -1
|
||||
# One nanosecond is discarded due to differences in time resolution
|
||||
check initTime(0, 101).toWinTime.fromWinTime.nanosecond == 100
|
||||
check initTime(0, 101).toWinTime.fromWinTime.nanosecond == 100
|
||||
check initTime(0, 101).toWinTime.fromWinTime.nanosecond == 100
|
||||
|
||||
test "issue 7620":
|
||||
let layout = "M/d/yyyy' 'h:mm:ss' 'tt' 'z"
|
||||
let t7620_am = parse("4/15/2017 12:01:02 AM +0", layout, utc())
|
||||
check t7620_am.format(layout) == "4/15/2017 12:01:02 AM Z"
|
||||
let t7620_pm = parse("4/15/2017 12:01:02 PM +0", layout, utc())
|
||||
check t7620_pm.format(layout) == "4/15/2017 12:01:02 PM Z"
|
||||
|
||||
test "format":
|
||||
var dt = initDateTime(1, mJan, -0001,
|
||||
17, 01, 02, 123_456_789,
|
||||
staticTz(hours = 1, minutes = 2, seconds = 3))
|
||||
check dt.format("d") == "1"
|
||||
check dt.format("dd") == "01"
|
||||
check dt.format("ddd") == "Fri"
|
||||
check dt.format("dddd") == "Friday"
|
||||
check dt.format("h") == "5"
|
||||
check dt.format("hh") == "05"
|
||||
check dt.format("H") == "17"
|
||||
check dt.format("HH") == "17"
|
||||
check dt.format("m") == "1"
|
||||
check dt.format("mm") == "01"
|
||||
check dt.format("M") == "1"
|
||||
check dt.format("MM") == "01"
|
||||
check dt.format("MMM") == "Jan"
|
||||
check dt.format("MMMM") == "January"
|
||||
check dt.format("s") == "2"
|
||||
check dt.format("ss") == "02"
|
||||
check dt.format("t") == "P"
|
||||
check dt.format("tt") == "PM"
|
||||
check dt.format("yy") == "02"
|
||||
check dt.format("yyyy") == "0002"
|
||||
check dt.format("YYYY") == "2"
|
||||
check dt.format("uuuu") == "-0001"
|
||||
check dt.format("UUUU") == "-1"
|
||||
check dt.format("z") == "-1"
|
||||
check dt.format("zz") == "-01"
|
||||
check dt.format("zzz") == "-01:02"
|
||||
check dt.format("zzzz") == "-01:02:03"
|
||||
check dt.format("g") == "BC"
|
||||
|
||||
check dt.format("fff") == "123"
|
||||
check dt.format("ffffff") == "123456"
|
||||
check dt.format("fffffffff") == "123456789"
|
||||
dt.nanosecond = 1
|
||||
check dt.format("fff") == "000"
|
||||
check dt.format("ffffff") == "000000"
|
||||
check dt.format("fffffffff") == "000000001"
|
||||
|
||||
dt.year = 12345
|
||||
check dt.format("yyyy") == "+12345"
|
||||
check dt.format("uuuu") == "+12345"
|
||||
dt.year = -12345
|
||||
check dt.format("yyyy") == "+12346"
|
||||
check dt.format("uuuu") == "-12345"
|
||||
|
||||
expect ValueError:
|
||||
discard initTimeFormat("'")
|
||||
|
||||
expect ValueError:
|
||||
discard initTimeFormat("'foo")
|
||||
|
||||
expect ValueError:
|
||||
discard initTimeFormat("foo'")
|
||||
|
||||
test "parse":
|
||||
check $parse("20180101", "yyyyMMdd", utc()) == "2018-01-01T00:00:00Z"
|
||||
parseTestExcp("+120180101", "yyyyMMdd")
|
||||
|
||||
check parse("1", "YYYY", utc()).year == 1
|
||||
check parse("1 BC", "YYYY g", utc()).year == 0
|
||||
check parse("0001 BC", "yyyy g", utc()).year == 0
|
||||
check parse("+12345 BC", "yyyy g", utc()).year == -12344
|
||||
check parse("1 AD", "YYYY g", utc()).year == 1
|
||||
check parse("0001 AD", "yyyy g", utc()).year == 1
|
||||
check parse("+12345 AD", "yyyy g", utc()).year == 12345
|
||||
|
||||
check parse("-1", "UUUU", utc()).year == -1
|
||||
check parse("-0001", "uuuu", utc()).year == -1
|
||||
|
||||
discard parse("foobar", "'foobar'")
|
||||
discard parse("foo'bar", "'foo''''bar'")
|
||||
discard parse("'", "''")
|
||||
|
||||
parseTestExcp("2000 A", "yyyy g")
|
||||
|
||||
Reference in New Issue
Block a user