summary refs log tree commit diff stats
path: root/lib/pure/times.nim
diff options
context:
space:
mode:
Diffstat (limited to 'lib/pure/times.nim')
-rw-r--r--lib/pure/times.nim3767
1 files changed, 2249 insertions, 1518 deletions
diff --git a/lib/pure/times.nim b/lib/pure/times.nim
index 7fd60b818..e59153455 100644
--- a/lib/pure/times.nim
+++ b/lib/pure/times.nim
@@ -1,203 +1,408 @@
 #
 #
 #            Nim's Runtime Library
-#        (c) Copyright 2017 Nim contributors
+#        (c) Copyright 2018 Nim contributors
 #
 #    See the file "copying.txt", included in this
 #    distribution, for details about the copyright.
 #
 
+##[
+  The `times` module contains routines and types for dealing with time using
+  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 `times` module supports nanosecond time resolution, the
+  resolution used by `getTime()` depends on the platform and backend
+  (JS is limited to millisecond precision).
+
+  Examples
+  ========
+
+    ```nim
+    import std/[times, os]
+    # Simple benchmarking
+    let time = cpuTime()
+    sleep(100) # Replace this with something to be timed
+    echo "Time taken: ", cpuTime() - time
+
+    # Current date & time
+    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
+
+    # Arithmetic using Duration
+    echo "One hour from now      : ", now() + 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
+  ============================
+
+  The `DateTime` type can be parsed and formatted using the different
+  `parse` and `format` procedures.
+
+    ```nim
+    let dt = parse("2000-01-01", "yyyy-MM-dd")
+    echo dt.format("yyyy-MM-dd")
+    ```
+
+  The different format patterns that are supported are documented below.
+
+  ===========  =================================================================================  ==============================================
+  Pattern      Description                                                                        Example
+  ===========  =================================================================================  ==============================================
+  `d`          Numeric value representing the day of the month,                                   | `1/04/2012 -> 1`
+               it will be either one or two digits long.                                          | `21/04/2012 -> 21`
+  `dd`         Same as above, but is always two digits.                                           | `1/04/2012 -> 01`
+                                                                                                  | `21/04/2012 -> 21`
+  `ddd`        Three letter string which indicates the day of the week.                           | `Saturday -> Sat`
+                                                                                                  | `Monday -> Mon`
+  `dddd`       Full string for the day of the week.                                               | `Saturday -> Saturday`
+                                                                                                  | `Monday -> Monday`
+  `GG`         The last two digits of the Iso Week-Year                                           | `30/12/2012 -> 13`
+  `GGGG`       The Iso week-calendar year padded to four digits                                   | `30/12/2012 -> 2013`
+  `h`          The hours in one digit if possible. Ranging from 1-12.                             | `5pm -> 5`
+                                                                                                  | `2am -> 2`
+  `hh`         The hours in two digits always. If the hour is one digit, 0 is prepended.          | `5pm -> 05`
+                                                                                                  | `11am -> 11`
+  `H`          The hours in one digit if possible, ranging from 0-23.                             | `5pm -> 17`
+                                                                                                  | `2am -> 2`
+  `HH`         The hours in two digits always. 0 is prepended if the hour is one digit.           | `5pm -> 17`
+                                                                                                  | `2am -> 02`
+  `m`          The minutes in one digit if possible.                                              | `5:30 -> 30`
+                                                                                                  | `2:01 -> 1`
+  `mm`         Same as above but always two digits, 0 is prepended if the minute is one digit.    | `5:30 -> 30`
+                                                                                                  | `2:01 -> 01`
+  `M`          The month in one digit if possible.                                                | `September -> 9`
+                                                                                                  | `December -> 12`
+  `MM`         The month in two digits always. 0 is prepended if the month value is one digit.    | `September -> 09`
+                                                                                                  | `December -> 12`
+  `MMM`        Abbreviated three-letter form of the month.                                        | `September -> Sep`
+                                                                                                  | `December -> Dec`
+  `MMMM`       Full month string, properly capitalized.                                           | `September -> September`
+  `s`          Seconds as one digit if possible.                                                  | `00:00:06 -> 6`
+  `ss`         Same as above but always two digits. 0 is prepended if the second is one digit.    | `00:00:06 -> 06`
+  `t`          `A` when time is in the AM. `P` when time is in the PM.                            | `5pm -> P`
+                                                                                                  | `2am -> A`
+  `tt`         Same as above, but `AM` and `PM` instead of `A` and `P` respectively.              | `5pm -> PM`
+                                                                                                  | `2am -> AM`
+  `yy`         The last two digits of the year. When parsing, the current century is assumed.     | `2012 AD -> 12`
+  `yyyy`       The year, padded to at least four digits.                                          | `2012 AD -> 2012`
+               Is always positive, even when the year is BC.                                      | `24 AD -> 0024`
+               When the year is more than four digits, '+' is prepended.                          | `24 BC -> 00024`
+                                                                                                  | `12345 AD -> +12345`
+  `YYYY`       The year without any padding.                                                      | `2012 AD -> 2012`
+               Is always positive, even when the year is BC.                                      | `24 AD -> 24`
+                                                                                                  | `24 BC -> 24`
+                                                                                                  | `12345 AD -> 12345`
+  `uuuu`       The year, padded to at least four digits. Will be negative when the year is BC.    | `2012 AD -> 2012`
+               When the year is more than four digits, '+' is prepended unless the year is BC.    | `24 AD -> 0024`
+                                                                                                  | `24 BC -> -0023`
+                                                                                                  | `12345 AD -> +12345`
+  `UUUU`       The year without any padding. Will be negative when the year is BC.                | `2012 AD -> 2012`
+                                                                                                  | `24 AD -> 24`
+                                                                                                  | `24 BC -> -23`
+                                                                                                  | `12345 AD -> 12345`
+  `V`          The Iso Week-Number as one or two digits                                           | `3/2/2012 -> 5`
+                                                                                                  | `1/4/2012 -> 13`
+  `VV`         The Iso Week-Number as two digits always. 0 is prepended if one digit.             | `3/2/2012 -> 05`
+                                                                                                  | `1/4/2012 -> 13`
+  `z`          Displays the timezone offset from UTC.                                             | `UTC+7 -> +7`
+                                                                                                  | `UTC-5 -> -5`
+  `zz`         Same as above but with leading 0.                                                  | `UTC+7 -> +07`
+                                                                                                  | `UTC-5 -> -05`
+  `zzz`        Same as above but with `:mm` where *mm* represents minutes.                        | `UTC+7 -> +07:00`
+                                                                                                  | `UTC-5 -> -05:00`
+  `ZZZ`        Same as above but with `mm` where *mm* represents minutes.                         | `UTC+7 -> +0700`
+                                                                                                  | `UTC-5 -> -0500`
+  `zzzz`       Same as above but with `:ss` where *ss* represents seconds.                        | `UTC+7 -> +07:00:00`
+                                                                                                  | `UTC-5 -> -05:00:00`
+  `ZZZZ`       Same as above but with `ss` where *ss* represents seconds.                         | `UTC+7 -> +070000`
+                                                                                                  | `UTC-5 -> -050000`
+  `g`          Era: AD or BC                                                                      | `300 AD -> AD`
+                                                                                                  | `300 BC -> BC`
+  `fff`        Milliseconds display                                                               | `1000000 nanoseconds -> 1`
+  `ffffff`     Microseconds display                                                               | `1000000 nanoseconds -> 1000`
+  `fffffffff`  Nanoseconds display                                                                | `1000000 nanoseconds -> 1000000`
+  ===========  =================================================================================  ==============================================
+
+  Other strings can be inserted by putting them in `''`. For example
+  `hh'->'mm` will give `01->56`.  In addition to spaces,
+  the following characters can be inserted without quoting them:
+  `:` `-` `,` `.` `(` `)` `/` `[` `]`.
+  A literal `'` can be specified with `''`.
+
+  However you don't need to necessarily separate format patterns, as an
+  unambiguous format string like `yyyyMMddhhmmss` is also valid (although
+  only for years in the range 1..9999).
+
+  Duration vs TimeInterval
+  ============================
+  The `times` module exports two similar types that are both used to
+  represent some amount of time: `Duration <#Duration>`_ and
+  `TimeInterval <#TimeInterval>`_.
+  This section explains how they differ and when one should be preferred 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 equivalent.
+
+  Arithmetic 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 preferred.
+
+  TimeInterval
+  ----------------------------
+  A `TimeInterval` represents an 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 separate fields for every unit. The
+  `TimeInterval`'s returned from this module generally don't normalize
+  **anything**, so even units that could be normalized (like seconds,
+  milliseconds and so on) are left untouched.
+
+  Arithmetic 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 exactly one day has
+  passed. However, the UTC offsets are different, which means that the
+  UTC offset was changed somewhere in 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.
+
+  See also
+  ========
+  * `monotimes module <monotimes.html>`_
+]##
+
+import std/[strutils, math, options]
+
+import std/private/since
+include "system/inclrtl"
 
-## 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:
-##
-## .. code-block:: nim
-##
-##  import times, os
-##  let time = cpuTime()
-##
-##  sleep(100)   # replace this with something to be timed
-##  echo "Time taken: ",cpuTime() - time
-##
-##  echo "My formatted time: ", format(now(), "d MMMM yyyy HH:mm")
-##  echo "Using predefined formats: ", getClockStr(), " ", getDateStr()
-##
-##  echo "cpuTime()  float value: ", cpuTime()
-##  echo "An hour from now      : ", now() + 1.hours
-##  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!
-
-import
-  strutils, parseutils, algorithm, math
+when defined(nimPreviewSlimSystem):
+  import std/assertions
 
-include "system/inclrtl"
 
-# This is really bad, but overflow checks are broken badly for
-# ints on the JS backend. See #6752.
-when defined(JS):
+when defined(js):
+  import std/jscore
+
+  # This is really bad, but overflow checks are broken badly for
+  # ints on the JS backend. See #6752.
   {.push overflowChecks: off.}
   proc `*`(a, b: int64): int64 =
-    system.`* `(a, b)
+    system.`*`(a, b)
   proc `*`(a, b: int): int =
-    system.`* `(a, b)
+    system.`*`(a, b)
   proc `+`(a, b: int64): int64 =
-    system.`+ `(a, b)
+    system.`+`(a, b)
   proc `+`(a, b: int): int =
-    system.`+ `(a, b)
+    system.`+`(a, b)
   proc `-`(a, b: int64): int64 =
-    system.`- `(a, b)
+    system.`-`(a, b)
   proc `-`(a, b: int): int =
-    system.`- `(a, b)
+    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
+elif defined(posix):
+  import std/posix
 
   type CTime = posix.Time
 
-  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):
-    var timezone {.importc, header: "<time.h>".}: int
-    tzset()
+  when defined(macosx):
+    proc gettimeofday(tp: var Timeval, unused: pointer = nil)
+      {.importc: "gettimeofday", header: "<sys/time.h>", sideEffect.}
 
 elif defined(windows):
-  import winlean
+  import std/winlean, std/time_t
 
-  when defined(i386) and defined(gcc):
-    type CTime {.importc: "time_t", header: "<time.h>".} = distinct int32
-  else:
-    # newest version of Visual C++ defines time_t to be of 64 bits
-    type CTime {.importc: "time_t", header: "<time.h>".} = distinct int64
-  # visual c's c runtime exposes these under a different name
-  var timezone {.importc: "_timezone", header: "<time.h>".}: int
+  type
+    CTime = time_t.Time
+    Tm {.importc: "struct tm", header: "<time.h>", final, pure.} = object
+      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>", sideEffect.}
 
 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, mFeb, mMar, mApr, mMay, mJun, mJul, mAug, mSep, mOct, mNov, mDec
+  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")
+    mFeb = "February"
+    mMar = "March"
+    mApr = "April"
+    mMay = "May"
+    mJun = "June"
+    mJul = "July"
+    mAug = "August"
+    mSep = "September"
+    mOct = "October"
+    mNov = "November"
+    mDec = "December"
 
   WeekDay* = enum ## Represents a weekday.
-    dMon, dTue, dWed, dThu, dFri, dSat, dSun
+    dMon = "Monday"
+    dTue = "Tuesday"
+    dWed = "Wednesday"
+    dThu = "Thursday"
+    dFri = "Friday"
+    dSat = "Saturday"
+    dSun = "Sunday"
 
+type
   MonthdayRange* = range[1..31]
   HourRange* = range[0..23]
   MinuteRange* = range[0..59]
-  SecondRange* = range[0..60]
+  SecondRange* = range[0..60] ## \
+    ## Includes the value 60 to allow for a leap second. Note however
+    ## that the `second` of a `DateTime` will never be a leap second.
   YeardayRange* = range[0..365]
   NanosecondRange* = range[0..999_999_999]
 
+  IsoWeekRange* = range[1 .. 53]
+    ## An ISO 8601 calendar week number.
+  IsoYear* = distinct int
+    ## An ISO 8601 calendar year number.
+    ##
+    ## .. warning:: The ISO week-based year can correspond to the following or previous year from 29 December to January 3.
+
   Time* = object ## Represents a point in time.
     seconds: int64
     nanosecond: NanosecondRange
 
-  DateTime* = object of RootObj ## Represents a time in different parts.
-                                ## Although this type can represent leap
-                                ## seconds, they are generally not supported
-                                ## in this module. They are not ignored,
-                                ## 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.
-    minute*: MinuteRange      ## The number of minutes after the hour,
-                              ## in the range 0 to 59.
-    hour*: HourRange          ## The number of hours past midnight,
-                              ## in the range 0 to 23.
-    monthday*: MonthdayRange  ## The day of the month, in the range 1 to 31.
-    month*: Month             ## The current month.
-    year*: int                ## The current year, using astronomical year numbering
-                              ## (meaning that before year 1 is year 0, then year -1 and so on).
-    weekday*: WeekDay         ## The current day of the week.
-    yearday*: YeardayRange    ## The number of days since January 1,
-                              ## in the range 0 to 365.
-    isDst*: bool              ## Determines whether DST is in effect.
-                              ## Always false for the JavaScript backend.
-    timezone*: Timezone       ## The timezone represented as an implementation of ``Timezone``.
-    utcOffset*: int           ## The offset in seconds west of UTC, including any offset due to DST.
-                              ## Note that the sign of this number is the opposite
-                              ## of the one in a formatted offset string like ``+01:00``
-                              ## (which would be parsed into the UTC offset ``-3600``).
-
-  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
-    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.
+  DateTime* = object of RootObj  ## \
+    ## Represents a time in different parts. Although this type can represent
+    ## leap seconds, they are generally not supported in this module. They are
+    ## not ignored, but the `DateTime`'s returned by procedures in this
+    ## module will never have a leap second.
+    nanosecond: NanosecondRange
+    second: SecondRange
+    minute: MinuteRange
+    hour: HourRange
+    monthdayZero: int
+    monthZero: int
+    year: int
+    weekday: WeekDay
+    yearday: YeardayRange
+    isDst: bool
+    timezone: Timezone
+    utcOffset: int
+
+  Duration* = object ## Represents a fixed duration of time, meaning a duration
+                     ## that has constant length independent of the context.
+                     ##
+                     ## To create a new `Duration`, use `initDuration
+                     ## <#initDuration,int64,int64,int64,int64,int64,int64,int64,int64>`_.
+                     ## Instead of trying to access the private attributes, use
+                     ## `inSeconds <#inSeconds,Duration>`_ for converting to seconds and
+                     ## `inNanoseconds <#inNanoseconds,Duration>`_ for converting to nanoseconds.
     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* = object ## Timezone interface for supporting ``DateTime``'s of arbritary timezones.
-                     ## The ``times`` module only supplies implementations for the systems local time and UTC.
-                     ## The members ``zoneInfoFromUtc`` and ``zoneInfoFromTz`` should not be accessed directly
-                     ## and are only exported so that ``Timezone`` can be implemented by other modules.
-    zoneInfoFromUtc*: proc (time: Time): ZonedTime {.tags: [], raises: [], benign.}
-    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 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 ## Offset from UTC in seconds.
-                    ## The point in time represented by ``ZonedTime`` is ``adjTime + utcOffset.seconds``.
-    isDst*: bool    ## Determines whether DST is in effect.
+    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 <#DateTime>`_ or
+      ## `Time <#Time>`_.
+      ##
+      ## Create a new `TimeInterval` with `initTimeInterval proc
+      ## <#initTimeInterval,int,int,int,int,int,int,int,int,int,int>`_.
+      ##
+      ## 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
+      ## can be either 365 or 366 days long). The non-fixed time units are
+      ## 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 <#Duration>`_ should be used instead.
+    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
+    weeks*: int          ## The number of weeks
+    months*: int         ## The number of months
+    years*: int          ## The number of years
+
+  Timezone* = ref object ## \
+      ## Timezone interface for supporting `DateTime <#DateTime>`_\s of arbitrary
+      ## timezones. The `times` module only supplies implementations for the
+      ## system's local time and UTC.
+    zonedTimeFromTimeImpl: proc (x: Time): ZonedTime
+        {.tags: [], raises: [], benign.}
+    zonedTimeFromAdjTimeImpl: proc (x: Time): ZonedTime
+        {.tags: [], raises: [], benign.}
+    name: string
+
+  ZonedTime* = object ## Represents a point in time with an associated
+                      ## UTC offset and DST flag. This type is only used for
+                      ## implementing timezones.
+    time*: Time       ## The point in time being represented.
+    utcOffset*: int   ## The offset in seconds west of UTC,
+                      ## including any offset due to DST.
+    isDst*: bool      ## Determines whether DST is in effect.
 
   DurationParts* = array[FixedTimeUnit, int64] # Array of Duration parts starts
   TimeIntervalParts* = array[TimeUnit, int] # Array of Duration parts starts
-  TimesMutableTypes = DateTime | Time | Duration | TimeInterval
-
-{.deprecated: [TMonth: Month, TWeekDay: WeekDay, TTime: Time,
-    TTimeInterval: TimeInterval, TTimeInfo: DateTime, TimeInfo: DateTime].}
 
 const
   secondsInMin = 60
   secondsInHour = 60*60
   secondsInDay = 60*60*24
-  minutesInHour = 60
   rateDiff = 10000000'i64 # 100 nsecs
-  # The number of hectonanoseconds between 1601/01/01 (windows epoch)
-  # and 1970/01/01 (unix epoch).
+                          # The number of hectonanoseconds between 1601/01/01 (windows epoch)
+                          # and 1970/01/01 (unix epoch).
   epochDiff = 116444736000000000'i64
 
 const unitWeights: array[FixedTimeUnit, int64] = [
@@ -211,8 +416,26 @@ const unitWeights: array[FixedTimeUnit, int64] = [
   7 * secondsInDay * 1e9.int64,
 ]
 
-proc convert*[T: SomeInteger](unitFrom, unitTo: FixedTimeUnit, quantity: T): T {.inline.} =
+when (NimMajor, NimMinor) >= (1, 4):
+  # Newer versions of Nim don't track defects
+  {.pragma: parseFormatRaises, raises: [TimeParseError, TimeFormatParseError].}
+  {.pragma: parseRaises, raises: [TimeParseError].}
+else:
+  # Still track when using older versions
+  {.pragma: parseFormatRaises, raises: [TimeParseError, TimeFormatParseError, Defect].}
+  {.pragma: parseRaises, raises: [TimeParseError, Defect].}
+
+
+#
+# Helper procs
+#
+
+{.pragma: operator, rtl, noSideEffect, benign.}
+
+proc convert*[T: SomeInteger](unitFrom, unitTo: FixedTimeUnit, quantity: T): T
+    {.inline.} =
   ## Convert a quantity of some duration unit to another duration unit.
+  ## This proc only deals with integers, so the result might be truncated.
   runnableExamples:
     doAssert convert(Days, Hours, 2) == 48
     doAssert convert(Days, Weeks, 13) == 1 # Truncated
@@ -224,8 +447,8 @@ proc convert*[T: SomeInteger](unitFrom, unitTo: FixedTimeUnit, quantity: T): 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``.
+  ## 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 nanosecond = nanoseconds mod convert(Seconds, Nanoseconds, 1)
   if nanosecond < 0:
@@ -233,129 +456,36 @@ proc normalize[T: Duration|Time](seconds, nanoseconds: int64): T =
     result.seconds -= 1
   result.nanosecond = nanosecond.int
 
-# Forward declarations
-proc utcZoneInfoFromUtc(time: Time): ZonedTime {.tags: [], raises: [], benign .}
-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 initTime*(unix: int64, nanosecond: NanosecondRange): Time
-  {.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 =
-  ## Get the fractional part of a ``Time`` as the number
-  ## of nanoseconds of the second.
-  time.nanosecond
-
-
-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.nanosecond)
-
-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.nanosecond)
-
-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.nanosecond
-
-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.nanosecond)
-
-
-proc fromUnix*(unix: int64): Time {.benign, tags: [], raises: [], noSideEffect.} =
-  ## Convert a unix timestamp (seconds since ``1970-01-01T00:00:00Z``) to a ``Time``.
-  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``).
-  runnableExamples:
-    doAssert fromUnix(0).toUnix() == 0
-
-  t.seconds
-
-proc fromWinTime*(win: int64): Time =
-  ## Convert a Windows file time (100-nanosecond intervals since ``1601-01-01T00:00:00Z``)
-  ## to a ``Time``.
-  let hnsecsSinceEpoch = (win - epochDiff)
-  var seconds = hnsecsSinceEpoch div rateDiff
-  var nanos = ((hnsecsSinceEpoch mod rateDiff) * 100).int
-  if nanos < 0:
-    nanos += convert(Seconds, Nanoseconds, 1)
-    seconds -= 1
-  result = initTime(seconds, nanos)
-
-proc toWinTime*(t: Time): int64 =
-  ## 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
-
 proc isLeapYear*(year: int): bool =
