Sub second time resolution (#6978)

* Add deprecation warnings to recently deprecated procs

* Fix bad usage of the times module

* Introduce sub second resolution

* Fix usage of C's time()

* Switch to nanosecond resolution

* Make Time & Duration opaque again and fix some errors

* Change back to TimeInterval for shorthands

* Fix JS test

* Fix build error for windows

* Undeprecate epochTime

* Documentation and minor changes

* Lots of bugfixes and doc comments

* Attempt to make travis & appveyor green

* Fix edge cases for dealing with the local timezone

* Workaround JS backend overflow/underflow bug

* Use better workaround for not knowing the size of time_t

* Use all available timezones for tests

* Fix indentation

* Add procs for accessing the fractional part of a duration

* Order time units from smallest to largest since it makes more sense

* Include months and years in `TimeUnit`

* Review fix
This commit is contained in:
Oscar Nihlgård
2018-04-13 07:36:31 +02:00
committed by Andreas Rumpf
parent 19a1cc914f
commit f6df2d9956
7 changed files with 699 additions and 296 deletions

View File

@@ -77,8 +77,7 @@ proc setupVM*(module: PSym; cache: IdentCache; scriptName: string;
cbos copyDir:
os.copyDir(getString(a, 0), getString(a, 1))
cbos getLastModificationTime:
# depends on Time's implementation!
setResult(a, int64(getLastModificationTime(getString(a, 0))))
setResult(a, getLastModificationTime(getString(a, 0)).toUnix)
cbos findExe:
setResult(a, os.findExe(getString(a, 0)))

View File

@@ -71,7 +71,7 @@ proc genOid*(): Oid =
proc rand(): cint {.importc: "rand", header: "<stdlib.h>", nodecl.}
proc srand(seed: cint) {.importc: "srand", header: "<stdlib.h>", nodecl.}
var t = getTime().int32
var t = getTime().toUnix.int32
var i = int32(atomicInc(incr))

View File

@@ -225,10 +225,10 @@ proc fileNewer*(a, b: string): bool {.rtl, extern: "nos$1".} =
## Returns true if the file `a` is newer than file `b`, i.e. if `a`'s
## modification time is later than `b`'s.
when defined(posix):
result = getLastModificationTime(a) - getLastModificationTime(b) >= 0
result = getLastModificationTime(a) - getLastModificationTime(b) >= DurationZero
# Posix's resolution sucks so, we use '>=' for posix.
else:
result = getLastModificationTime(a) - getLastModificationTime(b) > 0
result = getLastModificationTime(a) - getLastModificationTime(b) > DurationZero
proc getCurrentDir*(): string {.rtl, extern: "nos$1", tags: [].} =
## Returns the `current working directory`:idx:.

View File

@@ -191,8 +191,8 @@ when not defined(nimscript):
proc randomize*() {.benign.} =
## Initializes the random number generator with a "random"
## number, i.e. a tickcount. Note: Does not work for NimScript.
let time = int64(times.epochTime() * 1_000_000_000)
randomize(time)
let now = times.getTime()
randomize(convert(Seconds, Nanoseconds, now.toUnix) + now.nanoseconds)
{.pop.}

File diff suppressed because it is too large Load Diff

View File

@@ -17,14 +17,14 @@ block localTime:
let a = fromUnix(1_000_000_000)
let b = fromUnix(1_500_000_000)
doAssert b - a == 500_000_000
doAssert b - a == initDuration(seconds = 500_000_000)
# Because we can't change the timezone JS uses, we define a simple static timezone for testing.
proc staticZoneInfoFromUtc(time: Time): ZonedTime =
result.utcOffset = -7200
result.isDst = false
result.adjTime = (time.toUnix + 7200).Time
result.adjTime = time + 7200.seconds
proc staticZoneInfoFromTz(adjTime: Time): ZonedTIme =
result.utcOffset = -7200
@@ -40,5 +40,4 @@ block timezoneTests:
doAssert $dt.utc.inZone(utcPlus2) == $dt
doAssert $initDateTime(01, mJan, 1911, 12, 00, 00, utc()) == "1911-01-01T12:00:00+00:00"
# See #6752
# doAssert $initDateTime(01, mJan, 1900, 12, 00, 00, utc()) == "0023-01-01T12:00:00+00:00"
doAssert $initDateTime(01, mJan, 0023, 12, 00, 00, utc()) == "0023-01-01T12:00:00+00:00"

View File

@@ -37,8 +37,8 @@ var t4 = fromUnix(876124714).utc # Mon 6 Oct 08:58:34 BST 1997
t4.checkFormat("M MM MMM MMMM", "10 10 Oct October")
# Interval tests
(t4 - initInterval(years = 2)).checkFormat("yyyy", "1995")
(t4 - initInterval(years = 7, minutes = 34, seconds = 24)).checkFormat("yyyy mm ss", "1990 24 10")
(t4 - initTimeInterval(years = 2)).checkFormat("yyyy", "1995")
(t4 - initTimeInterval(years = 7, minutes = 34, seconds = 24)).checkFormat("yyyy mm ss", "1990 24 10")
# checking dayOfWeek matches known days
doAssert getDayOfWeek(01, mJan, 0000) == dSat
@@ -56,32 +56,34 @@ doAssert toUnix(toTime(t4L)) + t4L.utcOffset == toUnix(toTime(t4))
# adding intervals
var
a1L = toUnix(toTime(t4L + initInterval(hours = 1))) + t4L.utcOffset
a1L = toUnix(toTime(t4L + initTimeInterval(hours = 1))) + t4L.utcOffset
a1G = toUnix(toTime(t4)) + 60 * 60
doAssert a1L == a1G
# subtracting intervals
a1L = toUnix(toTime(t4L - initInterval(hours = 1))) + t4L.utcOffset
a1L = toUnix(toTime(t4L - initTimeInterval(hours = 1))) + t4L.utcOffset
a1G = toUnix(toTime(t4)) - (60 * 60)
doAssert a1L == a1G
# add/subtract TimeIntervals and Time/TimeInfo
doAssert getTime() - 1.seconds == getTime() - 3.seconds + 2.seconds
doAssert getTime() + 65.seconds == getTime() + 1.minutes + 5.seconds
doAssert getTime() + 60.minutes == getTime() + 1.hours
doAssert getTime() + 24.hours == getTime() + 1.days
doAssert getTime() + 13.months == getTime() + 1.years + 1.months
var
ti1 = getTime() + 1.years
ti1 -= 1.years
doAssert ti1 == getTime()
ti1 += 1.days
doAssert ti1 == getTime() + 1.days
let now = getTime().utc
doAssert now + convert(Seconds, Nanoseconds, 1).nanoseconds == now + 1.seconds
doAssert now + 1.weeks == now + 7.days
doAssert now - 1.seconds == now - 3.seconds + 2.seconds
doAssert now + 65.seconds == now + 1.minutes + 5.seconds
doAssert now + 60.minutes == now + 1.hours
doAssert now + 24.hours == now + 1.days
doAssert now + 13.months == now + 1.years + 1.months
var ti1 = now + 1.years
ti1 = ti1 - 1.years
doAssert ti1 == now
ti1 = ti1 + 1.days
doAssert ti1 == now + 1.days
# Bug with adding a day to a Time
let day = 24.hours
let tomorrow = getTime() + day
doAssert tomorrow - getTime() == 60*60*24
let tomorrow = now + day
doAssert tomorrow - now == initDuration(days = 1)
# Comparison between Time objects should be detected by compiler
# as 'noSideEffect'.
@@ -207,8 +209,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) == fromUnix(1451962800)
doAssert toTime(parsedJul) == fromUnix(1467342000)
doAssert toTime(parsedJan).toUnix == 1451962800
doAssert toTime(parsedJul).toUnix == 1467342000
suite "ttimes":
@@ -220,7 +222,7 @@ suite "ttimes":
let orig_tz = getEnv("TZ")
var tz_cnt = 0
for tz_fn in walkFiles(tz_dir & "/*"):
for tz_fn in walkFiles(tz_dir & "/**/*"):
if symlinkExists(tz_fn) or tz_fn.endsWith(".tab") or
tz_fn.endsWith(".list"):
continue
@@ -252,19 +254,20 @@ suite "ttimes":
var local = fromUnix(1469275200).local
var utc = fromUnix(1469275200).utc
let claimedOffset = local.utcOffset
let claimedOffset = initDuration(seconds = local.utcOffset)
local.utcOffset = 0
check claimedOffset == utc.toTime - local.toTime
test "issue #5704":
putEnv("TZ", "Asia/Seoul")
let diff = parse("19700101-000000", "yyyyMMdd-hhmmss").toTime - parse("19000101-000000", "yyyyMMdd-hhmmss").toTime
check diff == 2208986872
check diff == initDuration(seconds = 2208986872)
test "issue #6465":
putEnv("TZ", "Europe/Stockholm")
let dt = parse("2017-03-25 12:00", "yyyy-MM-dd hh:mm")
check $(dt + 1.days) == "2017-03-26T12:00:00+02:00"
check $(dt + initTimeInterval(days = 1)) == "2017-03-26T12:00:00+02:00"
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"
@@ -362,12 +365,40 @@ suite "ttimes":
test "subtract months":
var dt = initDateTime(1, mFeb, 2017, 00, 00, 00, utc())
check $(dt - 1.months) == "2017-01-01T00:00:00+00:00"
check $(dt - initTimeInterval(months = 1)) == "2017-01-01T00:00:00+00:00"
dt = initDateTime(15, mMar, 2017, 00, 00, 00, utc())
check $(dt - 1.months) == "2017-02-15T00:00:00+00:00"
check $(dt - initTimeInterval(months = 1)) == "2017-02-15T00:00:00+00:00"
dt = initDateTime(31, mMar, 2017, 00, 00, 00, utc())
# This happens due to monthday overflow. It's consistent with Phobos.
check $(dt - 1.months) == "2017-03-03T00:00:00+00:00"
check $(dt - initTimeInterval(months = 1)) == "2017-03-03T00:00:00+00:00"
test "duration":
let d = initDuration
check d(hours = 48) + d(days = 5) == d(weeks = 1)
let dt = initDateTime(01, mFeb, 2000, 00, 00, 00, 0, utc()) + d(milliseconds = 1)
check dt.nanosecond == convert(Milliseconds, Nanoseconds, 1)
check d(seconds = 1, milliseconds = 500) * 2 == d(seconds = 3)
check d(seconds = 3) div 2 == d(seconds = 1, milliseconds = 500)
check d(milliseconds = 1001).seconds == 1
check d(seconds = 1, milliseconds = 500) - d(milliseconds = 1250) ==
d(milliseconds = 250)
check d(seconds = 1, milliseconds = 1) < d(seconds = 1, milliseconds = 2)
check d(seconds = 1) <= d(seconds = 1)
check d(seconds = 0) - d(milliseconds = 1500) == d(milliseconds = -1500)
check d(milliseconds = -1500) == d(seconds = -1, milliseconds = -500)
check d(seconds = -1, milliseconds = 500) == d(milliseconds = -500)
test "large/small dates":
discard initDateTime(1, mJan, -35_000, 12, 00, 00, utc())
# with local tz
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()) +
initDuration(seconds = 1)
check dt.second == 1
let dt2 = dt + 35_001.years
check $dt2 == "0001-01-01T12:00:01+00:00"
test "compare datetimes":
var dt1 = now()