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