-  ## Returns true if ``year`` is a leap year.
+  ## Returns true if `year` is a leap year.
+  runnableExamples:
+    doAssert isLeapYear(2000)
+    doAssert not isLeapYear(1900)
   year mod 4 == 0 and (year mod 100 != 0 or year mod 400 == 0)
 
 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
+  runnableExamples:
+    doAssert getDaysInMonth(mFeb, 2000) == 29
+    doAssert getDaysInMonth(mFeb, 2001) == 28
   case month
   of mFeb: result = if isLeapYear(year): 29 else: 28
   of mApr, mJun, mSep, mNov: result = 30
   else: result = 31
 
-proc getDaysInYear*(year: int): int =
-  ## Get the number of days in a ``year``
-  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),
-    $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 =
   ## 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).
-  assertValidDate monthday, month, year
+  ## 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
+  assertValidDate monthday, month, year
   var (y, m, d) = (year, ord(month), monthday.int)
   if m <= 2:
     y.dec
@@ -366,9 +496,11 @@ proc toEpochDay(monthday: MonthdayRange, month: Month, year: int): int64 =
   let doe = yoe * 365 + yoe div 4 - yoe div 100 + doy
   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.
-  ## 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
   var z = epochday
   z.inc 719468
@@ -382,32 +514,122 @@ proc fromEpochDay(epochday: int64): tuple[monthday: MonthdayRange, month: Month,
   let m = mp + (if mp < 10: 3 else: -9)
   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.
-  ## Equivalent with ``initDateTime(day, month, year).yearday``.
+  ## Equivalent with `dateTime(year, month, monthday, 0, 0, 0, 0).yearday`.
+  runnableExamples:
+    doAssert getDayOfYear(1, mJan, 2000) == 0
+    doAssert getDayOfYear(10, mJan, 2000) == 9
+    doAssert getDayOfYear(10, mFeb, 2000) == 40
+
   assertValidDate monthday, month, year
-  const daysUntilMonth:     array[Month, int] = [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]
+  const daysUntilMonth: array[Month, int] =
+    [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):
     result = daysUntilMonthLeap[month] + monthday - 1
   else:
     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.
-  ## Equivalent with ``initDateTime(day, month, year).weekday``.
+  ## Equivalent with `dateTime(year, month, monthday, 0, 0, 0, 0).weekday`.
+  runnableExamples:
+    doAssert getDayOfWeek(13, mJun, 1990) == dWed
+    doAssert $getDayOfWeek(13, mJun, 1990) == "Wednesday"
+
   assertValidDate monthday, month, year
   # 1970-01-01 is a Thursday, we adjust to the previous Monday
-  let days = toEpochday(monthday, month, year) - 3
-  let weeks = (if days >= 0: days else: days - 6) div 7
+  let days = toEpochDay(monthday, month, year) - 3
+  let weeks = floorDiv(days, 7'i64)
   let wd = days - weeks * 7
   # The value of d is 0 for a Sunday, 1 for a Monday, 2 for a Tuesday, etc.
   # so we must correct for the WeekDay type.
   result = if wd == 0: dSun else: WeekDay(wd - 1)
 
+proc getDaysInYear*(year: int): int =
+  ## Get the number of days in a `year`
+  runnableExamples:
+    doAssert getDaysInYear(2000) == 366
+    doAssert getDaysInYear(2001) == 365
+  result = 365 + (if isLeapYear(year): 1 else: 0)
+
+proc `==`*(a, b: IsoYear): bool {.borrow.}
+proc `$`*(p: IsoYear): string {.borrow.}
+
+proc getWeeksInIsoYear*(y: IsoYear): IsoWeekRange {.since: (1, 5).} =
+  ## Returns the number of weeks in the specified ISO 8601 week-based year, which can be
+  ## either 53 or 52.
+  runnableExamples:
+    assert getWeeksInIsoYear(IsoYear(2019)) == 52
+    assert getWeeksInIsoYear(IsoYear(2020)) == 53
+
+  var y = int(y)
 
-{. pragma: operator, rtl, noSideEffect, benign .}
+  # support negative years
+  y = if y < 0: 400 + y mod 400 else: y
+
+  # source: https://webspace.science.uu.nl/~gent0113/calendar/isocalendar.htm
+  let p = (y + (y div 4) - (y div 100) + (y div 400)) mod 7
+  let y1 = y - 1
+  let p1 = (y1 + (y1 div 4) - (y1 div 100) + (y1 div 400)) mod 7
+  if p == 4 or p1 == 3: 53 else: 52
+
+proc getIsoWeekAndYear*(dt: DateTime):
+  tuple[isoweek: IsoWeekRange, isoyear: IsoYear] {.since: (1, 5).} =
+  ## Returns the ISO 8601 week and year.
+  ##
+  ## .. warning:: The ISO week-based year can correspond to the following or previous year from 29 December to January 3.
+  runnableExamples:
+    assert getIsoWeekAndYear(initDateTime(21, mApr, 2018, 00, 00, 00)) == (isoweek: 16.IsoWeekRange, isoyear: 2018.IsoYear)
+    block:
+      let (w, y) = getIsoWeekAndYear(initDateTime(30, mDec, 2019, 00, 00, 00))
+      assert w == 01.IsoWeekRange
+      assert y == 2020.IsoYear
+    assert getIsoWeekAndYear(initDateTime(13, mSep, 2020, 00, 00, 00)) == (isoweek: 37.IsoWeekRange, isoyear: 2020.IsoYear)
+    block:
+      let (w, y) = getIsoWeekAndYear(initDateTime(2, mJan, 2021, 00, 00, 00))
+      assert w.int > 52
+      assert w.int < 54
+      assert y.int mod 100 == 20
+
+  # source: https://webspace.science.uu.nl/~gent0113/calendar/isocalendar.htm
+  var w = (dt.yearday.int - dt.weekday.int + 10) div 7
+  if w < 1:
+    (isoweek: getWeeksInIsoYear(IsoYear(dt.year - 1)), isoyear: IsoYear(dt.year - 1))
+  elif (w > getWeeksInIsoYear(IsoYear(dt.year))):
+    (isoweek: IsoWeekRange(1), isoyear: IsoYear(dt.year + 1))
+  else:
+    (isoweek: IsoWeekRange(w), isoyear: IsoYear(dt.year))
+
+proc stringifyUnit(value: int | int64, unit: TimeUnit): string =
+  ## Stringify time unit with it's name, lowercased
+  let strUnit = $unit
+  result = ""
+  result.addInt value
+  result.add ' '
+  if abs(value) != 1:
+    result.add(strUnit.toLowerAscii())
+  else:
+    result.add(strUnit[0..^2].toLowerAscii())
+
+proc humanizeParts(parts: seq[string]): string =
+  ## Make date string parts human-readable
+  result = ""
+  if parts.len == 0:
+    result.add "0 nanoseconds"
+  elif parts.len == 1:
+    result = parts[0]
+  elif parts.len == 2:
+    result = parts[0] & " and " & parts[1]
+  else:
+    for i in 0..high(parts)-1:
+      result.add parts[i] & ", "
+    result.add "and " & parts[high(parts)]
 
 template subImpl[T: Duration|Time](a: Duration|Time, b: Duration|Time): T =
   normalize[T](a.seconds - b.seconds, a.nanosecond - b.nanosecond)
@@ -426,12 +648,24 @@ template lqImpl(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
 
+#
+# Duration
+#
+
+const DurationZero* = Duration() ## \
+  ## Zero value for durations. Useful for comparisons.
+  ##   ```nim
+  ##   doAssert initDuration(seconds = 1) > DurationZero
+  ##   doAssert initDuration(seconds = 0) == DurationZero
+  ##   ```
+
 proc initDuration*(nanoseconds, microseconds, milliseconds,
                    seconds, minutes, hours, days, weeks: int64 = 0): Duration =
+  ## Create a new `Duration <#Duration>`_.
   runnableExamples:
     let dur = initDuration(seconds = 1, milliseconds = 1)
-    doAssert dur.milliseconds == 1
-    doAssert dur.seconds == 1
+    doAssert dur.inMilliseconds == 1001
+    doAssert dur.inSeconds == 1
 
   let seconds = convert(Weeks, Seconds, weeks) +
     convert(Days, Seconds, days) +
@@ -447,25 +681,93 @@ proc initDuration*(nanoseconds, microseconds, milliseconds,
   # Nanoseconds might be negative so we must normalize.
   result = normalize[Duration](seconds, nanoseconds)
 
-const DurationZero* = initDuration() ## \
-  ## Zero value for durations. Useful for comparisons.
-  ##
-  ## .. code-block:: nim
-  ##
-  ##   doAssert initDuration(seconds = 1) > DurationZero
-  ##   doAssert initDuration(seconds = 0) == DurationZero
+template convert(dur: Duration, unit: static[FixedTimeUnit]): int64 =
+  # The correction is required due to how durations are normalized.
+  # For example,` initDuration(nanoseconds = -1)` is stored as
+  # { seconds = -1, nanoseconds = 999999999 }.
+  when unit == Nanoseconds:
+    dur.seconds * 1_000_000_000 + dur.nanosecond
+  else:
+    let correction = dur.seconds < 0 and dur.nanosecond > 0
+    when unit >= Seconds:
+      convert(Seconds, unit, dur.seconds + ord(correction))
+    else:
+      if correction:
+        convert(Seconds, unit, dur.seconds + 1) -
+          convert(Nanoseconds, unit,
+            convert(Seconds, Nanoseconds, 1) - dur.nanosecond)
+      else:
+        convert(Seconds, unit, dur.seconds) +
+          convert(Nanoseconds, unit, dur.nanosecond)
+
+proc inWeeks*(dur: Duration): int64 =
+  ## Converts the duration to the number of whole weeks.
+  runnableExamples:
+    let dur = initDuration(days = 8)
+    doAssert dur.inWeeks == 1
+  dur.convert(Weeks)
+
+proc inDays*(dur: Duration): int64 =
+  ## Converts the duration to the number of whole days.
+  runnableExamples:
+    let dur = initDuration(hours = -50)
+    doAssert dur.inDays == -2
+  dur.convert(Days)
+
+proc inHours*(dur: Duration): int64 =
+  ## Converts the duration to the number of whole hours.
+  runnableExamples:
+    let dur = initDuration(minutes = 60, days = 2)
+    doAssert dur.inHours == 49
+  dur.convert(Hours)
+
+proc inMinutes*(dur: Duration): int64 =
+  ## Converts the duration to the number of whole minutes.
+  runnableExamples:
+    let dur = initDuration(hours = 2, seconds = 10)
+    doAssert dur.inMinutes == 120
+  dur.convert(Minutes)
+
+proc inSeconds*(dur: Duration): int64 =
+  ## Converts the duration to the number of whole seconds.
+  runnableExamples:
+    let dur = initDuration(hours = 2, milliseconds = 10)
+    doAssert dur.inSeconds == 2 * 60 * 60
+  dur.convert(Seconds)
+
+proc inMilliseconds*(dur: Duration): int64 =
+  ## Converts the duration to the number of whole milliseconds.
+  runnableExamples:
+    let dur = initDuration(seconds = -2)
+    doAssert dur.inMilliseconds == -2000
+  dur.convert(Milliseconds)
+
+proc inMicroseconds*(dur: Duration): int64 =
+  ## Converts the duration to the number of whole microseconds.
+  runnableExamples:
+    let dur = initDuration(seconds = -2)
+    doAssert dur.inMicroseconds == -2000000
+  dur.convert(Microseconds)
+
+proc inNanoseconds*(dur: Duration): int64 =
+  ## Converts the duration to the number of whole nanoseconds.
+  runnableExamples:
+    let dur = initDuration(seconds = -2)
+    doAssert dur.inNanoseconds == -2000000000
+  dur.convert(Nanoseconds)
 
 proc toParts*(dur: Duration): DurationParts =
   ## Converts a duration into an array consisting of fixed time units.
   ##
   ## Each value in the array gives information about a specific unit of
-  ## time, for example ``result[Days]`` gives a count of days.
+  ## time, for example `result[Days]` gives a count of days.
   ##
-  ## This procedure is useful for converting ``Duration`` values to strings.
+  ## This procedure is useful for converting `Duration` values to strings.
   runnableExamples:
-    var dp = toParts(initDuration(weeks=2, days=1))
+    var dp = toParts(initDuration(weeks = 2, days = 1))
     doAssert dp[Days] == 1
     doAssert dp[Weeks] == 2
+    doAssert dp[Minutes] == 0
     dp = toParts(initDuration(days = -1))
     doAssert dp[Days] == -1
 
@@ -489,178 +791,383 @@ proc toParts*(dur: Duration): DurationParts =
 
     result[unit] = quantity
 
-proc stringifyUnit*(value: int | int64, unit: string): string =
-  ## Stringify time unit with it's name, lowercased
-  runnableExamples:
-    doAssert stringifyUnit(2, "Seconds") == "2 seconds"
-    doAssert stringifyUnit(1, "Years") == "1 year"
-  result = ""
-  result.add($value)
-  result.add(" ")
-  if abs(value) != 1:
-    result.add(unit.toLowerAscii())
-  else:
-    result.add(unit[0..^2].toLowerAscii())
-
-proc humanizeParts(parts: seq[string]): string =
-  ## Make date string parts human-readable
-
-  result = ""
-  if parts.len == 0:
-    result.add "0 nanoseconds"
-  elif 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 `$`*(dur: Duration): string =
-  ## Human friendly string representation of ``Duration``.
+  ## Human friendly string representation of a `Duration`.
   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"
+    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 numParts = toParts(dur)
 
   for unit in countdown(Weeks, Nanoseconds):
     let quantity = numParts[unit]
     if quantity != 0.int64:
-      parts.add(stringifyUnit(quantity, $unit))
+      parts.add(stringifyUnit(quantity, unit))
 
   result = humanizeParts(parts)
 
-proc `+`*(a, b: Duration): Duration {.operator.} =
+proc `+`*(a, b: Duration): Duration {.operator, extern: "ntAddDuration".} =
   ## 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.} =
+proc `-`*(a, b: Duration): Duration {.operator, extern: "ntSubDuration".} =
   ## 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.} =
+proc `-`*(a: Duration): Duration {.operator, extern: "ntReverseDuration".} =
   ## Reverse a duration.
   runnableExamples:
     doAssert -initDuration(seconds = 1) == initDuration(seconds = -1)
   normalize[Duration](-a.seconds, -a.nanosecond)
 
-proc `<`*(a, b: Duration): bool {.operator.} =
+proc `<`*(a, b: Duration): bool {.operator, extern: "ntLtDuration".} =
   ## Note that a duration can be negative,
-  ## so even if ``a < b`` is true ``a`` might
+  ## so even if `a < b` is true `a` might
   ## represent a larger absolute duration.
-  ## Use ``abs(a) < abs(b)`` to compare the absolute
+  ## Use `abs(a) < abs(b)` to compare the absolute
   ## duration.
   runnableExamples:
-    doAssert initDuration(seconds =  1) < initDuration(seconds = 2)
+    doAssert initDuration(seconds = 1) < initDuration(seconds = 2)
     doAssert initDuration(seconds = -2) < initDuration(seconds = 1)
+    doAssert initDuration(seconds = -2).abs < initDuration(seconds = 1).abs == false
   ltImpl(a, b)
 
-proc `<=`*(a, b: Duration): bool {.operator.} =
+proc `<=`*(a, b: Duration): bool {.operator, extern: "ntLeDuration".} =
   lqImpl(a, b)
 
-proc `==`*(a, b: Duration): bool {.operator.} =
+proc `==`*(a, b: Duration): bool {.operator, extern: "ntEqDuration".} =
+  runnableExamples:
+    let
+      d1 = initDuration(weeks = 1)
+      d2 = initDuration(days = 7)
+    doAssert d1 == d2
   eqImpl(a, b)
 
-proc `*`*(a: int64, b: Duration): Duration {.operator} =
+proc `*`*(a: int64, b: Duration): Duration {.operator,
+    extern: "ntMulInt64Duration".} =
   ## Multiply a duration by some scalar.
   runnableExamples:
     doAssert 5 * initDuration(seconds = 1) == initDuration(seconds = 5)
+    doAssert 3 * initDuration(minutes = 45) == initDuration(hours = 2, minutes = 15)
   normalize[Duration](a * b.seconds, a * b.nanosecond)
 
-proc `*`*(a: Duration, b: int64): Duration {.operator} =
+proc `*`*(a: Duration, b: int64): Duration {.operator,
+    extern: "ntMulDuration".} =
   ## Multiply a duration by some scalar.
   runnableExamples:
     doAssert initDuration(seconds = 1) * 5 == initDuration(seconds = 5)
+    doAssert initDuration(minutes = 45) * 3 == initDuration(hours = 2, minutes = 15)
   b * a
 
-proc `div`*(a: Duration, b: int64): Duration {.operator} =
+proc `+=`*(d1: var Duration, d2: Duration) =
+  d1 = d1 + d2
+
+proc `-=`*(dt: var Duration, ti: Duration) =
+  dt = dt - ti
+
+proc `*=`*(a: var Duration, b: int) =
+  a = a * b
+
+proc `div`*(a: Duration, b: int64): Duration {.operator,
+    extern: "ntDivDuration".} =
   ## Integer division for durations.
   runnableExamples:
-    doAssert initDuration(seconds = 3) div 2 == initDuration(milliseconds = 1500)
-    doAssert initDuration(nanoseconds = 3) div 2 == initDuration(nanoseconds = 1)
+    doAssert initDuration(seconds = 3) div 2 ==
+      initDuration(milliseconds = 1500)
+    doAssert initDuration(minutes = 45) div 30 ==
+      initDuration(minutes = 1, seconds = 30)
+    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.nanosecond + carryOver) div b)
 
+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.nanosecond)
+
+#
+# Time
+#
+
 proc initTime*(unix: int64, nanosecond: NanosecondRange): Time =
-  ## Create a ``Time`` from a unix timestamp and a nanosecond part.
+  ## Create a `Time <#Time>`_ from a unix timestamp and a nanosecond part.
   result.seconds = unix
   result.nanosecond = nanosecond
 
+proc nanosecond*(time: Time): NanosecondRange =
+  ## Get the fractional part of a `Time` as the number
+  ## of nanoseconds of the second.
+  time.nanosecond
+
+proc fromUnix*(unix: int64): Time
+    {.benign, tags: [], raises: [], noSideEffect.} =
+  ## Convert a unix timestamp (seconds since `1970-01-01T00:00:00Z`)
+  ## to a `Time`.
+  runnableExamples:
+    doAssert $fromUnix(0).utc == "1970-01-01T00:00:00Z"
+  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`).
+  ## See also `toUnixFloat` for subsecond resolution.
+  runnableExamples:
+    doAssert fromUnix(0).toUnix() == 0
+  t.seconds
+
+proc fromUnixFloat(seconds: float): Time {.benign, tags: [], raises: [], noSideEffect.} =
+  ## Convert a unix timestamp in seconds to a `Time`; same as `fromUnix`
+  ## but with subsecond resolution.
+  runnableExamples:
+    doAssert fromUnixFloat(123456.0) == fromUnixFloat(123456)
+    doAssert fromUnixFloat(-123456.0) == fromUnixFloat(-123456)
+  let secs = seconds.floor
+  let nsecs = (seconds - secs) * 1e9
+  initTime(secs.int64, nsecs.NanosecondRange)
+
+proc toUnixFloat(t: Time): float {.benign, tags: [], raises: [].} =
+  ## Same as `toUnix` but using subsecond resolution.
+  runnableExamples:
+    let t = getTime()
+    # `<` because of rounding errors
+    doAssert abs(t.toUnixFloat().fromUnixFloat - t) < initDuration(nanoseconds = 1000)
+  t.seconds.float + t.nanosecond / convert(Seconds, Nanoseconds, 1)
+
+since((1, 1)):
+  export fromUnixFloat
+  export toUnixFloat
+
+
+proc fromWinTime*(win: int64): Time =
+  ## Convert a Windows file time (100-nanosecond intervals since
+  ## `1601-01-01T00:00:00Z`) to a `Time`.
+  const hnsecsPerSec = convert(Seconds, Nanoseconds, 1) div 100
+  let nanos = floorMod(win, hnsecsPerSec) * 100
+  let seconds = floorDiv(win - epochDiff, hnsecsPerSec)
+  result = initTime(seconds, nanos)
+
+proc toWinTime*(t: Time): int64 =
+  ## 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
+
+proc getTimeImpl(typ: typedesc[Time]): Time =
+  discard "implemented in the vm"
+
+proc getTime*(): Time {.tags: [TimeEffect], benign.} =
+  ## Gets the current time as a `Time` with up to nanosecond resolution.
+  when nimvm:
+    result = getTimeImpl(Time)
+  else:
+    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)
+    elif defined(macosx):
+      var a {.noinit.}: Timeval
+      gettimeofday(a)
+      result = initTime(a.tv_sec.int64,
+                        convert(Microseconds, Nanoseconds, a.tv_usec.int))
+    elif defined(posix):
+      var ts {.noinit.}: Timespec
+      discard clock_gettime(CLOCK_REALTIME, ts)
+      result = initTime(ts.tv_sec.int64, ts.tv_nsec.int)
+    elif defined(windows):
+      var f {.noinit.}: FILETIME
+      getSystemTimeAsFileTime(f)
+      result = fromWinTime(rdFileTime(f))
+
 proc `-`*(a, b: Time): Duration {.operator, extern: "ntDiffTime".} =
   ## Computes the duration between two points in time.
+  runnableExamples:
+    doAssert initTime(1000, 100) - initTime(500, 20) ==
+      initDuration(minutes = 8, seconds = 20, nanoseconds = 80)
   subImpl[Duration](a, b)
 
 proc `+`*(a: Time, b: Duration): Time {.operator, extern: "ntAddTime".} =
-  ## Add a duration of time to a ``Time``.
+  ## 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``.
+  ## Subtracts a duration of time from a `Time`.
   runnableExamples:
     doAssert (fromUnix(0) - initDuration(seconds = 1)) == fromUnix(-1)
   subImpl[Time](a, b)
 
 proc `<`*(a, b: Time): bool {.operator, extern: "ntLtTime".} =
-  ## Returns true iff ``a < b``, that is iff a happened before b.
+  ## Returns true if `a < b`, that is if `a` happened before `b`.
+  runnableExamples:
+    doAssert initTime(50, 0) < initTime(99, 0)
   ltImpl(a, b)
 
-proc `<=` * (a, b: Time): bool {.operator, extern: "ntLeTime".} =
-  ## Returns true iff ``a <= b``.
+proc `<=`*(a, b: Time): bool {.operator, extern: "ntLeTime".} =
+  ## Returns true if `a <= b`.
   lqImpl(a, b)
 
 proc `==`*(a, b: Time): bool {.operator, extern: "ntEqTime".} =
-  ## Returns true if ``a == b``, that is if both times represent the same point in time.
+  ## Returns true if `a == b`, that is if both times represent the same point in time.
   eqImpl(a, b)
 
+proc `+=`*(t: var Time, b: Duration) =
+  t = t + b
+
+proc `-=`*(t: var Time, b: Duration) =
+  t = t - b
+
 proc high*(typ: typedesc[Time]): Time =
   initTime(high(int64), high(NanosecondRange))
 
 proc low*(typ: typedesc[Time]): Time =
-  initTime(low(int64), 0)
+  initTime(0, 0)
 
-proc high*(typ: typedesc[Duration]): Duration =
-  ## Get the longest representable duration.
-  initDuration(seconds = high(int64), nanoseconds = high(NanosecondRange))
+#
+# DateTime & Timezone
+#
 
-proc low*(typ: typedesc[Duration]): Duration =
-  ## Get the longest representable duration of negative direction.
-  initDuration(seconds = low(int64))
+template assertDateTimeInitialized(dt: DateTime) =
+  assert dt.monthdayZero != 0, "Uninitialized datetime"
+
+proc nanosecond*(dt: DateTime): NanosecondRange {.inline.} =
+  ## The number of nanoseconds after the second,
+  ## in the range 0 to 999_999_999.
+  assertDateTimeInitialized(dt)
+  dt.nanosecond
+
+proc second*(dt: DateTime): SecondRange {.inline.} =
+  ## The number of seconds after the minute,
+  ## in the range 0 to 59.
+  assertDateTimeInitialized(dt)
+  dt.second
+
+proc minute*(dt: DateTime): MinuteRange {.inline.} =
+  ## The number of minutes after the hour,
+  ## in the range 0 to 59.
+  assertDateTimeInitialized(dt)
+  dt.minute
+
+proc hour*(dt: DateTime): HourRange {.inline.} =
+  ## The number of hours past midnight,
+  ## in the range 0 to 23.
+  assertDateTimeInitialized(dt)
+  dt.hour
+
+proc monthday*(dt: DateTime): MonthdayRange {.inline.} =
+  ## The day of the month, in the range 1 to 31.
+  assertDateTimeInitialized(dt)
+  # 'cast' to avoid extra range check
+  cast[MonthdayRange](dt.monthdayZero)
+
+proc month*(dt: DateTime): Month =
+  ## The month as an enum, the ordinal value
+  ## is in the range 1 to 12.
+  assertDateTimeInitialized(dt)
+  # 'cast' to avoid extra range check
+  cast[Month](dt.monthZero)
+
+proc year*(dt: DateTime): int {.inline.} =
+  ## The year, using astronomical year numbering
+  ## (meaning that before year 1 is year 0,
+  ## then year -1 and so on).
+  assertDateTimeInitialized(dt)
+  dt.year
+
+proc weekday*(dt: DateTime): WeekDay {.inline.} =
+  ## The day of the week as an enum, the ordinal
+  ## value is in the range 0 (monday) to 6 (sunday).
+  assertDateTimeInitialized(dt)
+  dt.weekday
+
+proc yearday*(dt: DateTime): YeardayRange {.inline.} =
+  ## The number of days since January 1,
+  ## in the range 0 to 365.
+  assertDateTimeInitialized(dt)
+  dt.yearday
+
+proc isDst*(dt: DateTime): bool {.inline.} =
+  ## Determines whether DST is in effect.
+  ## Always false for the JavaScript backend.
+  assertDateTimeInitialized(dt)
+  dt.isDst
+
+proc timezone*(dt: DateTime): Timezone {.inline.} =
+  ## The timezone represented as an implementation
+  ## of `Timezone`.
+  assertDateTimeInitialized(dt)
+  dt.timezone
+
+proc utcOffset*(dt: DateTime): int {.inline.} =
+  ## The offset in seconds west of UTC, including
+  ## any offset due to DST. Note that the sign of
+  ## 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`).
+  assertDateTimeInitialized(dt)
+  dt.utcOffset
+
+proc isInitialized(dt: DateTime): bool =
+  # Returns true if `dt` is not the (invalid) default value for `DateTime`.
+  runnableExamples:
+    doAssert now().isInitialized
+    doAssert not default(DateTime).isInitialized
+  dt.monthZero != 0
 
