diff options
author | Oscar NihlgÄrd <oscarnihlgard@gmail.com> | 2018-04-13 07:36:31 +0200 |
---|---|---|
committer | Andreas Rumpf <rumpf_a@web.de> | 2018-04-13 07:36:30 +0200 |
commit | f6df2d9956a79eda048639107274ce60cbcf3542 (patch) | |
tree | ce567080709419e0f04448335eba9ff24b1b3c15 /lib/pure | |
parent | 19a1cc914fb8a744243a9730f120f6e932134106 (diff) | |
download | Nim-f6df2d9956a79eda048639107274ce60cbcf3542.tar.gz |
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
Diffstat (limited to 'lib/pure')
-rw-r--r-- | lib/pure/oids.nim | 2 | ||||
-rw-r--r-- | lib/pure/os.nim | 4 | ||||
-rw-r--r-- | lib/pure/random.nim | 4 | ||||
-rw-r--r-- | lib/pure/times.nim | 894 |
4 files changed, 639 insertions, 265 deletions
diff --git a/lib/pure/oids.nim b/lib/pure/oids.nim index 427a68964..26a55de6c 100644 --- a/lib/pure/oids.nim +++ b/lib/pure/oids.nim @@ -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)) diff --git a/lib/pure/os.nim b/lib/pure/os.nim index c581996f6..c06a9bd79 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -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:. diff --git a/lib/pure/random.nim b/lib/pure/random.nim index 97ad12b99..968a326d2 100644 --- a/lib/pure/random.nim +++ b/lib/pure/random.nim @@ -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.} diff --git a/lib/pure/times.nim b/lib/pure/times.nim index c53a300d2..2f1e0dfd6 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -1,16 +1,18 @@ # # # Nim's Runtime Library -# (c) Copyright 2015 Andreas Rumpf +# (c) Copyright 2017 Nim contributors # # See the file "copying.txt", included in this # distribution, for details about the copyright. # -## This module contains routines and types for dealing with time. -## This module is available for the `JavaScript target -## <backends.html#the-javascript-target>`_. The proleptic Gregorian calendar is the only calendar supported. +## This module contains routines and types for dealing with time using a proleptic Gregorian calendar. +## It's is available for the `JavaScript target <backends.html#the-javascript-target>`_. +## +## The types uses nanosecond time resolution, but the underlying resolution used by ``getTime()`` +## depends on the platform and backend (JS is limited to millisecond precision). ## ## Examples: ## @@ -25,10 +27,9 @@ ## echo "My formatted time: ", format(now(), "d MMMM yyyy HH:mm") ## echo "Using predefined formats: ", getClockStr(), " ", getDateStr() ## -## echo "epochTime() float value: ", epochTime() -## echo "cpuTime() float value: ", cpuTime() +## echo "cpuTime() float value: ", cpuTime() ## echo "An hour from now : ", now() + 1.hours -## echo "An hour from (UTC) now: ", getTime().utc + initInterval(0,0,0,1) +## echo "An hour from (UTC) now: ", getTime().utc + initDuration(hours = 1) {.push debugger:off.} # the user does not want to trace a part # of the standard library! @@ -38,12 +39,36 @@ import include "system/inclrtl" +# This is really bad, but overflow checks are broken badly for +# ints on the JS backend. See #6752. +when defined(JS): + {.push overflowChecks: off.} + proc `*`(a, b: int64): int64 = + system.`* `(a, b) + proc `*`(a, b: int): int = + system.`* `(a, b) + proc `+`(a, b: int64): int64 = + system.`+ `(a, b) + proc `+`(a, b: int): int = + system.`+ `(a, b) + proc `-`(a, b: int64): int64 = + system.`- `(a, b) + proc `-`(a, b: int): int = + system.`- `(a, b) + proc inc(a: var int, b: int) = + system.inc(a, b) + proc inc(a: var int64, b: int) = + system.inc(a, b) + {.pop.} + when defined(posix): import posix type CTime = posix.Time - proc posix_gettimeofday(tp: var Timeval, unused: pointer = nil) {. + var CLOCK_REALTIME {.importc: "CLOCK_REALTIME", header: "<time.h>".}: Clockid + + proc gettimeofday(tp: var Timeval, unused: pointer = nil) {. importc: "gettimeofday", header: "<sys/time.h>".} when not defined(freebsd) and not defined(netbsd) and not defined(openbsd): @@ -74,16 +99,11 @@ type MinuteRange* = range[0..59] SecondRange* = range[0..60] YeardayRange* = range[0..365] + NanosecondRange* = range[0..999_999_999] - TimeImpl = int64 - - Time* = distinct TimeImpl ## Represents a point in time. - ## This is currently implemented as a ``int64`` representing - ## seconds since ``1970-01-01T00:00:00Z``, but don't - ## rely on this knowledge because it might change - ## in the future to allow for higher precision. - ## Use the procs ``toUnix`` and ``fromUnix`` to - ## work with unix timestamps instead. + Time* = object ## Represents a point in time. + seconds: int64 + nanoseconds: NanosecondRange DateTime* = object of RootObj ## Represents a time in different parts. ## Although this type can represent leap @@ -92,6 +112,8 @@ type ## but the ``DateTime``'s returned by ## procedures in this module will never have ## a leap second. + nanosecond*: NanosecondRange ## The number of nanoseconds after the second, + ## in the range 0 to 999_999_999. second*: SecondRange ## The number of seconds after the minute, ## normally in the range 0 to 59, but can ## be up to 60 to allow for a leap second. @@ -114,19 +136,36 @@ type ## of the one in a formatted offset string like ``+01:00`` ## (which would be parsed into the UTC offset ``-3600``). - TimeInterval* = object ## Represents a duration of time. Can be used to add and subtract - ## from a ``DateTime`` or ``Time``. - ## Note that a ``TimeInterval`` doesn't represent a fixed duration of time, + TimeInterval* = object ## Represents a non-fixed duration of time. Can be used to add and subtract + ## non-fixed time units from a ``DateTime`` or ``Time``. + ## ``TimeInterval`` doesn't represent a fixed duration of time, ## since the duration of some units depend on the context (e.g a year ## can be either 365 or 366 days long). The non-fixed time units are years, ## months and days. + + nanoseconds*: int ## The number of nanoseconds + microseconds*: int ## The number of microseconds milliseconds*: int ## The number of milliseconds - seconds*: int ## The number of seconds - minutes*: int ## The number of minutes - hours*: int ## The number of hours - days*: int ## The number of days - months*: int ## The number of months - years*: int ## The number of years + seconds*: int ## The number of seconds + minutes*: int ## The number of minutes + hours*: int ## The number of hours + days*: int ## The number of days + weeks*: int ## The number of weeks + months*: int ## The number of months + years*: int ## The number of years + + Duration* = object ## Represents a fixed duration of time. + ## Uses the same time resolution as ``Time``. + ## This type should be prefered over ``TimeInterval`` unless + ## non-static time units is needed. + seconds: int64 + nanoseconds: NanosecondRange + + TimeUnit* = enum ## Different units of time. + Nanoseconds, Microseconds, Milliseconds, Seconds, Minutes, Hours, Days, Weeks, Months, Years + + FixedTimeUnit* = range[Nanoseconds..Weeks] ## Subrange of ``TimeUnit`` that only includes units of fixed duration. + ## These are the units that can be represented by a ``Duration``. Timezone* = object ## Timezone interface for supporting ``DateTime``'s of arbritary timezones. ## The ``times`` module only supplies implementations for the systems local time and UTC. @@ -136,11 +175,13 @@ type zoneInfoFromTz*: proc (adjTime: Time): ZonedTime {.tags: [], raises: [], benign.} name*: string ## The name of the timezone, f.ex 'Europe/Stockholm' or 'Etc/UTC'. Used for checking equality. ## Se also: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones - ZonedTime* = object ## Represents a zooned instant in time that is not associated with any calendar. + + ZonedTime* = object ## Represents a zoned instant in time that is not associated with any calendar. ## This type is only used for implementing timezones. - adjTime*: Time ## Time adjusted to a timezone. - utcOffset*: int - isDst*: bool + adjTime*: Time ## Time adjusted to a timezone. + utcOffset*: int ## Offset from UTC in seconds. + ## The point in time represented by ``ZonedTime`` is ``adjTime + utcOffset.seconds``. + isDst*: bool ## Determines whether DST is in effect. {.deprecated: [TMonth: Month, TWeekDay: WeekDay, TTime: Time, TTimeInterval: TimeInterval, TTimeInfo: DateTime, TimeInfo: DateTime].} @@ -150,14 +191,185 @@ const secondsInHour = 60*60 secondsInDay = 60*60*24 minutesInHour = 60 + # The number of hectonanoseconds between 1601/01/01 (windows epoch) + # and 1970/01/01 (unix epoch). + epochDiff = 116444736000000000'i64 + +const unitWeights: array[FixedTimeUnit, int64] = [ + 1'i64, + 1000, + 1_000_000, + 1e9.int64, + secondsInMin * 1e9.int64, + secondsInHour * 1e9.int64, + secondsInDay * 1e9.int64, + 7 * secondsInDay * 1e9.int64, +] + +proc convert*[T: SomeInteger](unitFrom, unitTo: FixedTimeUnit, quantity: T): T {.inline.} = + ## Convert a quantity of some duration unit to another duration unit. + runnableExamples: + doAssert convert(Days, Hours, 2) == 48 + doAssert convert(Days, Weeks, 13) == 1 # Truncated + doAssert convert(Seconds, Milliseconds, -1) == -1000 + if unitFrom < unitTo: + (quantity div (unitWeights[unitTo] div unitWeights[unitFrom])).T + else: + ((unitWeights[unitFrom] div unitWeights[unitTo]) * quantity).T + +proc normalize[T: Duration|Time](seconds, nanoseconds: int64): T = + ## Normalize a (seconds, nanoseconds) pair and return it as either + ## a ``Duration`` or ``Time``. A normalized ``Duration|Time`` has a + ## positive nanosecond part in the range ``NanosecondRange``. + result.seconds = seconds + convert(Nanoseconds, Seconds, nanoseconds) + var nanoseconds = nanoseconds mod convert(Seconds, Nanoseconds, 1) + if nanoseconds < 0: + nanoseconds += convert(Seconds, Nanoseconds, 1) + result.seconds -= 1 + result.nanoseconds = nanoseconds.int + +proc initTime*(unix: int64, nanoseconds: NanosecondRange): Time = + ## Create a ``Time`` from a unix timestamp and a nanosecond part. + result.seconds = unix + result.nanoseconds = nanoseconds + +proc nanoseconds*(time: Time): NanosecondRange = + ## Get the fractional part of a ``Time`` as the number + ## of nanoseconds of the second. + time.nanoseconds + +proc initDuration*(nanoseconds, microseconds, milliseconds, + seconds, minutes, hours, days, weeks: int64 = 0): Duration = + let seconds = convert(Weeks, Seconds, weeks) + + convert(Days, Seconds, days) + + convert(Minutes, Seconds, minutes) + + convert(Hours, Seconds, hours) + + convert(Seconds, Seconds, seconds) + + convert(Milliseconds, Seconds, milliseconds) + + convert(Microseconds, Seconds, microseconds) + + convert(Nanoseconds, Seconds, nanoseconds) + let nanoseconds = (convert(Milliseconds, Nanoseconds, milliseconds mod 1000) + + convert(Microseconds, Nanoseconds, microseconds mod 1_000_000) + + nanoseconds mod 1_000_000_000).int + # Nanoseconds might be negative so we must normalize. + result = normalize[Duration](seconds, nanoseconds) + +proc weeks*(dur: Duration): int64 {.inline.} = + ## Number of whole weeks represented by the duration. + convert(Seconds, Weeks, dur.seconds) + +proc days*(dur: Duration): int64 {.inline.} = + ## Number of whole days represented by the duration. + convert(Seconds, Days, dur.seconds) + +proc minutes*(dur: Duration): int64 {.inline.} = + ## Number of whole minutes represented by the duration. + convert(Seconds, Minutes, dur.seconds) + +proc hours*(dur: Duration): int64 {.inline.} = + ## Number of whole hours represented by the duration. + convert(Seconds, Hours, dur.seconds) + +proc seconds*(dur: Duration): int64 {.inline.} = + ## Number of whole seconds represented by the duration. + dur.seconds + +proc milliseconds*(dur: Duration): int {.inline.} = + ## Number of whole milliseconds represented by the **fractional** + ## part of the duration. + runnableExamples: + let dur = initDuration(seconds = 1, milliseconds = 1) + doAssert dur.milliseconds == 1 + convert(Nanoseconds, Milliseconds, dur.nanoseconds) + +proc microseconds*(dur: Duration): int {.inline.} = + ## Number of whole microseconds represented by the **fractional** + ## part of the duration. + runnableExamples: + let dur = initDuration(seconds = 1, microseconds = 1) + doAssert dur.microseconds == 1 + convert(Nanoseconds, Microseconds, dur.nanoseconds) + +proc nanoseconds*(dur: Duration): int {.inline.} = + ## Number of whole nanoseconds represented by the **fractional** + ## part of the duration. + runnableExamples: + let dur = initDuration(seconds = 1, nanoseconds = 1) + doAssert dur.nanoseconds == 1 + dur.nanoseconds + +proc fractional*(dur: Duration): Duration {.inline.} = + ## The fractional part of duration, as a duration. + runnableExamples: + let dur = initDuration(seconds = 1, nanoseconds = 5) + doAssert dur.fractional == initDuration(nanoseconds = 5) + initDuration(nanoseconds = dur.nanoseconds) + +const DurationZero* = initDuration() ## Zero value for durations. Useful for comparisons. + ## + ## .. code-block:: nim + ## + ## doAssert initDuration(seconds = 1) > DurationZero + ## doAssert initDuration(seconds = 0) == DurationZero + +proc `$`*(dur: Duration): string = + ## Human friendly string representation of ``dur``. + runnableExamples: + doAssert $initDuration(seconds = 2) == "2 seconds" + doAssert $initDuration(weeks = 1, days = 2) == "1 week and 2 days" + doAssert $initDuration(hours = 1, minutes = 2, seconds = 3) == "1 hour, 2 minutes, and 3 seconds" + doAssert $initDuration(milliseconds = -1500) == "-1 second and -500 milliseconds" + var parts = newSeq[string]() + var remS = dur.seconds + var remNs = dur.nanoseconds.int + + # Normally ``nanoseconds`` should always be positive, but + # that makes no sense when printing. + if remS < 0: + remNs -= convert(Seconds, Nanoseconds, 1) + remS.inc 1 + + const unitStrings: array[FixedTimeUnit, string] = [ + "nanoseconds", "microsecond", "millisecond", "second", "minute", "hour", "day", "week" + ] + + for unit in countdown(Weeks, Seconds): + let quantity = convert(Seconds, unit, remS) + remS = remS mod convert(unit, Seconds, 1) + + if quantity.abs == 1: + parts.add $quantity & " " & unitStrings[unit] + elif quantity != 0: + parts.add $quantity & " " & unitStrings[unit] & "s" + + for unit in countdown(Milliseconds, Nanoseconds): + let quantity = convert(Nanoseconds, unit, remNs) + remNs = remNs mod convert(unit, Nanoseconds, 1) + + if quantity.abs == 1: + parts.add $quantity & " " & unitStrings[unit] + elif quantity != 0: + parts.add $quantity & " " & unitStrings[unit] & "s" + + result = "" + if parts.len == 1: + result = parts[0] + elif parts.len == 2: + result = parts[0] & " and " & parts[1] + else: + for part in parts[0..high(parts)-1]: + result.add part & ", " + result.add "and " & parts[high(parts)] proc fromUnix*(unix: int64): Time {.benign, tags: [], raises: [], noSideEffect.} = ## Convert a unix timestamp (seconds since ``1970-01-01T00:00:00Z``) to a ``Time``. - Time(unix) + runnableExamples: + doAssert $fromUnix(0).utc == "1970-01-01T00:00:00+00:00" + initTime(unix, 0) proc toUnix*(t: Time): int64 {.benign, tags: [], raises: [], noSideEffect.} = ## Convert ``t`` to a unix timestamp (seconds since ``1970-01-01T00:00:00Z``). - t.int64 + t.seconds proc isLeapYear*(year: int): bool = ## Returns true if ``year`` is a leap year. @@ -177,7 +389,7 @@ proc getDaysInYear*(year: int): int = proc assertValidDate(monthday: MonthdayRange, month: Month, year: int) {.inline.} = assert monthday <= getDaysInMonth(month, year), - $year & "-" & $ord(month) & "-" & $monthday & " is not a valid date" + $year & "-" & intToStr(ord(month), 2) & "-" & $monthday & " is not a valid date" proc toEpochDay(monthday: MonthdayRange, month: Month, year: int): int64 = ## Get the epoch day from a year/month/day date. @@ -240,42 +452,154 @@ proc utcZoneInfoFromTz(adjTime: Time): ZonedTime {.tags: [], raises: [], benign proc localZoneInfoFromUtc(time: Time): ZonedTime {.tags: [], raises: [], benign .} proc localZoneInfoFromTz(adjTime: Time): ZonedTime {.tags: [], raises: [], benign .} -proc `-`*(a, b: Time): int64 {. - rtl, extern: "ntDiffTime", tags: [], raises: [], noSideEffect, benign, deprecated.} = - ## Computes the difference of two calendar times. Result is in seconds. - ## This is deprecated because it will need to change when sub second time resolution is implemented. - ## Use ``a.toUnix - b.toUnix`` instead. - ## - ## .. code-block:: nim - ## let a = fromSeconds(1_000_000_000) - ## let b = fromSeconds(1_500_000_000) - ## echo initInterval(seconds=int(b - a)) - ## # (milliseconds: 0, seconds: 20, minutes: 53, hours: 0, days: 5787, months: 0, years: 0) - a.toUnix - b.toUnix - -proc `<`*(a, b: Time): bool {. - rtl, extern: "ntLtTime", tags: [], raises: [], noSideEffect, borrow.} +{. pragma: operator, rtl, noSideEffect, benign .} + +template subImpl[T: Duration|Time](a: Duration|Time, b: Duration|Time): T = + normalize[T](a.seconds - b.seconds, a.nanoseconds - b.nanoseconds) + +template addImpl[T: Duration|Time](a: Duration|Time, b: Duration|Time): T = + normalize[T](a.seconds + b.seconds, a.nanoseconds + b.nanoseconds) + +template ltImpl(a: Duration|Time, b: Duration|Time): bool = + a.seconds < b.seconds or ( + a.seconds == b.seconds and a.nanoseconds < b.nanoseconds) + +template lqImpl(a: Duration|Time, b: Duration|Time): bool = + a.seconds <= b.seconds or ( + a.seconds == b.seconds and a.nanoseconds <= b.seconds) + +template eqImpl(a: Duration|Time, b: Duration|Time): bool = + a.seconds == b.seconds and a.nanoseconds == b.nanoseconds + +proc `+`*(a, b: Duration): Duration {.operator.} = + ## Add two durations together. + runnableExamples: + doAssert initDuration(seconds = 1) + initDuration(days = 1) == + initDuration(seconds = 1, days = 1) + addImpl[Duration](a, b) + +proc `-`*(a, b: Duration): Duration {.operator.} = + ## Subtract a duration from another. + runnableExamples: + doAssert initDuration(seconds = 1, days = 1) - initDuration(seconds = 1) == + initDuration(days = 1) + subImpl[Duration](a, b) + +proc `-`*(a: Duration): Duration {.operator.} = + ## Reverse a duration. + runnableExamples: + doAssert -initDuration(seconds = 1) == initDuration(seconds = -1) + normalize[Duration](-a.seconds, -a.nanoseconds) + +proc `<`*(a, b: Duration): bool {.operator.} = + ## Note that a duration can be negative, + ## so even if ``a < b`` is true ``a`` might + ## represent a larger absolute duration. + ## Use ``abs(a) < abs(b)`` to compare the absolute + ## duration. + runnableExamples: + doAssert initDuration(seconds = 1) < initDuration(seconds = 2) + doAssert initDuration(seconds = -2) < initDuration(seconds = 1) + ltImpl(a, b) + +proc `<=`*(a, b: Duration): bool {.operator.} = + lqImpl(a, b) + +proc `==`*(a, b: Duration): bool {.operator.} = + eqImpl(a, b) + +proc `*`*(a: int64, b: Duration): Duration {.operator} = + ## Multiply a duration by some scalar. + runnableExamples: + doAssert 5 * initDuration(seconds = 1) == initDuration(seconds = 5) + normalize[Duration](a * b.seconds, a * b.nanoseconds) + +proc `*`*(a: Duration, b: int64): Duration {.operator} = + ## Multiply a duration by some scalar. + runnableExamples: + doAssert 5 * initDuration(seconds = 1) == initDuration(seconds = 5) + b * a + +proc `div`*(a: Duration, b: int64): Duration {.operator} = + ## Integer division for durations. + runnableExamples: + doAssert initDuration(seconds = 3) div 2 == initDuration(milliseconds = 1500) + doAssert initDuration(nanoseconds = 3) div 2 == initDuration(nanoseconds = 1) + let carryOver = convert(Seconds, Nanoseconds, a.seconds mod b) + normalize[Duration](a.seconds div b, (a.nanoseconds + carryOver) div b) + +proc `-`*(a, b: Time): Duration {.operator, extern: "ntDiffTime".} = + ## Computes the duration between two points in time. + subImpl[Duration](a, b) + +proc `+`*(a: Time, b: Duration): Time {.operator, extern: "ntAddTime".} = + ## Add a duration of time to a ``Time``. + runnableExamples: + doAssert (fromUnix(0) + initDuration(seconds = 1)) == fromUnix(1) + addImpl[Time](a, b) + +proc `-`*(a: Time, b: Duration): Time {.operator, extern: "ntSubTime".} = + ## Subtracts a duration of time from a ``Time``. + runnableExamples: + doAssert (fromUnix(0) - initDuration(seconds = 1)) == fromUnix(-1) + subImpl[Time](a, b) + +proc `+=`*(a: var Time, b: Duration) {.operator.} = + ## Modify ``a`` in place by subtracting ``b``. + a = addImpl[Time](a, b) + +proc `-=`*(a: var Time, b: Duration) {.operator.} = + ## Modify ``a`` in place by adding ``b``. + a = subImpl[Time](a, b) + +proc `<`*(a, b: Time): bool {.operator, extern: "ntLtTime".} = ## Returns true iff ``a < b``, that is iff a happened before b. + ltImpl(a, b) -proc `<=` * (a, b: Time): bool {. - rtl, extern: "ntLeTime", tags: [], raises: [], noSideEffect, borrow.} +proc `<=` * (a, b: Time): bool {.operator, extern: "ntLeTime".} = ## Returns true iff ``a <= b``. + lqImpl(a, b) -proc `==`*(a, b: Time): bool {. - rtl, extern: "ntEqTime", tags: [], raises: [], noSideEffect, borrow.} +proc `==`*(a, b: Time): bool {.operator, extern: "ntEqTime".} = ## Returns true if ``a == b``, that is if both times represent the same point in time. + eqImpl(a, b) + +proc high*(typ: typedesc[Time]): Time = + initTime(high(int64), high(NanosecondRange)) + +proc low*(typ: typedesc[Time]): Time = + initTime(low(int64), 0) + +proc high*(typ: typedesc[Duration]): Duration = + ## Get the longest representable duration. + initDuration(seconds = high(int64), nanoseconds = high(NanosecondRange)) + +proc low*(typ: typedesc[Duration]): Duration = + ## Get the longest representable duration of negative direction. + initDuration(seconds = low(int64)) + +proc abs*(a: Duration): Duration = + runnableExamples: + doAssert initDuration(milliseconds = -1500).abs == + initDuration(milliseconds = 1500) + initDuration(seconds = abs(a.seconds), nanoseconds = -a.nanoseconds) proc toTime*(dt: DateTime): Time {.tags: [], raises: [], benign.} = ## Converts a broken-down time structure to ## calendar time representation. let epochDay = toEpochday(dt.monthday, dt.month, dt.year) - result = Time(epochDay * secondsInDay) - result.inc dt.hour * secondsInHour - result.inc dt.minute * 60 - result.inc dt.second + var seconds = epochDay * secondsInDay + seconds.inc dt.hour * secondsInHour + seconds.inc dt.minute * 60 + seconds.inc dt.second # The code above ignores the UTC offset of `timeInfo`, # so we need to compensate for that here. - result.inc dt.utcOffset + seconds.inc dt.utcOffset + result = initTime(seconds, dt.nanosecond) + +proc `-`*(dt1, dt2: DateTime): Duration = + ## Compute the duration between ``dt1`` and ``dt2``. + dt1.toTime - dt2.toTime proc `<`*(a, b: DateTime): bool = ## Returns true iff ``a < b``, that is iff a happened before b. @@ -290,9 +614,9 @@ proc `==`*(a, b: DateTime): bool = return a.toTime == b.toTime proc initDateTime(zt: ZonedTime, zone: Timezone): DateTime = - let adjTime = zt.adjTime.int64 - let epochday = (if adjTime >= 0: adjTime else: adjTime - (secondsInDay - 1)) div secondsInDay - var rem = zt.adjTime.int64 - epochday * secondsInDay + let s = zt.adjTime.seconds + let epochday = (if s >= 0: s else: s - (secondsInDay - 1)) div secondsInDay + var rem = s - epochday * secondsInDay let hour = rem div secondsInHour rem = rem - hour * secondsInHour let minute = rem div secondsInMin @@ -308,6 +632,7 @@ proc initDateTime(zt: ZonedTime, zone: Timezone): DateTime = hour: hour, minute: minute, second: second, + nanosecond: zt.adjTime.nanoseconds, weekday: getDayOfWeek(d, m, y), yearday: getDayOfYear(d, m, y), isDst: zt.isDst, @@ -334,10 +659,11 @@ proc `==`*(zone1, zone2: Timezone): bool = proc toAdjTime(dt: DateTime): Time = let epochDay = toEpochday(dt.monthday, dt.month, dt.year) - result = Time(epochDay * secondsInDay) - result.inc dt.hour * secondsInHour - result.inc dt.minute * secondsInMin - result.inc dt.second + var seconds = epochDay * secondsInDay + seconds.inc dt.hour * secondsInHour + seconds.inc dt.minute * secondsInMin + seconds.inc dt.second + result = initTime(seconds, dt.nanosecond) when defined(JS): type JsDate = object @@ -366,14 +692,14 @@ when defined(JS): proc setFullYear(js: JsDate, year: int): void {.tags: [], raises: [], benign, importcpp.} proc localZoneInfoFromUtc(time: Time): ZonedTime = - let jsDate = newDate(time.float * 1000) + let jsDate = newDate(time.seconds.float * 1000) let offset = jsDate.getTimezoneOffset() * secondsInMin - result.adjTime = Time(time.int64 - offset) + result.adjTime = time - initDuration(seconds = offset) result.utcOffset = offset result.isDst = false proc localZoneInfoFromTz(adjTime: Time): ZonedTime = - let utcDate = newDate(adjTime.float * 1000) + let utcDate = newDate(adjTime.seconds.float * 1000) let localDate = newDate(utcDate.getUTCFullYear(), utcDate.getUTCMonth(), utcDate.getUTCDate(), utcDate.getUTCHours(), utcDate.getUTCMinutes(), utcDate.getUTCSeconds(), 0) @@ -422,58 +748,52 @@ else: proc localtime(timer: ptr CTime): StructTmPtr {. importc: "localtime", header: "<time.h>", tags: [].} - proc toAdjTime(tm: StructTm): Time = + proc toAdjUnix(tm: StructTm): int64 = let epochDay = toEpochday(tm.monthday, (tm.month + 1).Month, tm.year.int + 1900) - result = Time(epochDay * secondsInDay) + result = epochDay * secondsInDay result.inc tm.hour * secondsInHour result.inc tm.minute * 60 result.inc tm.second - proc getStructTm(time: Time | int64): StructTm = - let timei64 = time.int64 - var a = - if timei64 < low(CTime): - CTime(low(CTime)) - elif timei64 > high(CTime): - CTime(high(CTime)) - else: - CTime(timei64) - result = localtime(addr(a))[] + proc getLocalOffsetAndDst(unix: int64): tuple[offset: int, dst: bool] = + var a = unix.CTime + let tmPtr = localtime(addr(a)) + if not tmPtr.isNil: + let tm = tmPtr[] + return ((unix - tm.toAdjUnix).int, tm.isdst > 0) + return (0, false) proc localZoneInfoFromUtc(time: Time): ZonedTime = - let tm = getStructTm(time) - let adjTime = tm.toAdjTime - result.adjTime = adjTime - result.utcOffset = (time.toUnix - adjTime.toUnix).int - result.isDst = tm.isdst > 0 + let (offset, dst) = getLocalOffsetAndDst(time.seconds) + result.adjTime = time - initDuration(seconds = offset) + result.utcOffset = offset + result.isDst = dst proc localZoneInfoFromTz(adjTime: Time): ZonedTime = - var adjTimei64 = adjTime.int64 - let past = adjTimei64 - secondsInDay - var tm = getStructTm(past) - let pastOffset = past - tm.toAdjTime.int64 + var adjUnix = adjTime.seconds + let past = adjUnix - secondsInDay + let (pastOffset, _) = getLocalOffsetAndDst(past) - let future = adjTimei64 + secondsInDay - tm = getStructTm(future) - let futureOffset = future - tm.toAdjTime.int64 + let future = adjUnix + secondsInDay + let (futureOffset, _) = getLocalOffsetAndDst(future) var utcOffset: int if pastOffset == futureOffset: utcOffset = pastOffset.int else: if pastOffset > futureOffset: - adjTimei64 -= secondsInHour + adjUnix -= secondsInHour - adjTimei64 += pastOffset - utcOffset = (adjTimei64 - getStructTm(adjTimei64).toAdjTime.int64).int + adjUnix += pastOffset + utcOffset = getLocalOffsetAndDst(adjUnix).offset # This extra roundtrip is needed to normalize any impossible datetimes # as a result of offset changes (normally due to dst) - let utcTime = adjTime.int64 + utcOffset - tm = getStructTm(utcTime) - result.adjTime = tm.toAdjTime - result.utcOffset = (utcTime - result.adjTime.int64).int - result.isDst = tm.isdst > 0 + let utcUnix = adjTime.seconds + utcOffset + let (finalOffset, dst) = getLocalOffsetAndDst(utcUnix) + result.adjTime = initTime(utcUnix - finalOffset, adjTime.nanoseconds) + result.utcOffset = finalOffset + result.isDst = dst proc utcZoneInfoFromUtc(time: Time): ZonedTime = result.adjTime = time @@ -485,18 +805,16 @@ proc utcZoneInfoFromTz(adjTime: Time): ZonedTime = proc utc*(): TimeZone = ## Get the ``Timezone`` implementation for the UTC timezone. - ## - ## .. code-block:: nim - ## doAssert now().utc.timezone == utc() - ## doAssert utc().name == "Etc/UTC" + runnableExamples: + doAssert now().utc.timezone == utc() + doAssert utc().name == "Etc/UTC" Timezone(zoneInfoFromUtc: utcZoneInfoFromUtc, zoneInfoFromTz: utcZoneInfoFromTz, name: "Etc/UTC") proc local*(): TimeZone = ## Get the ``Timezone`` implementation for the local timezone. - ## - ## .. code-block:: nim - ## doAssert now().timezone == local() - ## doAssert local().name == "LOCAL" + runnableExamples: + doAssert now().timezone == local() + doAssert local().name == "LOCAL" Timezone(zoneInfoFromUtc: localZoneInfoFromUtc, zoneInfoFromTz: localZoneInfoFromTz, name: "LOCAL") proc utc*(dt: DateTime): DateTime = @@ -515,9 +833,30 @@ proc local*(t: Time): DateTime = ## Shorthand for ``t.inZone(local())``. t.inZone(local()) -proc getTime*(): Time {.tags: [TimeEffect], benign.} - ## Gets the current time as a ``Time`` with second resolution. Use epochTime for higher - ## resolution. +proc getTime*(): Time {.tags: [TimeEffect], benign.} = + ## Gets the current time as a ``Time`` with nanosecond resolution. + when defined(JS): + let millis = newDate().getTime() + let seconds = convert(Milliseconds, Seconds, millis) + let nanos = convert(Milliseconds, Nanoseconds, + millis mod convert(Seconds, Milliseconds, 1).int) + result = initTime(seconds, nanos) + # I'm not entirely certain if freebsd needs to use `gettimeofday`. + elif defined(macosx) or defined(freebsd): + var a: Timeval + gettimeofday(a) + result = initTime(a.tv_sec.int64, convert(Microseconds, Nanoseconds, a.tv_usec.int)) + elif defined(posix): + var ts: Timespec + discard clock_gettime(CLOCK_REALTIME, ts) + result = initTime(ts.tv_sec.int64, ts.tv_nsec.int) + elif defined(windows): + var f: FILETIME + getSystemTimeAsFileTime(f) + let nanosSinceEpoch = (rdFileTime(f) - epochDiff) * 100 + let seconds = convert(Nanoseconds, Seconds, nanosSinceEpoch) + let nanos = (nanosSinceEpoch mod convert(Seconds, Nanoseconds, 1)).int + result = initTime(seconds, nanos) proc now*(): DateTime {.tags: [TimeEffect], benign.} = ## Get the current time as a ``DateTime`` in the local timezone. @@ -525,35 +864,39 @@ proc now*(): DateTime {.tags: [TimeEffect], benign.} = ## Shorthand for ``getTime().local``. getTime().local -proc initInterval*(milliseconds, seconds, minutes, hours, days, months, - years: int = 0): TimeInterval = +proc initTimeInterval*(nanoseconds, microseconds, milliseconds, + seconds, minutes, hours, + days, weeks, months, years: int = 0): TimeInterval = ## Creates a new ``TimeInterval``. ## ## You can also use the convenience procedures called ``milliseconds``, ## ``seconds``, ``minutes``, ``hours``, ``days``, ``months``, and ``years``. ## - ## Example: - ## - ## .. code-block:: nim - ## - ## let day = initInterval(hours=24) - ## let dt = initDateTime(01, mJan, 2000, 12, 00, 00, utc()) - ## doAssert $(dt + day) == "2000-01-02T12-00-00+00:00" + runnableExamples: + let day = initTimeInterval(hours=24) + let dt = initDateTime(01, mJan, 2000, 12, 00, 00, utc()) + doAssert $(dt + day) == "2000-01-02T12:00:00+00:00" + result.nanoseconds = nanoseconds + result.microseconds = microseconds result.milliseconds = milliseconds result.seconds = seconds result.minutes = minutes result.hours = hours result.days = days + result.weeks = weeks result.months = months result.years = years proc `+`*(ti1, ti2: TimeInterval): TimeInterval = ## Adds two ``TimeInterval`` objects together. + result.nanoseconds = ti1.nanoseconds + ti2.nanoseconds + result.microseconds = ti1.microseconds + ti2.microseconds result.milliseconds = ti1.milliseconds + ti2.milliseconds result.seconds = ti1.seconds + ti2.seconds result.minutes = ti1.minutes + ti2.minutes result.hours = ti1.hours + ti2.hours result.days = ti1.days + ti2.days + result.weeks = ti1.weeks + ti2.weeks result.months = ti1.months + ti2.months result.years = ti1.years + ti2.years @@ -565,11 +908,14 @@ proc `-`*(ti: TimeInterval): TimeInterval = ## let day = -initInterval(hours=24) ## echo day # -> (milliseconds: 0, seconds: 0, minutes: 0, hours: -24, days: 0, months: 0, years: 0) result = TimeInterval( + nanoseconds: -ti.nanoseconds, + microseconds: -ti.microseconds, milliseconds: -ti.milliseconds, seconds: -ti.seconds, minutes: -ti.minutes, hours: -ti.hours, days: -ti.days, + weeks: -ti.weeks, months: -ti.months, years: -ti.years ) @@ -577,82 +923,16 @@ proc `-`*(ti: TimeInterval): TimeInterval = proc `-`*(ti1, ti2: TimeInterval): TimeInterval = ## Subtracts TimeInterval ``ti1`` from ``ti2``. ## - ## Time components are compared one-by-one, see output: + ## Time components are subtracted one-by-one, see output: ## ## .. code-block:: nim ## let a = fromUnix(1_000_000_000) ## let b = fromUnix(1_500_000_000) ## echo b.toTimeInterval - a.toTimeInterval - ## # (milliseconds: 0, seconds: -40, minutes: -6, hours: 1, days: 5, months: -2, years: 16) - result = ti1 + (-ti2) - -proc evaluateInterval(dt: DateTime, interval: TimeInterval): tuple[adjDiff, absDiff: int64] = - ## Evaluates how many seconds the interval is worth - ## in the context of ``dt``. - ## The result in split into an adjusted diff and an absolute diff. - - var anew = dt - var newinterv = interval + ## # (nanoseconds: 0, microseconds: 0, milliseconds: 0, seconds: -40, + ## minutes: -6, hours: 1, days: 5, weeks: 0, months: -2, years: 16) - newinterv.months += interval.years * 12 - var curMonth = anew.month - # Subtracting - if newinterv.months < 0: - for mth in countDown(-1 * newinterv.months, 1): - if curMonth == mJan: - curMonth = mDec - anew.year.dec() - else: - curMonth.dec() - result.adjDiff -= getDaysInMonth(curMonth, anew.year) * secondsInDay - # Adding - else: - for mth in 1 .. newinterv.months: - result.adjDiff += getDaysInMonth(curMonth, anew.year) * secondsInDay - if curMonth == mDec: - curMonth = mJan - anew.year.inc() - else: - curMonth.inc() - result.adjDiff += newinterv.days * secondsInDay - result.absDiff += newinterv.hours * secondsInHour - result.absDiff += newinterv.minutes * secondsInMin - result.absDiff += newinterv.seconds - result.absDiff += newinterv.milliseconds div 1000 - -proc `+`*(dt: DateTime, interval: TimeInterval): DateTime = - ## Adds ``interval`` to ``dt``. Components from ``interval`` are added - ## in the order of their size, i.e first the ``years`` component, then the ``months`` - ## component and so on. The returned ``DateTime`` will have the same timezone as the input. - ## - ## Note that when adding months, monthday overflow is allowed. This means that if the resulting - ## month doesn't have enough days it, the month will be incremented and the monthday will be - ## set to the number of days overflowed. So adding one month to `31 October` will result in `31 November`, - ## which will overflow and result in `1 December`. - ## - ## .. code-block:: nim - ## let dt = initDateTime(30, mMar, 2017, 00, 00, 00, utc()) - ## doAssert $(dt + 1.months) == "2017-04-30T00:00:00+00:00" - ## # This is correct and happens due to monthday overflow. - ## doAssert $(dt - 1.months) == "2017-03-02T00:00:00+00:00" - let (adjDiff, absDiff) = evaluateInterval(dt, interval) - - if adjDiff.int64 != 0: - let zInfo = dt.timezone.zoneInfoFromTz(Time(dt.toAdjTime.int64 + adjDiff)) - - if absDiff != 0: - let time = Time(zInfo.adjTime.int64 + zInfo.utcOffset + absDiff) - result = initDateTime(dt.timezone.zoneInfoFromUtc(time), dt.timezone) - else: - result = initDateTime(zInfo, dt.timezone) - else: - result = initDateTime(dt.timezone.zoneInfoFromUtc(Time(dt.toTime.int64 + absDiff)), dt.timezone) - -proc `-`*(dt: DateTime, interval: TimeInterval): DateTime = - ## Subtract ``interval`` from ``dt``. Components from ``interval`` are subtracted - ## in the order of their size, i.e first the ``years`` component, then the ``months`` - ## component and so on. The returned ``DateTime`` will have the same timezone as the input. - dt + (-interval) + result = ti1 + (-ti2) proc getDateStr*(): string {.rtl, extern: "nt$1", tags: [TimeEffect].} = ## Gets the current date as a string of the format ``YYYY-MM-DD``. @@ -679,47 +959,139 @@ proc `$`*(m: Month): string = "November", "December"] return lookup[m] +proc nanoseconds*(nanos: int): TimeInterval {.inline.} = + ## TimeInterval of ``nanos`` nanoseconds. + initTimeInterval(nanoseconds = nanos) + +proc microseconds*(micros: int): TimeInterval {.inline.} = + ## TimeInterval of ``micros`` microseconds. + initTimeInterval(microseconds = micros) + proc milliseconds*(ms: int): TimeInterval {.inline.} = - ## TimeInterval of `ms` milliseconds - ## - ## Note: not all time procedures have millisecond resolution - initInterval(milliseconds = ms) + ## TimeInterval of ``ms`` milliseconds. + initTimeInterval(milliseconds = ms) proc seconds*(s: int): TimeInterval {.inline.} = - ## TimeInterval of `s` seconds + ## TimeInterval of ``s`` seconds. ## ## ``echo getTime() + 5.second`` - initInterval(seconds = s) + initTimeInterval(seconds = s) proc minutes*(m: int): TimeInterval {.inline.} = - ## TimeInterval of `m` minutes + ## TimeInterval of ``m`` minutes. ## ## ``echo getTime() + 5.minutes`` - initInterval(minutes = m) + initTimeInterval(minutes = m) proc hours*(h: int): TimeInterval {.inline.} = - ## TimeInterval of `h` hours + ## TimeInterval of ``h`` hours. ## ## ``echo getTime() + 2.hours`` - initInterval(hours = h) + initTimeInterval(hours = h) proc days*(d: int): TimeInterval {.inline.} = - ## TimeInterval of `d` days + ## TimeInterval of ``d`` days. ## ## ``echo getTime() + 2.days`` - initInterval(days = d) + initTimeInterval(days = d) + +proc weeks*(w: int): TimeInterval {.inline.} = + ## TimeInterval of ``w`` weeks. + ## + ## ``echo getTime() + 2.weeks`` + initTimeInterval(weeks = w) proc months*(m: int): TimeInterval {.inline.} = - ## TimeInterval of `m` months + ## TimeInterval of ``m`` months. ## ## ``echo getTime() + 2.months`` - initInterval(months = m) + initTimeInterval(months = m) proc years*(y: int): TimeInterval {.inline.} = - ## TimeInterval of `y` years + ## TimeInterval of ``y`` years. ## ## ``echo getTime() + 2.years`` - initInterval(years = y) + initTimeInterval(years = y) + +proc evaluateInterval(dt: DateTime, interval: TimeInterval): tuple[adjDur, absDur: Duration] = + ## Evaluates how many nanoseconds the interval is worth + ## in the context of ``dt``. + ## The result in split into an adjusted diff and an absolute diff. + var months = interval.years * 12 + interval.months + var curYear = dt.year + var curMonth = dt.month + # Subtracting + if months < 0: + for mth in countDown(-1 * months, 1): + if curMonth == mJan: + curMonth = mDec + curYear.dec + else: + curMonth.dec() + let days = getDaysInMonth(curMonth, curYear) + result.adjDur = result.adjDur - initDuration(days = days) + # Adding + else: + for mth in 1 .. months: + let days = getDaysInMonth(curMonth, curYear) + result.adjDur = result.adjDur + initDuration(days = days) + if curMonth == mDec: + curMonth = mJan + curYear.inc + else: + curMonth.inc() + + result.adjDur = result.adjDur + initDuration( + days = interval.days, + weeks = interval.weeks) + result.absDur = initDuration( + nanoseconds = interval.nanoseconds, + microseconds = interval.microseconds, + milliseconds = interval.milliseconds, + seconds = interval.seconds, + minutes = interval.minutes, + hours = interval.hours) + +proc `+`*(dt: DateTime, interval: TimeInterval): DateTime = + ## Adds ``interval`` to ``dt``. Components from ``interval`` are added + ## in the order of their size, i.e first the ``years`` component, then the ``months`` + ## component and so on. The returned ``DateTime`` will have the same timezone as the input. + ## + ## Note that when adding months, monthday overflow is allowed. This means that if the resulting + ## month doesn't have enough days it, the month will be incremented and the monthday will be + ## set to the number of days overflowed. So adding one month to `31 October` will result in `31 November`, + ## which will overflow and result in `1 December`. + ## + runnableExamples: + let dt = initDateTime(30, mMar, 2017, 00, 00, 00, utc()) + doAssert $(dt + 1.months) == "2017-04-30T00:00:00+00:00" + # This is correct and happens due to monthday overflow. + doAssert $(dt - 1.months) == "2017-03-02T00:00:00+00:00" + let (adjDur, absDur) = evaluateInterval(dt, interval) + + if adjDur != DurationZero: + var zInfo = dt.timezone.zoneInfoFromTz(dt.toAdjTime + adjDur) + if absDur != DurationZero: + let offsetDur = initDuration(seconds = zInfo.utcOffset) + zInfo = dt.timezone.zoneInfoFromUtc(zInfo.adjTime + offsetDur + absDur) + result = initDateTime(zInfo, dt.timezone) + else: + result = initDateTime(zInfo, dt.timezone) + else: + var zInfo = dt.timezone.zoneInfoFromUtc(dt.toTime + absDur) + result = initDateTime(zInfo, dt.timezone) + +proc `-`*(dt: DateTime, interval: TimeInterval): DateTime = + ## Subtract ``interval`` from ``dt``. Components from ``interval`` are subtracted + ## in the order of their size, i.e first the ``years`` component, then the ``months`` + ## component and so on. The returned ``DateTime`` will have the same timezone as the input. + dt + (-interval) + +proc `+`*(dt: DateTime, dur: Duration): DateTime = + (dt.toTime + dur).inZone(dt.timezone) + +proc `-`*(dt: DateTime, dur: Duration): DateTime = + (dt.toTime - dur).inZone(dt.timezone) proc `+=`*(time: var Time, interval: TimeInterval) = ## Modifies `time` by adding `interval`. @@ -1205,6 +1577,7 @@ proc parse*(value, layout: string, zone: Timezone = local()): DateTime = dt.hour = 0 dt.minute = 0 dt.second = 0 + dt.nanosecond = 0 dt.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: @@ -1278,69 +1651,44 @@ proc toTimeInterval*(time: Time): TimeInterval = ## echo a, " ", b # real dates ## echo a.toTimeInterval # meaningless value, don't use it by itself ## echo b.toTimeInterval - a.toTimeInterval - ## # (milliseconds: 0, seconds: -40, minutes: -6, hours: 1, days: 5, months: -2, years: 16) - # Milliseconds not available from Time + ## # (nanoseconds: 0, microseconds: 0, milliseconds: 0, seconds: -40, + ## minutes: -6, hours: 1, days: 5, weeks: 0, months: -2, years: 16) var dt = time.local - initInterval(0, dt.second, dt.minute, dt.hour, dt.monthday, dt.month.ord - 1, dt.year) + initTimeInterval(dt.nanosecond, 0, 0, dt.second, dt.minute, dt.hour, + dt.monthday, 0, dt.month.ord - 1, dt.year) proc initDateTime*(monthday: MonthdayRange, month: Month, year: int, - hour: HourRange, minute: MinuteRange, second: SecondRange, zone: Timezone = local()): DateTime = + hour: HourRange, minute: MinuteRange, second: SecondRange, + nanosecond: NanosecondRange, zone: Timezone = local()): DateTime = ## Create a new ``DateTime`` in the specified timezone. assertValidDate monthday, month, year - doAssert monthday <= getDaysInMonth(month, year), "Invalid date: " & $month & " " & $monthday & ", " & $year let dt = DateTime( monthday: monthday, year: year, month: month, hour: hour, minute: minute, - second: second + second: second, + nanosecond: nanosecond ) result = initDateTime(zone.zoneInfoFromTz(dt.toAdjTime), zone) -when not defined(JS): - proc epochTime*(): float {.rtl, extern: "nt$1", tags: [TimeEffect].} - ## gets time after the UNIX epoch (1970) in seconds. It is a float - ## because sub-second resolution is likely to be supported (depending - ## on the hardware/OS). - - proc cpuTime*(): float {.rtl, extern: "nt$1", tags: [TimeEffect].} - ## gets time spent that the CPU spent to run the current process in - ## seconds. This may be more useful for benchmarking than ``epochTime``. - ## However, it may measure the real time instead (depending on the OS). - ## The value of the result has no meaning. - ## To generate useful timing values, take the difference between - ## the results of two ``cpuTime`` calls: - ## - ## .. code-block:: nim - ## var t0 = cpuTime() - ## doWork() - ## echo "CPU time [s] ", cpuTime() - t0 - -when defined(JS): - proc getTime(): Time = - (newDate().getTime() div 1000).Time - - proc epochTime*(): float {.tags: [TimeEffect].} = - newDate().getTime() / 1000 +proc initDateTime*(monthday: MonthdayRange, month: Month, year: int, + hour: HourRange, minute: MinuteRange, second: SecondRange, + zone: Timezone = local()): DateTime = + ## Create a new ``DateTime`` in the specified timezone. + initDateTime(monthday, month, year, hour, minute, second, 0, zone) -else: +when not defined(JS): type Clock {.importc: "clock_t".} = distinct int - proc timec(timer: ptr CTime): CTime {. - importc: "time", header: "<time.h>", tags: [].} - proc getClock(): Clock {.importc: "clock", header: "<time.h>", tags: [TimeEffect].} var clocksPerSec {.importc: "CLOCKS_PER_SEC", nodecl.}: int - proc getTime(): Time = - timec(nil).Time - const - epochDiff = 116444736000000000'i64 rateDiff = 10000000'i64 # 100 nsecs proc unixTimeToWinTime*(time: CTime): int64 = @@ -1352,10 +1700,29 @@ else: result = CTime((time - epochDiff) div rateDiff) when not defined(useNimRtl): - proc epochTime(): float = + proc cpuTime*(): float {.rtl, extern: "nt$1", tags: [TimeEffect].} = + ## gets time spent that the CPU spent to run the current process in + ## seconds. This may be more useful for benchmarking than ``epochTime``. + ## However, it may measure the real time instead (depending on the OS). + ## The value of the result has no meaning. + ## To generate useful timing values, take the difference between + ## the results of two ``cpuTime`` calls: + ## + ## .. code-block:: nim + ## var t0 = cpuTime() + ## doWork() + ## echo "CPU time [s] ", cpuTime() - t0 + result = toFloat(int(getClock())) / toFloat(clocksPerSec) + + proc epochTime*(): float {.rtl, extern: "nt$1", tags: [TimeEffect].} = + ## gets time after the UNIX epoch (1970) in seconds. It is a float + ## because sub-second resolution is likely to be supported (depending + ## on the hardware/OS). + ## + ## ``getTime`` should generally be prefered over this proc. when defined(posix): var a: Timeval - posix_gettimeofday(a) + gettimeofday(a) result = toFloat(a.tv_sec) + toFloat(a.tv_usec)*0.00_0001 elif defined(windows): var f: winlean.FILETIME @@ -1367,30 +1734,37 @@ else: else: {.error: "unknown OS".} - proc cpuTime(): float = - result = toFloat(int(getClock())) / toFloat(clocksPerSec) +when defined(JS): + proc epochTime*(): float {.tags: [TimeEffect].} = + newDate().getTime() / 1000 # Deprecated procs +proc initInterval*(seconds, minutes, hours, days, months, + years: int = 0): TimeInterval {.deprecated.} = + ## **Deprecated since v0.18.0:** use ``initTimeInterval`` instead. + initTimeInterval(0, 0, 0, seconds, minutes, hours, days, 0, months, years) + proc fromSeconds*(since1970: float): Time {.tags: [], raises: [], benign, deprecated.} = ## Takes a float which contains the number of seconds since the unix epoch and ## returns a time object. ## ## **Deprecated since v0.18.0:** use ``fromUnix`` instead - Time(since1970) + let nanos = ((since1970 - since1970.int64.float) * convert(Seconds, Nanoseconds, 1).float).int + initTime(since1970.int64, nanos) proc fromSeconds*(since1970: int64): Time {.tags: [], raises: [], benign, deprecated.} = ## Takes an int which contains the number of seconds since the unix epoch and ## returns a time object. ## ## **Deprecated since v0.18.0:** use ``fromUnix`` instead - Time(since1970) + fromUnix(since1970) proc toSeconds*(time: Time): float {.tags: [], raises: [], benign, deprecated.} = ## Returns the time in seconds since the unix epoch. ## - ## **Deprecated since v0.18.0:** use ``toUnix`` instead - float(time) + ## **Deprecated since v0.18.0:** use ``fromUnix`` instead + time.seconds.float + time.nanoseconds / convert(Seconds, Nanoseconds, 1) proc getLocalTime*(time: Time): DateTime {.tags: [], raises: [], benign, deprecated.} = ## Converts the calendar time `time` to broken-time representation, @@ -1414,7 +1788,8 @@ proc getTimezone*(): int {.tags: [TimeEffect], raises: [], benign, deprecated.} when defined(JS): return newDate().getTimezoneOffset() * 60 elif defined(freebsd) or defined(netbsd) or defined(openbsd): - var a = timec(nil) + var a: CTime + discard time(a) let lt = localtime(addr(a)) # BSD stores in `gmtoff` offset east of UTC in seconds, # but posix systems using west of UTC in seconds @@ -1430,13 +1805,13 @@ proc timeInfoToTime*(dt: DateTime): Time {.tags: [], benign, deprecated.} = dt.toTime when defined(JS): - var startMilsecs = getTime() + var start = getTime() proc getStartMilsecs*(): int {.deprecated, tags: [TimeEffect], benign.} = ## get the milliseconds from the start of the program. **Deprecated since ## version 0.8.10.** Use ``epochTime`` or ``cpuTime`` instead. - when defined(JS): - ## get the milliseconds from the start of the program - return int(getTime() - startMilsecs) + let dur = getTime() - start + result = (convert(Seconds, Milliseconds, dur.seconds) + + convert(Nanoseconds, Milliseconds, dur.nanoseconds)).int else: proc getStartMilsecs*(): int {.deprecated, tags: [TimeEffect], benign.} = when defined(macosx): @@ -1444,9 +1819,6 @@ else: else: result = int(getClock()) div (clocksPerSec div 1000) -proc miliseconds*(t: TimeInterval): int {.deprecated.} = - t.milliseconds - proc timeToTimeInterval*(t: Time): TimeInterval {.deprecated.} = ## Converts a Time to a TimeInterval. ## @@ -1493,11 +1865,13 @@ proc timeToTimeInfo*(t: Time): DateTime {.deprecated.} = result = DateTime(year: y, yearday: yd, month: m, monthday: md, weekday: wd, hour: h, minute: mi, second: s) proc getDayOfWeek*(day, month, year: int): WeekDay {.tags: [], raises: [], benign, deprecated.} = + ## **Warning:** This procedure is deprecated since version 0.18.0. getDayOfWeek(day, month.Month, year) proc getDayOfWeekJulian*(day, month, year: int): WeekDay {.deprecated.} = ## Returns the day of the week enum from day, month and year, ## according to the Julian calendar. + ## **Warning:** This procedure is deprecated since version 0.18.0. # Day & month start from one. let a = (14 - month) div 12 |