Times cosmetic changes (#10237)

* Add more Date wrappers to jscore

* Times cosmetic changes
- Improved docs
- Code wrapped at 80 chars
- Formatting fixes using nimpretty
- Remove some old deprecated procs
This commit is contained in:
Oscar Nihlgård 2019-01-10 10:56:12 +01:00 • committed by Andreas Rumpf
commit b3435d22dc
2 changed files with 382 additions and 327 deletions

View file

@ -73,7 +73,7 @@ proc parse*(d: DateLib, s: cstring): int {.importcpp.}
proc newDate*(): DateTime {. proc newDate*(): DateTime {.
importcpp: "new Date()".} importcpp: "new Date()".}
proc newDate*(date: int|string): DateTime {. proc newDate*(date: int|int64|string): DateTime {.
importcpp: "new Date(#)".} importcpp: "new Date(#)".}
proc newDate*(year, month, day, hours, minutes, proc newDate*(year, month, day, hours, minutes,
@ -90,6 +90,16 @@ proc getSeconds*(d: DateTime): int {.importcpp.}
proc getYear*(d: DateTime): int {.importcpp.} proc getYear*(d: DateTime): int {.importcpp.}
proc getTime*(d: DateTime): int {.importcpp.} proc getTime*(d: DateTime): int {.importcpp.}
proc toString*(d: DateTime): cstring {.importcpp.} proc toString*(d: DateTime): cstring {.importcpp.}
proc getUTCDate*(d: DateTime): int {.importcpp.}
proc getUTCFullYear*(d: DateTime): int {.importcpp.}
proc getUTCHours*(d: DateTime): int {.importcpp.}
proc getUTCMilliseconds*(d: DateTime): int {.importcpp.}
proc getUTCMinutes*(d: DateTime): int {.importcpp.}
proc getUTCMonth*(d: DateTime): int {.importcpp.}
proc getUTCSeconds*(d: DateTime): int {.importcpp.}
proc getUTCDay*(d: DateTime): int {.importcpp.}
proc getTimezoneOffset*(d: DateTime): int {.importcpp.}
proc setFullYear*(d: DateTime, year: int) {.importcpp.}
#JSON library #JSON library
proc stringify*(l: JsonLib, s: JsRoot): cstring {.importcpp.} proc stringify*(l: JsonLib, s: JsRoot): cstring {.importcpp.}

View file

@ -1,35 +1,41 @@
# #
# #
# Nim's Runtime Library # Nim's Runtime Library
# (c) Copyright 2017 Nim contributors # (c) Copyright 2018 Nim contributors
# #
# See the file "copying.txt", included in this # See the file "copying.txt", included in this
# distribution, for details about the copyright. # distribution, for details about the copyright.
# #
##[ ##[
This module contains routines and types for dealing with time using a proleptic Gregorian calendar. The ``times`` module contains routines and types for dealing with time using
It's also available for the `JavaScript target <backends.html#the-javascript-target>`_. the `proleptic Gregorian calendar<https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar>`_.
It's also available for the
`JavaScript target <backends.html#backends-the-javascript-target>`_.
Although the types use nanosecond time resolution, the underlying resolution used by ``getTime()`` Although the ``times`` module support nanosecond time resolution, the
depends on the platform and backend (JS is limited to millisecond precision). resolution used by ``getTime()`` depends on the platform and backend
(JS is limited to millisecond precision).
Examples: Examples:
.. code-block:: nim .. code-block:: nim
import times, os import times, os
# Simple benchmarking
let time = cpuTime() let time = cpuTime()
sleep(100) # Replace this with something to be timed
sleep(100) # replace this with something to be timed
echo "Time taken: ", cpuTime() - time echo "Time taken: ", cpuTime() - time
echo "My formatted time: ", format(now(), "d MMMM yyyy HH:mm") # Current date & time
echo "Using predefined formats: ", getClockStr(), " ", getDateStr() let now1 = now() # Current timestamp as a DateTime in local time
let now2 = now().utc # Current timestamp as a DateTime in UTC
let now3 = getTime() # Current timestamp as a Time
echo "cpuTime() float value: ", cpuTime() # Arithmetic using Duration
echo "An hour from now : ", now() + 1.hours echo "One hour from now : ", now() + initDuration(hours = 1)
echo "An hour from (UTC) now: ", getTime().utc + initDuration(hours = 1) # Arithmetic using TimeInterval
echo "One year from now : ", now() + 1.years
echo "One month from now : ", now() + 1.months
Parsing and Formatting Dates Parsing and Formatting Dates
---------------------------- ----------------------------
@ -97,14 +103,14 @@
| ``24 AD -> 24`` | ``24 AD -> 24``
| ``24 BC -> -23`` | ``24 BC -> -23``
| ``12345 AD -> 12345`` | ``12345 AD -> 12345``
``z`` Displays the timezone offset from UTC. | ``GMT+7 -> +7`` ``z`` Displays the timezone offset from UTC. | ``UTC+7 -> +7``
| ``GMT-5 -> -5`` | ``UTC-5 -> -5``
``zz`` Same as above but with leading 0. | ``GMT+7 -> +07`` ``zz`` Same as above but with leading 0. | ``UTC+7 -> +07``
| ``GMT-5 -> -05`` | ``UTC-5 -> -05``
``zzz`` Same as above but with ``:mm`` where *mm* represents minutes. | ``GMT+7 -> +07:00`` ``zzz`` Same as above but with ``:mm`` where *mm* represents minutes. | ``UTC+7 -> +07:00``
| ``GMT-5 -> -05:00`` | ``UTC-5 -> -05:00``
``zzzz`` Same as above but with ``:ss`` where *ss* represents seconds. | ``GMT+7 -> +07:00:00`` ``zzzz`` Same as above but with ``:ss`` where *ss* represents seconds. | ``UTC+7 -> +07:00:00``
| ``GMT-5 -> -05:00:00`` | ``UTC-5 -> -05:00:00``
``g`` Era: AD or BC | ``300 AD -> AD`` ``g`` Era: AD or BC | ``300 AD -> AD``
| ``300 BC -> BC`` | ``300 BC -> BC``
``fff`` Milliseconds display | ``1000000 nanoseconds -> 1`` ``fff`` Milliseconds display | ``1000000 nanoseconds -> 1``
@ -117,19 +123,78 @@
inserted without quoting them: ``:`` ``-`` ``(`` ``)`` ``/`` ``[`` ``]`` inserted without quoting them: ``:`` ``-`` ``(`` ``)`` ``/`` ``[`` ``]``
``,``. A literal ``'`` can be specified with ``''``. ``,``. A literal ``'`` can be specified with ``''``.
However you don't need to necessarily separate format patterns, a However you don't need to necessarily separate format patterns, an
unambiguous format string like ``yyyyMMddhhmmss`` is valid too (although unambiguous format string like ``yyyyMMddhhmmss`` is valid too (although
only for years in the range 1..9999). only for years in the range 1..9999).
Duration vs TimeInterval
----------------------------
The ``times`` module exports two similiar types that are both used to
represent some amount of time: ``Duration`` and ``TimeInterval``.
This section explains how they differ and when one should be prefered over the
other (short answer: use ``Duration`` unless support for months and years is
needed).
Duration
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A ``Duration`` represents a duration of time stored as seconds and
nanoseconds. A ``Duration`` is always fully normalized, so
``initDuration(hours = 1)`` and ``initDuration(minutes = 60)`` are equivilant.
Arithmetics with a ``Duration`` is very fast, especially when used with the
``Time`` type, since it only involves basic arithmetic. Because ``Duration``
is more performant and easier to understand it should generally prefered.
TimeInterval
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A ``TimeInterval`` represents some amount of time expressed in calendar
units, for example "1 year and 2 days". Since some units cannot be
normalized (the length of a year is different for leap years for example),
the ``TimeInterval`` type uses seperate fields for every unit. The
``TimeInterval``'s returned form the this module generally don't normalize
**anything**, so even units that could be normalized (like seconds,
milliseconds and so on) are left untouched.
Arithmetics with a ``TimeInterval`` can be very slow, because it requires
timezone information.
Since it's slower and more complex, the ``TimeInterval`` type should be
avoided unless the program explicitly needs the features it offers that
``Duration`` doesn't have.
How long is a day?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
It should be especially noted that the handling of days differs between
``TimeInterval`` and ``Duration``. The ``Duration`` type always treats a day
as exactly 86400 seconds. For ``TimeInterval``, it's more complex.
As an example, consider the amount of time between these two timestamps, both
in the same timezone:
- 2018-03-25T12:00+02:00
- 2018-03-26T12:00+01:00
If only the date & time is considered, it appears that exatly one day has
passed. However, the UTC offsets are different, which means that the
UTC offset was changed somewhere between. This happens twice each year for
timezones that use daylight savings time. Because of this change, the amount
of time that has passed is actually 25 hours.
The ``TimeInterval`` type uses calendar units, and will say that exactly one
day has passed. The ``Duration`` type on the other hand normalizes everything
to seconds, and will therefore say that 90000 seconds has passed, which is
the same as 25 hours.
]## ]##
import import strutils, algorithm, math, options, strformat
strutils, algorithm, math, options, strformat
include "system/inclrtl" include "system/inclrtl"
when defined(JS):
import jscore
# This is really bad, but overflow checks are broken badly for # This is really bad, but overflow checks are broken badly for
# ints on the JS backend. See #6752. # ints on the JS backend. See #6752.
when defined(JS):
{.push overflowChecks: off.} {.push overflowChecks: off.}
proc `*`(a, b: int64): int64 = proc `*`(a, b: int64): int64 =
system.`*`(a, b) system.`*`(a, b)
@ -149,17 +214,18 @@ when defined(JS):
system.inc(a, b) system.inc(a, b)
{.pop.} {.pop.}
when defined(posix): elif defined(posix):
import posix import posix
type CTime = posix.Time type CTime = posix.Time
var var
realTimeClockId {.importc: "CLOCK_REALTIME", header: "<time.h>".}: Clockid realTimeClockId {.importc: "CLOCK_REALTIME", header: "<time.h>".}: Clockid
cpuClockId {.importc: "CLOCK_THREAD_CPUTIME_ID", header: "<time.h>".}: Clockid cpuClockId
{.importc: "CLOCK_THREAD_CPUTIME_ID", header: "<time.h>".}: Clockid
proc gettimeofday(tp: var Timeval, unused: pointer = nil) {. proc gettimeofday(tp: var Timeval, unused: pointer = nil)
importc: "gettimeofday", header: "<sys/time.h>".} {.importc: "gettimeofday", header: "<sys/time.h>".}
when not defined(freebsd) and not defined(netbsd) and not defined(openbsd): when not defined(freebsd) and not defined(netbsd) and not defined(openbsd):
var timezone {.importc, header: "<time.h>".}: int var timezone {.importc, header: "<time.h>".}: int
@ -178,8 +244,23 @@ elif defined(windows):
var timezone {.importc: "_timezone", header: "<time.h>".}: int var timezone {.importc: "_timezone", header: "<time.h>".}: int
type type
Month* = enum ## Represents a month. Note that the enum starts at ``1``, so ``ord(month)`` will give Tm {.importc: "struct tm", header: "<time.h>", final, pure.} = object
## the month number in the range ``[1..12]``. tm_sec*: cint ## Seconds [0,60].
tm_min*: cint ## Minutes [0,59].
tm_hour*: cint ## Hour [0,23].
tm_mday*: cint ## Day of month [1,31].
tm_mon*: cint ## Month of year [0,11].
tm_year*: cint ## Years since 1900.
tm_wday*: cint ## Day of week [0,6] (Sunday =0).
tm_yday*: cint ## Day of year [0,365].
tm_isdst*: cint ## Daylight Savings flag.
proc localtime(a1: var CTime): ptr Tm {.importc, header: "<time.h>".}
type
Month* = enum ## Represents a month. Note that the enum starts at ``1``,
## so ``ord(month)`` will give the month number in the
## range ``1..12``.
mJan = (1, "January") mJan = (1, "January")
mFeb = "February" mFeb = "February"
mMar = "March" mMar = "March"
@ -213,13 +294,19 @@ type
seconds: int64 seconds: int64
nanosecond: NanosecondRange nanosecond: NanosecondRange
DateTime* = object of RootObj ## Represents a time in different parts. DateTime* = object of RootObj ## \
## Although this type can represent leap ## Represents a time in different parts. Although this type can represent
## seconds, they are generally not supported ## leap seconds, they are generally not supported in this module. They are
## in this module. They are not ignored, ## not ignored, but the ``DateTime``'s returned by procedures in this
## but the ``DateTime``'s returned by ## module will never have a leap second.
## procedures in this module will never have ##
## a leap second. ## **Warning**: even though the fields of ``DateTime`` are exported,
## they should never be mutated directly. Doing so is unsafe and will
## result in the ``DateTime`` ending up in an invalid state.
##
## Instead of mutating the fields directly, use the ``Duration``
## and ``TimeInterval`` types for arithmetic and use the ``initDateTime``
## procedure for changing a specific field.
nanosecond*: NanosecondRange ## The number of nanoseconds after the second, nanosecond*: NanosecondRange ## The number of nanoseconds after the second,
## in the range 0 to 999_999_999. ## in the range 0 to 999_999_999.
second*: SecondRange ## The number of seconds after the minute, second*: SecondRange ## The number of seconds after the minute,
@ -230,27 +317,48 @@ type
hour*: HourRange ## The number of hours past midnight, hour*: HourRange ## The number of hours past midnight,
## in the range 0 to 23. ## in the range 0 to 23.
monthday*: MonthdayRange ## The day of the month, in the range 1 to 31. monthday*: MonthdayRange ## The day of the month, in the range 1 to 31.
month*: Month ## The current month. month*: Month ## The month.
year*: int ## The current year, using astronomical year numbering year*: int ## The year, using astronomical year numbering
## (meaning that before year 1 is year 0, then year -1 and so on). ## (meaning that before year 1 is year 0,
weekday*: WeekDay ## The current day of the week. ## then year -1 and so on).
weekday*: WeekDay ## The day of the week.
yearday*: YeardayRange ## The number of days since January 1, yearday*: YeardayRange ## The number of days since January 1,
## in the range 0 to 365. ## in the range 0 to 365.
isDst*: bool ## Determines whether DST is in effect. isDst*: bool ## Determines whether DST is in effect.
## Always false for the JavaScript backend. ## Always false for the JavaScript backend.
timezone*: Timezone ## The timezone represented as an implementation of ``Timezone``. timezone*: Timezone ## The timezone represented as an implementation
utcOffset*: int ## The offset in seconds west of UTC, including any offset due to DST. ## of ``Timezone``.
## Note that the sign of this number is the opposite utcOffset*: int ## The offset in seconds west of UTC, including
## of the one in a formatted offset string like ``+01:00`` ## any offset due to DST. Note that the sign of
## (which would be parsed into the UTC offset ``-3600``). ## this number is the opposite of the one in a
## formatted offset string like ``+01:00`` (which
## would be equivalent to the UTC offset
## ``-3600``).
TimeInterval* = object ## Represents a non-fixed duration of time. Can be used to add and subtract Duration* = object ## Represents a fixed duration of time, meaning a duration
## non-fixed time units from a ``DateTime`` or ``Time``. ## that has constant length independent of the context.
## ``TimeInterval`` doesn't represent a fixed duration of time, seconds: int64
nanosecond: 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``.
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``.
## Note that ``TimeInterval`` doesn't represent a fixed duration of time,
## since the duration of some units depend on the context (e.g a year ## 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, ## can be either 365 or 366 days long). The non-fixed time units are
## months and days. ## years, months, days and week.
##
## Note that ``TimeInterval``'s returned from the ``times`` module are
## never normalized. If you want to normalize a time unit, ``Duration``
## should be used instead.
nanoseconds*: int ## The number of nanoseconds nanoseconds*: int ## The number of nanoseconds
microseconds*: int ## The number of microseconds microseconds*: int ## The number of microseconds
milliseconds*: int ## The number of milliseconds milliseconds*: int ## The number of milliseconds
@ -262,19 +370,6 @@ type
months*: int ## The number of months months*: int ## The number of months
years*: int ## The number of years 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
nanosecond: 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* = ref object ## \ Timezone* = ref object ## \
## Timezone interface for supporting ``DateTime``'s of arbritary ## Timezone interface for supporting ``DateTime``'s of arbritary
## timezones. The ``times`` module only supplies implementations for the ## timezones. The ``times`` module only supplies implementations for the
@ -317,8 +412,10 @@ const unitWeights: array[FixedTimeUnit, int64] = [
7 * secondsInDay * 1e9.int64, 7 * secondsInDay * 1e9.int64,
] ]
proc convert*[T: SomeInteger](unitFrom, unitTo: FixedTimeUnit, quantity: T): T {.inline.} = proc convert*[T: SomeInteger](unitFrom, unitTo: FixedTimeUnit, quantity: T): T
{.inline.} =
## Convert a quantity of some duration unit to another duration unit. ## Convert a quantity of some duration unit to another duration unit.
## This proc only deals with integers, so the result might be truncated.
runnableExamples: runnableExamples:
doAssert convert(Days, Hours, 2) == 48 doAssert convert(Days, Hours, 2) == 48
doAssert convert(Days, Weeks, 13) == 1 # Truncated doAssert convert(Days, Weeks, 13) == 1 # Truncated
@ -340,21 +437,41 @@ proc normalize[T: Duration|Time](seconds, nanoseconds: int64): T =
result.nanosecond = nanosecond.int result.nanosecond = nanosecond.int
# Forward declarations # Forward declarations
proc utcTzInfo(time: Time): ZonedTime {.tags: [], raises: [], benign .} proc utcTzInfo(time: Time): ZonedTime
proc localZonedTimeFromTime(time: Time): ZonedTime {.tags: [], raises: [], benign .} {.tags: [], raises: [], benign.}
proc localZonedTimeFromAdjTime(adjTime: Time): ZonedTime {.tags: [], raises: [], benign .} proc localZonedTimeFromTime(time: Time): ZonedTime
{.tags: [], raises: [], benign.}
proc localZonedTimeFromAdjTime(adjTime: Time): ZonedTime
{.tags: [], raises: [], benign.}
proc initTime*(unix: int64, nanosecond: NanosecondRange): Time proc initTime*(unix: int64, nanosecond: NanosecondRange): Time
{.tags: [], raises: [], benign noSideEffect.} {.tags: [], raises: [], benign, noSideEffect.}
proc initDuration*(nanoseconds, microseconds, milliseconds,
seconds, minutes, hours, days, weeks: int64 = 0): Duration
{.tags: [], raises: [], benign noSideEffect.}
proc nanosecond*(time: Time): NanosecondRange = proc nanosecond*(time: Time): NanosecondRange =
## Get the fractional part of a ``Time`` as the number ## Get the fractional part of a ``Time`` as the number
## of nanoseconds of the second. ## of nanoseconds of the second.
time.nanosecond time.nanosecond
proc initDuration*(nanoseconds, microseconds, milliseconds,
seconds, minutes, hours, days, weeks: int64 = 0): Duration =
## Create a new duration.
runnableExamples:
let dur = initDuration(seconds = 1, milliseconds = 1)
doAssert dur.milliseconds == 1
doAssert dur.seconds == 1
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.} = proc weeks*(dur: Duration): int64 {.inline.} =
## Number of whole weeks represented by the duration. ## Number of whole weeks represented by the duration.
@ -407,9 +524,10 @@ proc fractional*(dur: Duration): Duration {.inline.} =
doAssert dur.fractional == initDuration(nanoseconds = 5) doAssert dur.fractional == initDuration(nanoseconds = 5)
initDuration(nanoseconds = dur.nanosecond) initDuration(nanoseconds = dur.nanosecond)
proc fromUnix*(unix: int64): Time
proc fromUnix*(unix: int64): Time {.benign, tags: [], raises: [], noSideEffect.} = {.benign, tags: [], raises: [], noSideEffect.} =
## Convert a unix timestamp (seconds since ``1970-01-01T00:00:00Z``) to a ``Time``. ## Convert a unix timestamp (seconds since ``1970-01-01T00:00:00Z``)
## to a ``Time``.
runnableExamples: runnableExamples:
doAssert $fromUnix(0).utc == "1970-01-01T00:00:00Z" doAssert $fromUnix(0).utc == "1970-01-01T00:00:00Z"
initTime(unix, 0) initTime(unix, 0)
@ -421,15 +539,16 @@ proc toUnix*(t: Time): int64 {.benign, tags: [], raises: [], noSideEffect.} =
t.seconds t.seconds
proc fromWinTime*(win: int64): Time = proc fromWinTime*(win: int64): Time =
## Convert a Windows file time (100-nanosecond intervals since ``1601-01-01T00:00:00Z``) ## Convert a Windows file time (100-nanosecond intervals since
## to a ``Time``. ## ``1601-01-01T00:00:00Z``) to a ``Time``.
const hnsecsPerSec = convert(Seconds, Nanoseconds, 1) div 100 const hnsecsPerSec = convert(Seconds, Nanoseconds, 1) div 100
let nanos = floorMod(win, hnsecsPerSec) * 100 let nanos = floorMod(win, hnsecsPerSec) * 100
let seconds = floorDiv(win - epochDiff, hnsecsPerSec) let seconds = floorDiv(win - epochDiff, hnsecsPerSec)
result = initTime(seconds, nanos) result = initTime(seconds, nanos)
proc toWinTime*(t: Time): int64 = proc toWinTime*(t: Time): int64 =
## Convert ``t`` to a Windows file time (100-nanosecond intervals since ``1601-01-01T00:00:00Z``). ## Convert ``t`` to a Windows file time (100-nanosecond intervals
## since ``1601-01-01T00:00:00Z``).
result = t.seconds * rateDiff + epochDiff + t.nanosecond div 100 result = t.seconds * rateDiff + epochDiff + t.nanosecond div 100
proc isLeapYear*(year: int): bool = proc isLeapYear*(year: int): bool =
@ -437,7 +556,7 @@ proc isLeapYear*(year: int): bool =
year mod 4 == 0 and (year mod 100 != 0 or year mod 400 == 0) year mod 4 == 0 and (year mod 100 != 0 or year mod 400 == 0)
proc getDaysInMonth*(month: Month, year: int): int = proc getDaysInMonth*(month: Month, year: int): int =
## Get the number of days in a ``month`` of a ``year``. ## Get the number of days in ``month`` of ``year``.
# http://www.dispersiondesign.com/articles/time/number_of_days_in_a_month # http://www.dispersiondesign.com/articles/time/number_of_days_in_a_month
case month case month
of mFeb: result = if isLeapYear(year): 29 else: 28 of mFeb: result = if isLeapYear(year): 29 else: 28
@ -448,15 +567,18 @@ proc getDaysInYear*(year: int): int =
## Get the number of days in a ``year`` ## Get the number of days in a ``year``
result = 365 + (if isLeapYear(year): 1 else: 0) result = 365 + (if isLeapYear(year): 1 else: 0)
proc assertValidDate(monthday: MonthdayRange, month: Month, year: int) {.inline.} = proc assertValidDate(monthday: MonthdayRange, month: Month, year: int)
{.inline.} =
assert monthday <= getDaysInMonth(month, year), assert monthday <= getDaysInMonth(month, year),
$year & "-" & intToStr(ord(month), 2) & "-" & $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 = proc toEpochDay(monthday: MonthdayRange, month: Month, year: int): int64 =
## Get the epoch day from a year/month/day date. ## Get the epoch day from a year/month/day date.
## The epoch day is the number of days since 1970/01/01 (it might be negative). ## The epoch day is the number of days since 1970/01/01
assertValidDate monthday, month, year ## (it might be negative).
# Based on http://howardhinnant.github.io/date_algorithms.html # Based on http://howardhinnant.github.io/date_algorithms.html
assertValidDate monthday, month, year
var (y, m, d) = (year, ord(month), monthday.int) var (y, m, d) = (year, ord(month), monthday.int)
if m <= 2: if m <= 2:
y.dec y.dec
@ -467,9 +589,11 @@ proc toEpochDay(monthday: MonthdayRange, month: Month, year: int): int64 =
let doe = yoe * 365 + yoe div 4 - yoe div 100 + doy let doe = yoe * 365 + yoe div 4 - yoe div 100 + doy
return era * 146097 + doe - 719468 return era * 146097 + doe - 719468
proc fromEpochDay(epochday: int64): tuple[monthday: MonthdayRange, month: Month, year: int] = proc fromEpochDay(epochday: int64):
tuple[monthday: MonthdayRange, month: Month, year: int] =
## Get the year/month/day date from a epoch day. ## Get the year/month/day date from a epoch day.
## The epoch day is the number of days since 1970/01/01 (it might be negative). ## The epoch day is the number of days since 1970/01/01
## (it might be negative).
# Based on http://howardhinnant.github.io/date_algorithms.html # Based on http://howardhinnant.github.io/date_algorithms.html
var z = epochday var z = epochday
z.inc 719468 z.inc 719468
@ -483,19 +607,23 @@ proc fromEpochDay(epochday: int64): tuple[monthday: MonthdayRange, month: Month,
let m = mp + (if mp < 10: 3 else: -9) let m = mp + (if mp < 10: 3 else: -9)
return (d.MonthdayRange, m.Month, (y + ord(m <= 2)).int) return (d.MonthdayRange, m.Month, (y + ord(m <= 2)).int)
proc getDayOfYear*(monthday: MonthdayRange, month: Month, year: int): YeardayRange {.tags: [], raises: [], benign .} = proc getDayOfYear*(monthday: MonthdayRange, month: Month, year: int):
YeardayRange {.tags: [], raises: [], benign.} =
## Returns the day of the year. ## Returns the day of the year.
## Equivalent with ``initDateTime(monthday, month, year, 0, 0, 0).yearday``. ## Equivalent with ``initDateTime(monthday, month, year, 0, 0, 0).yearday``.
assertValidDate monthday, month, year assertValidDate monthday, month, year
const daysUntilMonth: array[Month, int] = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334] const daysUntilMonth: array[Month, int] =
const daysUntilMonthLeap: array[Month, int] = [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335] [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]
const daysUntilMonthLeap: array[Month, int] =
[0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335]
if isLeapYear(year): if isLeapYear(year):
result = daysUntilMonthLeap[month] + monthday - 1 result = daysUntilMonthLeap[month] + monthday - 1
else: else:
result = daysUntilMonth[month] + monthday - 1 result = daysUntilMonth[month] + monthday - 1
proc getDayOfWeek*(monthday: MonthdayRange, month: Month, year: int): WeekDay {.tags: [], raises: [], benign .} = proc getDayOfWeek*(monthday: MonthdayRange, month: Month, year: int): WeekDay
{.tags: [], raises: [], benign.} =
## Returns the day of the week enum from day, month and year. ## Returns the day of the week enum from day, month and year.
## Equivalent with ``initDateTime(monthday, month, year, 0, 0, 0).weekday``. ## Equivalent with ``initDateTime(monthday, month, year, 0, 0, 0).weekday``.
assertValidDate monthday, month, year assertValidDate monthday, month, year
@ -507,7 +635,6 @@ proc getDayOfWeek*(monthday: MonthdayRange, month: Month, year: int): WeekDay {.
# so we must correct for the WeekDay type. # so we must correct for the WeekDay type.
result = if wd == 0: dSun else: WeekDay(wd - 1) result = if wd == 0: dSun else: WeekDay(wd - 1)
{.pragma: operator, rtl, noSideEffect, benign.} {.pragma: operator, rtl, noSideEffect, benign.}
template subImpl[T: Duration|Time](a: Duration|Time, b: Duration|Time): T = template subImpl[T: Duration|Time](a: Duration|Time, b: Duration|Time): T =
@ -526,28 +653,6 @@ template lqImpl(a: Duration|Time, b: Duration|Time): bool =
template eqImpl(a: Duration|Time, b: Duration|Time): bool = template eqImpl(a: Duration|Time, b: Duration|Time): bool =
a.seconds == b.seconds and a.nanosecond == b.nanosecond a.seconds == b.seconds and a.nanosecond == b.nanosecond
proc initDuration*(nanoseconds, microseconds, milliseconds,
seconds, minutes, hours, days, weeks: int64 = 0): Duration =
runnableExamples:
let dur = initDuration(seconds = 1, milliseconds = 1)
doAssert dur.milliseconds == 1
doAssert dur.seconds == 1
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)
const DurationZero* = initDuration() ## \ const DurationZero* = initDuration() ## \
## Zero value for durations. Useful for comparisons. ## Zero value for durations. Useful for comparisons.
## ##
@ -616,12 +721,14 @@ proc humanizeParts(parts: seq[string]): string =
result.add "and " & parts[high(parts)] result.add "and " & parts[high(parts)]
proc `$`*(dur: Duration): string = proc `$`*(dur: Duration): string =
## Human friendly string representation of ``Duration``. ## Human friendly string representation of a ``Duration``.
runnableExamples: runnableExamples:
doAssert $initDuration(seconds = 2) == "2 seconds" doAssert $initDuration(seconds = 2) == "2 seconds"
doAssert $initDuration(weeks = 1, days = 2) == "1 week and 2 days" 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(hours = 1, minutes = 2, seconds = 3) ==
doAssert $initDuration(milliseconds = -1500) == "-1 second and -500 milliseconds" "1 hour, 2 minutes, and 3 seconds"
doAssert $initDuration(milliseconds = -1500) ==
"-1 second and -500 milliseconds"
var parts = newSeq[string]() var parts = newSeq[string]()
var numParts = toParts(dur) var numParts = toParts(dur)
@ -669,23 +776,25 @@ proc `<=`*(a, b: Duration): bool {.operator.} =
proc `==`*(a, b: Duration): bool {.operator.} = proc `==`*(a, b: Duration): bool {.operator.} =
eqImpl(a, b) eqImpl(a, b)
proc `*`*(a: int64, b: Duration): Duration {.operator} = proc `*`*(a: int64, b: Duration): Duration {.operator.} =
## Multiply a duration by some scalar. ## Multiply a duration by some scalar.
runnableExamples: runnableExamples:
doAssert 5 * initDuration(seconds = 1) == initDuration(seconds = 5) doAssert 5 * initDuration(seconds = 1) == initDuration(seconds = 5)
normalize[Duration](a * b.seconds, a * b.nanosecond) normalize[Duration](a * b.seconds, a * b.nanosecond)
proc `*`*(a: Duration, b: int64): Duration {.operator} = proc `*`*(a: Duration, b: int64): Duration {.operator.} =
## Multiply a duration by some scalar. ## Multiply a duration by some scalar.
runnableExamples: runnableExamples:
doAssert initDuration(seconds = 1) * 5 == initDuration(seconds = 5) doAssert initDuration(seconds = 1) * 5 == initDuration(seconds = 5)
b * a b * a
proc `div`*(a: Duration, b: int64): Duration {.operator} = proc `div`*(a: Duration, b: int64): Duration {.operator.} =
## Integer division for durations. ## Integer division for durations.
runnableExamples: runnableExamples:
doAssert initDuration(seconds = 3) div 2 == initDuration(milliseconds = 1500) doAssert initDuration(seconds = 3) div 2 ==
doAssert initDuration(nanoseconds = 3) div 2 == initDuration(nanoseconds = 1) initDuration(milliseconds = 1500)
doAssert initDuration(nanoseconds = 3) div 2 ==
initDuration(nanoseconds = 1)
let carryOver = convert(Seconds, Nanoseconds, a.seconds mod b) let carryOver = convert(Seconds, Nanoseconds, a.seconds mod b)
normalize[Duration](a.seconds div b, (a.nanosecond + carryOver) div b) normalize[Duration](a.seconds div b, (a.nanosecond + carryOver) div b)
@ -783,8 +892,10 @@ proc initDateTime(zt: ZonedTime, zone: Timezone): DateTime =
proc newTimezone*( proc newTimezone*(
name: string, name: string,
zonedTimeFromTimeImpl: proc (time: Time): ZonedTime {.tags: [], raises: [], benign.}, zonedTimeFromTimeImpl: proc (time: Time): ZonedTime
zonedTimeFromAdjTimeImpl: proc (adjTime: Time): ZonedTime {.tags: [], raises: [], benign.} {.tags: [], raises: [], benign.},
zonedTimeFromAdjTimeImpl: proc (adjTime: Time): ZonedTime
{.tags: [], raises: [], benign.}
): Timezone = ): Timezone =
## Create a new ``Timezone``. ## Create a new ``Timezone``.
## ##
@ -847,11 +958,13 @@ proc `==`*(zone1, zone2: Timezone): bool =
doAssert local() != utc() doAssert local() != utc()
zone1.name == zone2.name zone1.name == zone2.name
proc inZone*(time: Time, zone: Timezone): DateTime {.tags: [], raises: [], benign.} = proc inZone*(time: Time, zone: Timezone): DateTime
{.tags: [], raises: [], benign.} =
## Convert ``time`` into a ``DateTime`` using ``zone`` as the timezone. ## Convert ``time`` into a ``DateTime`` using ``zone`` as the timezone.
result = initDateTime(zone.zonedTimeFromTime(time), zone) result = initDateTime(zone.zonedTimeFromTime(time), zone)
proc inZone*(dt: DateTime, zone: Timezone): DateTime {.tags: [], raises: [], benign.} = proc inZone*(dt: DateTime, zone: Timezone): DateTime
{.tags: [], raises: [], benign.} =
## Returns a ``DateTime`` representing the same point in time as ``dt`` but ## Returns a ``DateTime`` representing the same point in time as ``dt`` but
## using ``zone`` as the timezone. ## using ``zone`` as the timezone.
dt.toTime.inZone(zone) dt.toTime.inZone(zone)
@ -865,46 +978,23 @@ proc toAdjTime(dt: DateTime): Time =
result = initTime(seconds, dt.nanosecond) result = initTime(seconds, dt.nanosecond)
when defined(JS): when defined(JS):
type JsDate = object
proc newDate(year, month, date, hours, minutes, seconds, milliseconds: int): JsDate {.tags: [], raises: [], importc: "new Date".}
proc newDate(): JsDate {.importc: "new Date".}
proc newDate(value: float): JsDate {.importc: "new Date".}
proc getTimezoneOffset(js: JsDate): int {.tags: [], raises: [], benign, importcpp.}
proc getDay(js: JsDate): int {.tags: [], raises: [], benign, importcpp.}
proc getFullYear(js: JsDate): int {.tags: [], raises: [], benign, importcpp.}
proc getHours(js: JsDate): int {.tags: [], raises: [], benign, importcpp.}
proc getMilliseconds(js: JsDate): int {.tags: [], raises: [], benign, importcpp.}
proc getMinutes(js: JsDate): int {.tags: [], raises: [], benign, importcpp.}
proc getMonth(js: JsDate): int {.tags: [], raises: [], benign, importcpp.}
proc getSeconds(js: JsDate): int {.tags: [], raises: [], benign, importcpp.}
proc getTime(js: JsDate): int {.tags: [], raises: [], noSideEffect, benign, importcpp.}
proc getDate(js: JsDate): int {.tags: [], raises: [], benign, importcpp.}
proc getUTCDate(js: JsDate): int {.tags: [], raises: [], benign, importcpp.}
proc getUTCFullYear(js: JsDate): int {.tags: [], raises: [], benign, importcpp.}
proc getUTCHours(js: JsDate): int {.tags: [], raises: [], benign, importcpp.}
proc getUTCMilliseconds(js: JsDate): int {.tags: [], raises: [], benign, importcpp.}
proc getUTCMinutes(js: JsDate): int {.tags: [], raises: [], benign, importcpp.}
proc getUTCMonth(js: JsDate): int {.tags: [], raises: [], benign, importcpp.}
proc getUTCSeconds(js: JsDate): int {.tags: [], raises: [], benign, importcpp.}
proc getUTCDay(js: JsDate): int {.tags: [], raises: [], benign, importcpp.}
proc getYear(js: JsDate): int {.tags: [], raises: [], benign, importcpp.}
proc setFullYear(js: JsDate, year: int): void {.tags: [], raises: [], benign, importcpp.}
proc localZonedTimeFromTime(time: Time): ZonedTime = proc localZonedTimeFromTime(time: Time): ZonedTime =
let jsDate = newDate(time.seconds.float * 1000) let jsDate = newDate(time.seconds * 1000)
let offset = jsDate.getTimezoneOffset() * secondsInMin let offset = jsDate.getTimezoneOffset() * secondsInMin
result.time = time result.time = time
result.utcOffset = offset result.utcOffset = offset
result.isDst = false result.isDst = false
proc localZonedTimeFromAdjTime(adjTime: Time): ZonedTime = proc localZonedTimeFromAdjTime(adjTime: Time): ZonedTime =
let utcDate = newDate(adjTime.seconds.float * 1000) let utcDate = newDate(adjTime.seconds * 1000)
let localDate = newDate(utcDate.getUTCFullYear(), utcDate.getUTCMonth(), utcDate.getUTCDate(), let localDate = newDate(utcDate.getUTCFullYear(), utcDate.getUTCMonth(),
utcDate.getUTCHours(), utcDate.getUTCMinutes(), utcDate.getUTCSeconds(), 0) utcDate.getUTCDate(), utcDate.getUTCHours(), utcDate.getUTCMinutes(),
utcDate.getUTCSeconds(), 0)
# This is as dumb as it looks - JS doesn't support years in the range 0-99 in the constructor # This is as dumb as it looks - JS doesn't support years in the range
# because they are assumed to be 19xx... # 0-99 in the constructor because they are assumed to be 19xx...
# Because JS doesn't support timezone history, it doesn't really matter in practice. # Because JS doesn't support timezone history,
# it doesn't really matter in practice.
if utcDate.getUTCFullYear() in 0 .. 99: if utcDate.getUTCFullYear() in 0 .. 99:
localDate.setFullYear(utcDate.getUTCFullYear()) localDate.setFullYear(utcDate.getUTCFullYear())
@ -913,46 +1003,13 @@ when defined(JS):
result.isDst = false result.isDst = false
else: else:
when defined(freebsd) or defined(netbsd) or defined(openbsd) or proc toAdjUnix(tm: Tm): int64 =
defined(macosx): let epochDay = toEpochday(tm.tm_mday, (tm.tm_mon + 1).Month,
type tm.tm_year.int + 1900)
StructTm {.importc: "struct tm".} = object
second {.importc: "tm_sec".},
minute {.importc: "tm_min".},
hour {.importc: "tm_hour".},
monthday {.importc: "tm_mday".},
month {.importc: "tm_mon".},
year {.importc: "tm_year".},
weekday {.importc: "tm_wday".},
yearday {.importc: "tm_yday".},
isdst {.importc: "tm_isdst".}: cint
gmtoff {.importc: "tm_gmtoff".}: clong
else:
type
StructTm {.importc: "struct tm".} = object
second {.importc: "tm_sec".},
minute {.importc: "tm_min".},
hour {.importc: "tm_hour".},
monthday {.importc: "tm_mday".},
month {.importc: "tm_mon".},
year {.importc: "tm_year".},
weekday {.importc: "tm_wday".},
yearday {.importc: "tm_yday".},
isdst {.importc: "tm_isdst".}: cint
when defined(linux) and defined(amd64) or defined(haiku):
gmtoff {.importc: "tm_gmtoff".}: clong
zone {.importc: "tm_zone".}: cstring
type
StructTmPtr = ptr StructTm
proc localtime(timer: ptr CTime): StructTmPtr {. importc: "localtime", header: "<time.h>", tags: [].}
proc toAdjUnix(tm: StructTm): int64 =
let epochDay = toEpochday(tm.monthday, (tm.month + 1).Month, tm.year.int + 1900)
result = epochDay * secondsInDay result = epochDay * secondsInDay
result.inc tm.hour * secondsInHour result.inc tm.tm_hour * secondsInHour
result.inc tm.minute * 60 result.inc tm.tm_min * 60
result.inc tm.second result.inc tm.tm_sec
proc getLocalOffsetAndDst(unix: int64): tuple[offset: int, dst: bool] = proc getLocalOffsetAndDst(unix: int64): tuple[offset: int, dst: bool] =
# Windows can't handle unix < 0, so we fall back to unix = 0. # Windows can't handle unix < 0, so we fall back to unix = 0.
@ -960,7 +1017,7 @@ else:
when defined(windows): when defined(windows):
if unix < 0: if unix < 0:
var a = 0.CTime var a = 0.CTime
let tmPtr = localtime(addr(a)) let tmPtr = localtime(a)
if not tmPtr.isNil: if not tmPtr.isNil:
let tm = tmPtr[] let tm = tmPtr[]
return ((0 - tm.toAdjUnix).int, false) return ((0 - tm.toAdjUnix).int, false)
@ -969,10 +1026,10 @@ else:
# In case of a 32-bit time_t, we fallback to the closest available # In case of a 32-bit time_t, we fallback to the closest available
# timezone information. # timezone information.
var a = clamp(unix, low(CTime), high(CTime)).CTime var a = clamp(unix, low(CTime), high(CTime)).CTime
let tmPtr = localtime(addr(a)) let tmPtr = localtime(a)
if not tmPtr.isNil: if not tmPtr.isNil:
let tm = tmPtr[] let tm = tmPtr[]
return ((a.int64 - tm.toAdjUnix).int, tm.isdst > 0) return ((a.int64 - tm.toAdjUnix).int, tm.tm_isdst > 0)
return (0, false) return (0, false)
proc localZonedTimeFromTime(time: Time): ZonedTime = proc localZonedTimeFromTime(time: Time): ZonedTime =
@ -1060,7 +1117,8 @@ proc getTime*(): Time {.tags: [TimeEffect], benign.} =
elif defined(macosx) or defined(freebsd): elif defined(macosx) or defined(freebsd):
var a: Timeval var a: Timeval
gettimeofday(a) gettimeofday(a)
result = initTime(a.tv_sec.int64, convert(Microseconds, Nanoseconds, a.tv_usec.int)) result = initTime(a.tv_sec.int64,
convert(Microseconds, Nanoseconds, a.tv_usec.int))
elif defined(posix): elif defined(posix):
var ts: Timespec var ts: Timespec
discard clock_gettime(realTimeClockId, ts) discard clock_gettime(realTimeClockId, ts)
@ -1081,13 +1139,17 @@ proc initTimeInterval*(nanoseconds, microseconds, milliseconds,
days, weeks, months, years: int = 0): TimeInterval = days, weeks, months, years: int = 0): TimeInterval =
## Creates a new ``TimeInterval``. ## Creates a new ``TimeInterval``.
## ##
## This proc doesn't perform any normalization! For example,
## ``initTimeInterval(hours = 24)`` and ``initTimeInterval(days = 1)`` are
## not equal.
##
## You can also use the convenience procedures called ``milliseconds``, ## You can also use the convenience procedures called ``milliseconds``,
## ``seconds``, ``minutes``, ``hours``, ``days``, ``months``, and ``years``. ## ``seconds``, ``minutes``, ``hours``, ``days``, ``months``, and ``years``.
##
runnableExamples: runnableExamples:
let day = initTimeInterval(hours = 24) let day = initTimeInterval(hours = 24)
let dt = initDateTime(01, mJan, 2000, 12, 00, 00, utc()) let dt = initDateTime(01, mJan, 2000, 12, 00, 00, utc())
doAssert $(dt + day) == "2000-01-02T12:00:00Z" doAssert $(dt + day) == "2000-01-02T12:00:00Z"
doAssert initTimeInterval(hours = 24) != initTimeInterval(days = 1)
result.nanoseconds = nanoseconds result.nanoseconds = nanoseconds
result.microseconds = microseconds result.microseconds = microseconds
result.milliseconds = milliseconds result.milliseconds = milliseconds
@ -1155,8 +1217,8 @@ proc getClockStr*(): string {.rtl, extern: "nt$1", tags: [TimeEffect].} =
':' & intToStr(dt.second, 2) ':' & intToStr(dt.second, 2)
proc toParts* (ti: TimeInterval): TimeIntervalParts = proc toParts* (ti: TimeInterval): TimeIntervalParts =
## Converts a `TimeInterval` into an array consisting of its time units, ## Converts a ``TimeInterval`` into an array consisting of its time units,
## starting with nanoseconds and ending with years ## starting with nanoseconds and ending with years.
## ##
## This procedure is useful for converting ``TimeInterval`` values to strings. ## This procedure is useful for converting ``TimeInterval`` values to strings.
## E.g. then you need to implement custom interval printing ## E.g. then you need to implement custom interval printing
@ -1171,9 +1233,10 @@ proc toParts* (ti: TimeInterval): TimeIntervalParts =
index += 1 index += 1
proc `$`*(ti: TimeInterval): string = proc `$`*(ti: TimeInterval): string =
## Get string representation of `TimeInterval` ## Get string representation of ``TimeInterval``.
runnableExamples: runnableExamples:
doAssert $initTimeInterval(years=1, nanoseconds=123) == "1 year and 123 nanoseconds" doAssert $initTimeInterval(years = 1, nanoseconds = 123) ==
"1 year and 123 nanoseconds"
doAssert $initTimeInterval() == "0 nanoseconds" doAssert $initTimeInterval() == "0 nanoseconds"
var parts: seq[string] = @[] var parts: seq[string] = @[]
@ -1199,7 +1262,7 @@ proc milliseconds*(ms: int): TimeInterval {.inline.} =
proc seconds*(s: int): TimeInterval {.inline.} = proc seconds*(s: int): TimeInterval {.inline.} =
## TimeInterval of ``s`` seconds. ## TimeInterval of ``s`` seconds.
## ##
## ``echo getTime() + 5.second`` ## ``echo getTime() + 5.seconds``
initTimeInterval(seconds = s) initTimeInterval(seconds = s)
proc minutes*(m: int): TimeInterval {.inline.} = proc minutes*(m: int): TimeInterval {.inline.} =
@ -1238,7 +1301,8 @@ proc years*(y: int): TimeInterval {.inline.} =
## ``echo getTime() + 2.years`` ## ``echo getTime() + 2.years``
initTimeInterval(years = y) initTimeInterval(years = y)
proc evaluateInterval(dt: DateTime, interval: TimeInterval): tuple[adjDur, absDur: Duration] = proc evaluateInterval(dt: DateTime, interval: TimeInterval):
tuple[adjDur, absDur: Duration] =
## Evaluates how many nanoseconds the interval is worth ## Evaluates how many nanoseconds the interval is worth
## in the context of ``dt``. ## in the context of ``dt``.
## The result in split into an adjusted diff and an absolute diff. ## The result in split into an adjusted diff and an absolute diff.
@ -1277,10 +1341,10 @@ proc evaluateInterval(dt: DateTime, interval: TimeInterval): tuple[adjDur, absDu
minutes = interval.minutes, minutes = interval.minutes,
hours = interval.hours) hours = interval.hours)
proc initDateTime*(monthday: MonthdayRange, month: Month, year: int, proc initDateTime*(monthday: MonthdayRange, month: Month, year: int,
hour: HourRange, minute: MinuteRange, second: SecondRange, hour: HourRange, minute: MinuteRange, second: SecondRange,
nanosecond: NanosecondRange, zone: Timezone = local()): DateTime = nanosecond: NanosecondRange,
zone: Timezone = local()): DateTime =
## Create a new ``DateTime`` in the specified timezone. ## Create a new ``DateTime`` in the specified timezone.
runnableExamples: runnableExamples:
let dt1 = initDateTime(30, mMar, 2017, 00, 00, 00, 00, utc()) let dt1 = initDateTime(30, mMar, 2017, 00, 00, 00, 00, utc())
@ -1310,14 +1374,15 @@ proc initDateTime*(monthday: MonthdayRange, month: Month, year: int,
proc `+`*(dt: DateTime, interval: TimeInterval): DateTime = proc `+`*(dt: DateTime, interval: TimeInterval): DateTime =
## Adds ``interval`` to ``dt``. Components from ``interval`` are added ## Adds ``interval`` to ``dt``. Components from ``interval`` are added
## in the order of their size, i.e first the ``years`` component, then the ``months`` ## in the order of their size, i.e first the ``years`` component, then the
## component and so on. The returned ``DateTime`` will have the same timezone as the input. ## ``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`.
## ##
## 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: runnableExamples:
let dt = initDateTime(30, mMar, 2017, 00, 00, 00, utc()) let dt = initDateTime(30, mMar, 2017, 00, 00, 00, utc())
doAssert $(dt + 1.months) == "2017-04-30T00:00:00Z" doAssert $(dt + 1.months) == "2017-04-30T00:00:00Z"
@ -1337,9 +1402,10 @@ proc `+`*(dt: DateTime, interval: TimeInterval): DateTime =
result = initDateTime(zt, dt.timezone) result = initDateTime(zt, dt.timezone)
proc `-`*(dt: DateTime, interval: TimeInterval): DateTime = proc `-`*(dt: DateTime, interval: TimeInterval): DateTime =
## Subtract ``interval`` from ``dt``. Components from ``interval`` are subtracted ## Subtract ``interval`` from ``dt``. Components from ``interval`` are
## in the order of their size, i.e first the ``years`` component, then the ``months`` ## subtracted in the order of their size, i.e first the ``years`` component,
## component and so on. The returned ``DateTime`` will have the same timezone as the input. ## then the ``months`` component and so on. The returned ``DateTime`` will
## have the same timezone as the input.
runnableExamples: runnableExamples:
let dt = initDateTime(30, mMar, 2017, 00, 00, 00, utc()) let dt = initDateTime(30, mMar, 2017, 00, 00, 00, utc())
doAssert $(dt - 5.days) == "2017-03-25T00:00:00Z" doAssert $(dt - 5.days) == "2017-03-25T00:00:00Z"
@ -1373,15 +1439,15 @@ proc `-`*(dt1, dt2: DateTime): Duration =
dt1.toTime - dt2.toTime dt1.toTime - dt2.toTime
proc `<`*(a, b: DateTime): bool = proc `<`*(a, b: DateTime): bool =
## Returns true iff ``a < b``, that is iff a happened before b. ## Returns true iff ``a`` happened before ``b``.
return a.toTime < b.toTime return a.toTime < b.toTime
proc `<=`*(a, b: DateTime): bool = proc `<=`*(a, b: DateTime): bool =
## Returns true iff ``a <= b``. ## Returns true iff ``a`` happened before or at the same time as ``b``.
return a.toTime <= b.toTime return a.toTime <= b.toTime
proc `==`*(a, b: DateTime): bool = proc `==`*(a, b: DateTime): bool =
## Returns true if ``a == b``, that is if both dates represent the same point in time. ## Returns true iff ``a`` and ``b`` represent the same point in time.
return a.toTime == b.toTime return a.toTime == b.toTime
proc isStaticInterval(interval: TimeInterval): bool = proc isStaticInterval(interval: TimeInterval): bool =
@ -2261,8 +2327,8 @@ proc parse*(input, f: string, tz: Timezone = local()): DateTime
let dtFormat = initTimeFormat(f) let dtFormat = initTimeFormat(f)
result = input.parse(dtFormat, tz) result = input.parse(dtFormat, tz)
proc parse*(input: string, f: static[string], zone: Timezone = local()): DateTime proc parse*(input: string, f: static[string], zone: Timezone = local()):
{.raises: [TimeParseError, Defect].} = DateTime {.raises: [TimeParseError, Defect].} =
## Overload that validates ``f`` at compile time. ## Overload that validates ``f`` at compile time.
const f2 = initTimeFormat(f) const f2 = initTimeFormat(f)
result = input.parse(f2, zone) result = input.parse(f2, zone)
@ -2298,7 +2364,7 @@ proc `$`*(dt: DateTime): string {.tags: [], raises: [], benign.} =
result = format(dt, "yyyy-MM-dd'T'HH:mm:sszzz") result = format(dt, "yyyy-MM-dd'T'HH:mm:sszzz")
proc `$`*(time: Time): string {.tags: [], raises: [], benign.} = proc `$`*(time: Time): string {.tags: [], raises: [], benign.} =
## converts a `Time` value to a string representation. It will use the local ## Converts a `Time` value to a string representation. It will use the local
## time zone and use the format ``yyyy-MM-dd'T'HH-mm-sszzz``. ## time zone and use the format ``yyyy-MM-dd'T'HH-mm-sszzz``.
runnableExamples: runnableExamples:
let dt = initDateTime(01, mJan, 1970, 00, 00, 00, local()) let dt = initDateTime(01, mJan, 1970, 00, 00, 00, local())
@ -2347,7 +2413,8 @@ when not defined(JS):
type type
Clock {.importc: "clock_t".} = distinct int Clock {.importc: "clock_t".} = distinct int
proc getClock(): Clock {.importc: "clock", header: "<time.h>", tags: [TimeEffect].} proc getClock(): Clock
{.importc: "clock", header: "<time.h>", tags: [TimeEffect].}
var var
clocksPerSec {.importc: "CLOCKS_PER_SEC", nodecl.}: int clocksPerSec {.importc: "CLOCKS_PER_SEC", nodecl.}: int
@ -2405,59 +2472,68 @@ when defined(JS):
# Deprecated procs # Deprecated procs
when not defined(JS): when not defined(JS):
proc unixTimeToWinTime*(time: CTime): int64 {.deprecated: "Use toWinTime instead".} = proc unixTimeToWinTime*(time: CTime): int64
{.deprecated: "Use toWinTime instead".} =
## Converts a UNIX `Time` (``time_t``) to a Windows file time ## Converts a UNIX `Time` (``time_t``) to a Windows file time
## ##
## **Deprecated:** use ``toWinTime`` instead. ## **Deprecated:** use ``toWinTime`` instead.
result = int64(time) * rateDiff + epochDiff result = int64(time) * rateDiff + epochDiff
proc winTimeToUnixTime*(time: int64): CTime {.deprecated: "Use fromWinTime instead".} = proc winTimeToUnixTime*(time: int64): CTime
{.deprecated: "Use fromWinTime instead".} =
## Converts a Windows time to a UNIX `Time` (``time_t``) ## Converts a Windows time to a UNIX `Time` (``time_t``)
## ##
## **Deprecated:** use ``fromWinTime`` instead. ## **Deprecated:** use ``fromWinTime`` instead.
result = CTime((time - epochDiff) div rateDiff) result = CTime((time - epochDiff) div rateDiff)
proc initInterval*(seconds, minutes, hours, days, months, proc initInterval*(seconds, minutes, hours, days, months, years: int = 0):
years: int = 0): TimeInterval {.deprecated.} = TimeInterval {.deprecated.} =
## **Deprecated since v0.18.0:** use ``initTimeInterval`` instead. ## **Deprecated since v0.18.0:** use ``initTimeInterval`` instead.
initTimeInterval(0, 0, 0, seconds, minutes, hours, days, 0, months, years) initTimeInterval(0, 0, 0, seconds, minutes, hours, days, 0, months, years)
proc fromSeconds*(since1970: float): Time {.tags: [], raises: [], benign, deprecated.} = proc fromSeconds*(since1970: float): Time
{.tags: [], raises: [], benign, deprecated.} =
## Takes a float which contains the number of seconds since the unix epoch and ## Takes a float which contains the number of seconds since the unix epoch and
## returns a time object. ## returns a time object.
## ##
## **Deprecated since v0.18.0:** use ``fromUnix`` instead ## **Deprecated since v0.18.0:** use ``fromUnix`` instead
let nanos = ((since1970 - since1970.int64.float) * convert(Seconds, Nanoseconds, 1).float).int let nanos = ((since1970 - since1970.int64.float) *
convert(Seconds, Nanoseconds, 1).float).int
initTime(since1970.int64, nanos) initTime(since1970.int64, nanos)
proc fromSeconds*(since1970: int64): Time {.tags: [], raises: [], benign, deprecated.} = proc fromSeconds*(since1970: int64): Time
{.tags: [], raises: [], benign, deprecated.} =
## Takes an int which contains the number of seconds since the unix epoch and ## Takes an int which contains the number of seconds since the unix epoch and
## returns a time object. ## returns a time object.
## ##
## **Deprecated since v0.18.0:** use ``fromUnix`` instead ## **Deprecated since v0.18.0:** use ``fromUnix`` instead
fromUnix(since1970) fromUnix(since1970)
proc toSeconds*(time: Time): float {.tags: [], raises: [], benign, deprecated.} = proc toSeconds*(time: Time): float
{.tags: [], raises: [], benign, deprecated.} =
## Returns the time in seconds since the unix epoch. ## Returns the time in seconds since the unix epoch.
## ##
## **Deprecated since v0.18.0:** use ``toUnix`` instead ## **Deprecated since v0.18.0:** use ``toUnix`` instead
time.seconds.float + time.nanosecond / convert(Seconds, Nanoseconds, 1) time.seconds.float + time.nanosecond / convert(Seconds, Nanoseconds, 1)
proc getLocalTime*(time: Time): DateTime {.tags: [], raises: [], benign, deprecated.} = proc getLocalTime*(time: Time): DateTime
{.tags: [], raises: [], benign, deprecated.} =
## Converts the calendar time `time` to broken-time representation, ## Converts the calendar time `time` to broken-time representation,
## expressed relative to the user's specified time zone. ## expressed relative to the user's specified time zone.
## ##
## **Deprecated since v0.18.0:** use ``local`` instead ## **Deprecated since v0.18.0:** use ``local`` instead
time.local time.local
proc getGMTime*(time: Time): DateTime {.tags: [], raises: [], benign, deprecated.} = proc getGMTime*(time: Time): DateTime
{.tags: [], raises: [], benign, deprecated.} =
## Converts the calendar time `time` to broken-down time representation, ## Converts the calendar time `time` to broken-down time representation,
## expressed in Coordinated Universal Time (UTC). ## expressed in Coordinated Universal Time (UTC).
## ##
## **Deprecated since v0.18.0:** use ``utc`` instead ## **Deprecated since v0.18.0:** use ``utc`` instead
time.utc time.utc
proc getTimezone*(): int {.tags: [TimeEffect], raises: [], benign, deprecated.} = proc getTimezone*(): int
{.tags: [TimeEffect], raises: [], benign, deprecated.} =
## Returns the offset of the local (non-DST) timezone in seconds west of UTC. ## Returns the offset of the local (non-DST) timezone in seconds west of UTC.
## ##
## **Deprecated since v0.18.0:** use ``now().utcOffset`` to get the current ## **Deprecated since v0.18.0:** use ``now().utcOffset`` to get the current
@ -2465,45 +2541,14 @@ proc getTimezone*(): int {.tags: [TimeEffect], raises: [], benign, deprecated.}
when defined(JS): when defined(JS):
return newDate().getTimezoneOffset() * 60 return newDate().getTimezoneOffset() * 60
elif defined(freebsd) or defined(netbsd) or defined(openbsd): elif defined(freebsd) or defined(netbsd) or defined(openbsd):
var a: CTime # This is wrong since it will include DST offsets, but the behavior has
discard time(a) # always been wrong for bsd and the proc is deprecated so lets ignore it.
let lt = localtime(addr(a)) return now().utcOffset
# BSD stores in `gmtoff` offset east of UTC in seconds,
# but posix systems using west of UTC in seconds
return -(lt.gmtoff)
else: else:
return timezone return timezone
proc timeInfoToTime*(dt: DateTime): Time {.tags: [], benign, deprecated.} = proc getDayOfWeek*(day, month, year: int): WeekDay
## Converts a broken-down time structure to calendar time representation. {.tags: [], raises: [], benign, deprecated.} =
##
## **Deprecated since v0.14.0:** use ``toTime`` instead.
dt.toTime
when defined(JS):
var start = getTime()
proc getStartMilsecs*(): int {.deprecated, tags: [TimeEffect], benign.} =
let dur = getTime() - start
result = (convert(Seconds, Milliseconds, dur.seconds) +
convert(Nanoseconds, Milliseconds, dur.nanosecond)).int
else:
proc getStartMilsecs*(): int {.deprecated, tags: [TimeEffect], benign.} =
## get the milliseconds from the start of the program.
##
## **Deprecated since v0.8.10:** use ``epochTime`` or ``cpuTime`` instead.
when defined(macosx):
result = toInt(toFloat(int(getClock())) / (toFloat(clocksPerSec) / 1000.0))
else:
result = int(getClock()) div (clocksPerSec div 1000)
proc timeToTimeInterval*(t: Time): TimeInterval {.deprecated.} =
## Converts a Time to a TimeInterval.
##
## **Deprecated since v0.14.0:** use ``toTimeInterval`` instead.
# Milliseconds not available from Time
t.toTimeInterval()
proc getDayOfWeek*(day, month, year: int): WeekDay {.tags: [], raises: [], benign, deprecated.} =
## **Deprecated since v0.18.0:** use ## **Deprecated since v0.18.0:** use
## ``getDayOfWeek(monthday: MonthdayRange; month: Month; year: int)`` instead. ## ``getDayOfWeek(monthday: MonthdayRange; month: Month; year: int)`` instead.
getDayOfWeek(day, month.Month, year) getDayOfWeek(day, month.Month, year)