-proc abs*(a: Duration): Duration =
+since((1, 3)):
+  export isInitialized
+
+proc isLeapDay*(dt: DateTime): bool {.since: (1, 1).} =
+  ## Returns whether `t` is a leap day, i.e. Feb 29 in a leap year. This matters
+  ## as it affects time offset calculations.
   runnableExamples:
-    doAssert initDuration(milliseconds = -1500).abs ==
-      initDuration(milliseconds = 1500)
-  initDuration(seconds = abs(a.seconds), nanoseconds = -a.nanosecond)
+    let dt = dateTime(2020, mFeb, 29, 00, 00, 00, 00, utc())
+    doAssert dt.isLeapDay
+    doAssert dt+1.years-1.years != dt
+    let dt2 = dateTime(2020, mFeb, 28, 00, 00, 00, 00, utc())
+    doAssert not dt2.isLeapDay
+    doAssert dt2+1.years-1.years == dt2
+    doAssertRaises(Exception): discard dateTime(2021, mFeb, 29, 00, 00, 00, 00, utc())
+  assertDateTimeInitialized dt
+  dt.year.isLeapYear and dt.month == mFeb and dt.monthday == 29
 
 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)
+  ## Converts a `DateTime` to a `Time` representing the same point in time.
+  assertDateTimeInitialized dt
+  let epochDay = toEpochDay(dt.monthday, dt.month, dt.year)
   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.
   seconds.inc dt.utcOffset
   result = initTime(seconds, dt.nanosecond)
 
 proc initDateTime(zt: ZonedTime, zone: Timezone): DateTime =
-  ## Create a new ``DateTime`` using ``ZonedTime`` in the specified timezone.
-  let s = zt.adjTime.seconds
-  let epochday = (if s >= 0: s else: s - (secondsInDay - 1)) div secondsInDay
+  ## Create a new `DateTime` using `ZonedTime` in the specified timezone.
+  let adjTime = zt.time - initDuration(seconds = zt.utcOffset)
+  let s = adjTime.seconds
+  let epochday = floorDiv(s, secondsInDay)
   var rem = s - epochday * secondsInDay
   let hour = rem div secondsInHour
   rem = rem - hour * secondsInHour
@@ -668,16 +1175,16 @@ proc initDateTime(zt: ZonedTime, zone: Timezone): DateTime =
   rem = rem - minute * secondsInMin
   let second = rem
 
-  let (d, m, y) = fromEpochday(epochday)
+  let (d, m, y) = fromEpochDay(epochday)
 
   DateTime(
     year: y,
-    month: m,
-    monthday: d,
+    monthZero: m.int,
+    monthdayZero: d,
     hour: hour,
     minute: minute,
     second: second,
-    nanosecond: zt.adjTime.nanosecond,
+    nanosecond: zt.time.nanosecond,
     weekday: getDayOfWeek(d, m, y),
     yearday: getDayOfYear(d, m, y),
     isDst: zt.isDst,
@@ -685,136 +1192,155 @@ proc initDateTime(zt: ZonedTime, zone: Timezone): DateTime =
     utcOffset: zt.utcOffset
   )
 
-proc inZone*(time: Time, zone: Timezone): DateTime {.tags: [], raises: [], benign.} =
-  ## Break down ``time`` into a ``DateTime`` using ``zone`` as the timezone.
-  let zoneInfo = zone.zoneInfoFromUtc(time)
-  result = initDateTime(zoneInfo, zone)
+proc newTimezone*(
+      name: string,
+      zonedTimeFromTimeImpl: proc (time: Time): ZonedTime
+          {.tags: [], raises: [], benign.},
+      zonedTimeFromAdjTimeImpl: proc (adjTime: Time): ZonedTime
+          {.tags: [], raises: [], benign.}
+    ): owned Timezone =
+  ## Create a new `Timezone`.
+  ##
+  ## `zonedTimeFromTimeImpl` and `zonedTimeFromAdjTimeImpl` is used
+  ## as the underlying implementations for `zonedTimeFromTime` and
+  ## `zonedTimeFromAdjTime`.
+  ##
+  ## If possible, the name parameter should match the name used in the
+  ## tz database. If the timezone doesn't exist in the tz database, or if the
+  ## timezone name is unknown, then any string that describes the timezone
+  ## unambiguously can be used. Note that the timezones name is used for
+  ## checking equality!
+  runnableExamples:
+    proc utcTzInfo(time: Time): ZonedTime =
+      ZonedTime(utcOffset: 0, isDst: false, time: time)
+    let utc = newTimezone("Etc/UTC", utcTzInfo, utcTzInfo)
+  Timezone(
+    name: name,
+    zonedTimeFromTimeImpl: zonedTimeFromTimeImpl,
+    zonedTimeFromAdjTimeImpl: zonedTimeFromAdjTimeImpl
+  )
+
+proc name*(zone: Timezone): string =
+  ## The name of the timezone.
+  ##
+  ## If possible, the name will be the name used in the tz database.
+  ## If the timezone doesn't exist in the tz database, or if the timezone
+  ## name is unknown, then any string that describes the timezone
+  ## unambiguously might be used. For example, the string "LOCAL" is used
+  ## for the system's local timezone.
+  ##
+  ## See also: https://en.wikipedia.org/wiki/Tz_database
+  zone.name
+
+proc zonedTimeFromTime*(zone: Timezone, time: Time): ZonedTime =
+  ## Returns the `ZonedTime` for some point in time.
+  zone.zonedTimeFromTimeImpl(time)
 
-proc inZone*(dt: DateTime, zone: Timezone): DateTime  {.tags: [], raises: [], benign.} =
-  ## Convert ``dt`` into a ``DateTime`` using ``zone`` as the timezone.
-  dt.toTime.inZone(zone)
+proc zonedTimeFromAdjTime*(zone: Timezone, adjTime: Time): ZonedTime =
+  ## Returns the `ZonedTime` for some local time.
+  ##
+  ## Note that the `Time` argument does not represent a point in time, it
+  ## represent a local time! E.g if `adjTime` is `fromUnix(0)`, it should be
+  ## interpreted as 1970-01-01T00:00:00 in the `zone` timezone, not in UTC.
+  zone.zonedTimeFromAdjTimeImpl(adjTime)
 
 proc `$`*(zone: Timezone): string =
   ## Returns the name of the timezone.
-  zone.name
+  if zone != nil: result = zone.name
 
 proc `==`*(zone1, zone2: Timezone): bool =
-  ## Two ``Timezone``'s are considered equal if their name is equal.
+  ## Two `Timezone`'s are considered equal if their name is equal.
+  runnableExamples:
+    doAssert local() == local()
+    doAssert local() != utc()
+  if system.`==`(zone1, zone2):
+    return true
+  if zone1.isNil or zone2.isNil:
+    return false
   zone1.name == zone2.name
 
+proc inZone*(time: Time, zone: Timezone): DateTime
+    {.tags: [], raises: [], benign.} =
+  ## Convert `time` into a `DateTime` using `zone` as the timezone.
+  result = initDateTime(zone.zonedTimeFromTime(time), zone)
+
+proc inZone*(dt: DateTime, zone: Timezone): DateTime
+    {.tags: [], raises: [], benign.} =
+  ## Returns a `DateTime` representing the same point in time as `dt` but
+  ## using `zone` as the timezone.
+  assertDateTimeInitialized dt
+  dt.toTime.inZone(zone)
+
 proc toAdjTime(dt: DateTime): Time =
-  let epochDay = toEpochday(dt.monthday, dt.month, dt.year)
+  let epochDay = toEpochDay(dt.monthday, dt.month, dt.year)
   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
-    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 localZoneInfoFromUtc(time: Time): ZonedTime =
-      let jsDate = newDate(time.seconds.float * 1000)
-      let offset = jsDate.getTimezoneOffset() * secondsInMin
-      result.adjTime = time - initDuration(seconds = offset)
-      result.utcOffset = offset
-      result.isDst = false
-
-    proc localZoneInfoFromTz(adjTime: Time): ZonedTime =
-      let utcDate = newDate(adjTime.seconds.float * 1000)
-      let localDate = newDate(utcDate.getUTCFullYear(), utcDate.getUTCMonth(), 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
-      # because they are assumed to be 19xx...
-      # Because JS doesn't support timezone history, it doesn't really matter in practice.
-      if utcDate.getUTCFullYear() in 0 .. 99:
-        localDate.setFullYear(utcDate.getUTCFullYear())
-
-      result.adjTime = adjTime
-      result.utcOffset = localDate.getTimezoneOffset() * secondsInMin
-      result.isDst = false
+when defined(js):
+  proc localZonedTimeFromTime(time: Time): ZonedTime {.benign.} =
+    let jsDate = newDate(time.seconds * 1000)
+    let offset = jsDate.getTimezoneOffset() * secondsInMin
+    result.time = time
+    result.utcOffset = offset
+    result.isDst = false
 
-else:
-  when defined(freebsd) or defined(netbsd) or defined(openbsd) or
-      defined(macosx):
-    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
-        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):
-          gmtoff {.importc: "tm_gmtoff".}: clong
-          zone {.importc: "tm_zone".}: cstring
-  type
-    StructTmPtr = ptr StructTm
+  proc localZonedTimeFromAdjTime(adjTime: Time): ZonedTime {.benign.} =
+    let utcDate = newDate(adjTime.seconds * 1000)
+    let localDate = newDate(utcDate.getUTCFullYear(), utcDate.getUTCMonth(),
+        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 because they are assumed to be 19xx...
+    # Because JS doesn't support timezone history,
+    # it doesn't really matter in practice.
+    if utcDate.getUTCFullYear() in 0 .. 99:
+      localDate.setFullYear(utcDate.getUTCFullYear())
 
-  proc localtime(timer: ptr CTime): StructTmPtr {. importc: "localtime", header: "<time.h>", tags: [].}
+    result.utcOffset = localDate.getTimezoneOffset() * secondsInMin
+    result.time = adjTime + initDuration(seconds = result.utcOffset)
+    result.isDst = false
 
-  proc toAdjUnix(tm: StructTm): int64 =
-    let epochDay = toEpochday(tm.monthday, (tm.month + 1).Month, tm.year.int + 1900)
+else:
+  proc toAdjUnix(tm: Tm): int64 =
+    let epochDay = toEpochDay(tm.tm_mday, (tm.tm_mon + 1).Month,
+                              tm.tm_year.int + 1900)
     result = epochDay * secondsInDay
-    result.inc tm.hour * secondsInHour
-    result.inc tm.minute * 60
-    result.inc tm.second
+    result.inc tm.tm_hour * secondsInHour
+    result.inc tm.tm_min * 60
+    result.inc tm.tm_sec
 
   proc getLocalOffsetAndDst(unix: int64): tuple[offset: int, dst: bool] =
-    var a = unix.CTime
-    let tmPtr = localtime(addr(a))
+    # Windows can't handle unix < 0, so we fall back to unix = 0.
+    # FIXME: This should be improved by falling back to the WinAPI instead.
+    when defined(windows):
+      if unix < 0:
+        var a = 0.CTime
+        let tmPtr = localtime(a)
+        if not tmPtr.isNil:
+          let tm = tmPtr[]
+          return ((0 - tm.toAdjUnix).int, false)
+        return (0, false)
+
+    # In case of a 32-bit time_t, we fallback to the closest available
+    # timezone information.
+    var a = clamp(unix, low(CTime).int64, high(CTime).int64).CTime
+    let tmPtr = localtime(a)
     if not tmPtr.isNil:
       let tm = tmPtr[]
-      return ((unix - tm.toAdjUnix).int, tm.isdst > 0)
+      return ((a.int64 - tm.toAdjUnix).int, tm.tm_isdst > 0)
     return (0, false)
 
-  proc localZoneInfoFromUtc(time: Time): ZonedTime =
+  proc localZonedTimeFromTime(time: Time): ZonedTime {.benign.} =
     let (offset, dst) = getLocalOffsetAndDst(time.seconds)
-    result.adjTime = time - initDuration(seconds = offset)
+    result.time = time
     result.utcOffset = offset
     result.isDst = dst
 
-  proc localZoneInfoFromTz(adjTime: Time): ZonedTime  =
+  proc localZonedTimeFromAdjTime(adjTime: Time): ZonedTime {.benign.} =
     var adjUnix = adjTime.seconds
     let past = adjUnix - secondsInDay
     let (pastOffset, _) = getLocalOffsetAndDst(past)
@@ -824,7 +1350,7 @@ else:
 
     var utcOffset: int
     if pastOffset == futureOffset:
-        utcOffset = pastOffset.int
+      utcOffset = pastOffset.int
     else:
       if pastOffset > futureOffset:
         adjUnix -= secondsInHour
@@ -836,88 +1362,1010 @@ else:
     # as a result of offset changes (normally due to dst)
     let utcUnix = adjTime.seconds + utcOffset
     let (finalOffset, dst) = getLocalOffsetAndDst(utcUnix)
-    result.adjTime = initTime(utcUnix - finalOffset, adjTime.nanosecond)
+    result.time = initTime(utcUnix, adjTime.nanosecond)
     result.utcOffset = finalOffset
     result.isDst = dst
 
-proc utcZoneInfoFromUtc(time: Time): ZonedTime =
-  result.adjTime = time
-  result.utcOffset = 0
-  result.isDst = false
+proc utcTzInfo(time: Time): ZonedTime =
+  ZonedTime(utcOffset: 0, isDst: false, time: time)
 
-proc utcZoneInfoFromTz(adjTime: Time): ZonedTime =
-  utcZoneInfoFromUtc(adjTime) # adjTime == time since we are in UTC
+var utcInstance {.threadvar.}: Timezone
+var localInstance {.threadvar.}: Timezone
 
-proc utc*(): TimeZone =
-  ## Get the ``Timezone`` implementation for the UTC timezone.
+proc utc*(): Timezone =
+  ## Get the `Timezone` implementation for the UTC timezone.
   runnableExamples:
     doAssert now().utc.timezone == utc()
     doAssert utc().name == "Etc/UTC"
-  Timezone(zoneInfoFromUtc: utcZoneInfoFromUtc, zoneInfoFromTz: utcZoneInfoFromTz, name: "Etc/UTC")
+  if utcInstance.isNil:
+    utcInstance = newTimezone("Etc/UTC", utcTzInfo, utcTzInfo)
+  result = utcInstance
 
-proc local*(): TimeZone =
-  ## Get the ``Timezone`` implementation for the local timezone.
+proc local*(): Timezone =
+  ## Get the `Timezone` implementation for the local timezone.
   runnableExamples:
-   doAssert now().timezone == local()
-   doAssert local().name == "LOCAL"
-  Timezone(zoneInfoFromUtc: localZoneInfoFromUtc, zoneInfoFromTz: localZoneInfoFromTz, name: "LOCAL")
+    doAssert now().timezone == local()
+    doAssert local().name == "LOCAL"
+  if localInstance.isNil:
+    localInstance = newTimezone("LOCAL", localZonedTimeFromTime,
+      localZonedTimeFromAdjTime)
+  result = localInstance
 
 proc utc*(dt: DateTime): DateTime =
-  ## Shorthand for ``dt.inZone(utc())``.
+  ## Shorthand for `dt.inZone(utc())`.
   dt.inZone(utc())
 
 proc local*(dt: DateTime): DateTime =
-  ## Shorthand for ``dt.inZone(local())``.
+  ## Shorthand for `dt.inZone(local())`.
   dt.inZone(local())
 
 proc utc*(t: Time): DateTime =
-  ## Shorthand for ``t.inZone(utc())``.
+  ## Shorthand for `t.inZone(utc())`.
   t.inZone(utc())
 
 proc local*(t: Time): DateTime =
-  ## Shorthand for ``t.inZone(local())``.
+  ## Shorthand for `t.inZone(local())`.
   t.inZone(local())
 
-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)
-    result = fromWinTime(rdFileTime(f))
-
 proc now*(): DateTime {.tags: [TimeEffect], benign.} =
-  ## Get the current time as a  ``DateTime`` in the local timezone.
+  ## Get the current time as a  `DateTime` in the local timezone.
+  ## Shorthand for `getTime().local`.
   ##
-  ## Shorthand for ``getTime().local``.
+  ## .. warning:: Unsuitable for benchmarking, use `monotimes.getMonoTime` or
+  ##    `cpuTime` instead, depending on the use case.
   getTime().local
 
+proc dateTime*(year: int, month: Month, monthday: MonthdayRange,
+               hour: HourRange = 0, minute: MinuteRange = 0, second: SecondRange = 0,
+               nanosecond: NanosecondRange = 0,
+               zone: Timezone = local()): DateTime =
+  ## Create a new `DateTime <#DateTime>`_ in the specified timezone.
+  runnableExamples:
+    assert $dateTime(2017, mMar, 30, zone = utc()) == "2017-03-30T00:00:00Z"
+
+  assertValidDate monthday, month, year
+  let dt = DateTime(
+    monthdayZero: monthday,
+    year: year,
+    monthZero: month.int,
+    hour: hour,
+    minute: minute,
+    second: second,
+    nanosecond: nanosecond
+  )
+  result = initDateTime(zone.zonedTimeFromAdjTime(dt.toAdjTime), zone)
+
+proc initDateTime*(monthday: MonthdayRange, month: Month, year: int,
+                   hour: HourRange, minute: MinuteRange, second: SecondRange,
+                   nanosecond: NanosecondRange,
+                   zone: Timezone = local()): DateTime {.deprecated: "use `dateTime`".} =
+  ## Create a new `DateTime <#DateTime>`_ in the specified timezone.
+  runnableExamples("--warning:deprecated:off"):
+    assert $initDateTime(30, mMar, 2017, 00, 00, 00, 00, utc()) == "2017-03-30T00:00:00Z"
+  dateTime(year, month, monthday, hour, minute, second, nanosecond, zone)
+
+proc initDateTime*(monthday: MonthdayRange, month: Month, year: int,
+                   hour: HourRange, minute: MinuteRange, second: SecondRange,
+                   zone: Timezone = local()): DateTime {.deprecated: "use `dateTime`".} =
+  ## Create a new `DateTime <#DateTime>`_ in the specified timezone.
+  runnableExamples("--warning:deprecated:off"):
+    assert $initDateTime(30, mMar, 2017, 00, 00, 00, utc()) == "2017-03-30T00:00:00Z"
+  dateTime(year, month, monthday, hour, minute, second, 0, zone)
+
+proc `+`*(dt: DateTime, dur: Duration): DateTime =
+  runnableExamples:
+    let dt = dateTime(2017, mMar, 30, 00, 00, 00, 00, utc())
+    let dur = initDuration(hours = 5)
+    doAssert $(dt + dur) == "2017-03-30T05:00:00Z"
+
+  (dt.toTime + dur).inZone(dt.timezone)
+
+proc `-`*(dt: DateTime, dur: Duration): DateTime =
+  runnableExamples:
+    let dt = dateTime(2017, mMar, 30, 00, 00, 00, 00, utc())
+    let dur = initDuration(days = 5)
+    doAssert $(dt - dur) == "2017-03-25T00:00:00Z"
+
+  (dt.toTime - dur).inZone(dt.timezone)
+
+proc `-`*(dt1, dt2: DateTime): Duration =
+  ## Compute the duration between `dt1` and `dt2`.
+  runnableExamples:
+    let dt1 = dateTime(2017, mMar, 30, 00, 00, 00, 00, utc())
+    let dt2 = dateTime(2017, mMar, 25, 00, 00, 00, 00, utc())
+
+    doAssert dt1 - dt2 == initDuration(days = 5)
+
+  dt1.toTime - dt2.toTime
+
+proc `<`*(a, b: DateTime): bool =
+  ## Returns true if `a` happened before `b`.
+  return a.toTime < b.toTime
+
+proc `<=`*(a, b: DateTime): bool =
+  ## Returns true if `a` happened before or at the same time as `b`.
+  return a.toTime <= b.toTime
+
+proc `==`*(a, b: DateTime): bool =
+  ## Returns true if `a` and `b` represent the same point in time.
+  if not a.isInitialized: not b.isInitialized
+  elif not b.isInitialized: false
+  else: a.toTime == b.toTime
+
+proc `+=`*(a: var DateTime, b: Duration) =
+  a = a + b
+
+proc `-=`*(a: var DateTime, b: Duration) =
+  a = a - b
+
+proc getDateStr*(dt = now()): string {.rtl, extern: "nt$1", tags: [TimeEffect].} =
+  ## Gets the current local date as a string of the format `YYYY-MM-dd`.
+  runnableExamples:
+    echo getDateStr(now() - 1.months)
+  assertDateTimeInitialized dt
+  result = newStringOfCap(10)  # len("YYYY-MM-dd") == 10
+  result.addInt dt.year
+  result.add '-'
+  result.add intToStr(dt.monthZero, 2)
+  result.add '-'
+  result.add intToStr(dt.monthday, 2)
+
+proc getClockStr*(dt = now()): string {.rtl, extern: "nt$1", tags: [TimeEffect].} =
+  ## Gets the current local clock time as a string of the format `HH:mm:ss`.
+  runnableExamples:
+    echo getClockStr(now() - 1.hours)
+  assertDateTimeInitialized dt
+  result = newStringOfCap(8)  # len("HH:mm:ss") == 8
+  result.add intToStr(dt.hour, 2)
+  result.add ':'
+  result.add intToStr(dt.minute, 2)
+  result.add ':'
+  result.add intToStr(dt.second, 2)
+
+
+#
+# Iso week forward declarations
+#
+
+proc initDateTime*(weekday: WeekDay, isoweek: IsoWeekRange, isoyear: IsoYear,
+                   hour: HourRange, minute: MinuteRange, second: SecondRange,
+                   nanosecond: NanosecondRange,
+                   zone: Timezone = local()): DateTime {.gcsafe, raises: [], tags: [], since: (1, 5).}
+
+proc initDateTime*(weekday: WeekDay, isoweek: IsoWeekRange, isoyear: IsoYear,
+                   hour: HourRange, minute: MinuteRange, second: SecondRange,
+                   zone: Timezone = local()): DateTime {.gcsafe, raises: [], tags: [], since: (1, 5).}
+
+#
+# TimeFormat
+#
+
+when defined(nimHasStyleChecks):
+  {.push styleChecks: off.}
+
+type
+  DateTimeLocale* = object
+    MMM*: array[mJan..mDec, string]
+    MMMM*: array[mJan..mDec, string]
+    ddd*: array[dMon..dSun, string]
+    dddd*: array[dMon..dSun, string]
+
+when defined(nimHasStyleChecks):
+  {.pop.}
+
+type
+  AmPm = enum
+    apUnknown, apAm, apPm
+
+  Era = enum
+    eraUnknown, eraAd, eraBc
+
+  ParsedTime = object
+    amPm: AmPm
+    era: Era
+    year: Option[int]
+    month: Option[int]
+    monthday: Option[int]
+    isoyear: Option[int]
+    yearweek: Option[int]
+    weekday: Option[WeekDay]
+    utcOffset: Option[int]
+
+    # '0' as default for these work fine
+    # so no need for `Option`.
+    hour: int
+    minute: int
+    second: int
+    nanosecond: int
+
+  FormatTokenKind = enum
+    tkPattern, tkLiteral
+
+  FormatPattern {.pure.} = enum
+    d, dd, ddd, dddd
+    GG, GGGG
+    h, hh, H, HH
+    m, mm, M, MM, MMM, MMMM
+    s, ss
+    fff, ffffff, fffffffff
+    t, tt
+    yy, yyyy
+    YYYY
+    uuuu
+    UUUU
+    V, VV
+    z, zz, zzz, zzzz
+    ZZZ, ZZZZ
+    g
+
+    # This is a special value used to mark literal format values.
+    # See the doc comment for `TimeFormat.patterns`.
+    Lit
+
+  TimeFormat* = object  ## Represents a format for parsing and printing
+                        ## time types.
+                        ##
+                        ## To create a new `TimeFormat` use `initTimeFormat proc
+                        ## <#initTimeFormat,string>`_.
+    patterns: seq[byte] ## \
+      ## Contains the patterns encoded as bytes.
+      ## Literal values are encoded in a special way.
+      ## They start with `Lit.byte`, then the length of the literal, then the
+      ## raw char values of the literal. For example, the literal `foo` would
+      ## be encoded as `@[Lit.byte, 3.byte, 'f'.byte, 'o'.byte, 'o'.byte]`.
+    formatStr: string
+
+  TimeParseError* = object of ValueError ## \
+    ## Raised when parsing input using a `TimeFormat` fails.
+
+  TimeFormatParseError* = object of ValueError ## \
+    ## Raised when parsing a `TimeFormat` string fails.
+
+const
+  DefaultLocale* = DateTimeLocale(
+    MMM: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct",
+        "Nov", "Dec"],
+    MMMM: ["January", "February", "March", "April", "May", "June", "July",
+        "August", "September", "October", "November", "December"],
+    ddd: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
+    dddd: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday",
+        "Sunday"],
+  )
+
+  FormatLiterals = {' ', '-', '/', ':', '(', ')', '[', ']', ',', '.'}
+
+proc `$`*(f: TimeFormat): string =
+  ## Returns the format string that was used to construct `f`.
+  runnableExamples:
+    let f = initTimeFormat("yyyy-MM-dd")
+    doAssert $f == "yyyy-MM-dd"
+  f.formatStr
+
+proc raiseParseException(f: TimeFormat, input: string, msg: string) =
+  raise newException(TimeParseError,
+                     "Failed to parse '" & input & "' with format '" & $f &
+                     "'. " & msg)
+
+proc parseInt(s: string, b: var int, start = 0, maxLen = int.high,
+              allowSign = false): int =
+  var sign = -1
+  var i = start
+  let stop = start + min(s.high - start + 1, maxLen) - 1
+  if allowSign and i <= stop:
+    if s[i] == '+':
+      inc(i)
+    elif s[i] == '-':
+      inc(i)
+      sign = 1
+  if i <= stop and s[i] in {'0'..'9'}:
+    b = 0
+    while i <= stop and s[i] in {'0'..'9'}:
+      let c = ord(s[i]) - ord('0')
+      if b >= (low(int) + c) div 10:
+        b = b * 10 - c
+      else:
+        return 0
+      inc(i)
+    if sign == -1 and b == low(int):
+      return 0
+    b = b * sign
+    result = i - start
+
+iterator tokens(f: string): tuple[kind: FormatTokenKind, token: string] =
+  var i = 0
+  var currToken = ""
+
+  template yieldCurrToken() =
+    if currToken.len != 0:
+      yield (tkPattern, currToken)
+      currToken = ""
+
+  while i < f.len:
+    case f[i]
+    of '\'':
+      yieldCurrToken()
+      if i.succ < f.len and f[i.succ] == '\'':
+        yield (tkLiteral, "'")
+        i.inc 2
+      else:
+        var token = ""
+        inc(i) # Skip '
+        while i < f.len and f[i] != '\'':
+          token.add f[i]
+          i.inc
+
+        if i > f.high:
+          raise newException(TimeFormatParseError,
+                             "Unclosed ' in time format string. " &
+                             "For a literal ', use ''.")
+        i.inc
+        yield (tkLiteral, token)
+    of FormatLiterals:
+      yieldCurrToken()
+      yield (tkLiteral, $f[i])
+      i.inc
+    else:
+      # Check if the letter being added matches previous accumulated buffer.
+      if currToken.len == 0 or currToken[0] == f[i]:
+        currToken.add(f[i])
+        i.inc
+      else:
+        yield (tkPattern, currToken)
+        currToken = $f[i]
+        i.inc
+
+  yieldCurrToken()
+
+proc stringToPattern(str: string): FormatPattern =
+  case str
+  of "d": result = d
+  of "dd": result = dd
+  of "ddd": result = ddd
+  of "dddd": result = dddd
+  of "GG": result = GG
+  of "GGGG": result = GGGG
+  of "h": result = h
+  of "hh": result = hh
+  of "H": result = H
+  of "HH": result = HH
+  of "m": result = m
+  of "mm": result = mm
+  of "M": result = M
+  of "MM": result = MM
+  of "MMM": result = MMM
+  of "MMMM": result = MMMM
+  of "s": result = s
+  of "ss": result = ss
+  of "fff": result = fff
+  of "ffffff": result = ffffff
+  of "fffffffff": result = fffffffff
+  of "t": result = t
+  of "tt": result = tt
+  of "yy": result = yy
+  of "yyyy": result = yyyy
+  of "YYYY": result = YYYY
+  of "uuuu": result = uuuu
+  of "UUUU": result = UUUU
+  of "V": result = V
+  of "VV": result = VV
+  of "z": result = z
+  of "zz": result = zz
+  of "zzz": result = zzz
+  of "zzzz": result = zzzz
+  of "ZZZ": result = ZZZ
+  of "ZZZZ": result = ZZZZ
+  of "g": result = g
+  else: raise newException(TimeFormatParseError,
+                           "'" & str & "' is not a valid pattern")
+
+proc initTimeFormat*(format: string): TimeFormat =
+  ## Construct a new time format for parsing & formatting time types.
+  ##
+  ## See `Parsing and formatting dates`_ for documentation of the
+  ## `format` argument.
+  runnableExamples:
+    let f = initTimeFormat("yyyy-MM-dd")
+    doAssert "2000-01-01" == "2000-01-01".parse(f).format(f)
+  result.formatStr = format
+  result.patterns = @[]
+  for kind, token in format.tokens:
+    case kind
+    of tkLiteral:
+      case token
+      else:
+        result.patterns.add(FormatPattern.Lit.byte)
+        if token.len > 255:
+          raise newException(TimeFormatParseError,
+                             "Format literal is to long:" & token)
+        result.patterns.add(token.len.byte)
+        for c in token:
+          result.patterns.add(c.byte)
+    of tkPattern:
+      result.patterns.add(stringToPattern(token).byte)
+
+proc formatPattern(dt: DateTime, pattern: FormatPattern, result: var string,
+    loc: DateTimeLocale) =
+  template yearOfEra(dt: DateTime): int =
+    if dt.year <= 0: abs(dt.year) + 1 else: dt.year
+
+  case pattern
+  of d:
+    result.add $dt.monthday
+  of dd:
+    result.add dt.monthday.intToStr(2)
+  of ddd:
+    result.add loc.ddd[dt.weekday]
+  of dddd:
+    result.add loc.dddd[dt.weekday]
+  of GG:
+    result.add (dt.getIsoWeekAndYear.isoyear.int mod 100).intToStr(2)
+  of GGGG:
+    result.add $dt.getIsoWeekAndYear.isoyear
+  of h:
+    result.add(
+      if dt.hour == 0: "12"
+      elif dt.hour > 12: $(dt.hour - 12)
+      else: $dt.hour
+    )
+  of hh:
+    result.add(
+      if dt.hour == 0: "12"
+      elif dt.hour > 12: (dt.hour - 12).intToStr(2)
+      else: dt.hour.intToStr(2)
+    )
+  of H:
+    result.add $dt.hour
+  of HH:
+    result.add dt.hour.intToStr(2)
+  of m:
+    result.add $dt.minute
+  of mm:
+    result.add dt.minute.intToStr(2)
+  of M:
+    result.add $ord(dt.month)
+  of MM:
+    result.add ord(dt.month).intToStr(2)
+  of MMM:
+    result.add loc.MMM[dt.month]
+  of MMMM:
+    result.add loc.MMMM[dt.month]
+  of s:
+    result.add $dt.second
+  of ss:
+    result.add dt.second.intToStr(2)
+  of fff:
+    result.add(intToStr(convert(Nanoseconds, Milliseconds, dt.nanosecond), 3))
+  of ffffff:
+    result.add(intToStr(convert(Nanoseconds, Microseconds, dt.nanosecond), 6))
+  of fffffffff:
+    result.add(intToStr(dt.nanosecond, 9))
+  of t:
+    result.add if dt.hour >= 12: "P" else: "A"
+  of tt:
+    result.add if dt.hour >= 12: "PM" else: "AM"
+  of yy:
+    result.add (dt.yearOfEra mod 100).intToStr(2)
+  of yyyy:
+    let year = dt.yearOfEra
+    if year < 10000:
+      result.add year.intToStr(4)
+    else:
+      result.add '+' & $year
+  of YYYY:
+    if dt.year < 1:
+      result.add $(abs(dt.year) + 1)
+    else:
+      result.add $dt.year
+  of uuuu:
+    let year = dt.year
+    if year < 10000 or year < 0:
+      result.add year.intToStr(4)
+    else:
+      result.add '+' & $year
+  of UUUU:
+    result.add $dt.year
+  of V:
+    result.add $dt.getIsoWeekAndYear.isoweek
+  of VV:
+    result.add dt.getIsoWeekAndYear.isoweek.intToStr(2)
+  of z, zz, zzz, zzzz, ZZZ, ZZZZ:
+    if dt.timezone != nil and dt.timezone.name == "Etc/UTC":
+      result.add 'Z'
+    else:
+      result.add if -dt.utcOffset >= 0: '+' else: '-'
+      let absOffset = abs(dt.utcOffset)
+      case pattern:
+      of z:
+        result.add $(absOffset div 3600)
+      of zz:
+        result.add (absOffset div 3600).intToStr(2)
+      of zzz, ZZZ:
+        let h = (absOffset div 3600).intToStr(2)
+        let m = ((absOffset div 60) mod 60).intToStr(2)
+        let sep = if pattern == zzz: ":" else: ""
+        result.add h & sep & m
+      of zzzz, ZZZZ:
+        let absOffset = abs(dt.utcOffset)
+        let h = (absOffset div 3600).intToStr(2)
+        let m = ((absOffset div 60) mod 60).intToStr(2)
+        let s = (absOffset mod 60).intToStr(2)
+        let sep = if pattern == zzzz: ":" else: ""
+        result.add h & sep & m & sep & s
+      else: assert false
+  of g:
+    result.add if dt.year < 1: "BC" else: "AD"
+  of Lit: assert false # Can't happen
+
+proc parsePattern(input: string, pattern: FormatPattern, i: var int,
+                  parsed: var ParsedTime, loc: DateTimeLocale): bool =
+  template takeInt(allowedWidth: Slice[int], allowSign = false): int =
+    var sv = 0
+    var pd = parseInt(input, sv, i, allowedWidth.b, allowSign)
+    if pd < allowedWidth.a:
+      return false
+    i.inc pd
+    sv
+
+  template contains[T](t: typedesc[T], i: int): bool =
+    i in low(t)..high(t)
+
+  result = true
+
+  case pattern
+  of d:
+    let monthday = takeInt(1..2)
+    parsed.monthday = some(monthday)
+    result = monthday in MonthdayRange
+  of dd:
+    let monthday = takeInt(2..2)
+    parsed.monthday = some(monthday)
+    result = monthday in MonthdayRange
+  of ddd:
+    result = false
+    for d, v in loc.ddd:
+      if input.substr(i, i+v.len-1).cmpIgnoreCase(v) == 0:
+        parsed.weekday = some(d.WeekDay)
+        result = true
+        i.inc v.len
+        break
+  of dddd:
+    result = false
+    for d, v in loc.dddd:
+      if input.substr(i, i+v.len-1).cmpIgnoreCase(v) == 0:
+        parsed.weekday = some(d.WeekDay)
+        result = true
+        i.inc v.len
+        break
+  of GG:
+    # Assumes current century
+    var isoyear = takeInt(2..2)
+    var thisCen = now().year div 100
+    parsed.isoyear = some(thisCen*100 + isoyear)
+    result = isoyear > 0
+  of GGGG:
+    let isoyear = takeInt(1..high(int))
+    parsed.isoyear = some(isoyear)
+    result = isoyear > 0
+  of h, H:
+    parsed.hour = takeInt(1..2)
+    result = parsed.hour in HourRange
+  of hh, HH:
+    parsed.hour = takeInt(2..2)
+    result = parsed.hour in HourRange
+  of m:
+    parsed.minute = takeInt(1..2)
+    result = parsed.hour in MinuteRange
+  of mm:
+    parsed.minute = takeInt(2..2)
+    result = parsed.hour in MinuteRange
+  of M:
+    let month = takeInt(1..2)
+    result = month in 1..12
+    parsed.month = some(month)
+  of MM:
+    let month = takeInt(2..2)
+    result = month in 1..12
+    parsed.month = some(month)
+  of MMM:
+    result = false
+    for n, v in loc.MMM:
+      if input.substr(i, i+v.len-1).cmpIgnoreCase(v) == 0:
+        result = true
+        i.inc v.len
+        parsed.month = some(n.int)
+        break
+  of MMMM:
+    result = false
+    for n, v in loc.MMMM:
+      if input.substr(i, i+v.len-1).cmpIgnoreCase(v) == 0:
+        result = true
+        i.inc v.len
+        parsed.month = some(n.int)
+        break
+  of s:
+    parsed.second = takeInt(1..2)
+  of ss:
+    parsed.second = takeInt(2..2)
+  of fff, ffffff, fffffffff:
+    let len = ($pattern).len
+    let v = takeInt(len..len)
+    parsed.nanosecond = v * 10^(9 - len)
+    result = parsed.nanosecond in NanosecondRange
+  of t:
+    case input[i]:
+    of 'P':
+      parsed.amPm = apPm
+    of 'A':
+      parsed.amPm = apAm
+    else:
+      result = false
+    i.inc 1
+  of tt:
+    if input.substr(i, i+1).cmpIgnoreCase("AM") == 0:
+      parsed.amPm = apAm
+      i.inc 2
+    elif input.substr(i, i+1).cmpIgnoreCase("PM") == 0:
+      parsed.amPm = apPm
+      i.inc 2
+    else:
+      result = false
+  of yy:
+    # Assumes current century
+    var year = takeInt(2..2)
+    var thisCen = now().year div 100
+    parsed.year = some(thisCen*100 + year)
+    result = year > 0
+  of yyyy:
+    let year =
+      if input[i] in {'+', '-'}:
+        takeInt(4..high(int), allowSign = true)
+      else:
+        takeInt(4..4)
+    result = year > 0
+    parsed.year = some(year)
+  of YYYY:
+    let year = takeInt(1..high(int))
+    parsed.year = some(year)
+    result = year > 0
+  of uuuu:
+    let year =
+      if input[i] in {'+', '-'}:
+        takeInt(4..high(int), allowSign = true)
+      else:
+        takeInt(4..4)
+    parsed.year = some(year)
+  of UUUU:
+    parsed.year = some(takeInt(1..high(int), allowSign = true))
+  of V:
+    let yearweek = takeInt(1..2)
+    parsed.yearweek = some(yearweek)
+    result = yearweek in IsoWeekRange
+  of VV:
+    let yearweek = takeInt(2..2)
+    parsed.yearweek = some(yearweek)
+    result = yearweek in IsoWeekRange
+  of z, zz, zzz, zzzz, ZZZ, ZZZZ:
+    case input[i]
+    of '+', '-':
+      let sign = if input[i] == '-': 1 else: -1
+      i.inc
+      var offset = 0
+      case pattern
+      of z:
+        offset = takeInt(1..2) * 3600
+      of zz:
+        offset = takeInt(2..2) * 3600
+      of zzz, ZZZ:
+        offset.inc takeInt(2..2) * 3600
+        if pattern == zzz:
+          if input[i] != ':':
+            return false
+          i.inc
+        offset.inc takeInt(2..2) * 60
+      of zzzz, ZZZZ:
+        offset.inc takeInt(2..2) * 3600
+        if pattern == zzzz:
+          if input[i] != ':':
+            return false
+          i.inc
+        offset.inc takeInt(2..2) * 60
+        if pattern == zzzz:
+          if input[i] != ':':
+            return false
+          i.inc
+        offset.inc takeInt(2..2)
+      else: assert false
+      parsed.utcOffset = some(offset * sign)
+    of 'Z':
+      parsed.utcOffset = some(0)
+      i.inc
+    else:
+      result = false
+  of g:
+    if input.substr(i, i+1).cmpIgnoreCase("BC") == 0:
+      parsed.era = eraBc
+      i.inc 2
+    elif input.substr(i, i+1).cmpIgnoreCase("AD") == 0:
+      parsed.era = eraAd
+      i.inc 2
+    else:
+      result = false
+  of Lit: raiseAssert "Can't happen"
+
+proc toDateTime(p: ParsedTime, zone: Timezone, f: TimeFormat,
+                input: string): DateTime =
+  var year = p.year.get(0)
+  var month = p.month.get(1).Month
+  var monthday = p.monthday.get(1)
+  year =
+    case p.era
+    of eraUnknown:
+      year
+    of eraBc:
+      if year < 1:
+        raiseParseException(f, input,
+          "Expected year to be positive " &
+          "(use 'UUUU' or 'uuuu' for negative years).")
+      -year + 1
+    of eraAd:
+      if year < 1:
+        raiseParseException(f, input,
+          "Expected year to be positive " &
+          "(use 'UUUU' or 'uuuu' for negative years).")
+      year
+
+  let hour =
+    case p.amPm
+    of apUnknown:
+      p.hour
+    of apAm:
+      if p.hour notin 1..12:
+        raiseParseException(f, input,
+          "AM/PM time must be in the interval 1..12")
+      if p.hour == 12: 0 else: p.hour
+    of apPm:
+      if p.hour notin 1..12:
+        raiseParseException(f, input,
+          "AM/PM time must be in the interval 1..12")
+      if p.hour == 12: p.hour else: p.hour + 12
+  let minute = p.minute
+  let second = p.second
+  let nanosecond = p.nanosecond
+
+  if monthday > getDaysInMonth(month, year):
+    raiseParseException(f, input,
+      $year & "-" & ord(month).intToStr(2) &
+      "-" & $monthday & " is not a valid date")
+
+  if p.utcOffset.isNone:
+    # No timezone parsed - assume timezone is `zone`
+    result = dateTime(year, month, monthday, hour, minute, second, nanosecond, zone)
+  else:
+    # Otherwise convert to `zone`
+    result = (dateTime(year, month, monthday, hour, minute, second, nanosecond, utc()).toTime +
+      initDuration(seconds = p.utcOffset.get())).inZone(zone)
+
+proc toDateTimeByWeek(p: ParsedTime, zone: Timezone, f: TimeFormat,
+                   input: string): DateTime =
+  var isoyear = p.isoyear.get(0)
+  var yearweek = p.yearweek.get(1)
+  var weekday = p.weekday.get(dMon)
+
+  if p.amPm != apUnknown:
+    raiseParseException(f, input, "Parsing iso weekyear dates does not support am/pm")
+
+  if p.year.isSome:
+    raiseParseException(f, input, "Use iso-year GG or GGGG as year with iso week number")
+
+  if p.month.isSome:
+    raiseParseException(f, input, "Use either iso week number V or VV or month")
+
+  if p.monthday.isSome:
+    raiseParseException(f, input, "Use weekday ddd or dddd as day with with iso week number")
+
+  if p.isoyear.isNone:
+    raiseParseException(f, input, "Need iso-year with week number")
+
+  let hour = p.hour
+  let minute = p.minute
+  let second = p.second
+  let nanosecond = p.nanosecond
+
+  if p.utcOffset.isNone:
+    result = initDateTime(weekday, yearweek.IsoWeekRange, isoyear.IsoYear, hour, minute, second, nanosecond, zone)
+  else:
+    result = (initDateTime(weekday, yearweek.IsoWeekRange, isoyear.IsoYear, hour, minute, second, nanosecond, zone).toTime +
+      initDuration(seconds = p.utcOffset.get())).inZone(zone)
+
+proc format*(dt: DateTime, f: TimeFormat,
+    loc: DateTimeLocale = DefaultLocale): string {.raises: [].} =
+  ## Format `dt` using the format specified by `f`.
+  runnableExamples:
+    let f = initTimeFormat("yyyy-MM-dd")
+    let dt = dateTime(2000, mJan, 01, 00, 00, 00, 00, utc())
+    doAssert "2000-01-01" == dt.format(f)
+  assertDateTimeInitialized dt
+  result = ""
+  var idx = 0
+  while idx <= f.patterns.high:
+    case f.patterns[idx].FormatPattern
+    of Lit:
+      idx.inc
+      let len = f.patterns[idx]
+      for i in 1'u8..len:
+        idx.inc
+        result.add f.patterns[idx].char
+      idx.inc
+    else:
+      formatPattern(dt, f.patterns[idx].FormatPattern, result = result, loc = loc)
+      idx.inc
+
+proc format*(dt: DateTime, f: string, loc: DateTimeLocale = DefaultLocale): string
+    {.raises: [TimeFormatParseError].} =
+  ## Shorthand for constructing a `TimeFormat` and using it to format `dt`.
+  ##
+  ## See `Parsing and formatting dates`_ for documentation of the
+  ## `format` argument.
+  runnableExamples:
+    let dt = dateTime(2000, mJan, 01, 00, 00, 00, 00, utc())
+    doAssert "2000-01-01" == format(dt, "yyyy-MM-dd")
+  let dtFormat = initTimeFormat(f)
+  result = dt.format(dtFormat, loc)
+
+proc format*(dt: DateTime, f: static[string]): string {.raises: [].} =
+  ## Overload that validates `format` at compile time.
+  const f2 = initTimeFormat(f)
+  result = dt.format(f2)
+
+proc formatValue*(result: var string; value: DateTime | Time, specifier: string) =
+  ## adapter for strformat. Not intended to be called directly.
+  result.add format(value,
+    if specifier.len == 0: "yyyy-MM-dd'T'HH:mm:sszzz" else: specifier)
+
+proc format*(time: Time, f: string, zone: Timezone = local()): string
+    {.raises: [TimeFormatParseError].} =
+  ## Shorthand for constructing a `TimeFormat` and using it to format
+  ## `time`. Will use the timezone specified by `zone`.
+  ##
+  ## See `Parsing and formatting dates`_ for documentation of the
+  ## `f` argument.
+  runnableExamples:
+    var dt = dateTime(1970, mJan, 01, 00, 00, 00, 00, utc())
+    var tm = dt.toTime()
+    doAssert format(tm, "yyyy-MM-dd'T'HH:mm:ss", utc()) == "1970-01-01T00:00:00"
+  time.inZone(zone).format(f)
+
+proc format*(time: Time, f: static[string], zone: Timezone = local()): string
+    {.raises: [].} =
+  ## Overload that validates `f` at compile time.
+  const f2 = initTimeFormat(f)
+  result = time.inZone(zone).format(f2)
+
+proc parse*(input: string, f: TimeFormat, zone: Timezone = local(),
+    loc: DateTimeLocale = DefaultLocale): DateTime {.parseRaises.} =
+  ## Parses `input` as a `DateTime` using the format specified by `f`.
+  ## If no UTC offset was parsed, then `input` is assumed to be specified in
+  ## the `zone` timezone. If a UTC offset was parsed, the result will be
+  ## converted to the `zone` timezone.
+  ##
+  ## Month and day names from the passed in `loc` are used.
+  runnableExamples:
+    let f = initTimeFormat("yyyy-MM-dd")
+    let dt = dateTime(2000, mJan, 01, 00, 00, 00, 00, utc())
+    doAssert dt == "2000-01-01".parse(f, utc())
+  var inpIdx = 0 # Input index
+  var patIdx = 0 # Pattern index
+  var parsed: ParsedTime
+  while inpIdx <= input.high and patIdx <= f.patterns.high:
+    let pattern = f.patterns[patIdx].FormatPattern
+    case pattern
+    of Lit:
+      patIdx.inc
+      let len = f.patterns[patIdx]
+      patIdx.inc
+      for _ in 1'u8..len:
+        if input[inpIdx] != f.patterns[patIdx].char:
+          raiseParseException(f, input,
+                              "Unexpected character: " & input[inpIdx])
+        inpIdx.inc
+        patIdx.inc
+    else:
+      if not parsePattern(input, pattern, inpIdx, parsed, loc):
+        raiseParseException(f, input, "Failed on pattern '" & $pattern & "'")
+      patIdx.inc
+
+  if inpIdx <= input.high:
+    raiseParseException(f, input,
+                        "Parsing ended but there was still input remaining")
+
+  if patIdx <= f.patterns.high:
+    raiseParseException(f, input,
+                            "Parsing ended but there was still patterns remaining")
+
+  if parsed.yearweek.isSome:
+    result = toDateTimeByWeek(parsed, zone, f, input)
+  elif parsed.isoyear.isSome:
+    raiseParseException(f, input, "Iso year GG or GGGG require iso week V or VV")
+  else:
+    result = toDateTime(parsed, zone, f, input)
+
+proc parse*(input, f: string, tz: Timezone = local(),
+    loc: DateTimeLocale = DefaultLocale): DateTime {.parseFormatRaises.} =
+  ## Shorthand for constructing a `TimeFormat` and using it to parse
+  ## `input` as a `DateTime`.
+  ##
+  ## See `Parsing and formatting dates`_ for documentation of the
+  ## `f` argument.
+  runnableExamples:
+    let dt = dateTime(2000, mJan, 01, 00, 00, 00, 00, utc())
+    doAssert dt == parse("2000-01-01", "yyyy-MM-dd", utc())
+  let dtFormat = initTimeFormat(f)
+  result = input.parse(dtFormat, tz, loc = loc)
+
+proc parse*(input: string, f: static[string], zone: Timezone = local(),
+    loc: DateTimeLocale = DefaultLocale): DateTime {.parseRaises.} =
+  ## Overload that validates `f` at compile time.
+  const f2 = initTimeFormat(f)
+  result = input.parse(f2, zone, loc = loc)
+
+proc parseTime*(input, f: string, zone: Timezone): Time {.parseFormatRaises.} =
+  ## Shorthand for constructing a `TimeFormat` and using it to parse
+  ## `input` as a `DateTime`, then converting it a `Time`.
+  ##
+  ## See `Parsing and formatting dates`_ for documentation of the
+  ## `format` argument.
+  runnableExamples:
+    let tStr = "1970-01-01T00:00:00+00:00"
+    doAssert parseTime(tStr, "yyyy-MM-dd'T'HH:mm:sszzz", utc()) == fromUnix(0)
+  parse(input, f, zone).toTime()
+
+proc parseTime*(input: string, f: static[string], zone: Timezone): Time
+    {.parseRaises.} =
+  ## Overload that validates `format` at compile time.
+  const f2 = initTimeFormat(f)
+  result = input.parse(f2, zone).toTime()
+
+proc `$`*(dt: DateTime): string {.tags: [], raises: [], benign.} =
+  ## Converts a `DateTime` object to a string representation.
+  ## It uses the format `yyyy-MM-dd'T'HH:mm:sszzz`.
+  runnableExamples:
+    let dt = dateTime(2000, mJan, 01, 12, 00, 00, 00, utc())
+    doAssert $dt == "2000-01-01T12:00:00Z"
+    doAssert $default(DateTime) == "Uninitialized DateTime"
+  if not dt.isInitialized:
+    result = "Uninitialized DateTime"
+  else:
+    result = format(dt, "yyyy-MM-dd'T'HH:mm:sszzz")
+
+proc `$`*(time: Time): string {.tags: [], raises: [], benign.} =
+  ## 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`.
+  runnableExamples:
+    let dt = dateTime(1970, mJan, 01, 00, 00, 00, 00, local())
+    let tm = dt.toTime()
+    doAssert $tm == "1970-01-01T00:00:00" & format(dt, "zzz")
+  $time.local
+
+#
+# TimeInterval
+#
+
 proc initTimeInterval*(nanoseconds, microseconds, milliseconds,
                        seconds, minutes, hours,
                        days, weeks, months, years: int = 0): TimeInterval =
-  ## Creates a new ``TimeInterval``.
+  ## Creates a new `TimeInterval <#TimeInterval>`_.
   ##
-  ## You can also use the convenience procedures called ``milliseconds``,
-  ## ``seconds``, ``minutes``, ``hours``, ``days``, ``months``, and ``years``.
+  ## 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`,
+  ## `seconds`, `minutes`, `hours`, `days`, `months`, and `years`.
   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"
+    let day = initTimeInterval(hours = 24)
+    let dt = dateTime(2000, mJan, 01, 12, 00, 00, 00, utc())
+    doAssert $(dt + day) == "2000-01-02T12:00:00Z"
+    doAssert initTimeInterval(hours = 24) != initTimeInterval(days = 1)
   result.nanoseconds = nanoseconds
   result.microseconds = microseconds
   result.milliseconds = milliseconds
@@ -930,7 +2378,7 @@ proc initTimeInterval*(nanoseconds, microseconds, milliseconds,
   result.years = years
 
 proc `+`*(ti1, ti2: TimeInterval): TimeInterval =
-  ## Adds two ``TimeInterval`` objects together.
+  ## Adds two `TimeInterval` objects together.
   result.nanoseconds = ti1.nanoseconds + ti2.nanoseconds
   result.microseconds = ti1.microseconds + ti2.microseconds
   result.milliseconds = ti1.milliseconds + ti2.milliseconds
@@ -945,7 +2393,7 @@ proc `+`*(ti1, ti2: TimeInterval): TimeInterval =
 proc `-`*(ti: TimeInterval): TimeInterval =
   ## Reverses a time interval
   runnableExamples:
-    let day = -initTimeInterval(hours=24)
+    let day = -initTimeInterval(hours = 24)
     doAssert day.hours == -24
 
   result = TimeInterval(
@@ -962,50 +2410,148 @@ proc `-`*(ti: TimeInterval): TimeInterval =
   )
 
 proc `-`*(ti1, ti2: TimeInterval): TimeInterval =
-  ## Subtracts TimeInterval ``ti1`` from ``ti2``.
+  ## Subtracts TimeInterval `ti1` from `ti2`.
   ##
   ## Time components are subtracted one-by-one, see output:
   runnableExamples:
-    let ti1 = initTimeInterval(hours=24)
-    let ti2 = initTimeInterval(hours=4)
-    doAssert (ti1 - ti2) == initTimeInterval(hours=20)
+    let ti1 = initTimeInterval(hours = 24)
+    let ti2 = initTimeInterval(hours = 4)
+    doAssert (ti1 - ti2) == initTimeInterval(hours = 20)
 
   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``.
-  var ti = now()
-  result = $ti.year & '-' & intToStr(ord(ti.month), 2) &
-    '-' & intToStr(ti.monthday, 2)
-
-proc getClockStr*(): string {.rtl, extern: "nt$1", tags: [TimeEffect].} =
-  ## Gets the current clock time as a string of the format ``HH:MM:SS``.
-  var ti = now()
-  result = intToStr(ti.hour, 2) & ':' & intToStr(ti.minute, 2) &
-    ':' & intToStr(ti.second, 2)
-
-proc `$`*(day: WeekDay): string =
-  ## Stringify operator for ``WeekDay``.
-  const lookup: array[WeekDay, string] = ["Monday", "Tuesday", "Wednesday",
-     "Thursday", "Friday", "Saturday", "Sunday"]
-  return lookup[day]
-
-proc `$`*(m: Month): string =
-  ## Stringify operator for ``Month``.
-  const lookup: array[Month, string] = ["January", "February", "March",
-      "April", "May", "June", "July", "August", "September", "October",
-      "November", "December"]
-  return lookup[m]
-
-
-proc toParts* (ti: TimeInterval): TimeIntervalParts =
+proc `+=`*(a: var TimeInterval, b: TimeInterval) =
+  a = a + b
+
+proc `-=`*(a: var TimeInterval, b: TimeInterval) =
+  a = a - b
+
+proc isStaticInterval(interval: TimeInterval): bool =
+  interval.years == 0 and interval.months == 0 and
+    interval.days == 0 and interval.weeks == 0
+
+proc evaluateStaticInterval(interval: TimeInterval): Duration =
+  assert interval.isStaticInterval
+  initDuration(nanoseconds = interval.nanoseconds,
+    microseconds = interval.microseconds,
+    milliseconds = interval.milliseconds,
+    seconds = interval.seconds,
+    minutes = interval.minutes,
+    hours = interval.hours)
+
+proc between*(startDt, endDt: DateTime): TimeInterval =
+  ## Gives the difference between `startDt` and `endDt` as a
+  ## `TimeInterval`. The following guarantees about the result is given:
+  ##
+  ## - All fields will have the same sign.
+  ## - If `startDt.timezone == endDt.timezone`, it is guaranteed that
+  ##   `startDt + between(startDt, endDt) == endDt`.
+  ## - If `startDt.timezone != endDt.timezone`, then the result will be
+  ##   equivalent to `between(startDt.utc, endDt.utc)`.
+  runnableExamples:
+    var a = dateTime(2015, mMar, 25, 12, 0, 0, 00, utc())
+    var b = dateTime(2017, mApr, 1, 15, 0, 15, 00, utc())
+    var ti = initTimeInterval(years = 2, weeks = 1, hours = 3, seconds = 15)
+    doAssert between(a, b) == ti
+    doAssert between(a, b) == -between(b, a)
+
+  if startDt.timezone != endDt.timezone:
+    return between(startDt.utc, endDt.utc)
+  elif endDt < startDt:
+    return -between(endDt, startDt)
+
+  type Date = tuple[year, month, monthday: int]
+  var startDate: Date = (startDt.year, startDt.month.ord, startDt.monthday)
+  var endDate: Date = (endDt.year, endDt.month.ord, endDt.monthday)
+
+  # Subtract one day from endDate if time of day is earlier than startDay
+  # The subtracted day will be counted by fixed units (hour and lower)
+  # at the end of this proc
+  if (endDt.hour, endDt.minute, endDt.second, endDt.nanosecond) <
+      (startDt.hour, startDt.minute, startDt.second, startDt.nanosecond):
+    if endDate.month == 1 and endDate.monthday == 1:
+      endDate.year.dec
+      endDate.monthday = 31
+      endDate.month = 12
+    elif endDate.monthday == 1:
+      endDate.month.dec
+      endDate.monthday = getDaysInMonth(endDate.month.Month, endDate.year)
+    else:
+      endDate.monthday.dec
+
+  # Years
+  result.years = endDate.year - startDate.year - 1
+  if (startDate.month, startDate.monthday) <= (endDate.month, endDate.monthday):
+    result.years.inc
+  startDate.year.inc result.years
+
+  # Months
+  if startDate.year < endDate.year:
+    result.months.inc 12 - startDate.month # Move to dec
+    if endDate.month != 1 or (startDate.monthday <= endDate.monthday):
+      result.months.inc
+      startDate.year = endDate.year
+      startDate.month = 1
+    else:
+      startDate.month = 12
+  if startDate.year == endDate.year:
+    if (startDate.monthday <= endDate.monthday):
+      result.months.inc endDate.month - startDate.month
+      startDate.month = endDate.month
+    elif endDate.month != 1:
+      let month = endDate.month - 1
+      let daysInMonth = getDaysInMonth(month.Month, startDate.year)
+      if daysInMonth < startDate.monthday:
+        if startDate.monthday - daysInMonth < endDate.monthday:
+          result.months.inc endDate.month - startDate.month - 1
+          startDate.month = endDate.month
+          startDate.monthday = startDate.monthday - daysInMonth
+        else:
+          result.months.inc endDate.month - startDate.month - 2
+          startDate.month = endDate.month - 2
+      else:
+        result.months.inc endDate.month - startDate.month - 1
+        startDate.month = endDate.month - 1
+
+  # Days
+  # This means that start = dec and end = jan
+  if startDate.year < endDate.year:
+    result.days.inc 31 - startDate.monthday + endDate.monthday
+    startDate = endDate
+  else:
+    while startDate.month < endDate.month:
+      let daysInMonth = getDaysInMonth(startDate.month.Month, startDate.year)
+      result.days.inc daysInMonth - startDate.monthday + 1
+      startDate.month.inc
+      startDate.monthday = 1
+    result.days.inc endDate.monthday - startDate.monthday
+    result.weeks = result.days div 7
+    result.days = result.days mod 7
+    startDate = endDate
+
+  # Handle hours, minutes, seconds, milliseconds, microseconds and nanoseconds
+  let newStartDt = dateTime(startDate.year, startDate.month.Month,
+    startDate.monthday, startDt.hour, startDt.minute, startDt.second,
+    startDt.nanosecond, startDt.timezone)
+  let dur = endDt - newStartDt
+  let parts = toParts(dur)
+  # There can still be a full day in `parts` since `Duration` and `TimeInterval`
+  # models days differently.
+  result.hours = parts[Hours].int + parts[Days].int * 24
+  result.minutes = parts[Minutes].int
+  result.seconds = parts[Seconds].int
+  result.milliseconds = parts[Milliseconds].int
+  result.microseconds = parts[Microseconds].int
+  result.nanoseconds = parts[Nanoseconds].int
+
+proc toParts*(ti: TimeInterval): TimeIntervalParts =
   ## 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
   runnableExamples:
-    var tp = toParts(initTimeInterval(years=1, nanoseconds=123))
+    var tp = toParts(initTimeInterval(years = 1, nanoseconds = 123))
     doAssert tp[Years] == 1
     doAssert tp[Nanoseconds] == 123
 
@@ -1015,83 +2561,86 @@ proc toParts* (ti: TimeInterval): TimeIntervalParts =
     index += 1
 
 proc `$`*(ti: TimeInterval): string =
-  ## Get string representation of `TimeInterval`
+  ## Get string representation of `TimeInterval`.
   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"
 
   var parts: seq[string] = @[]
   var tiParts = toParts(ti)
   for unit in countdown(Years, Nanoseconds):
     if tiParts[unit] != 0:
-      parts.add(stringifyUnit(tiParts[unit], $unit))
+      parts.add(stringifyUnit(tiParts[unit], unit))
 
   result = humanizeParts(parts)
 
 proc nanoseconds*(nanos: int): TimeInterval {.inline.} =
-  ## TimeInterval of ``nanos`` nanoseconds.
+  ## TimeInterval of `nanos` nanoseconds.
   initTimeInterval(nanoseconds = nanos)
 
 proc microseconds*(micros: int): TimeInterval {.inline.} =
-  ## TimeInterval of ``micros`` microseconds.
+  ## TimeInterval of `micros` microseconds.
   initTimeInterval(microseconds = micros)
 
 proc milliseconds*(ms: int): TimeInterval {.inline.} =
-  ## TimeInterval of ``ms`` milliseconds.
+  ## 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``
+  ## `echo getTime() + 5.seconds`
   initTimeInterval(seconds = s)
 
 proc minutes*(m: int): TimeInterval {.inline.} =
-  ## TimeInterval of ``m`` minutes.
+  ## TimeInterval of `m` minutes.
   ##
-  ## ``echo getTime() + 5.minutes``
+  ## `echo getTime() + 5.minutes`
   initTimeInterval(minutes = m)
 
 proc hours*(h: int): TimeInterval {.inline.} =
-  ## TimeInterval of ``h`` hours.
+  ## TimeInterval of `h` hours.
   ##
-  ## ``echo getTime() + 2.hours``
+  ## `echo getTime() + 2.hours`
   initTimeInterval(hours = h)
 
 proc days*(d: int): TimeInterval {.inline.} =
-  ## TimeInterval of ``d`` days.
+  ## TimeInterval of `d` days.
   ##
-  ## ``echo getTime() + 2.days``
+  ## `echo getTime() + 2.days`
   initTimeInterval(days = d)
 
 proc weeks*(w: int): TimeInterval {.inline.} =
-  ## TimeInterval of ``w`` weeks.
+  ## TimeInterval of `w` weeks.
   ##
-  ## ``echo getTime() + 2.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``
+  ## `echo getTime() + 2.months`
   initTimeInterval(months = m)
 
 proc years*(y: int): TimeInterval {.inline.} =
-  ## TimeInterval of ``y`` years.
+  ## TimeInterval of `y` years.
   ##
-  ## ``echo getTime() + 2.years``
+  ## `echo getTime() + 2.years`
   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
-  ## in the context of ``dt``.
+  ## 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
+  result = default(tuple[adjDur, absDur: Duration])
   # Subtracting
   if months < 0:
-    for mth in countDown(-1 * months, 1):
+    for mth in countdown(-1 * months, 1):
       if curMonth == mJan:
         curMonth = mDec
         curYear.dec
@@ -1121,230 +2670,46 @@ proc evaluateInterval(dt: DateTime, interval: TimeInterval): tuple[adjDur, absDu
     minutes = interval.minutes,
     hours = interval.hours)
 
-
-proc initDateTime*(monthday: MonthdayRange, month: Month, year: int,
-                   hour: HourRange, minute: MinuteRange, second: SecondRange,
-                   nanosecond: NanosecondRange, zone: Timezone = local()): DateTime =
-  ## Create a new ``DateTime`` in the specified timezone.
-  runnableExamples:
-    let dt1 = initDateTime(30, mMar, 2017, 00, 00, 00, 00, utc())
-    doAssert $dt1 == "2017-03-30T00:00:00+00:00"
-
-  assertValidDate monthday, month, year
-  let dt = DateTime(
-    monthday:  monthday,
-    year:  year,
-    month:  month,
-    hour:  hour,
-    minute:  minute,
-    second:  second,
-    nanosecond: nanosecond
-  )
-  result = initDateTime(zone.zoneInfoFromTz(dt.toAdjTime), zone)
-
-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.
-  runnableExamples:
-    let dt1 = initDateTime(30, mMar, 2017, 00, 00, 00, utc())
-    doAssert $dt1 == "2017-03-30T00:00:00+00:00"
-  initDateTime(monthday, month, year, hour, minute, second, 0, zone)
-
-
 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`.
+  ## 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"
+    let dt = dateTime(2017, mMar, 30, 00, 00, 00, 00, utc())
+    doAssert $(dt + 1.months) == "2017-04-30T00:00:00Z"
     # This is correct and happens due to monthday overflow.
-    doAssert $(dt - 1.months) == "2017-03-02T00:00:00+00:00"
+    doAssert $(dt - 1.months) == "2017-03-02T00:00:00Z"
   let (adjDur, absDur) = evaluateInterval(dt, interval)
 
   if adjDur != DurationZero:
-    var zInfo = dt.timezone.zoneInfoFromTz(dt.toAdjTime + adjDur)
+    var zt = dt.timezone.zonedTimeFromAdjTime(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)
+      zt = dt.timezone.zonedTimeFromTime(zt.time + absDur)
+      result = initDateTime(zt, dt.timezone)
     else:
-      result = initDateTime(zInfo, dt.timezone)
+      result = initDateTime(zt, dt.timezone)
   else:
-    var zInfo = dt.timezone.zoneInfoFromUtc(dt.toTime + absDur)
-    result = initDateTime(zInfo, dt.timezone)
+    var zt = dt.timezone.zonedTimeFromTime(dt.toTime + absDur)
+    result = initDateTime(zt, 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.
+  ## 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.
   runnableExamples:
-    let dt = initDateTime(30, mMar, 2017, 00, 00, 00, utc())
-    doAssert $(dt - 5.days) == "2017-03-25T00:00:00+00:00"
+    let dt = dateTime(2017, mMar, 30, 00, 00, 00, 00, utc())
+    doAssert $(dt - 5.days) == "2017-03-25T00:00:00Z"
 
   dt + (-interval)
 
-proc `+`*(dt: DateTime, dur: Duration): DateTime =
-  runnableExamples:
-    let dt = initDateTime(30, mMar, 2017, 00, 00, 00, utc())
-    let dur = initDuration(hours = 5)
-    doAssert $(dt + dur) == "2017-03-30T05:00:00+00:00"
-
-  (dt.toTime + dur).inZone(dt.timezone)
-
-proc `-`*(dt: DateTime, dur: Duration): DateTime =
-  runnableExamples:
-    let dt = initDateTime(30, mMar, 2017, 00, 00, 00, utc())
-    let dur = initDuration(days = 5)
-    doAssert $(dt - dur) == "2017-03-25T00:00:00+00:00"
-
-  (dt.toTime - dur).inZone(dt.timezone)
-
-proc `-`*(dt1, dt2: DateTime): Duration =
-  ## Compute the duration between ``dt1`` and ``dt2``.
-  runnableExamples:
-    let dt1 = initDateTime(30, mMar, 2017, 00, 00, 00, utc())
-    let dt2 = initDateTime(25, mMar, 2017, 00, 00, 00, utc())
-
-    doAssert dt1 - dt2 == initDuration(days = 5)
-
-  dt1.toTime - dt2.toTime
-
-proc `<`*(a, b: DateTime): bool =
-  ## Returns true iff ``a < b``, that is iff a happened before b.
-  return a.toTime < b.toTime
-
-proc `<=` * (a, b: DateTime): bool =
-  ## Returns true iff ``a <= b``.
-  return a.toTime <= b.toTime
-
-proc `==`*(a, b: DateTime): bool =
-  ## Returns true if ``a == b``, that is if both dates represent the same point in datetime.
-  return a.toTime == b.toTime
-
-
-proc isStaticInterval(interval: TimeInterval): bool =
-  interval.years == 0 and interval.months == 0 and
-    interval.days == 0 and interval.weeks == 0
-
-proc evaluateStaticInterval(interval: TimeInterval): Duration =
-  assert interval.isStaticInterval
-  initDuration(nanoseconds = interval.nanoseconds,
-    microseconds = interval.microseconds,
-    milliseconds = interval.milliseconds,
-    seconds = interval.seconds,
-    minutes = interval.minutes,
-    hours = interval.hours)
-
-proc between*(startDt, endDt: DateTime): TimeInterval =
-  ## Evaluate difference between two dates in ``TimeInterval`` format, so, it
-  ## will be relative.
-  ##
-  ## **Warning:** It's not recommended to use ``between`` for ``DateTime's`` in
-  ## different ``TimeZone's``.
-  ## ``a + between(a, b) == b`` is only guaranteed when ``a`` and ``b`` are in UTC.
-  runnableExamples:
-    var a = initDateTime(year = 2018, month = Month(3), monthday = 25,
-                     hour = 0, minute = 59, second = 59, nanosecond = 1,
-                     zone = utc()).local
-    var b = initDateTime(year = 2018, month = Month(3), monthday = 25,
-                     hour = 1, minute =  1, second =  1, nanosecond = 0,
-                     zone = utc()).local
-    doAssert between(a, b) == initTimeInterval(
-      nanoseconds=999, milliseconds=999, microseconds=999, seconds=1, minutes=1)
-
-    a = parse("2018-01-09T00:00:00+00:00", "yyyy-MM-dd'T'HH:mm:sszzz", utc())
-    b = parse("2018-01-10T23:00:00-02:00", "yyyy-MM-dd'T'HH:mm:sszzz")
-    doAssert between(a, b) == initTimeInterval(hours=1, days=2)
-    ## Though, here correct answer should be 1 day 25 hours (cause this day in
-    ## this tz is actually 26 hours). That's why operating different TZ is
-    ## discouraged
-
-  var startDt = startDt.utc()
-  var endDt = endDt.utc()
-
-  if endDt == startDt:
-    return initTimeInterval()
-  elif endDt < startDt:
-    return -between(endDt, startDt)
-
-  var coeffs: array[FixedTimeUnit, int64] = unitWeights
-  var timeParts: array[FixedTimeUnit, int]
-  for unit in Nanoseconds..Weeks:
-    timeParts[unit] = 0
-
-  for unit in Seconds..Days:
-    coeffs[unit] = coeffs[unit] div unitWeights[Seconds]
-
-  var startTimepart = initTime(
-    nanosecond = startDt.nanosecond,
-    unix = startDt.hour * coeffs[Hours] + startDt.minute * coeffs[Minutes] +
-    startDt.second
-  )
-  var endTimepart = initTime(
-    nanosecond = endDt.nanosecond,
-    unix = endDt.hour * coeffs[Hours] + endDt.minute * coeffs[Minutes] +
-    endDt.second
-  )
-  # We wand timeParts for Seconds..Hours be positive, so we'll borrow one day
-  if endTimepart < startTimepart:
-    timeParts[Days] = -1
-
-  let diffTime = endTimepart - startTimepart
-  timeParts[Seconds] = diffTime.seconds.int()
-  #Nanoseconds - preliminary count
-  timeParts[Nanoseconds] = diffTime.nanoseconds
-  for unit in countdown(Milliseconds, Microseconds):
-    timeParts[unit] += timeParts[Nanoseconds] div coeffs[unit].int()
-    timeParts[Nanoseconds] -= timeParts[unit] * coeffs[unit].int()
-
-  #Counting Seconds .. Hours - final, Days - preliminary
-  for unit in countdown(Days, Minutes):
-    timeParts[unit] += timeParts[Seconds] div coeffs[unit].int()
-    # Here is accounted the borrowed day
-    timeParts[Seconds] -= timeParts[unit] * coeffs[unit].int()
-
-  # Set Nanoseconds .. Hours in result
-  result.nanoseconds = timeParts[Nanoseconds]
-  result.microseconds = timeParts[Microseconds]
-  result.milliseconds = timeParts[Milliseconds]
-  result.seconds = timeParts[Seconds]
-  result.minutes = timeParts[Minutes]
-  result.hours = timeParts[Hours]
-
-  #Days
-  if endDt.monthday.int + timeParts[Days] < startDt.monthday.int():
-    if endDt.month > 1.Month:
-      endDt.month -= 1.Month
-    else:
-      endDt.month = 12.Month
-      endDt.year -= 1
-    timeParts[Days] += endDt.monthday.int() + getDaysInMonth(
-      endDt.month, endDt.year) - startDt.monthday.int()
-  else:
-    timeParts[Days] += endDt.monthday.int() -
-      startDt.monthday.int()
-
-  result.days = timeParts[Days]
-
-  #Months
-  if endDt.month < startDt.month:
-      result.months = endDt.month.int() + 12 - startDt.month.int()
-      endDt.year -= 1
-  else:
-    result.months = endDt.month.int() -
-      startDt.month.int()
-
-  # Years
-  result.years = endDt.year - startDt.year
-
 proc `+`*(time: Time, interval: TimeInterval): Time =
   ## Adds `interval` to `time`.
   ## If `interval` contains any years, months, weeks or days the operation
@@ -1371,788 +2736,154 @@ proc `-`*(time: Time, interval: TimeInterval): Time =
   else:
     toTime(time.local - interval)
 
-proc `+=`*[T, U: TimesMutableTypes](a: var T, b: U) =
-  ## Modify ``a`` in place by adding ``b``.
-  runnableExamples:
-    var tm = fromUnix(0)
-    tm += initDuration(seconds = 1)
-    doAssert tm == fromUnix(1)
+proc `+=`*(a: var DateTime, b: TimeInterval) =
   a = a + b
 
-proc `-=`*[T, U: TimesMutableTypes](a: var T, b: U) =
-  ## Modify ``a`` in place by subtracting ``b``.
-  runnableExamples:
-    var tm = fromUnix(5)
-    tm -= initDuration(seconds = 5)
-    doAssert tm == fromUnix(0)
+proc `-=`*(a: var DateTime, b: TimeInterval) =
   a = a - b
 
-proc `*=`*[T: TimesMutableTypes, U](a: var T, b: U) =
-  # Mutable type is often multiplied by number
-  runnableExamples:
-    var dur = initDuration(seconds = 1)
-    dur *= 5
-    doAssert dur == initDuration(seconds = 5)
+proc `+=`*(t: var Time, b: TimeInterval) =
+  t = t + b
 
-  a = a * b
+proc `-=`*(t: var Time, b: TimeInterval) =
+  t = t - b
 
-proc formatToken(dt: DateTime, token: string, buf: var string) =
-  ## Helper of the format proc to parse individual tokens.
-  ##
-  ## Pass the found token in the user input string, and the buffer where the
-  ## final string is being built. This has to be a var value because certain
-  ## formatting tokens require modifying the previous characters.
-  case token
-  of "d":
-    buf.add($dt.monthday)
-  of "dd":
-    if dt.monthday < 10:
-      buf.add("0")
-    buf.add($dt.monthday)
-  of "ddd":
-    buf.add(($dt.weekday)[0 .. 2])
-  of "dddd":
-    buf.add($dt.weekday)
-  of "h":
-    if dt.hour == 0: buf.add("12")
-    else: buf.add($(if dt.hour > 12: dt.hour - 12 else: dt.hour))
-  of "hh":
-    if dt.hour == 0:
-      buf.add("12")
-    else:
-      let amerHour = if dt.hour > 12: dt.hour - 12 else: dt.hour
-      if amerHour < 10:
-        buf.add('0')
-      buf.add($amerHour)
-  of "H":
-    buf.add($dt.hour)
-  of "HH":
-    if dt.hour < 10:
-      buf.add('0')
-    buf.add($dt.hour)
-  of "m":
-    buf.add($dt.minute)
-  of "mm":
-    if dt.minute < 10:
-      buf.add('0')
-    buf.add($dt.minute)
-  of "M":
-    buf.add($ord(dt.month))
-  of "MM":
-    if dt.month < mOct:
-      buf.add('0')
-    buf.add($ord(dt.month))
-  of "MMM":
-    buf.add(($dt.month)[0..2])
-  of "MMMM":
-    buf.add($dt.month)
-  of "s":
-    buf.add($dt.second)
-  of "ss":
-    if dt.second < 10:
-      buf.add('0')
-    buf.add($dt.second)
-  of "t":
-    if dt.hour >= 12:
-      buf.add('P')
-    else: buf.add('A')
-  of "tt":
-    if dt.hour >= 12:
-      buf.add("PM")
-    else: buf.add("AM")
-  of "y":
-    var fr = ($dt.year).len()-1
-    if fr < 0: fr = 0
-    buf.add(($dt.year)[fr .. ($dt.year).len()-1])
-  of "yy":
-    var fr = ($dt.year).len()-2
-    if fr < 0: fr = 0
-    var fyear = ($dt.year)[fr .. ($dt.year).len()-1]
-    if fyear.len != 2: fyear = repeat('0', 2-fyear.len()) & fyear
-    buf.add(fyear)
-  of "yyy":
-    var fr = ($dt.year).len()-3
-    if fr < 0: fr = 0
-    var fyear = ($dt.year)[fr .. ($dt.year).len()-1]
-    if fyear.len != 3: fyear = repeat('0', 3-fyear.len()) & fyear
-    buf.add(fyear)
-  of "yyyy":
-    var fr = ($dt.year).len()-4
-    if fr < 0: fr = 0
-    var fyear = ($dt.year)[fr .. ($dt.year).len()-1]
-    if fyear.len != 4: fyear = repeat('0', 4-fyear.len()) & fyear
-    buf.add(fyear)
-  of "yyyyy":
-    var fr = ($dt.year).len()-5
-    if fr < 0: fr = 0
-    var fyear = ($dt.year)[fr .. ($dt.year).len()-1]
-    if fyear.len != 5: fyear = repeat('0', 5-fyear.len()) & fyear
-    buf.add(fyear)
-  of "z":
-    let
-      nonDstTz = dt.utcOffset
-      hours = abs(nonDstTz) div secondsInHour
-    if nonDstTz <= 0: buf.add('+')
-    else: buf.add('-')
-    buf.add($hours)
-  of "zz":
-    let
-      nonDstTz = dt.utcOffset
-      hours = abs(nonDstTz) div secondsInHour
-    if nonDstTz <= 0: buf.add('+')
-    else: buf.add('-')
-    if hours < 10: buf.add('0')
-    buf.add($hours)
-  of "zzz":
-    let
-      nonDstTz = dt.utcOffset
-      hours = abs(nonDstTz) div secondsInHour
-      minutes = (abs(nonDstTz) div secondsInMin) mod minutesInHour
-    if nonDstTz <= 0: buf.add('+')
-    else: buf.add('-')
-    if hours < 10: buf.add('0')
-    buf.add($hours)
-    buf.add(':')
-    if minutes < 10: buf.add('0')
-    buf.add($minutes)
-  of "fff":
-    buf.add(intToStr(convert(Nanoseconds, Milliseconds, dt.nanosecond), 3))
-  of "ffffff":
-    buf.add(intToStr(convert(Nanoseconds, Microseconds, dt.nanosecond), 6))
-  of "fffffffff":
-    buf.add(intToStr(dt.nanosecond, 9))
-  of "":
-    discard
-  else:
-    raise newException(ValueError, "Invalid format string: " & token)
+#
+# Iso week
+#
 
-proc format*(dt: DateTime, f: string): string {.tags: [].}=
-  ## This procedure formats `dt` as specified by `f`. The following format
-  ## specifiers are available:
-  ##
-  ## ============  =================================================================================  ================================================
-  ## Specifier     Description                                                                        Example
-  ## ============  =================================================================================  ================================================
-  ##    d          Numeric value of the day of the month, it will be one or two digits long.          ``1/04/2012 -> 1``, ``21/04/2012 -> 21``
-  ##    dd         Same as above, but always two digits.                                              ``1/04/2012 -> 01``, ``21/04/2012 -> 21``
-  ##    ddd        Three letter string which indicates the day of the week.                           ``Saturday -> Sat``, ``Monday -> Mon``
-  ##    dddd       Full string for the day of the week.                                               ``Saturday -> Saturday``, ``Monday -> Monday``
-  ##    h          The hours in one digit if possible. Ranging from 0-12.                             ``5pm -> 5``, ``2am -> 2``
-  ##    hh         The hours in two digits always. If the hour is one digit 0 is prepended.           ``5pm -> 05``, ``11am -> 11``
-  ##    H          The hours in one digit if possible, randing from 0-24.                             ``5pm -> 17``, ``2am -> 2``
-  ##    HH         The hours in two digits always. 0 is prepended if the hour is one digit.           ``5pm -> 17``, ``2am -> 02``
-  ##    m          The minutes in 1 digit if possible.                                                ``5:30 -> 30``, ``2:01 -> 1``
-  ##    mm         Same as above but always 2 digits, 0 is prepended if the minute is one digit.      ``5:30 -> 30``, ``2:01 -> 01``
-  ##    M          The month in one digit if possible.                                                ``September -> 9``, ``December -> 12``
-  ##    MM         The month in two digits always. 0 is prepended.                                    ``September -> 09``, ``December -> 12``
-  ##    MMM        Abbreviated three-letter form of the month.                                        ``September -> Sep``, ``December -> Dec``
-  ##    MMMM       Full month string, properly capitalized.                                           ``September -> September``
-  ##    s          Seconds as one digit if possible.                                                  ``00:00:06 -> 6``
-  ##    ss         Same as above but always two digits. 0 is prepended.                               ``00:00:06 -> 06``
-  ##    t          ``A`` when time is in the AM. ``P`` when time is in the PM.
-  ##    tt         Same as above, but ``AM`` and ``PM`` instead of ``A`` and ``P`` respectively.
-  ##    y(yyyy)    This displays the year to different digits. You most likely only want 2 or 4 'y's
-  ##    yy         Displays the year to two digits.                                                   ``2012 -> 12``
-  ##    yyyy       Displays the year to four digits.                                                  ``2012 -> 2012``
-  ##    z          Displays the timezone offset from UTC.                                             ``GMT+7 -> +7``, ``GMT-5 -> -5``
-  ##    zz         Same as above but with leading 0.                                                  ``GMT+7 -> +07``, ``GMT-5 -> -05``
-  ##    zzz        Same as above but with ``:mm`` where *mm* represents minutes.                      ``GMT+7 -> +07:00``, ``GMT-5 -> -05:00``
-  ##    fff        Milliseconds display                                                               ``1000000 nanoseconds -> 1``
-  ##    ffffff     Microseconds display                                                               ``1000000 nanoseconds -> 1000``
-  ##    fffffffff  Nanoseconds display                                                                ``1000000 nanoseconds -> 1000000``
-  ## ============  =================================================================================  ================================================
+proc initDateTime*(weekday: WeekDay, isoweek: IsoWeekRange, isoyear: IsoYear,
+                   hour: HourRange, minute: MinuteRange, second: SecondRange,
+                   nanosecond: NanosecondRange,
+                   zone: Timezone = local()): DateTime {.raises: [], tags: [], since: (1, 5).} =
+  ## Create a new `DateTime <#DateTime>`_ from a weekday and an ISO 8601 week number and year
+  ## in the specified timezone.
   ##
-  ## Other strings can be inserted by putting them in ``''``. For example
-  ## ``hh'->'mm`` will give ``01->56``.  The following characters can be
-  ## inserted without quoting them: ``:`` ``-`` ``(`` ``)`` ``/`` ``[`` ``]``
-  ## ``,``. However you don't need to necessarily separate format specifiers, a
-  ## unambiguous format string like ``yyyyMMddhhmmss`` is valid too.
-  runnableExamples:
-    let dt = initDateTime(01, mJan, 2000, 12, 00, 00, 01, utc())
-    doAssert format(dt, "yyyy-MM-dd'T'HH:mm:ss'.'fffffffffzzz") == "2000-01-01T12:00:00.000000001+00:00"
-
-  result = ""
-  var i = 0
-  var currentF = ""
-  while i < f.len:
-    case f[i]
-    of ' ', '-', '/', ':', '\'', '(', ')', '[', ']', ',':
-      formatToken(dt, currentF, result)
-
-      currentF = ""
-
-      if f[i] == '\'':
-        inc(i) # Skip '
-        while i < f.len-1 and f[i] != '\'':
-          result.add(f[i])
-          inc(i)
-      else: result.add(f[i])
-
-    else:
-      # Check if the letter being added matches previous accumulated buffer.
-      if currentF.len == 0 or currentF[high(currentF)] == f[i]:
-        currentF.add(f[i])
-      else:
-        formatToken(dt, currentF, result)
-        dec(i) # Move position back to re-process the character separately.
-        currentF = ""
-
-    inc(i)
-  formatToken(dt, currentF, result)
-
-proc format*(time: Time, f: string, zone: Timezone = local()): string {.tags: [].} =
-  ## Converts a `Time` value to a string representation. It will use format from
-  ## ``format(dt: DateTime, f: string)``.
+  ## .. warning:: The ISO week-based year can correspond to the following or previous year from 29 December to January 3.
   runnableExamples:
-    var dt = initDateTime(01, mJan, 1970, 00, 00, 00, utc())
-    var tm = dt.toTime()
-    doAssert format(tm, "yyyy-MM-dd'T'HH:mm:ss", utc()) == "1970-01-01T00:00:00"
-  time.inZone(zone).format(f)
+    assert initDateTime(21, mApr, 2018, 00, 00, 00) == initDateTime(dSat, 16, 2018.IsoYear, 00, 00, 00)
+    assert initDateTime(30, mDec, 2019, 00, 00, 00) == initDateTime(dMon, 01, 2020.IsoYear, 00, 00, 00)
+    assert initDateTime(13, mSep, 2020, 00, 00, 00) == initDateTime(dSun, 37, 2020.IsoYear, 00, 00, 00)
+    assert initDateTime(2, mJan, 2021, 00, 00, 00) == initDateTime(dSat, 53, 2020.IsoYear, 00, 00, 00)
 
-proc `$`*(dt: DateTime): string {.tags: [], raises: [], benign.} =
-  ## Converts a `DateTime` object to a string representation.
-  ## It uses the format ``yyyy-MM-dd'T'HH-mm-sszzz``.
-  runnableExamples:
-    let dt = initDateTime(01, mJan, 2000, 12, 00, 00, utc())
-    doAssert $dt == "2000-01-01T12:00:00+00:00"
-  try:
-    result = format(dt, "yyyy-MM-dd'T'HH:mm:sszzz") # todo: optimize this
-  except ValueError: assert false # cannot happen because format string is valid
+  # source https://webspace.science.uu.nl/~gent0113/calendar/isocalendar.htm
+  let d = isoweek * 7 + weekday.int - initDateTime(4, mJan, isoyear.int, 00, 00, 00, zone).weekday.int - 4
+  initDateTime(1, mJan, isoyear.int, hour, minute, second, nanosecond, zone) + initTimeInterval(days=d)
 
-proc `$`*(time: Time): string {.tags: [], raises: [], benign.} =
-  ## 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``.
-  runnableExamples:
-    let dt = initDateTime(01, mJan, 1970, 00, 00, 00, local())
-    let tm = dt.toTime()
-    doAssert $tm == "1970-01-01T00:00:00" & format(dt, "zzz")
-  $time.local
+proc initDateTime*(weekday: WeekDay, isoweek: IsoWeekRange, isoyear: IsoYear,
+                   hour: HourRange, minute: MinuteRange, second: SecondRange,
+                   zone: Timezone = local()): DateTime {.raises: [], tags: [], since: (1, 5).} =
+  initDateTime(weekday, isoweek, isoyear, hour, minute, second, 0, zone)
 
-{.pop.}
-
-proc parseToken(dt: var DateTime; token, value: string; j: var int) =
-  ## Helper of the parse proc to parse individual tokens.
-
-  # Overwrite system.`[]` to raise a ValueError on index out of bounds.
-  proc `[]`[T, U](s: string, x: HSlice[T, U]): string =
-    if x.a >= s.len or x.b >= s.len:
-      raise newException(ValueError, "Value is missing required tokens, got: " &
-                         s)
-    return system.`[]`(s, x)
-
-  var sv: int
-  case token
-  of "d":
-    var pd = parseInt(value[j..j+1], sv)
-    dt.monthday = sv
-    j += pd
-  of "dd":
-    dt.monthday = value[j..j+1].parseInt()
-    j += 2
-  of "ddd":
-    case value[j..j+2].toLowerAscii()
-    of "sun": dt.weekday = dSun
-    of "mon": dt.weekday = dMon
-    of "tue": dt.weekday = dTue
-    of "wed": dt.weekday = dWed
-    of "thu": dt.weekday = dThu
-    of "fri": dt.weekday = dFri
-    of "sat": dt.weekday = dSat
-    else:
-      raise newException(ValueError,
-        "Couldn't parse day of week (ddd), got: " & value[j..j+2])
-    j += 3
-  of "dddd":
-    if value.len >= j+6 and value[j..j+5].cmpIgnoreCase("sunday") == 0:
-      dt.weekday = dSun
-      j += 6
-    elif value.len >= j+6 and value[j..j+5].cmpIgnoreCase("monday") == 0:
-      dt.weekday = dMon
-      j += 6
-    elif value.len >= j+7 and value[j..j+6].cmpIgnoreCase("tuesday") == 0:
-      dt.weekday = dTue
-      j += 7
-    elif value.len >= j+9 and value[j..j+8].cmpIgnoreCase("wednesday") == 0:
-      dt.weekday = dWed
-      j += 9
-    elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("thursday") == 0:
-      dt.weekday = dThu
-      j += 8
-    elif value.len >= j+6 and value[j..j+5].cmpIgnoreCase("friday") == 0:
-      dt.weekday = dFri
-      j += 6
-    elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("saturday") == 0:
-      dt.weekday = dSat
-      j += 8
-    else:
-      raise newException(ValueError,
-        "Couldn't parse day of week (dddd), got: " & value)
-  of "h", "H":
-    var pd = parseInt(value[j..j+1], sv)
-    dt.hour = sv
-    j += pd
-  of "hh", "HH":
-    dt.hour = value[j..j+1].parseInt()
-    j += 2
-  of "m":
-    var pd = parseInt(value[j..j+1], sv)
-    dt.minute = sv
-    j += pd
-  of "mm":
-    dt.minute = value[j..j+1].parseInt()
-    j += 2
-  of "M":
-    var pd = parseInt(value[j..j+1], sv)
-    dt.month = sv.Month
-    j += pd
-  of "MM":
-    var month = value[j..j+1].parseInt()
-    j += 2
-    dt.month = month.Month
-  of "MMM":
-    case value[j..j+2].toLowerAscii():
-    of "jan": dt.month = mJan
-    of "feb": dt.month = mFeb
-    of "mar": dt.month = mMar
-    of "apr": dt.month = mApr
-    of "may": dt.month = mMay
-    of "jun": dt.month = mJun
-    of "jul": dt.month = mJul
-    of "aug": dt.month = mAug
-    of "sep": dt.month = mSep
-    of "oct": dt.month = mOct
-    of "nov": dt.month = mNov
-    of "dec": dt.month = mDec
-    else:
-      raise newException(ValueError,
-        "Couldn't parse month (MMM), got: " & value)
-    j += 3
-  of "MMMM":
-    if value.len >= j+7 and value[j..j+6].cmpIgnoreCase("january") == 0:
-      dt.month = mJan
-      j += 7
-    elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("february") == 0:
-      dt.month = mFeb
-      j += 8
-    elif value.len >= j+5 and value[j..j+4].cmpIgnoreCase("march") == 0:
-      dt.month = mMar
-      j += 5
-    elif value.len >= j+5 and value[j..j+4].cmpIgnoreCase("april") == 0:
-      dt.month = mApr
-      j += 5
-    elif value.len >= j+3 and value[j..j+2].cmpIgnoreCase("may") == 0:
-      dt.month = mMay
-      j += 3
-    elif value.len >= j+4 and value[j..j+3].cmpIgnoreCase("june") == 0:
-      dt.month = mJun
-      j += 4
-    elif value.len >= j+4 and value[j..j+3].cmpIgnoreCase("july") == 0:
-      dt.month = mJul
-      j += 4
-    elif value.len >= j+6 and value[j..j+5].cmpIgnoreCase("august") == 0:
-      dt.month = mAug
-      j += 6
-    elif value.len >= j+9 and value[j..j+8].cmpIgnoreCase("september") == 0:
-      dt.month = mSep
-      j += 9
-    elif value.len >= j+7 and value[j..j+6].cmpIgnoreCase("october") == 0:
-      dt.month = mOct
-      j += 7
-    elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("november") == 0:
-      dt.month = mNov
-      j += 8
-    elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("december") == 0:
-      dt.month = mDec
-      j += 8
-    else:
-      raise newException(ValueError,
-        "Couldn't parse month (MMMM), got: " & value)
-  of "s":
-    var pd = parseInt(value[j..j+1], sv)
-    dt.second = sv
-    j += pd
-  of "ss":
-    dt.second = value[j..j+1].parseInt()
-    j += 2
-  of "t":
-    if value[j] == 'A' and dt.hour == 12:
-      dt.hour = 0
-    elif value[j] == 'P' and dt.hour > 0 and dt.hour < 12:
-      dt.hour += 12
-    j += 1
-  of "tt":
-    if value[j..j+1] == "AM" and dt.hour == 12:
-      dt.hour = 0
-    elif value[j..j+1] == "PM" and dt.hour > 0 and dt.hour < 12:
-      dt.hour += 12
-    j += 2
-  of "yy":
-    # Assumes current century
-    var year = value[j..j+1].parseInt()
-    var thisCen = now().year div 100
-    dt.year = thisCen*100 + year
-    j += 2
-  of "yyyy":
-    dt.year = value[j..j+3].parseInt()
-    j += 4
-  of "z":
-    dt.isDst = false
-    let ch = if j < value.len: value[j] else: '\0'
-    if ch == '+':
-      dt.utcOffset = 0 - parseInt($value[j+1]) * secondsInHour
-    elif ch == '-':
-      dt.utcOffset = parseInt($value[j+1]) * secondsInHour
-    elif ch == 'Z':
-      dt.utcOffset = 0
-      j += 1
-      return
-    else:
-      raise newException(ValueError,
-        "Couldn't parse timezone offset (z), got: " & ch)
-    j += 2
-  of "zz":
-    dt.isDst = false
-    let ch = if j < value.len: value[j] else: '\0'
-    if ch == '+':
-      dt.utcOffset = 0 - value[j+1..j+2].parseInt() * secondsInHour
-    elif ch == '-':
-      dt.utcOffset = value[j+1..j+2].parseInt() * secondsInHour
-    elif ch == 'Z':
-      dt.utcOffset = 0
-      j += 1
-      return
-    else:
-      raise newException(ValueError,
-        "Couldn't parse timezone offset (zz), got: " & ch)
-    j += 3
-  of "zzz":
-    dt.isDst = false
-    var factor = 0
-    let ch = if j < value.len: value[j] else: '\0'
-    if ch == '+': factor = -1
-    elif ch == '-': factor = 1
-    elif ch == 'Z':
-      dt.utcOffset = 0
-      j += 1
-      return
-    else:
-      raise newException(ValueError,
-        "Couldn't parse timezone offset (zzz), got: " & ch)
-    dt.utcOffset = factor * value[j+1..j+2].parseInt() * secondsInHour
-    j += 4
-    dt.utcOffset += factor * value[j..j+1].parseInt() * 60
-    j += 2
-  of "fff", "ffffff", "fffffffff":
-    var numStr = ""
-    let n = parseWhile(value[j..len(value) - 1], numStr, {'0'..'9'})
-    dt.nanosecond = parseInt(numStr) * (10 ^ (9 - n))
-    j += n
-  else:
-    # Ignore the token and move forward in the value string by the same length
-    j += token.len
+#
+# Other
+#
 
-proc parse*(value, layout: string, zone: Timezone = local()): DateTime =
-  ## This procedure parses a date/time string using the standard format
-  ## identifiers as listed below. The procedure defaults information not provided
-  ## in the format string from the running program (month, year, etc).
-  ##
-  ## The return value will always be in the `zone` timezone. If no UTC offset was
-  ## parsed, then the input will be assumed to be specified in the `zone` timezone
-  ## already, so no timezone conversion will be done in that case.
+proc epochTime*(): float {.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).
   ##
-  ## =======================  =================================================================================  ================================================
-  ## Specifier                Description                                                                        Example
-  ## =======================  =================================================================================  ================================================
-  ##    d                     Numeric value of the day of the month, it will be one or two digits long.          ``1/04/2012 -> 1``, ``21/04/2012 -> 21``
-  ##    dd                    Same as above, but always two digits.                                              ``1/04/2012 -> 01``, ``21/04/2012 -> 21``
-  ##    ddd                   Three letter string which indicates the day of the week.                           ``Saturday -> Sat``, ``Monday -> Mon``
-  ##    dddd                  Full string for the day of the week.                                               ``Saturday -> Saturday``, ``Monday -> Monday``
-  ##    h                     The hours in one digit if possible. Ranging from 0-12.                             ``5pm -> 5``, ``2am -> 2``
-  ##    hh                    The hours in two digits always. If the hour is one digit 0 is prepended.           ``5pm -> 05``, ``11am -> 11``
-  ##    H                     The hours in one digit if possible, randing from 0-24.                             ``5pm -> 17``, ``2am -> 2``
-  ##    HH                    The hours in two digits always. 0 is prepended if the hour is one digit.           ``5pm -> 17``, ``2am -> 02``
-  ##    m                     The minutes in 1 digit if possible.                                                ``5:30 -> 30``, ``2:01 -> 1``
-  ##    mm                    Same as above but always 2 digits, 0 is prepended if the minute is one digit.      ``5:30 -> 30``, ``2:01 -> 01``
-  ##    M                     The month in one digit if possible.                                                ``September -> 9``, ``December -> 12``
-  ##    MM                    The month in two digits always. 0 is prepended.                                    ``September -> 09``, ``December -> 12``
-  ##    MMM                   Abbreviated three-letter form of the month.                                        ``September -> Sep``, ``December -> Dec``
-  ##    MMMM                  Full month string, properly capitalized.                                           ``September -> September``
-  ##    s                     Seconds as one digit if possible.                                                  ``00:00:06 -> 6``
-  ##    ss                    Same as above but always two digits. 0 is prepended.                               ``00:00:06 -> 06``
-  ##    t                     ``A`` when time is in the AM. ``P`` when time is in the PM.
-  ##    tt                    Same as above, but ``AM`` and ``PM`` instead of ``A`` and ``P`` respectively.
-  ##    yy                    Displays the year to two digits.                                                   ``2012 -> 12``
-  ##    yyyy                  Displays the year to four digits.                                                  ``2012 -> 2012``
-  ##    z                     Displays the timezone offset from UTC. ``Z`` is parsed as ``+0``                   ``GMT+7 -> +7``, ``GMT-5 -> -5``
-  ##    zz                    Same as above but with leading 0.                                                  ``GMT+7 -> +07``, ``GMT-5 -> -05``
-  ##    zzz                   Same as above but with ``:mm`` where *mm* represents minutes.                      ``GMT+7 -> +07:00``, ``GMT-5 -> -05:00``
-  ##    fff/ffffff/fffffffff  for consistency with format - nanoseconds                                          ``1 -> 1 nanosecond``
-  ## =======================  =================================================================================  ================================================
+  ## `getTime` should generally be preferred over this proc.
   ##
-  ## Other strings can be inserted by putting them in ``''``. For example
-  ## ``hh'->'mm`` will give ``01->56``.  The following characters can be
-  ## inserted without quoting them: ``:`` ``-`` ``(`` ``)`` ``/`` ``[`` ``]``
-  ## ``,``. However you don't need to necessarily separate format specifiers, a
-  ## unambiguous format string like ``yyyyMMddhhmmss`` is valid too.
-  runnableExamples:
-    let tStr = "1970-01-01T00:00:00.0+00:00"
-    doAssert parse(tStr, "yyyy-MM-dd'T'HH:mm:ss.fffzzz") == fromUnix(0).utc
-
-  var i = 0 # pointer for format string
-  var j = 0 # pointer for value string
-  var token = ""
-  # Assumes current day of month, month and year, but time is reset to 00:00:00. Weekday will be reset after parsing.
-  var dt = now()
-  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 i < layout.len:
-    case layout[i]
-    of ' ', '-', '/', ':', '\'', '(', ')', '[', ']', ',':
-      if token.len > 0:
-        parseToken(dt, token, value, j)
-      # Reset token
-      token = ""
-      # Skip separator and everything between single quotes
-      # These are literals in both the layout and the value string
-      if layout[i] == '\'':
-        inc(i)
-        while i < layout.len-1 and layout[i] != '\'':
-          inc(i)
-          inc(j)
-        inc(i)
-      else:
-        inc(i)
-        inc(j)
-    else:
-      # Check if the letter being added matches previous accumulated buffer.
-      if token.len == 0 or token[high(token)] == layout[i]:
-        token.add(layout[i])
-        inc(i)
-      else:
-        parseToken(dt, token, value, j)
-        token = ""
-
-  if i >= layout.len and token.len > 0:
-    parseToken(dt, token, value, j)
-  if dt.isDst:
-    # No timezone parsed - assume timezone is `zone`
-    result = initDateTime(zone.zoneInfoFromTz(dt.toAdjTime), zone)
+  ## .. warning:: Unsuitable for benchmarking (but still better than `now`),
+  ##    use `monotimes.getMonoTime` or `cpuTime` instead, depending on the use case.
+  when defined(js):
+    result = newDate().getTime() / 1000
+  elif defined(macosx):
+    var a {.noinit.}: Timeval
+    gettimeofday(a)
+    result = toBiggestFloat(a.tv_sec.int64) + toBiggestFloat(
+        a.tv_usec)*0.00_0001
+  elif defined(posix):
+    var ts {.noinit.}: Timespec
+    discard clock_gettime(CLOCK_REALTIME, ts)
+    result = toBiggestFloat(ts.tv_sec.int64) +
+      toBiggestFloat(ts.tv_nsec.int64) / 1_000_000_000
+  elif defined(windows):
+    var f {.noinit.}: winlean.FILETIME
+    getSystemTimeAsFileTime(f)
+    var i64 = rdFileTime(f) - epochDiff
+    var secs = i64 div rateDiff
+    var subsecs = i64 mod rateDiff
+    result = toFloat(int(secs)) + toFloat(int(subsecs)) * 0.0000001
   else:
-    # Otherwise convert to `zone`
-    result = dt.toTime.inZone(zone)
-
-proc parseTime*(value, layout: string, zone: Timezone): Time =
-  ## Simple wrapper for parsing string to time
-  runnableExamples:
-    let tStr = "1970-01-01T00:00:00+00:00"
-    doAssert parseTime(tStr, "yyyy-MM-dd'T'HH:mm:sszzz", local()) == fromUnix(0)
-  parse(value, layout, zone).toTime()
-
-proc countLeapYears*(yearSpan: int): int =
-  ## Returns the number of leap years spanned by a given number of years.
-  ##
-  ## **Note:** For leap years, start date is assumed to be 1 AD.
-  ## counts the number of leap years up to January 1st of a given year.
-  ## Keep in mind that if specified year is a leap year, the leap day
-  ## has not happened before January 1st of that year.
-  (yearSpan - 1) div 4 - (yearSpan - 1) div 100 + (yearSpan - 1) div 400
-
-proc countDays*(yearSpan: int): int =
-  ## Returns the number of days spanned by a given number of years.
-  (yearSpan - 1) * 365 + countLeapYears(yearSpan)
-
-proc countYears*(daySpan: int): int =
-  ## Returns the number of years spanned by a given number of days.
-  ((daySpan - countLeapYears(daySpan div 365)) div 365)
-
-proc countYearsAndDays*(daySpan: int): tuple[years: int, days: int] =
-  ## Returns the number of years spanned by a given number of days and the
-  ## remainder as days.
-  let days = daySpan - countLeapYears(daySpan div 365)
-  result.years = days div 365
-  result.days = days mod 365
-
-proc toTimeInterval*(time: Time): TimeInterval =
-  ## Converts a Time to a TimeInterval.
-  ##
-  ## To be used when diffing times. Consider using `between` instead.
-  runnableExamples:
-    let a = fromUnix(10)
-    let b = fromUnix(1_500_000_000)
-    let ti = b.toTimeInterval() - a.toTimeInterval()
-    doAssert a + ti == b
-  var dt = time.local
-  initTimeInterval(dt.nanosecond, 0, 0, dt.second, dt.minute, dt.hour,
-    dt.monthday, 0, dt.month.ord - 1, dt.year)
+    {.error: "unknown OS".}
 
-when not defined(JS):
+when not defined(js):
   type
     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], used, sideEffect.}
 
   var
-    clocksPerSec {.importc: "CLOCKS_PER_SEC", nodecl.}: int
-
-  when not defined(useNimRtl):
-    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:
-      runnableExamples:
-        var t0 = cpuTime()
-        # some useless work here (calculate fibonacci)
-        var fib = @[0, 1, 1]
-        for i in 1..10:
-          fib.add(fib[^1] + fib[^2])
-        echo "CPU time [s] ", cpuTime() - t0
-        echo "Fib is [s] ", fib
+    clocksPerSec {.importc: "CLOCKS_PER_SEC", nodecl, used.}: int
+
+  proc cpuTime*(): float {.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:
+    runnableExamples:
+      var t0 = cpuTime()
+      # some useless work here (calculate fibonacci)
+      var fib = @[0, 1, 1]
+      for i in 1..10:
+        fib.add(fib[^1] + fib[^2])
+      echo "CPU time [s] ", cpuTime() - t0
+      echo "Fib is [s] ", fib
+    ## When the flag `--benchmarkVM` is passed to the compiler, this proc is
+    ## also available at compile time
+    when defined(posix) and not defined(osx) and declared(CLOCK_THREAD_CPUTIME_ID):
+      # 'clocksPerSec' is a compile-time constant, possibly a
+      # rather awful one, so use clock_gettime instead
+      var ts: Timespec
+      discard clock_gettime(CLOCK_THREAD_CPUTIME_ID, ts)
+      result = toFloat(ts.tv_sec.int) +
+        toFloat(ts.tv_nsec.int) / 1_000_000_000
+    else:
       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
-        gettimeofday(a)
-        result = toBiggestFloat(a.tv_sec.int64) + toFloat(a.tv_usec)*0.00_0001
-      elif defined(windows):
-        var f: winlean.FILETIME
-        getSystemTimeAsFileTime(f)
-        var i64 = rdFileTime(f) - epochDiff
-        var secs = i64 div rateDiff
-        var subsecs = i64 mod rateDiff
-        result = toFloat(int(secs)) + toFloat(int(subsecs)) * 0.0000001
-      else:
-        {.error: "unknown OS".}
 
-when defined(JS):
-  proc epochTime*(): float {.tags: [TimeEffect].} =
-    newDate().getTime() / 1000
+#
+# Deprecations
+#
 
-# Deprecated procs
+proc `nanosecond=`*(dt: var DateTime, value: NanosecondRange) {.deprecated: "Deprecated since v1.3.1".} =
+  dt.nanosecond = value
 
-when not defined(JS):
-  proc unixTimeToWinTime*(time: CTime): int64 {.deprecated: "Use toWinTime instead".} =
-    ## Converts a UNIX `Time` (``time_t``) to a Windows file time
-    ##
-    ## **Deprecated:** use ``toWinTime`` instead.
-    result = int64(time) * rateDiff + epochDiff
+proc `second=`*(dt: var DateTime, value: SecondRange) {.deprecated: "Deprecated since v1.3.1".} =
+  dt.second = value
 
-  proc winTimeToUnixTime*(time: int64): CTime {.deprecated: "Use fromWinTime instead".} =
-    ## Converts a Windows time to a UNIX `Time` (``time_t``)
-    ##
-    ## **Deprecated:** use ``fromWinTime`` instead.
-    result = CTime((time - epochDiff) div rateDiff)
+proc `minute=`*(dt: var DateTime, value: MinuteRange) {.deprecated: "Deprecated since v1.3.1".} =
+  dt.minute = value
 
-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 `hour=`*(dt: var DateTime, value: HourRange) {.deprecated: "Deprecated since v1.3.1".} =
+  dt.hour = value
 
-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
-  let nanos = ((since1970 - since1970.int64.float) * convert(Seconds, Nanoseconds, 1).float).int
-  initTime(since1970.int64, nanos)
+proc `monthdayZero=`*(dt: var DateTime, value: int) {.deprecated: "Deprecated since v1.3.1".} =
+  dt.monthdayZero = value
 
-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
-  fromUnix(since1970)
+proc `monthZero=`*(dt: var DateTime, value: int) {.deprecated: "Deprecated since v1.3.1".} =
+  dt.monthZero = value
 
-proc toSeconds*(time: Time): float {.tags: [], raises: [], benign, deprecated.} =
-  ## Returns the time in seconds since the unix epoch.
-  ##
-  ## **Deprecated since v0.18.0:** use ``fromUnix`` instead
-  time.seconds.float + time.nanosecond / convert(Seconds, Nanoseconds, 1)
+proc `year=`*(dt: var DateTime, value: int) {.deprecated: "Deprecated since v1.3.1".} =
+  dt.year = value
 
-proc getLocalTime*(time: Time): DateTime {.tags: [], raises: [], benign, deprecated.} =
-  ## Converts the calendar time `time` to broken-time representation,
-  ## expressed relative to the user's specified time zone.
-  ##
-  ## **Deprecated since v0.18.0:** use ``local`` instead
-  time.local
+proc `weekday=`*(dt: var DateTime, value: WeekDay) {.deprecated: "Deprecated since v1.3.1".} =
+  dt.weekday = value
 
-proc getGMTime*(time: Time): DateTime {.tags: [], raises: [], benign, deprecated.} =
-  ## Converts the calendar time `time` to broken-down time representation,
-  ## expressed in Coordinated Universal Time (UTC).
-  ##
-  ## **Deprecated since v0.18.0:** use ``utc`` instead
-  time.utc
+proc `yearday=`*(dt: var DateTime, value: YeardayRange) {.deprecated: "Deprecated since v1.3.1".} =
+  dt.yearday = value
 
-proc getTimezone*(): int {.tags: [TimeEffect], raises: [], benign, deprecated.} =
-  ## 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
-  ## utc offset (including DST).
-  when defined(JS):
-    return newDate().getTimezoneOffset() * 60
-  elif defined(freebsd) or defined(netbsd) or defined(openbsd):
-    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
-    return -(lt.gmtoff)
-  else:
-    return timezone
+proc `isDst=`*(dt: var DateTime, value: bool) {.deprecated: "Deprecated since v1.3.1".} =
+  dt.isDst = value
 
-proc timeInfoToTime*(dt: DateTime): Time {.tags: [], benign, deprecated.} =
-  ## Converts a broken-down time structure to calendar time representation.
-  ##
-  ## **Deprecated since v0.14.0:** use ``toTime`` instead.
-  dt.toTime
-
-when defined(JS):
-  var start = getTime()
-  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.
-    let dur = getTime() - start
-    result = (convert(Seconds, Milliseconds, dur.seconds) +
-      convert(Nanoseconds, Milliseconds, dur.nanosecond)).int
-else:
-  proc getStartMilsecs*(): int {.deprecated, tags: [TimeEffect], benign.} =
-    when defined(macosx):
-      result = toInt(toFloat(int(getClock())) / (toFloat(clocksPerSec) / 1000.0))
-    else:
-      result = int(getClock()) div (clocksPerSec div 1000)
+proc `timezone=`*(dt: var DateTime, value: Timezone) {.deprecated: "Deprecated since v1.3.1".} =
+  dt.timezone = value
 
-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
-  ## ``getDayOfWeek(monthday: MonthdayRange; month: Month; year: int)`` instead.
-  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.
-  ## **Deprecated since v0.18.0:**
-  # Day & month start from one.
-  let
-    a = (14 - month) div 12
-    y = year - a
-    m = month + (12*a) - 2
-    d = (5 + day + y + (y div 4) + (31*m) div 12) mod 7
-  result = d.WeekDay
+proc `utcOffset=`*(dt: var DateTime, value: int) {.deprecated: "Deprecated since v1.3.1".} =
+  dt.utcOffset = value