summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--lib/posix/posix.nim3
-rw-r--r--lib/posix/posix_linux_amd64.nim6
-rw-r--r--lib/posix/posix_other.nim36
-rw-r--r--lib/pure/cookies.nim6
-rw-r--r--lib/pure/ioselects/ioselectors_epoll.nim7
-rw-r--r--lib/pure/ioselects/ioselectors_kqueue.nim4
-rw-r--r--lib/pure/oids.nim2
-rw-r--r--lib/pure/os.nim35
-rw-r--r--lib/pure/osproc.nim16
-rw-r--r--lib/pure/random.nim8
-rw-r--r--lib/pure/times.nim1531
-rw-r--r--tests/js/ttimes.nim53
-rw-r--r--tests/stdlib/ttimes.nim366
13 files changed, 1106 insertions, 967 deletions
diff --git a/lib/posix/posix.nim b/lib/posix/posix.nim
index b635c0b0b..fba35868c 100644
--- a/lib/posix/posix.nim
+++ b/lib/posix/posix.nim
@@ -609,11 +609,12 @@ proc clock_nanosleep*(a1: ClockId, a2: cint, a3: var Timespec,
 proc clock_settime*(a1: ClockId, a2: var Timespec): cint {.
   importc, header: "<time.h>".}
 
+proc `==`*(a, b: Time): bool {.borrow.}
+proc `-`*(a, b: Time): Time {.borrow.}
 proc ctime*(a1: var Time): cstring {.importc, header: "<time.h>".}
 proc ctime_r*(a1: var Time, a2: cstring): cstring {.importc, header: "<time.h>".}
 proc difftime*(a1, a2: Time): cdouble {.importc, header: "<time.h>".}
 proc getdate*(a1: cstring): ptr Tm {.importc, header: "<time.h>".}
-
 proc gmtime*(a1: var Time): ptr Tm {.importc, header: "<time.h>".}
 proc gmtime_r*(a1: var Time, a2: var Tm): ptr Tm {.importc, header: "<time.h>".}
 proc localtime*(a1: var Time): ptr Tm {.importc, header: "<time.h>".}
diff --git a/lib/posix/posix_linux_amd64.nim b/lib/posix/posix_linux_amd64.nim
index c44128b16..9e6211b63 100644
--- a/lib/posix/posix_linux_amd64.nim
+++ b/lib/posix/posix_linux_amd64.nim
@@ -12,8 +12,6 @@
 
 # To be included from posix.nim!
 
-from times import Time
-
 const
   hasSpawnH = not defined(haiku) # should exist for every Posix system nowadays
   hasAioH = defined(linux)
@@ -40,13 +38,15 @@ type
 const SIG_HOLD* = cast[SigHandler](2)
 
 type
+  Time* {.importc: "time_t", header: "<time.h>".} = distinct clong
+
   Timespec* {.importc: "struct timespec",
                header: "<time.h>", final, pure.} = object ## struct timespec
     tv_sec*: Time  ## Seconds.
     tv_nsec*: clong  ## Nanoseconds.
 
   Dirent* {.importc: "struct dirent",
-             header: "<dirent.h>", final, pure.} = object ## dirent_t struct
+            header: "<dirent.h>", final, pure.} = object ## dirent_t struct
     d_ino*: Ino
     d_off*: Off
     d_reclen*: cushort
diff --git a/lib/posix/posix_other.nim b/lib/posix/posix_other.nim
index 7321889a8..e552bf807 100644
--- a/lib/posix/posix_other.nim
+++ b/lib/posix/posix_other.nim
@@ -9,8 +9,6 @@
 
 {.deadCodeElim:on.}
 
-from times import Time
-
 const
   hasSpawnH = not defined(haiku) # should exist for every Posix system nowadays
   hasAioH = defined(linux)
@@ -36,6 +34,8 @@ type
 {.deprecated: [TSocketHandle: SocketHandle].}
 
 type
+  Time* {.importc: "time_t", header: "<time.h>".} = distinct int
+
   Timespec* {.importc: "struct timespec",
                header: "<time.h>", final, pure.} = object ## struct timespec
     tv_sec*: Time  ## Seconds.
@@ -209,24 +209,24 @@ type
     st_gid*: Gid          ## Group ID of file.
     st_rdev*: Dev         ## Device ID (if file is character or block special).
     st_size*: Off         ## For regular files, the file size in bytes.
-                           ## For symbolic links, the length in bytes of the
-                           ## pathname contained in the symbolic link.
-                           ## For a shared memory object, the length in bytes.
-                           ## For a typed memory object, the length in bytes.
-                           ## For other file types, the use of this field is
-                           ## unspecified.
+                          ## For symbolic links, the length in bytes of the
+                          ## pathname contained in the symbolic link.
+                          ## For a shared memory object, the length in bytes.
+                          ## For a typed memory object, the length in bytes.
+                          ## For other file types, the use of this field is
+                          ## unspecified.
     when defined(macosx) or defined(android):
-      st_atime*: Time      ## Time of last access.
-      st_mtime*: Time      ## Time of last data modification.
-      st_ctime*: Time      ## Time of last status change.
+      st_atime*: Time     ## Time of last access.
+      st_mtime*: Time     ## Time of last data modification.
+      st_ctime*: Time     ## Time of last status change.
     else:
-      st_atim*: Timespec   ## Time of last access.
-      st_mtim*: Timespec   ## Time of last data modification.
-      st_ctim*: Timespec   ## Time of last status change.
-    st_blksize*: Blksize   ## A file system-specific preferred I/O block size
-                           ## for this object. In some file system types, this
-                           ## may vary from file to file.
-    st_blocks*: Blkcnt     ## Number of blocks allocated for this object.
+      st_atim*: Timespec  ## Time of last access.
+      st_mtim*: Timespec  ## Time of last data modification.
+      st_ctim*: Timespec  ## Time of last status change.
+    st_blksize*: Blksize  ## A file system-specific preferred I/O block size
+                          ## for this object. In some file system types, this
+                          ## may vary from file to file.
+    st_blocks*: Blkcnt    ## Number of blocks allocated for this object.
 
 
   Statvfs* {.importc: "struct statvfs", header: "<sys/statvfs.h>",
diff --git a/lib/pure/cookies.nim b/lib/pure/cookies.nim
index 07b37c7d4..8f16717ac 100644
--- a/lib/pure/cookies.nim
+++ b/lib/pure/cookies.nim
@@ -51,7 +51,7 @@ proc setCookie*(key, value: string, domain = "", path = "",
   if secure: result.add("; Secure")
   if httpOnly: result.add("; HttpOnly")
 
-proc setCookie*(key, value: string, expires: TimeInfo,
+proc setCookie*(key, value: string, expires: DateTime,
                 domain = "", path = "", noName = false,
                 secure = false, httpOnly = false): string =
   ## Creates a command in the format of
@@ -63,9 +63,9 @@ proc setCookie*(key, value: string, expires: TimeInfo,
                    noname, secure, httpOnly)
 
 when isMainModule:
-  var tim = Time(int(getTime()) + 76 * (60 * 60 * 24))
+  var tim = fromUnix(getTime().toUnix + 76 * (60 * 60 * 24))
 
-  let cookie = setCookie("test", "value", tim.getGMTime())
+  let cookie = setCookie("test", "value", tim.utc)
   when not defined(testing):
     echo cookie
   let start = "Set-Cookie: test=value; Expires="
diff --git a/lib/pure/ioselects/ioselectors_epoll.nim b/lib/pure/ioselects/ioselectors_epoll.nim
index 35cdace09..8827f239f 100644
--- a/lib/pure/ioselects/ioselectors_epoll.nim
+++ b/lib/pure/ioselects/ioselectors_epoll.nim
@@ -277,15 +277,16 @@ proc registerTimer*[T](s: Selector[T], timeout: int, oneshot: bool,
   var events = {Event.Timer}
   var epv = EpollEvent(events: EPOLLIN or EPOLLRDHUP)
   epv.data.u64 = fdi.uint
+
   if oneshot:
-    new_ts.it_interval.tv_sec = 0.Time
+    new_ts.it_interval.tv_sec = posix.Time(0)
     new_ts.it_interval.tv_nsec = 0
-    new_ts.it_value.tv_sec = (timeout div 1_000).Time
+    new_ts.it_value.tv_sec = posix.Time(timeout div 1_000)
     new_ts.it_value.tv_nsec = (timeout %% 1_000) * 1_000_000
     incl(events, Event.Oneshot)
     epv.events = epv.events or EPOLLONESHOT
   else:
-    new_ts.it_interval.tv_sec = (timeout div 1000).Time
+    new_ts.it_interval.tv_sec = posix.Time(timeout div 1000)
     new_ts.it_interval.tv_nsec = (timeout %% 1_000) * 1_000_000
     new_ts.it_value.tv_sec = new_ts.it_interval.tv_sec
     new_ts.it_value.tv_nsec = new_ts.it_interval.tv_nsec
diff --git a/lib/pure/ioselects/ioselectors_kqueue.nim b/lib/pure/ioselects/ioselectors_kqueue.nim
index 3e2ec64a8..af5aa15df 100644
--- a/lib/pure/ioselects/ioselectors_kqueue.nim
+++ b/lib/pure/ioselects/ioselectors_kqueue.nim
@@ -452,10 +452,10 @@ proc selectInto*[T](s: Selector[T], timeout: int,
 
   if timeout != -1:
     if timeout >= 1000:
-      tv.tv_sec = (timeout div 1_000).Time
+      tv.tv_sec = posix.Time(timeout div 1_000)
       tv.tv_nsec = (timeout %% 1_000) * 1_000_000
     else:
-      tv.tv_sec = 0.Time
+      tv.tv_sec = posix.Time(0)
       tv.tv_nsec = timeout * 1_000_000
   else:
     ptv = nil
diff --git a/lib/pure/oids.nim b/lib/pure/oids.nim
index 60b53dbe0..427a68964 100644
--- a/lib/pure/oids.nim
+++ b/lib/pure/oids.nim
@@ -88,7 +88,7 @@ proc generatedTime*(oid: Oid): Time =
   var tmp: int32
   var dummy = oid.time
   bigEndian32(addr(tmp), addr(dummy))
-  result = Time(tmp)
+  result = fromUnix(tmp)
 
 when not defined(testing) and isMainModule:
   let xo = genOid()
diff --git a/lib/pure/os.nim b/lib/pure/os.nim
index a59134007..87f6def29 100644
--- a/lib/pure/os.nim
+++ b/lib/pure/os.nim
@@ -173,33 +173,33 @@ proc findExe*(exe: string, followSymlinks: bool = true;
         return x
   result = ""
 
-proc getLastModificationTime*(file: string): Time {.rtl, extern: "nos$1".} =
+proc getLastModificationTime*(file: string): times.Time {.rtl, extern: "nos$1".} =
   ## Returns the `file`'s last modification time.
   when defined(posix):
     var res: Stat
     if stat(file, res) < 0'i32: raiseOSError(osLastError())
-    return res.st_mtime
+    return fromUnix(res.st_mtime.int64)
   else:
     var f: WIN32_FIND_DATA
     var h = findFirstFile(file, f)
     if h == -1'i32: raiseOSError(osLastError())
-    result = winTimeToUnixTime(rdFileTime(f.ftLastWriteTime))
+    result = fromUnix(winTimeToUnixTime(rdFileTime(f.ftLastWriteTime)).int64)
     findClose(h)
 
-proc getLastAccessTime*(file: string): Time {.rtl, extern: "nos$1".} =
+proc getLastAccessTime*(file: string): times.Time {.rtl, extern: "nos$1".} =
   ## Returns the `file`'s last read or write access time.
   when defined(posix):
     var res: Stat
     if stat(file, res) < 0'i32: raiseOSError(osLastError())
-    return res.st_atime
+    return fromUnix(res.st_atime.int64)
   else:
     var f: WIN32_FIND_DATA
     var h = findFirstFile(file, f)
     if h == -1'i32: raiseOSError(osLastError())
-    result = winTimeToUnixTime(rdFileTime(f.ftLastAccessTime))
+    result = fromUnix(winTimeToUnixTime(rdFileTime(f.ftLastAccessTime)).int64)
     findClose(h)
 
-proc getCreationTime*(file: string): Time {.rtl, extern: "nos$1".} =
+proc getCreationTime*(file: string): times.Time {.rtl, extern: "nos$1".} =
   ## Returns the `file`'s creation time.
   ##
   ## **Note:** Under POSIX OS's, the returned time may actually be the time at
@@ -208,12 +208,12 @@ proc getCreationTime*(file: string): Time {.rtl, extern: "nos$1".} =
   when defined(posix):
     var res: Stat
     if stat(file, res) < 0'i32: raiseOSError(osLastError())
-    return res.st_ctime
+    return fromUnix(res.st_ctime.int64)
   else:
     var f: WIN32_FIND_DATA
     var h = findFirstFile(file, f)
     if h == -1'i32: raiseOSError(osLastError())
-    result = winTimeToUnixTime(rdFileTime(f.ftCreationTime))
+    result = fromUnix(winTimeToUnixTime(rdFileTime(f.ftCreationTime)).int64)
     findClose(h)
 
 proc fileNewer*(a, b: string): bool {.rtl, extern: "nos$1".} =
@@ -1443,7 +1443,7 @@ proc sleep*(milsecs: int) {.rtl, extern: "nos$1", tags: [TimeEffect].} =
     winlean.sleep(int32(milsecs))
   else:
     var a, b: Timespec
-    a.tv_sec = Time(milsecs div 1000)
+    a.tv_sec = posix.Time(milsecs div 1000)
     a.tv_nsec = (milsecs mod 1000) * 1000 * 1000
     discard posix.nanosleep(a, b)
 
@@ -1481,16 +1481,17 @@ type
     size*: BiggestInt # Size of file.
     permissions*: set[FilePermission] # File permissions
     linkCount*: BiggestInt # Number of hard links the file object has.
-    lastAccessTime*: Time # Time file was last accessed.
-    lastWriteTime*: Time # Time file was last modified/written to.
-    creationTime*: Time # Time file was created. Not supported on all systems!
+    lastAccessTime*: times.Time # Time file was last accessed.
+    lastWriteTime*: times.Time # Time file was last modified/written to.
+    creationTime*: times.Time # Time file was created. Not supported on all systems!
 
 template rawToFormalFileInfo(rawInfo, path, formalInfo): untyped =
   ## Transforms the native file info structure into the one nim uses.
   ## 'rawInfo' is either a 'TBY_HANDLE_FILE_INFORMATION' structure on Windows,
   ## or a 'Stat' structure on posix
   when defined(Windows):
-    template toTime(e: FILETIME): untyped {.gensym.} = winTimeToUnixTime(rdFileTime(e)) # local templates default to bind semantics
+    template toTime(e: FILETIME): untyped {.gensym.} =
+      fromUnix(winTimeToUnixTime(rdFileTime(e)).int64) # local templates default to bind semantics
     template merge(a, b): untyped = a or (b shl 32)
     formalInfo.id.device = rawInfo.dwVolumeSerialNumber
     formalInfo.id.file = merge(rawInfo.nFileIndexLow, rawInfo.nFileIndexHigh)
@@ -1522,9 +1523,9 @@ template rawToFormalFileInfo(rawInfo, path, formalInfo): untyped =
     formalInfo.id = (rawInfo.st_dev, rawInfo.st_ino)
     formalInfo.size = rawInfo.st_size
     formalInfo.linkCount = rawInfo.st_Nlink.BiggestInt
-    formalInfo.lastAccessTime = rawInfo.st_atime
-    formalInfo.lastWriteTime = rawInfo.st_mtime
-    formalInfo.creationTime = rawInfo.st_ctime
+    formalInfo.lastAccessTime = fromUnix(rawInfo.st_atime.int64)
+    formalInfo.lastWriteTime = fromUnix(rawInfo.st_mtime.int64)
+    formalInfo.creationTime = fromUnix(rawInfo.st_ctime.int64)
 
     result.permissions = {}
     checkAndIncludeMode(S_IRUSR, fpUserRead)
diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim
index f0542ea98..1625845d1 100644
--- a/lib/pure/osproc.nim
+++ b/lib/pure/osproc.nim
@@ -1060,10 +1060,10 @@ elif not defined(useNimRtl):
         var tmspec: Timespec
 
         if timeout >= 1000:
-          tmspec.tv_sec = (timeout div 1_000).Time
+          tmspec.tv_sec = posix.Time(timeout div 1_000)
           tmspec.tv_nsec = (timeout %% 1_000) * 1_000_000
         else:
-          tmspec.tv_sec = 0.Time
+          tmspec.tv_sec = posix.Time(0)
           tmspec.tv_nsec = (timeout * 1_000_000)
 
         try:
@@ -1109,20 +1109,20 @@ elif not defined(useNimRtl):
         var b: Timespec
         b.tv_sec = e.tv_sec
         b.tv_nsec = e.tv_nsec
-        e.tv_sec = (e.tv_sec - s.tv_sec).Time
+        e.tv_sec = e.tv_sec - s.tv_sec
         if e.tv_nsec >= s.tv_nsec:
           e.tv_nsec -= s.tv_nsec
         else:
-          if e.tv_sec == 0.Time:
+          if e.tv_sec == posix.Time(0):
             raise newException(ValueError, "System time was modified")
           else:
             diff = s.tv_nsec - e.tv_nsec
             e.tv_nsec = 1_000_000_000 - diff
-        t.tv_sec = (t.tv_sec - e.tv_sec).Time
+        t.tv_sec = t.tv_sec - e.tv_sec
         if t.tv_nsec >= e.tv_nsec:
           t.tv_nsec -= e.tv_nsec
         else:
-          t.tv_sec = (int(t.tv_sec) - 1).Time
+          t.tv_sec = t.tv_sec - posix.Time(1)
           diff = e.tv_nsec - t.tv_nsec
           t.tv_nsec = 1_000_000_000 - diff
         s.tv_sec = b.tv_sec
@@ -1154,10 +1154,10 @@ elif not defined(useNimRtl):
             raiseOSError(osLastError())
 
         if timeout >= 1000:
-          tmspec.tv_sec = (timeout div 1_000).Time
+          tmspec.tv_sec = posix.Time(timeout div 1_000)
           tmspec.tv_nsec = (timeout %% 1_000) * 1_000_000
         else:
-          tmspec.tv_sec = 0.Time
+          tmspec.tv_sec = posix.Time(0)
           tmspec.tv_nsec = (timeout * 1_000_000)
 
         try:
diff --git a/lib/pure/random.nim b/lib/pure/random.nim
index 7edd93c08..de419b9fb 100644
--- a/lib/pure/random.nim
+++ b/lib/pure/random.nim
@@ -190,12 +190,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.
-    when defined(JS):
-      proc getMil(t: Time): int {.importcpp: "getTime", nodecl.}
-      randomize(getMil times.getTime())
-    else:
-      let time = int64(times.epochTime() * 1_000_000_000)
-      randomize(time)
+    let time = int64(times.epochTime() * 1_000_000_000)
+    randomize(time)
 
 {.pop.}
 
diff --git a/lib/pure/times.nim b/lib/pure/times.nim
index c1d6c3e53..dcc817b7b 100644
--- a/lib/pure/times.nim
+++ b/lib/pure/times.nim
@@ -10,27 +10,26 @@
 
 ## This module contains routines and types for dealing with time.
 ## This module is available for the `JavaScript target
-## <backends.html#the-javascript-target>`_.
+## <backends.html#the-javascript-target>`_. The proleptic Gregorian calendar is the only calendar supported.
 ##
 ## Examples:
 ##
 ## .. code-block:: nim
 ##
 ##  import times, os
-##  var
-##    t = cpuTime()
+##  let time = cpuTime()
 ##
 ##  sleep(100)   # replace this with something to be timed
-##  echo "Time taken: ",cpuTime() - t
+##  echo "Time taken: ",cpuTime() - time
 ##
-##  echo "My formatted time: ", format(getLocalTime(getTime()), "d MMMM yyyy HH:mm")
+##  echo "My formatted time: ", format(now(), "d MMMM yyyy HH:mm")
 ##  echo "Using predefined formats: ", getClockStr(), " ", getDateStr()
 ##
 ##  echo "epochTime() float value: ", epochTime()
 ##  echo "getTime()   float value: ", toSeconds(getTime())
 ##  echo "cpuTime()   float value: ", cpuTime()
-##  echo "An hour from now      : ", getLocalTime(getTime()) + 1.hours
-##  echo "An hour from (UTC) now: ", getGmTime(getTime()) + initInterval(0,0,0,1)
+##  echo "An hour from now      : ", now() + 1.hours
+##  echo "An hour from (UTC) now: ", getTime().utc + initInterval(0,0,0,1)
 
 {.push debugger:off.} # the user does not want to trace a part
                       # of the standard library!
@@ -40,132 +39,85 @@ import
 
 include "system/inclrtl"
 
-type
-  Month* = enum ## represents a month
-    mJan, mFeb, mMar, mApr, mMay, mJun, mJul, mAug, mSep, mOct, mNov, mDec
-  WeekDay* = enum ## represents a weekday
-    dMon, dTue, dWed, dThu, dFri, dSat, dSun
-
-when defined(posix) and not defined(JS):
-  when defined(linux) and defined(amd64):
-    type
-      TimeImpl {.importc: "time_t", header: "<time.h>".} = clong
-      Time* = distinct TimeImpl ## distinct type that represents a time
-                                ## measured as number of seconds since the epoch
-
-      Timeval {.importc: "struct timeval",
-                header: "<sys/select.h>".} = object ## struct timeval
-        tv_sec: clong  ## Seconds.
-        tv_usec: clong ## Microseconds.
-  else:
-    type
-      TimeImpl {.importc: "time_t", header: "<time.h>".} = int
-      Time* = distinct TimeImpl ## distinct type that represents a time
-                                ## measured as number of seconds since the epoch
-
-      Timeval {.importc: "struct timeval",
-                header: "<sys/select.h>".} = object ## struct timeval
-        tv_sec: int  ## Seconds.
-        tv_usec: int ## Microseconds.
+when defined(posix):
+  import posix
 
-  # we cannot import posix.nim here, because posix.nim depends on times.nim.
-  # Ok, we could, but I don't want circular dependencies.
-  # And gettimeofday() is not defined in the posix module anyway. Sigh.
+  type CTime = posix.Time
 
   proc posix_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
-    proc tzset(): void {.importc, header: "<time.h>".}
     tzset()
 
 elif defined(windows):
   import winlean
 
   # newest version of Visual C++ defines time_t to be of 64 bits
-  type TimeImpl {.importc: "time_t", header: "<time.h>".} = int64
+  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
-    Time* = distinct TimeImpl
+  var timezone {.importc: "_timezone", header: "<time.h>".}: int
 
+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
 
-elif defined(JS):
-  type
-    TimeBase = float
-    Time* = distinct TimeBase
-
-  proc getDay(t: Time): int {.tags: [], raises: [], benign, importcpp.}
-  proc getFullYear(t: Time): int {.tags: [], raises: [], benign, importcpp.}
-  proc getHours(t: Time): int {.tags: [], raises: [], benign, importcpp.}
-  proc getMilliseconds(t: Time): int {.tags: [], raises: [], benign, importcpp.}
-  proc getMinutes(t: Time): int {.tags: [], raises: [], benign, importcpp.}
-  proc getMonth(t: Time): int {.tags: [], raises: [], benign, importcpp.}
-  proc getSeconds(t: Time): int {.tags: [], raises: [], benign, importcpp.}
-  proc getTime(t: Time): int {.tags: [], raises: [], noSideEffect, benign, importcpp.}
-  proc getTimezoneOffset(t: Time): int {.tags: [], raises: [], benign, importcpp.}
-  proc getDate(t: Time): int {.tags: [], raises: [], benign, importcpp.}
-  proc getUTCDate(t: Time): int {.tags: [], raises: [], benign, importcpp.}
-  proc getUTCFullYear(t: Time): int {.tags: [], raises: [], benign, importcpp.}
-  proc getUTCHours(t: Time): int {.tags: [], raises: [], benign, importcpp.}
-  proc getUTCMilliseconds(t: Time): int {.tags: [], raises: [], benign, importcpp.}
-  proc getUTCMinutes(t: Time): int {.tags: [], raises: [], benign, importcpp.}
-  proc getUTCMonth(t: Time): int {.tags: [], raises: [], benign, importcpp.}
-  proc getUTCSeconds(t: Time): int {.tags: [], raises: [], benign, importcpp.}
-  proc getUTCDay(t: Time): int {.tags: [], raises: [], benign, importcpp.}
-  proc getYear(t: Time): int {.tags: [], raises: [], benign, importcpp.}
-  proc parse(t: Time; s: cstring): Time {.tags: [], raises: [], benign, importcpp.}
-  proc setDate(t: Time; x: int) {.tags: [], raises: [], benign, importcpp.}
-  proc setFullYear(t: Time; x: int) {.tags: [], raises: [], benign, importcpp.}
-  proc setHours(t: Time; x: int) {.tags: [], raises: [], benign, importcpp.}
-  proc setMilliseconds(t: Time; x: int) {.tags: [], raises: [], benign, importcpp.}
-  proc setMinutes(t: Time; x: int) {.tags: [], raises: [], benign, importcpp.}
-  proc setMonth(t: Time; x: int) {.tags: [], raises: [], benign, importcpp.}
-  proc setSeconds(t: Time; x: int) {.tags: [], raises: [], benign, importcpp.}
-  proc setTime(t: Time; x: int) {.tags: [], raises: [], benign, importcpp.}
-  proc setUTCDate(t: Time; x: int) {.tags: [], raises: [], benign, importcpp.}
-  proc setUTCFullYear(t: Time; x: int) {.tags: [], raises: [], benign, importcpp.}
-  proc setUTCHours(t: Time; x: int) {.tags: [], raises: [], benign, importcpp.}
-  proc setUTCMilliseconds(t: Time; x: int) {.tags: [], raises: [], benign, importcpp.}
-  proc setUTCMinutes(t: Time; x: int) {.tags: [], raises: [], benign, importcpp.}
-  proc setUTCMonth(t: Time; x: int) {.tags: [], raises: [], benign, importcpp.}
-  proc setUTCSeconds(t: Time; x: int) {.tags: [], raises: [], benign, importcpp.}
-  proc setYear(t: Time; x: int) {.tags: [], raises: [], benign, importcpp.}
-  proc toGMTString(t: Time): cstring {.tags: [], raises: [], benign, importcpp.}
-  proc toLocaleString(t: Time): cstring {.tags: [], raises: [], benign, importcpp.}
+  WeekDay* = enum ## Represents a weekday.
+    dMon, dTue, dWed, dThu, dFri, dSat, dSun
 
-type
-  TimeInfo* = object of RootObj ## represents a time in different parts
-    second*: range[0..61]     ## The number of seconds after the minute,
+  MonthdayRange* = range[1..31]
+  HourRange* = range[0..23]
+  MinuteRange* = range[0..59]
+  SecondRange* = range[0..60]
+  YeardayRange* = range[0..365]
+
+  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.
+
+  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.
+    second*: SecondRange      ## The number of seconds after the minute,
                               ## normally in the range 0 to 59, but can
-                              ## be up to 61 to allow for leap seconds.
-    minute*: range[0..59]     ## The number of minutes after the hour,
+                              ## 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*: range[0..23]       ## The number of hours past midnight,
+    hour*: HourRange          ## The number of hours past midnight,
                               ## in the range 0 to 23.
-    monthday*: range[1..31]   ## The day of the month, in the range 1 to 31.
+    monthday*: MonthdayRange  ## The day of the month, in the range 1 to 31.
     month*: Month             ## The current month.
-    year*: int                ## The current year.
+    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*: range[0..365]   ## The number of days since January 1,
+    yearday*: YeardayRange    ## The number of days since January 1,
                               ## in the range 0 to 365.
-                              ## Always 0 if the target is JS.
-    isDST*: bool              ## Determines whether DST is in effect.
-                              ## Semantically, this adds another negative hour
-                              ## offset to the time in addition to the timezone.
-    timezone*: int            ## The offset of the (non-DST) timezone in seconds
-                              ## west of UTC. Note that the sign of this number
-                              ## is the opposite of the one in a formatted
-                              ## timezone string like ``+01:00`` (which would be
-                              ## parsed into the timezone ``-3600``).
-
-  ## I make some assumptions about the data in here. Either
-  ## everything should be positive or everything negative. Zero is
-  ## fine too. Mixed signs will lead to unexpected results.
-  TimeInterval* = object ## a time interval
+    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 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,
+                         ## 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.
     milliseconds*: int ## The number of milliseconds
     seconds*: int     ## The number of seconds
     minutes*: int     ## The number of minutes
@@ -174,92 +126,394 @@ type
     months*: int      ## The number of months
     years*: int       ## The number of years
 
+  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 {.nimcall, tags: [], raises: [], benign .}
+    zoneInfoFromTz*:  proc (adjTime: Time): ZonedTime {.nimcall, 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.
+                      ## This type is only used for implementing timezones.
+    adjTime*: Time ## Time adjusted to a timezone.
+    utcOffset*: int
+    isDst*: bool
+
 {.deprecated: [TMonth: Month, TWeekDay: WeekDay, TTime: Time,
-    TTimeInterval: TimeInterval, TTimeInfo: TimeInfo].}
+    TTimeInterval: TimeInterval, TTimeInfo: DateTime, TimeInfo: DateTime].}
 
-proc getTime*(): Time {.tags: [TimeEffect], benign.}
-  ## gets the current calendar time as a UNIX epoch value (number of seconds
-  ## elapsed since 1970) with integer precission. Use epochTime for higher
-  ## resolution.
-proc getLocalTime*(t: Time): TimeInfo {.tags: [TimeEffect], raises: [], benign.}
-  ## converts the calendar time `t` to broken-time representation,
-  ## expressed relative to the user's specified time zone.
-proc getGMTime*(t: Time): TimeInfo {.tags: [TimeEffect], raises: [], benign.}
-  ## converts the calendar time `t` to broken-down time representation,
-  ## expressed in Coordinated Universal Time (UTC).
+const
+  secondsInMin = 60
+  secondsInHour = 60*60
+  secondsInDay = 60*60*24
+  minutesInHour = 60
 
-proc timeInfoToTime*(timeInfo: TimeInfo): Time
-    {.tags: [TimeEffect], benign, deprecated.}
-  ## converts a broken-down time structure to
-  ## calendar time representation. The function ignores the specified
-  ## contents of the structure members `weekday` and `yearday` and recomputes
-  ## them from the other information in the broken-down time structure.
-  ##
-  ## **Warning:** This procedure is deprecated since version 0.14.0.
-  ## Use ``toTime`` instead.
+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)
 
-proc toTime*(timeInfo: TimeInfo): Time {.tags: [TimeEffect], benign.}
-  ## converts a broken-down time structure to
-  ## calendar time representation. The function ignores the specified
-  ## contents of the structure members `weekday` and `yearday` and recomputes
-  ## them from the other information in the broken-down time structure.
+proc toUnix*(t: Time): int64 {.benign, tags: [], raises: [], noSideEffect.} =
+  ## Convert ``t`` to a unix timestamp (seconds since ``1970-01-01T00:00:00Z``).
+  t.int64
 
-proc fromSeconds*(since1970: float): Time {.tags: [], raises: [], benign.}
-  ## Takes a float which contains the number of seconds since the unix epoch and
-  ## returns a time object.
+proc isLeapYear*(year: int): bool =
+  ## Returns true if ``year`` is a leap year.
+  year mod 4 == 0 and (year mod 100 != 0 or year mod 400 == 0)
 
-proc fromSeconds*(since1970: int64): Time {.tags: [], raises: [], benign.} =
-  ## Takes an int which contains the number of seconds since the unix epoch and
-  ## returns a time object.
-  fromSeconds(float(since1970))
+proc getDaysInMonth*(month: Month, year: int): int =
+  ## Get the number of days in a ``month`` of a ``year``.
+  # http://www.dispersiondesign.com/articles/time/number_of_days_in_a_month
+  case month
+  of mFeb: result = if isLeapYear(year): 29 else: 28
+  of mApr, mJun, mSep, mNov: result = 30
+  else: result = 31
 
-proc toSeconds*(time: Time): float {.tags: [], raises: [], benign.}
-  ## Returns the time in seconds since the unix epoch.
+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.} =
+  assert monthday <= getDaysInMonth(month, year),
+    $year & "-" & $ord(month) & "-" & $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  
+  # Based on http://howardhinnant.github.io/date_algorithms.html
+  var (y, m, d) = (year, ord(month), monthday.int)
+  if m <= 2:
+    y.dec
+
+  let era = (if y >= 0: y else: y-399) div 400
+  let yoe = y - era * 400
+  let doy = (153 * (m + (if m > 2: -3 else: 9)) + 2) div 5 + d-1
+  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] =
+  ## 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).  
+  # Based on http://howardhinnant.github.io/date_algorithms.html
+  var z = epochday
+  z.inc 719468
+  let era = (if z >= 0: z else: z - 146096) div 146097
+  let doe = z - era * 146097
+  let yoe = (doe - doe div 1460 + doe div 36524 - doe div 146096) div 365
+  let y = yoe + era * 400;
+  let doy = doe - (365 * yoe + yoe div 4 - yoe div 100)
+  let mp = (5 * doy + 2) div 153
+  let d = doy - (153 * mp + 2) div 5 + 1
+  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 .} =
+  ## Returns the day of the year.
+  ## Equivalent with ``initDateTime(day, month, year).yearday``.
+  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]
+
+  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 .} =
+  ## Returns the day of the week enum from day, month and year.
+  ## Equivalent with ``initDateTime(day, month, year).weekday``.
+  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 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)
+
+# 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 `-`*(a, b: Time): int64 {.
-  rtl, extern: "ntDiffTime", tags: [], raises: [], noSideEffect, benign.}
-  ## computes the difference of two calendar times. Result is in seconds.
+    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.} =
-  ## returns true iff ``a < b``, that is iff a happened before b.
-  when defined(js):
-    result = TimeBase(a) < TimeBase(b)
-  else:
-    result = a - b < 0
+    rtl, extern: "ntLtTime", tags: [], raises: [], noSideEffect, borrow.}
+  ## Returns true iff ``a < b``, that is iff a happened before b.
 
 proc `<=` * (a, b: Time): bool {.
-  rtl, extern: "ntLeTime", tags: [], raises: [], noSideEffect.}=
-  ## returns true iff ``a <= b``.
-  when defined(js):
-    result = TimeBase(a) <= TimeBase(b)
-  else:
-    result = a - b <= 0
+    rtl, extern: "ntLeTime", tags: [], raises: [], noSideEffect, borrow.}
+  ## Returns true iff ``a <= b``.
 
 proc `==`*(a, b: Time): bool {.
-  rtl, extern: "ntEqTime", tags: [], raises: [], noSideEffect.} =
-  ## returns true if ``a == b``, that is if both times represent the same value
-  when defined(js):
-    result = TimeBase(a) == TimeBase(b)
+    rtl, extern: "ntEqTime", tags: [], raises: [], noSideEffect, borrow.}
+  ## Returns true if ``a == b``, that is if both times represent the same point in time.
+
+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
+  # The code above ignores the UTC offset of `timeInfo`,
+  # so we need to compensate for that here.
+  result.inc dt.utcOffset
+
+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 hour = rem div secondsInHour
+  rem = rem - hour * secondsInHour
+  let minute = rem div secondsInMin
+  rem = rem - minute * secondsInMin
+  let second = rem
+
+  let (d, m, y) = fromEpochday(epochday)
+
+  DateTime(
+    year: y,
+    month: m,
+    monthday: d,
+    hour: hour,
+    minute: minute,
+    second: second,
+    weekday: getDayOfWeek(d, m, y),
+    yearday: getDayOfYear(d, m, y),
+    isDst: zt.isDst,
+    timezone: zone,
+    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 inZone*(dt: DateTime, zone: Timezone): DateTime  {.tags: [], raises: [], benign.} =
+  ## Convert ``dt`` into a ``DateTime`` using ``zone`` as the timezone.
+  dt.toTime.inZone(zone)
+
+proc `$`*(zone: Timezone): string =
+  ## Returns the name of the timezone.
+  zone.name
+
+proc `==`*(zone1, zone2: Timezone): bool =
+  ## Two ``Timezone``'s are considered equal if their name is equal.
+  zone1.name == zone2.name
+
+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
+
+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.float * 1000)
+      let offset = jsDate.getTimezoneOffset() * secondsInMin
+      result.adjTime = Time(time.int64 - offset)
+      result.utcOffset = offset
+      result.isDst = false
+
+    proc localZoneInfoFromTz(adjTime: Time): ZonedTime =
+      let utcDate = newDate(adjTime.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
+
+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:
-    result = a - b == 0
+    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 localtime(timer: ptr CTime): StructTmPtr {. importc: "localtime", header: "<time.h>", tags: [].}
+
+  proc toAdjTime(tm: StructTm): Time =
+    let epochDay = toEpochday(tm.monthday, (tm.month + 1).Month, tm.year.int + 1900)
+    result = Time(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 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
+
+  proc localZoneInfoFromTz(adjTime: Time): ZonedTime  =
+    var adjTimei64 = adjTime.int64
+    let past = adjTimei64 - secondsInDay
+    var tm = getStructTm(past)
+    let pastOffset = past - tm.toAdjTime.int64
+
+    let future = adjTimei64 + secondsInDay
+    tm = getStructTm(future)
+    let futureOffset = future - tm.toAdjTime.int64
+
+    var utcOffset: int
+    if pastOffset == futureOffset:
+        utcOffset = pastOffset.int
+    else:
+      if pastOffset > futureOffset:
+        adjTimei64 -= secondsInHour
+
+      adjTimei64 += pastOffset
+      utcOffset = (adjTimei64 - getStructTm(adjTimei64).toAdjTime.int64).int
+
+    # 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
+
+proc utcZoneInfoFromUtc(time: Time): ZonedTime =
+  result.adjTime = time
+  result.utcOffset = 0
+  result.isDst = false
+
+proc utcZoneInfoFromTz(adjTime: Time): ZonedTime =
+  utcZoneInfoFromUtc(adjTime) # adjTime == time since we are in UTC
+
+proc utc*(): TimeZone =
+  ## Get the ``Timezone`` implementation for the UTC timezone.
+  ##
+  ## .. code-block:: nim
+  ##  doAssert now().utc.timezone == utc()
+  ##  doAssert utc().name == "Etc/UTC"
+  Timezone(zoneInfoFromUtc: utcZoneInfoFromUtc, zoneInfoFromTz: utcZoneInfoFromTz, name: "Etc/UTC")
 
-proc getTimezone*(): int {.tags: [TimeEffect], raises: [], benign.}
-  ## returns the offset of the local (non-DST) timezone in seconds west of UTC.
+proc local*(): TimeZone =
+  ## Get the ``Timezone`` implementation for the local timezone.
+  ##
+  ## .. code-block:: nim
+  ##  doAssert now().timezone == local()
+  ##  doAssert local().name == "LOCAL"
+  Timezone(zoneInfoFromUtc: localZoneInfoFromUtc, zoneInfoFromTz: localZoneInfoFromTz, name: "LOCAL")
+
+proc utc*(dt: DateTime): DateTime =
+  ## Shorthand for ``dt.inZone(utc())``.
+  dt.inZone(utc())
+
+proc local*(dt: DateTime): DateTime =
+  ## Shorthand for ``dt.inZone(local())``.
+  dt.inZone(local())
+
+proc utc*(t: Time): DateTime =
+  ## Shorthand for ``t.inZone(utc())``.  
+  t.inZone(utc())
+
+proc local*(t: Time): DateTime =
+  ## Shorthand for ``t.inZone(local())``.  
+  t.inZone(local())
 
-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.
+proc getTime*(): Time {.tags: [TimeEffect], benign.}
+  ## Gets the current time as a ``Time`` with second resolution. Use epochTime for higher
+  ## resolution.
+
+proc now*(): DateTime {.tags: [TimeEffect], benign.} =
+  ## Get the current time as a  ``DateTime`` in the local timezone.
+  ##
+  ## Shorthand for ``getTime().local``.
+  getTime().local
 
 proc initInterval*(milliseconds, seconds, minutes, hours, days, months,
                    years: int = 0): TimeInterval =
-  ## creates a new ``TimeInterval``.
+  ## Creates a new ``TimeInterval``.
   ##
   ## You can also use the convenience procedures called ``milliseconds``,
   ## ``seconds``, ``minutes``, ``hours``, ``days``, ``months``, and ``years``.
@@ -269,46 +523,33 @@ proc initInterval*(milliseconds, seconds, minutes, hours, days, months,
   ## .. code-block:: nim
   ##
   ##     let day = initInterval(hours=24)
-  ##     let tomorrow = getTime() + day
-  ##     echo(tomorrow)
-  var carryO = 0
-  result.milliseconds = `mod`(milliseconds, 1000)
-  carryO = `div`(milliseconds, 1000)
-  result.seconds = `mod`(carryO + seconds, 60)
-  carryO = `div`(carryO + seconds, 60)
-  result.minutes = `mod`(carryO + minutes, 60)
-  carryO = `div`(carryO + minutes, 60)
-  result.hours = `mod`(carryO + hours, 24)
-  carryO = `div`(carryO + hours, 24)
-  result.days = carryO + days
-
-  result.months = `mod`(months, 12)
-  carryO = `div`(months, 12)
-  result.years = carryO + years
+  ##     let dt = initDateTime(01, mJan, 2000, 12, 00, 00, utc())
+  ##     doAssert $(dt + day) == "2000-01-02T12-00-00+00:00"
+  result.milliseconds = milliseconds
+  result.seconds = seconds
+  result.minutes = minutes
+  result.hours = hours
+  result.days = days
+  result.months = months
+  result.years = years
 
 proc `+`*(ti1, ti2: TimeInterval): TimeInterval =
   ## Adds two ``TimeInterval`` objects together.
-  var carryO = 0
-  result.milliseconds = `mod`(ti1.milliseconds + ti2.milliseconds, 1000)
-  carryO = `div`(ti1.milliseconds + ti2.milliseconds, 1000)
-  result.seconds = `mod`(carryO + ti1.seconds + ti2.seconds, 60)
-  carryO = `div`(carryO + ti1.seconds + ti2.seconds, 60)
-  result.minutes = `mod`(carryO + ti1.minutes + ti2.minutes, 60)
-  carryO = `div`(carryO + ti1.minutes + ti2.minutes, 60)
-  result.hours = `mod`(carryO + ti1.hours + ti2.hours, 24)
-  carryO = `div`(carryO + ti1.hours + ti2.hours, 24)
-  result.days = carryO + ti1.days + ti2.days
-
-  result.months = `mod`(ti1.months + ti2.months, 12)
-  carryO = `div`(ti1.months + ti2.months, 12)
-  result.years = carryO + ti1.years + ti2.years
+  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.months = ti1.months + ti2.months
+  result.years = ti1.years + ti2.years
 
 proc `-`*(ti: TimeInterval): TimeInterval =
   ## Reverses a time interval
+  ##
   ## .. code-block:: nim
   ##
   ##     let day = -initInterval(hours=24)
-  ##     echo day  # -> (milliseconds: 0, seconds: 0, minutes: 0, hours: 0, days: -1, months: 0, years: 0)
+  ##     echo day  # -> (milliseconds: 0, seconds: 0, minutes: 0, hours: -24, days: 0, months: 0, years: 0)
   result = TimeInterval(
     milliseconds: -ti.milliseconds,
     seconds: -ti.seconds,
@@ -325,123 +566,100 @@ proc `-`*(ti1, ti2: TimeInterval): TimeInterval =
   ## Time components are compared one-by-one, see output:
   ##
   ## .. code-block:: nim
-  ##     let a = fromSeconds(1_000_000_000)
-  ##     let b = fromSeconds(1_500_000_000)
+  ##     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: -2, months: -2, years: 16)
+  ##     # (milliseconds: 0, seconds: -40, minutes: -6, hours: 1, days: 5, months: -2, years: 16)
   result = ti1 + (-ti2)
 
-proc isLeapYear*(year: int): bool =
-  ## returns true if ``year`` is a leap year
-
-  if year mod 400 == 0:
-    return true
-  elif year mod 100 == 0:
-    return false
-  elif year mod 4 == 0:
-    return true
-  else:
-    return false
-
-proc getDaysInMonth*(month: Month, year: int): int =
-  ## Get the number of days in a ``month`` of a ``year``
+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.
 
-  # http://www.dispersiondesign.com/articles/time/number_of_days_in_a_month
-  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 toSeconds(a: TimeInfo, interval: TimeInterval): float =
-  ## Calculates how many seconds the interval is worth by adding up
-  ## all the fields
-
-  var anew = a
+  var anew = dt
   var newinterv = interval
-  result = 0
 
   newinterv.months += interval.years * 12
   var curMonth = anew.month
-  if newinterv.months < 0:   # subtracting
+  # Subtracting
+  if newinterv.months < 0:
     for mth in countDown(-1 * newinterv.months, 1):
-      result -= float(getDaysInMonth(curMonth, anew.year) * 24 * 60 * 60)
       if curMonth == mJan:
         curMonth = mDec
         anew.year.dec()
       else:
         curMonth.dec()
-  else:  # adding
+      result.adjDiff -= getDaysInMonth(curMonth, anew.year) * secondsInDay      
+  # Adding
+  else:
     for mth in 1 .. newinterv.months:
-      result += float(getDaysInMonth(curMonth, anew.year) * 24 * 60 * 60)
+      result.adjDiff += getDaysInMonth(curMonth, anew.year) * secondsInDay
       if curMonth == mDec:
         curMonth = mJan
         anew.year.inc()
       else:
         curMonth.inc()
-  result += float(newinterv.days * 24 * 60 * 60)
-  result += float(newinterv.hours * 60 * 60)
-  result += float(newinterv.minutes * 60)
-  result += float(newinterv.seconds)
-  result += newinterv.milliseconds / 1000
-
-proc `+`*(a: TimeInfo, interval: TimeInterval): TimeInfo =
-  ## adds ``interval`` time from TimeInfo ``a``.
+  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`.
   ##
-  ## **Note:** This has been only briefly tested and it may not be
-  ## very accurate.
-  let t = toSeconds(toTime(a))
-  let secs = toSeconds(a, interval)
-  if a.timezone == 0:
-    result = getGMTime(fromSeconds(t + secs))
-  else:
-    result = getLocalTime(fromSeconds(t + secs))
-
-proc `-`*(a: TimeInfo, interval: TimeInterval): TimeInfo =
-  ## subtracts ``interval`` time from TimeInfo ``a``.
-  ##
-  ## **Note:** This has been only briefly tested, it is inaccurate especially
-  ## when you subtract so much that you reach the Julian calendar.
-  let
-    t = toSeconds(toTime(a))
-    secs = toSeconds(a, -interval)
-  if a.timezone == 0:
-    result = getGMTime(fromSeconds(t + secs))
+  ## .. 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 = getLocalTime(fromSeconds(t + secs))
-
-proc miliseconds*(t: TimeInterval): int {.deprecated.} = t.milliseconds
+    result = initDateTime(dt.timezone.zoneInfoFromUtc(Time(dt.toTime.int64 + absDiff)), dt.timezone)
 
-proc `miliseconds=`*(t: var TimeInterval, milliseconds: int) {.deprecated.} =
-  ## An alias for a misspelled field in ``TimeInterval``.
-  ##
-  ## **Warning:** This should not be used! It will be removed in the next
-  ## version.
-  t.milliseconds = milliseconds
+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 getDateStr*(): string {.rtl, extern: "nt$1", tags: [TimeEffect].} =
-  ## gets the current date as a string of the format ``YYYY-MM-DD``.
-  var ti = getLocalTime(getTime())
-  result = $ti.year & '-' & intToStr(ord(ti.month)+1, 2) &
+  ## 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 = getLocalTime(getTime())
+  ## 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 =
-  ## stingify operator for ``WeekDay``.
+  ## Stringify operator for ``WeekDay``.
   const lookup: array[WeekDay, string] = ["Monday", "Tuesday", "Wednesday",
      "Thursday", "Friday", "Saturday", "Sunday"]
   return lookup[day]
 
 proc `$`*(m: Month): string =
-  ## stingify operator for ``Month``.
+  ## Stringify operator for ``Month``.
   const lookup: array[Month, string] = ["January", "February", "March",
       "April", "May", "June", "July", "August", "September", "October",
       "November", "December"]
@@ -450,74 +668,68 @@ proc `$`*(m: Month): string =
 proc milliseconds*(ms: int): TimeInterval {.inline.} =
   ## TimeInterval of `ms` milliseconds
   ##
-  ## Note: not all time functions have millisecond resolution
-  initInterval(`mod`(ms,1000), `div`(ms,1000))
+  ## Note: not all time procedures have millisecond resolution
+  initInterval(milliseconds = ms)
 
 proc seconds*(s: int): TimeInterval {.inline.} =
   ## TimeInterval of `s` seconds
   ##
   ## ``echo getTime() + 5.second``
-  initInterval(0,`mod`(s,60), `div`(s,60))
+  initInterval(seconds = s)
 
 proc minutes*(m: int): TimeInterval {.inline.} =
   ## TimeInterval of `m` minutes
   ##
   ## ``echo getTime() + 5.minutes``
-  initInterval(0,0,`mod`(m,60), `div`(m,60))
+  initInterval(minutes = m)
 
 proc hours*(h: int): TimeInterval {.inline.} =
   ## TimeInterval of `h` hours
   ##
   ## ``echo getTime() + 2.hours``
-  initInterval(0,0,0,`mod`(h,24),`div`(h,24))
+  initInterval(hours = h)
 
 proc days*(d: int): TimeInterval {.inline.} =
   ## TimeInterval of `d` days
   ##
   ## ``echo getTime() + 2.days``
-  initInterval(0,0,0,0,d)
+  initInterval(days = d)
 
 proc months*(m: int): TimeInterval {.inline.} =
   ## TimeInterval of `m` months
   ##
   ## ``echo getTime() + 2.months``
-  initInterval(0,0,0,0,0,`mod`(m,12),`div`(m,12))
+  initInterval(months = m)
 
 proc years*(y: int): TimeInterval {.inline.} =
   ## TimeInterval of `y` years
   ##
   ## ``echo getTime() + 2.years``
-  initInterval(0,0,0,0,0,0,y)
+  initInterval(years = y)
 
-proc `+=`*(t: var Time, ti: TimeInterval) =
-  ## modifies `t` by adding the interval `ti`
-  t = toTime(getLocalTime(t) + ti)
+proc `+=`*(time: var Time, interval: TimeInterval) =
+  ## Modifies `time` by adding `interval`.
+  time = toTime(time.local + interval)
 
-proc `+`*(t: Time, ti: TimeInterval): Time =
-  ## adds the interval `ti` to Time `t`
-  ## by converting to localTime, adding the interval, and converting back
+proc `+`*(time: Time, interval: TimeInterval): Time =
+  ## Adds `interval` to `time`
+  ## by converting to a ``DateTime`` in the local timezone,
+  ## adding the interval, and converting back to ``Time``.
   ##
   ## ``echo getTime() + 1.day``
-  result = toTime(getLocalTime(t) + ti)
+  result = toTime(time.local + interval)
 
-proc `-=`*(t: var Time, ti: TimeInterval) =
-  ## modifies `t` by subtracting the interval `ti`
-  t = toTime(getLocalTime(t) - ti)
+proc `-=`*(time: var Time, interval: TimeInterval) =
+  ## Modifies `time` by subtracting `interval`.
+  time = toTime(time.local - interval)
 
-proc `-`*(t: Time, ti: TimeInterval): Time =
-  ## subtracts the interval `ti` from Time `t`
+proc `-`*(time: Time, interval: TimeInterval): Time =
+  ## Subtracts `interval` from Time `time`.
   ##
   ## ``echo getTime() - 1.day``
-  result = toTime(getLocalTime(t) - ti)
+  result = toTime(time.local - interval)
 
-const
-  secondsInMin = 60
-  secondsInHour = 60*60
-  secondsInDay = 60*60*24
-  minutesInHour = 60
-  epochStartYear = 1970
-
-proc formatToken(info: TimeInfo, token: string, buf: var string) =
+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
@@ -525,96 +737,96 @@ proc formatToken(info: TimeInfo, token: string, buf: var string) =
   ## formatting tokens require modifying the previous characters.
   case token
   of "d":
-    buf.add($info.monthday)
+    buf.add($dt.monthday)
   of "dd":
-    if info.monthday < 10:
+    if dt.monthday < 10:
       buf.add("0")
-    buf.add($info.monthday)
+    buf.add($dt.monthday)
   of "ddd":
-    buf.add(($info.weekday)[0 .. 2])
+    buf.add(($dt.weekday)[0 .. 2])
   of "dddd":
-    buf.add($info.weekday)
+    buf.add($dt.weekday)
   of "h":
-    buf.add($(if info.hour > 12: info.hour - 12 else: info.hour))
+    buf.add($(if dt.hour > 12: dt.hour - 12 else: dt.hour))
   of "hh":
-    let amerHour = if info.hour > 12: info.hour - 12 else: info.hour
+    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($info.hour)
+    buf.add($dt.hour)
   of "HH":
-    if info.hour < 10:
+    if dt.hour < 10:
       buf.add('0')
-    buf.add($info.hour)
+    buf.add($dt.hour)
   of "m":
-    buf.add($info.minute)
+    buf.add($dt.minute)
   of "mm":
-    if info.minute < 10:
+    if dt.minute < 10:
       buf.add('0')
-    buf.add($info.minute)
+    buf.add($dt.minute)
   of "M":
-    buf.add($(int(info.month)+1))
+    buf.add($ord(dt.month))
   of "MM":
-    if info.month < mOct:
+    if dt.month < mOct:
       buf.add('0')
-    buf.add($(int(info.month)+1))
+    buf.add($ord(dt.month))
   of "MMM":
-    buf.add(($info.month)[0..2])
+    buf.add(($dt.month)[0..2])
   of "MMMM":
-    buf.add($info.month)
+    buf.add($dt.month)
   of "s":
-    buf.add($info.second)
+    buf.add($dt.second)
   of "ss":
-    if info.second < 10:
+    if dt.second < 10:
       buf.add('0')
-    buf.add($info.second)
+    buf.add($dt.second)
   of "t":
-    if info.hour >= 12:
+    if dt.hour >= 12:
       buf.add('P')
     else: buf.add('A')
   of "tt":
-    if info.hour >= 12:
+    if dt.hour >= 12:
       buf.add("PM")
     else: buf.add("AM")
   of "y":
-    var fr = ($info.year).len()-1
+    var fr = ($dt.year).len()-1
     if fr < 0: fr = 0
-    buf.add(($info.year)[fr .. ($info.year).len()-1])
+    buf.add(($dt.year)[fr .. ($dt.year).len()-1])
   of "yy":
-    var fr = ($info.year).len()-2
+    var fr = ($dt.year).len()-2
     if fr < 0: fr = 0
-    var fyear = ($info.year)[fr .. ($info.year).len()-1]
+    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 = ($info.year).len()-3
+    var fr = ($dt.year).len()-3
     if fr < 0: fr = 0
-    var fyear = ($info.year)[fr .. ($info.year).len()-1]
+    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 = ($info.year).len()-4
+    var fr = ($dt.year).len()-4
     if fr < 0: fr = 0
-    var fyear = ($info.year)[fr .. ($info.year).len()-1]
+    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 = ($info.year).len()-5
+    var fr = ($dt.year).len()-5
     if fr < 0: fr = 0
-    var fyear = ($info.year)[fr .. ($info.year).len()-1]
+    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 = info.timezone - int(info.isDst) * secondsInHour
+      nonDstTz = dt.utcOffset
       hours = abs(nonDstTz) div secondsInHour
     if nonDstTz <= 0: buf.add('+')
     else: buf.add('-')
     buf.add($hours)
   of "zz":
     let
-      nonDstTz = info.timezone - int(info.isDst) * secondsInHour
+      nonDstTz = dt.utcOffset
       hours = abs(nonDstTz) div secondsInHour
     if nonDstTz <= 0: buf.add('+')
     else: buf.add('-')
@@ -622,7 +834,7 @@ proc formatToken(info: TimeInfo, token: string, buf: var string) =
     buf.add($hours)
   of "zzz":
     let
-      nonDstTz = info.timezone - int(info.isDst) * secondsInHour
+      nonDstTz = dt.utcOffset
       hours = abs(nonDstTz) div secondsInHour
       minutes = (abs(nonDstTz) div secondsInMin) mod minutesInHour
     if nonDstTz <= 0: buf.add('+')
@@ -638,8 +850,8 @@ proc formatToken(info: TimeInfo, token: string, buf: var string) =
     raise newException(ValueError, "Invalid format string: " & token)
 
 
-proc format*(info: TimeInfo, f: string): string =
-  ## This function formats `info` as specified by `f`. The following format
+proc format*(dt: DateTime, f: string): string {.tags: [].}=
+  ## This procedure formats `dt` as specified by `f`. The following format
   ## specifiers are available:
   ##
   ## ==========  =================================================================================  ================================================
@@ -683,7 +895,7 @@ proc format*(info: TimeInfo, f: string): string =
   while true:
     case f[i]
     of ' ', '-', '/', ':', '\'', '\0', '(', ')', '[', ']', ',':
-      formatToken(info, currentF, result)
+      formatToken(dt, currentF, result)
 
       currentF = ""
       if f[i] == '\0': break
@@ -700,187 +912,187 @@ proc format*(info: TimeInfo, f: string): string =
       if currentF.len < 1 or currentF[high(currentF)] == f[i]:
         currentF.add(f[i])
       else:
-        formatToken(info, currentF, result)
+        formatToken(dt, currentF, result)
         dec(i) # Move position back to re-process the character separately.
         currentF = ""
 
     inc(i)
 
-proc `$`*(timeInfo: TimeInfo): string {.tags: [], raises: [], benign.} =
-  ## converts a `TimeInfo` object to a string representation.
+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``.
   try:
-    result = format(timeInfo, "yyyy-MM-dd'T'HH:mm:sszzz") # todo: optimize this
+    result = format(dt, "yyyy-MM-dd'T'HH:mm:sszzz") # todo: optimize this
   except ValueError: assert false # cannot happen because format string is valid
 
-proc `$`*(time: Time): string {.tags: [TimeEffect], raises: [], benign.} =
+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``.
-  $getLocalTime(time)
+  $time.local
 
 {.pop.}
 
-proc parseToken(info: var TimeInfo; token, value: string; j: var int) =
+proc parseToken(dt: var DateTime; token, value: string; j: var int) =
   ## Helper of the parse proc to parse individual tokens.
   var sv: int
   case token
   of "d":
     var pd = parseInt(value[j..j+1], sv)
-    info.monthday = sv
+    dt.monthday = sv
     j += pd
   of "dd":
-    info.monthday = value[j..j+1].parseInt()
+    dt.monthday = value[j..j+1].parseInt()
     j += 2
   of "ddd":
     case value[j..j+2].toLowerAscii()
-    of "sun": info.weekday = dSun
-    of "mon": info.weekday = dMon
-    of "tue": info.weekday = dTue
-    of "wed": info.weekday = dWed
-    of "thu": info.weekday = dThu
-    of "fri": info.weekday = dFri
-    of "sat": info.weekday = dSat
+    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:
-      info.weekday = dSun
+      dt.weekday = dSun
       j += 6
     elif value.len >= j+6 and value[j..j+5].cmpIgnoreCase("monday") == 0:
-      info.weekday = dMon
+      dt.weekday = dMon
       j += 6
     elif value.len >= j+7 and value[j..j+6].cmpIgnoreCase("tuesday") == 0:
-      info.weekday = dTue
+      dt.weekday = dTue
       j += 7
     elif value.len >= j+9 and value[j..j+8].cmpIgnoreCase("wednesday") == 0:
-      info.weekday = dWed
+      dt.weekday = dWed
       j += 9
     elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("thursday") == 0:
-      info.weekday = dThu
+      dt.weekday = dThu
       j += 8
     elif value.len >= j+6 and value[j..j+5].cmpIgnoreCase("friday") == 0:
-      info.weekday = dFri
+      dt.weekday = dFri
       j += 6
     elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("saturday") == 0:
-      info.weekday = dSat
+      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)
-    info.hour = sv
+    dt.hour = sv
     j += pd
   of "hh", "HH":
-    info.hour = value[j..j+1].parseInt()
+    dt.hour = value[j..j+1].parseInt()
     j += 2
   of "m":
     var pd = parseInt(value[j..j+1], sv)
-    info.minute = sv
+    dt.minute = sv
     j += pd
   of "mm":
-    info.minute = value[j..j+1].parseInt()
+    dt.minute = value[j..j+1].parseInt()
     j += 2
   of "M":
     var pd = parseInt(value[j..j+1], sv)
-    info.month = Month(sv-1)
+    dt.month = sv.Month
     j += pd
   of "MM":
     var month = value[j..j+1].parseInt()
     j += 2
-    info.month = Month(month-1)
+    dt.month = month.Month
   of "MMM":
     case value[j..j+2].toLowerAscii():
-    of "jan": info.month =  mJan
-    of "feb": info.month =  mFeb
-    of "mar": info.month =  mMar
-    of "apr": info.month =  mApr
-    of "may": info.month =  mMay
-    of "jun": info.month =  mJun
-    of "jul": info.month =  mJul
-    of "aug": info.month =  mAug
-    of "sep": info.month =  mSep
-    of "oct": info.month =  mOct
-    of "nov": info.month =  mNov
-    of "dec": info.month =  mDec
+    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:
-      info.month =  mJan
+      dt.month =  mJan
       j += 7
     elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("february") == 0:
-      info.month =  mFeb
+      dt.month =  mFeb
       j += 8
     elif value.len >= j+5 and value[j..j+4].cmpIgnoreCase("march") == 0:
-      info.month =  mMar
+      dt.month =  mMar
       j += 5
     elif value.len >= j+5 and value[j..j+4].cmpIgnoreCase("april") == 0:
-      info.month =  mApr
+      dt.month =  mApr
       j += 5
     elif value.len >= j+3 and value[j..j+2].cmpIgnoreCase("may") == 0:
-      info.month =  mMay
+      dt.month =  mMay
       j += 3
     elif value.len >= j+4 and value[j..j+3].cmpIgnoreCase("june") == 0:
-      info.month =  mJun
+      dt.month =  mJun
       j += 4
     elif value.len >= j+4 and value[j..j+3].cmpIgnoreCase("july") == 0:
-      info.month =  mJul
+      dt.month =  mJul
       j += 4
     elif value.len >= j+6 and value[j..j+5].cmpIgnoreCase("august") == 0:
-      info.month =  mAug
+      dt.month =  mAug
       j += 6
     elif value.len >= j+9 and value[j..j+8].cmpIgnoreCase("september") == 0:
-      info.month =  mSep
+      dt.month =  mSep
       j += 9
     elif value.len >= j+7 and value[j..j+6].cmpIgnoreCase("october") == 0:
-      info.month =  mOct
+      dt.month =  mOct
       j += 7
     elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("november") == 0:
-      info.month =  mNov
+      dt.month =  mNov
       j += 8
     elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("december") == 0:
-      info.month =  mDec
+      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)
-    info.second = sv
+    dt.second = sv
     j += pd
   of "ss":
-    info.second = value[j..j+1].parseInt()
+    dt.second = value[j..j+1].parseInt()
     j += 2
   of "t":
-    if value[j] == 'P' and info.hour > 0 and info.hour < 12:
-      info.hour += 12
+    if value[j] == 'P' and dt.hour > 0 and dt.hour < 12:
+      dt.hour += 12
     j += 1
   of "tt":
-    if value[j..j+1] == "PM" and info.hour > 0 and info.hour < 12:
-      info.hour += 12
+    if 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 = getLocalTime(getTime()).year div 100
-    info.year = thisCen*100 + year
+    var thisCen = now().year div 100
+    dt.year = thisCen*100 + year
     j += 2
   of "yyyy":
-    info.year = value[j..j+3].parseInt()
+    dt.year = value[j..j+3].parseInt()
     j += 4
   of "z":
-    info.isDST = false
+    dt.isDst = false
     if value[j] == '+':
-      info.timezone = 0 - parseInt($value[j+1]) * secondsInHour
+      dt.utcOffset = 0 - parseInt($value[j+1]) * secondsInHour
     elif value[j] == '-':
-      info.timezone = parseInt($value[j+1]) * secondsInHour
+      dt.utcOffset = parseInt($value[j+1]) * secondsInHour
     elif value[j] == 'Z':
-      info.timezone = 0
+      dt.utcOffset = 0
       j += 1
       return
     else:
@@ -888,13 +1100,13 @@ proc parseToken(info: var TimeInfo; token, value: string; j: var int) =
         "Couldn't parse timezone offset (z), got: " & value[j])
     j += 2
   of "zz":
-    info.isDST = false
+    dt.isDst = false
     if value[j] == '+':
-      info.timezone = 0 - value[j+1..j+2].parseInt() * secondsInHour
+      dt.utcOffset = 0 - value[j+1..j+2].parseInt() * secondsInHour
     elif value[j] == '-':
-      info.timezone = value[j+1..j+2].parseInt() * secondsInHour
+      dt.utcOffset = value[j+1..j+2].parseInt() * secondsInHour
     elif value[j] == 'Z':
-      info.timezone = 0
+      dt.utcOffset = 0
       j += 1
       return
     else:
@@ -902,31 +1114,33 @@ proc parseToken(info: var TimeInfo; token, value: string; j: var int) =
         "Couldn't parse timezone offset (zz), got: " & value[j])
     j += 3
   of "zzz":
-    info.isDST = false
+    dt.isDst = false
     var factor = 0
     if value[j] == '+': factor = -1
     elif value[j] == '-': factor = 1
     elif value[j] == 'Z':
-      info.timezone = 0
+      dt.utcOffset = 0
       j += 1
       return
     else:
       raise newException(ValueError,
         "Couldn't parse timezone offset (zzz), got: " & value[j])
-    info.timezone = factor * value[j+1..j+2].parseInt() * secondsInHour
+    dt.utcOffset = factor * value[j+1..j+2].parseInt() * secondsInHour
     j += 4
-    info.timezone += factor * value[j..j+1].parseInt() * 60
+    dt.utcOffset += factor * value[j..j+1].parseInt() * 60
     j += 2
   else:
     # Ignore the token and move forward in the value string by the same length
     j += token.len
 
-proc parse*(value, layout: string): TimeInfo =
-  ## This function parses a date/time string using the standard format
-  ## identifiers as listed below. The function defaults information not provided
-  ## in the format string from the running program (timezone, month, year, etc).
-  ## Daylight saving time is only set if no timezone is given and the given date
-  ## lies within the DST period of the current locale.
+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.
   ##
   ## ==========  =================================================================================  ================================================
   ## Specifier   Description                                                                        Example
@@ -965,17 +1179,17 @@ proc parse*(value, layout: string): TimeInfo =
   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 info = getLocalTime(getTime())
-  info.hour = 0
-  info.minute = 0
-  info.second = 0
-  info.isDST = true # using this is flag for checking whether a timezone has \
+  var dt = now()
+  dt.hour = 0
+  dt.minute = 0
+  dt.second = 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:
     case layout[i]
     of ' ', '-', '/', ':', '\'', '\0', '(', ')', '[', ']', ',':
       if token.len > 0:
-        parseToken(info, token, value, j)
+        parseToken(dt, token, value, j)
       # Reset token
       token = ""
       # Break if at end of line
@@ -997,26 +1211,15 @@ proc parse*(value, layout: string): TimeInfo =
         token.add(layout[i])
         inc(i)
       else:
-        parseToken(info, token, value, j)
+        parseToken(dt, token, value, j)
         token = ""
 
-  if info.isDST:
-    # means that no timezone has been parsed. In this case, we need to check
-    # whether the date is within DST of the local time.
-    let tmp = getLocalTime(toTime(info))
-    # correctly set isDST so that the following step works on the correct time
-    info.isDST = tmp.isDST
-
-  # Correct weekday and yearday; transform timestamp to local time.
-  # There currently is no way of returning this with the original (parsed)
-  # timezone while also setting weekday and yearday (we are depending on stdlib
-  # to provide this calculation).
-  return getLocalTime(toTime(info))
-
-# Leap year calculations are adapted from:
-# http://www.codeproject.com/Articles/7358/Ultra-fast-Algorithms-for-Working-with-Leap-Years
-# The dayOfTheWeek procs are adapated from:
-# http://stason.org/TULARC/society/calendars/2-5-What-day-of-the-week-was-2-August-1953.html
+  if dt.isDst:
+    # No timezone parsed - assume timezone is `zone`
+    result = initDateTime(zone.zoneInfoFromTz(dt.toAdjTime), zone)
+  else:
+    # Otherwise convert to `zone`
+    result = dt.toTime.inZone(zone)
 
 proc countLeapYears*(yearSpan: int): int =
   ## Returns the number of leap years spanned by a given number of years.
@@ -1042,75 +1245,7 @@ proc countYearsAndDays*(daySpan: int): tuple[years: int, days: int] =
   result.years = days div 365
   result.days = days mod 365
 
-proc getDayOfWeek*(day, month, year: int): WeekDay =
-  ## Returns the day of the week enum from day, month and year.
-  # Day & month start from one.
-  let
-    a = (14 - month) div 12
-    y = year - a
-    m = month + (12*a) - 2
-    d = (day + y + (y div 4) - (y div 100) + (y div 400) + (31*m) div 12) mod 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.
-  if d == 0: return dSun
-  result = (d-1).WeekDay
-
-proc getDayOfWeekJulian*(day, month, year: int): WeekDay =
-  ## Returns the day of the week enum from day, month and year,
-  ## according to the Julian calendar.
-  # 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 timeToTimeInfo*(t: Time): TimeInfo {.deprecated.} =
-  ## Converts a Time to TimeInfo.
-  ##
-  ## **Warning:** This procedure is deprecated since version 0.14.0.
-  ## Use ``getLocalTime`` or ``getGMTime`` instead.
-  let
-    secs = t.toSeconds().int
-    daysSinceEpoch = secs div secondsInDay
-    (yearsSinceEpoch, daysRemaining) = countYearsAndDays(daysSinceEpoch)
-    daySeconds = secs mod secondsInDay
-
-    y = yearsSinceEpoch + epochStartYear
-
-  var
-    mon = mJan
-    days = daysRemaining
-    daysInMonth = getDaysInMonth(mon, y)
-
-  # calculate month and day remainder
-  while days > daysInMonth and mon <= mDec:
-    days -= daysInMonth
-    mon.inc
-    daysInMonth = getDaysInMonth(mon, y)
-
-  let
-    yd = daysRemaining
-    m = mon  # month is zero indexed enum
-    md = days
-    # NB: month is zero indexed but dayOfWeek expects 1 indexed.
-    wd = getDayOfWeek(days, mon.int + 1, y).Weekday
-    h = daySeconds div secondsInHour + 1
-    mi = (daySeconds mod secondsInHour) div secondsInMin
-    s = daySeconds mod secondsInMin
-  result = TimeInfo(year: y, yearday: yd, month: m, monthday: md, weekday: wd, hour: h, minute: mi, second: s)
-
-proc timeToTimeInterval*(t: Time): TimeInterval {.deprecated.} =
-  ## Converts a Time to a TimeInterval.
-  ##
-  ## **Warning:** This procedure is deprecated since version 0.14.0.
-  ## Use ``toTimeInterval`` instead.
-  # Milliseconds not available from Time
-  var tInfo = t.getLocalTime()
-  initInterval(0, tInfo.second, tInfo.minute, tInfo.hour, tInfo.weekday.ord, tInfo.month.ord, tInfo.year)
-
-proc toTimeInterval*(t: Time): TimeInterval =
+proc toTimeInterval*(time: Time): TimeInterval =
   ## Converts a Time to a TimeInterval.
   ##
   ## To be used when diffing times.
@@ -1121,10 +1256,25 @@ proc toTimeInterval*(t: 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: -2, months: -2, years: 16)
+  ##     # (milliseconds: 0, seconds: -40, minutes: -6, hours: 1, days: 5, months: -2, years: 16)
   # Milliseconds not available from Time
-  var tInfo = t.getLocalTime()
-  initInterval(0, tInfo.second, tInfo.minute, tInfo.hour, tInfo.weekday.ord, tInfo.month.ord, tInfo.year)
+  var dt = time.local
+  initInterval(0, dt.second, dt.minute, dt.hour, dt.monthday, dt.month.ord - 1, dt.year)
+
+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.
+  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
+  )
+  result = initDateTime(zone.zoneInfoFromTz(dt.toAdjTime), zone)
 
 when not defined(JS):
   proc epochTime*(): float {.rtl, extern: "nt$1", tags: [TimeEffect].}
@@ -1145,160 +1295,39 @@ when not defined(JS):
     ##   doWork()
     ##   echo "CPU time [s] ", cpuTime() - t0
 
-when not defined(JS):
-  # C wrapper:
-  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
+when defined(JS):
+  proc getTime(): Time =
+    (newDate().getTime() div 1000).Time
+
+  proc epochTime*(): float {.tags: [TimeEffect].} =
+    newDate().getTime() / 1000
+
+else:
   type
-    TimeInfoPtr = ptr StructTM
     Clock {.importc: "clock_t".} = distinct int
 
-  when not defined(windows):
-    # This is not ANSI C, but common enough
-    proc timegm(t: StructTM): Time {.
-      importc: "timegm", header: "<time.h>", tags: [].}
-
-  proc localtime(timer: ptr Time): TimeInfoPtr {.
-    importc: "localtime", header: "<time.h>", tags: [].}
-  proc gmtime(timer: ptr Time): TimeInfoPtr {.
-    importc: "gmtime", header: "<time.h>", tags: [].}
-  proc timec(timer: ptr Time): Time {.
+  proc timec(timer: ptr CTime): CTime {.
     importc: "time", header: "<time.h>", tags: [].}
-  proc mktime(t: StructTM): Time {.
-    importc: "mktime", header: "<time.h>", tags: [].}
+
   proc getClock(): Clock {.importc: "clock", header: "<time.h>", tags: [TimeEffect].}
-  proc difftime(a, b: Time): float {.importc: "difftime", header: "<time.h>",
-    tags: [].}
 
   var
     clocksPerSec {.importc: "CLOCKS_PER_SEC", nodecl.}: int
 
-  # our own procs on top of that:
-  proc tmToTimeInfo(tm: StructTM, local: bool): TimeInfo =
-    const
-      weekDays: array[0..6, WeekDay] = [
-        dSun, dMon, dTue, dWed, dThu, dFri, dSat]
-    TimeInfo(second: int(tm.second),
-      minute: int(tm.minute),
-      hour: int(tm.hour),
-      monthday: int(tm.monthday),
-      month: Month(tm.month),
-      year: tm.year + 1900'i32,
-      weekday: weekDays[int(tm.weekday)],
-      yearday: int(tm.yearday),
-      isDST: tm.isdst > 0,
-      timezone: if local: getTimezone() else: 0
-    )
-
-
-  proc timeInfoToTM(t: TimeInfo): StructTM =
-    const
-      weekDays: array[WeekDay, int8] = [1'i8,2'i8,3'i8,4'i8,5'i8,6'i8,0'i8]
-    result.second = t.second
-    result.minute = t.minute
-    result.hour = t.hour
-    result.monthday = t.monthday
-    result.month = cint(t.month)
-    result.year = cint(t.year - 1900)
-    result.weekday = weekDays[t.weekday]
-    result.yearday = t.yearday
-    result.isdst = if t.isDST: 1 else: 0
-
-  when not defined(useNimRtl):
-    proc `-` (a, b: Time): int64 =
-      return toBiggestInt(difftime(a, b))
-
-  proc getStartMilsecs(): int =
-    #echo "clocks per sec: ", clocksPerSec, "clock: ", int(getClock())
-    #return getClock() div (clocksPerSec div 1000)
-    when defined(macosx):
-      result = toInt(toFloat(int(getClock())) / (toFloat(clocksPerSec) / 1000.0))
-    else:
-      result = int(getClock()) div (clocksPerSec div 1000)
-    when false:
-      var a: Timeval
-      posix_gettimeofday(a)
-      result = a.tv_sec * 1000'i64 + a.tv_usec div 1000'i64
-      #echo "result: ", result
-
-  proc getTime(): Time = return timec(nil)
-  proc getLocalTime(t: Time): TimeInfo =
-    var a = t
-    let lt = localtime(addr(a))
-    assert(not lt.isNil)
-    result = tmToTimeInfo(lt[], true)
-    # copying is needed anyway to provide reentrancity; thus
-    # the conversion is not expensive
-
-  proc getGMTime(t: Time): TimeInfo =
-    var a = t
-    result = tmToTimeInfo(gmtime(addr(a))[], false)
-    # copying is needed anyway to provide reentrancity; thus
-    # the conversion is not expensive
-
-  proc toTime(timeInfo: TimeInfo): Time =
-    var cTimeInfo = timeInfo # for C++ we have to make a copy
-    # because the header of mktime is broken in my version of libc
-
-    result = mktime(timeInfoToTM(cTimeInfo))
-    # mktime is defined to interpret the input as local time. As timeInfoToTM
-    # does ignore the timezone, we need to adjust this here.
-    result = Time(TimeImpl(result) - getTimezone() + timeInfo.timezone)
-
-  proc timeInfoToTime(timeInfo: TimeInfo): Time = toTime(timeInfo)
+  proc getTime(): Time =
+    timec(nil).Time
 
   const
     epochDiff = 116444736000000000'i64
     rateDiff = 10000000'i64 # 100 nsecs
 
-  proc unixTimeToWinTime*(t: Time): int64 =
+  proc unixTimeToWinTime*(time: CTime): int64 =
     ## converts a UNIX `Time` (``time_t``) to a Windows file time
-    result = int64(t) * rateDiff + epochDiff
+    result = int64(time) * rateDiff + epochDiff
 
-  proc winTimeToUnixTime*(t: int64): Time =
+  proc winTimeToUnixTime*(time: int64): CTime =
     ## converts a Windows time to a UNIX `Time` (``time_t``)
-    result = Time((t - epochDiff) div rateDiff)
-
-  proc getTimezone(): int =
-    when defined(freebsd) or defined(netbsd) or defined(openbsd):
-      var a = timec(nil)
-      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 fromSeconds(since1970: float): Time = Time(since1970)
-
-  proc toSeconds(time: Time): float = float(time)
+    result = CTime((time - epochDiff) div rateDiff)
 
   when not defined(useNimRtl):
     proc epochTime(): float =
@@ -1319,83 +1348,125 @@ when not defined(JS):
     proc cpuTime(): float =
       result = toFloat(int(getClock())) / toFloat(clocksPerSec)
 
-elif defined(JS):
-  proc newDate(): Time {.importc: "new Date".}
-  proc internGetTime(): Time {.importc: "new Date", tags: [].}
+# Deprecated procs
 
-  proc newDate(value: float): Time {.importc: "new Date".}
-  proc newDate(value: cstring): Time {.importc: "new Date".}
-  proc getTime(): Time =
-    # Warning: This is something different in JS.
-    return newDate()
+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.
+  Time(since1970)
 
-  const
-    weekDays: array[0..6, WeekDay] = [
-      dSun, dMon, dTue, dWed, dThu, dFri, dSat]
-
-  proc getLocalTime(t: Time): TimeInfo =
-    result.second = t.getSeconds()
-    result.minute = t.getMinutes()
-    result.hour = t.getHours()
-    result.monthday = t.getDate()
-    result.month = Month(t.getMonth())
-    result.year = t.getFullYear()
-    result.weekday = weekDays[t.getDay()]
-    result.timezone = getTimezone()
-
-    result.yearday = result.monthday - 1
-    for month in mJan..<result.month:
-      result.yearday += getDaysInMonth(month, result.year)
-
-  proc getGMTime(t: Time): TimeInfo =
-    result.second = t.getUTCSeconds()
-    result.minute = t.getUTCMinutes()
-    result.hour = t.getUTCHours()
-    result.monthday = t.getUTCDate()
-    result.month = Month(t.getUTCMonth())
-    result.year = t.getUTCFullYear()
-    result.weekday = weekDays[t.getUTCDay()]
-
-    result.yearday = result.monthday - 1
-    for month in mJan..<result.month:
-      result.yearday += getDaysInMonth(month, result.year)
-
-  proc timeInfoToTime(timeInfo: TimeInfo): Time = toTime(timeInfo)
-
-  proc toTime*(timeInfo: TimeInfo): Time = newDate($timeInfo)
-
-  proc `-` (a, b: Time): int64 =
-    return a.getTime() - b.getTime()
+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.
+  Time(since1970)
 
-  var
-    startMilsecs = getTime()
+proc toSeconds*(time: Time): float {.tags: [], raises: [], benign, deprecated.} =
+  ## Returns the time in seconds since the unix epoch.
+  float(time)
 
-  proc getStartMilsecs(): int =
-    ## get the milliseconds from the start of the program
-    return int(getTime() - startMilsecs)
+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.
+  time.local
+
+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).
+  time.utc
+
+proc getTimezone*(): int {.tags: [TimeEffect], raises: [], benign, deprecated.} =
+  ## Returns the offset of the local (non-DST) timezone in seconds west of UTC.
+  when defined(JS):
+    return newDate().getTimezoneOffset() * 60
+  elif defined(freebsd) or defined(netbsd) or defined(openbsd):
+    var a = timec(nil)
+    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 timeInfoToTime*(dt: DateTime): Time {.tags: [], benign, deprecated.} =
+  ## Converts a broken-down time structure to calendar time representation.
+  ##
+  ## **Warning:** This procedure is deprecated since version 0.14.0.
+  ## Use ``toTime`` instead.
+  dt.toTime  
+
+when defined(JS):
+  var startMilsecs = 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)
+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 fromSeconds(since1970: float): Time = result = newDate(since1970 * 1000)
+proc miliseconds*(t: TimeInterval): int {.deprecated.} =
+  t.milliseconds
 
-  proc toSeconds(time: Time): float = result = time.getTime() / 1000
+proc timeToTimeInterval*(t: Time): TimeInterval {.deprecated.} =
+  ## Converts a Time to a TimeInterval.
+  ##
+  ## **Warning:** This procedure is deprecated since version 0.14.0.
+  ## Use ``toTimeInterval`` instead.
+  # Milliseconds not available from Time
+  t.toTimeInterval()
 
-  proc getTimezone(): int = result = newDate().getTimezoneOffset() * 60
+proc timeToTimeInfo*(t: Time): DateTime {.deprecated.} =
+  ## Converts a Time to DateTime.
+  ##
+  ## **Warning:** This procedure is deprecated since version 0.14.0.
+  ## Use ``inZone`` instead.
+  const epochStartYear = 1970
 
-  proc epochTime*(): float {.tags: [TimeEffect].} = newDate().toSeconds()
+  let
+    secs = t.toSeconds().int
+    daysSinceEpoch = secs div secondsInDay
+    (yearsSinceEpoch, daysRemaining) = countYearsAndDays(daysSinceEpoch)
+    daySeconds = secs mod secondsInDay
 
+    y = yearsSinceEpoch + epochStartYear
 
-when isMainModule:
-  # this is testing non-exported function
   var
-    t4 = getGMTime(fromSeconds(876124714)) # Mon  6 Oct 08:58:34 BST 1997
-    t4L = getLocalTime(fromSeconds(876124714))
-  assert toSeconds(t4, initInterval(seconds=0)) == 0.0
-  assert toSeconds(t4L, initInterval(milliseconds=1)) == toSeconds(t4, initInterval(milliseconds=1))
-  assert toSeconds(t4L, initInterval(seconds=1)) == toSeconds(t4, initInterval(seconds=1))
-  assert toSeconds(t4L, initInterval(minutes=1)) == toSeconds(t4, initInterval(minutes=1))
-  assert toSeconds(t4L, initInterval(hours=1)) == toSeconds(t4, initInterval(hours=1))
-  assert toSeconds(t4L, initInterval(days=1)) == toSeconds(t4, initInterval(days=1))
-  assert toSeconds(t4L, initInterval(months=1)) == toSeconds(t4, initInterval(months=1))
-  assert toSeconds(t4L, initInterval(years=1)) == toSeconds(t4, initInterval(years=1))
-
-  # Further tests are in tests/stdlib/ttime.nim
-  # koch test c stdlib
+    mon = mJan
+    days = daysRemaining
+    daysInMonth = getDaysInMonth(mon, y)
+
+  # calculate month and day remainder
+  while days > daysInMonth and mon <= mDec:
+    days -= daysInMonth
+    mon.inc
+    daysInMonth = getDaysInMonth(mon, y)
+
+  let
+    yd = daysRemaining
+    m = mon  # month is zero indexed enum
+    md = days
+    # NB: month is zero indexed but dayOfWeek expects 1 indexed.
+    wd = getDayOfWeek(days, mon, y).Weekday
+    h = daySeconds div secondsInHour + 1
+    mi = (daySeconds mod secondsInHour) div secondsInMin
+    s = daySeconds mod secondsInMin
+  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.} =
+  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.
+  # 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
\ No newline at end of file
diff --git a/tests/js/ttimes.nim b/tests/js/ttimes.nim
index 2868c6d0f..63a4bb04f 100644
--- a/tests/js/ttimes.nim
+++ b/tests/js/ttimes.nim
@@ -1,4 +1,3 @@
-# test times module with js
 discard """
   action: run
 """
@@ -9,21 +8,37 @@ import times
 # Tue 19 Jan 03:14:07 GMT 2038
 
 block yeardayTest:
-  # check if yearday attribute is properly set on TimeInfo creation
-  doAssert fromSeconds(2147483647).getGMTime().yearday == 18
-
-block localTimezoneTest:
-  # check if timezone is properly set during Time to TimeInfo conversion
-  doAssert fromSeconds(2147483647).getLocalTime().timezone == getTimezone()
-
-block timestampPersistenceTest:
-  # check if timestamp persists during TimeInfo to Time conversion
-  const
-    timeString = "2017-03-21T12:34:56+03:00"
-    timeStringGmt = "2017-03-21T09:34:56+00:00"
-    timeStringGmt2 = "2017-03-21T08:34:56+00:00"
-    fmt = "yyyy-MM-dd'T'HH:mm:sszzz"
-  # XXX Check which one is the right solution here:
-
-  let x = $timeString.parse(fmt).toTime().getGMTime()
-  doAssert x == timeStringGmt or x == timeStringGmt2
+  doAssert fromUnix(2147483647).utc.yearday == 18
+
+block localTime:
+  var local = now()
+  let utc = local.utc
+  doAssert local.toTime == utc.toTime
+
+let a = fromUnix(1_000_000_000)
+let b = fromUnix(1_500_000_000)
+doAssert b - a == 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
+
+proc staticZoneInfoFromTz(adjTime: Time): ZonedTIme =
+  result.utcOffset = -7200
+  result.isDst = false
+  result.adjTime = adjTime
+
+let utcPlus2 = Timezone(zoneInfoFromUtc: staticZoneInfoFromUtc, zoneInfoFromTz: staticZoneInfoFromTz, name: "")
+
+block timezoneTests:
+  let dt = initDateTime(01, mJan, 2017, 12, 00, 00, utcPlus2)
+  doAssert $dt == "2017-01-01T12:00:00+02:00"
+  doAssert $dt.utc == "2017-01-01T10:00:00+00:00"
+  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
diff --git a/tests/stdlib/ttimes.nim b/tests/stdlib/ttimes.nim
index 84e00f8de..a6ac186cc 100644
--- a/tests/stdlib/ttimes.nim
+++ b/tests/stdlib/ttimes.nim
@@ -1,16 +1,17 @@
 # test the new time module
 discard """
   file: "ttimes.nim"
-  action: "run"
+  output: '''[Suite] ttimes
+'''
 """
 
 import
-  times, strutils
+  times, os, strutils, unittest
 
 # $ date --date='@2147483647'
 # Tue 19 Jan 03:14:07 GMT 2038
 
-proc checkFormat(t: TimeInfo, format, expected: string) =
+proc checkFormat(t: DateTime, format, expected: string) =
   let actual = t.format(format)
   if actual != expected:
     echo "Formatting failure!"
@@ -18,7 +19,7 @@ proc checkFormat(t: TimeInfo, format, expected: string) =
     echo "actual  : ", actual
     doAssert false
 
-let t = getGMTime(fromSeconds(2147483647))
+let t = fromUnix(2147483647).utc
 t.checkFormat("ddd dd MMM hh:mm:ss yyyy", "Tue 19 Jan 03:14:07 2038")
 t.checkFormat("ddd ddMMMhh:mm:ssyyyy", "Tue 19Jan03:14:072038")
 t.checkFormat("d dd ddd dddd h hh H HH m mm M MM MMM MMMM s" &
@@ -27,107 +28,41 @@ t.checkFormat("d dd ddd dddd h hh H HH m mm M MM MMM MMMM s" &
 
 t.checkFormat("yyyyMMddhhmmss", "20380119031407")
 
-let t2 = getGMTime(fromSeconds(160070789)) # Mon 27 Jan 16:06:29 GMT 1975
+let t2 = fromUnix(160070789).utc # Mon 27 Jan 16:06:29 GMT 1975
 t2.checkFormat("d dd ddd dddd h hh H HH m mm M MM MMM MMMM s" &
   " ss t tt y yy yyy yyyy yyyyy z zz zzz",
   "27 27 Mon Monday 4 04 16 16 6 06 1 01 Jan January 29 29 P PM 5 75 975 1975 01975 +0 +00 +00:00")
 
-var t4 = getGMTime(fromSeconds(876124714)) # Mon  6 Oct 08:58:34 BST 1997
+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")
 
-proc parseTest(s, f, sExpected: string, ydExpected: int) =
-  let
-    parsed = s.parse(f)
-    parsedStr = $getGMTime(toTime(parsed))
-  if parsedStr != sExpected:
-    echo "Parsing failure!"
-    echo "expected: ", sExpected
-    echo "actual  : ", parsedStr
-    doAssert false
-  doAssert(parsed.yearday == ydExpected)
-proc parseTestTimeOnly(s, f, sExpected: string) =
-  doAssert(sExpected in $s.parse(f))
-
-# because setting a specific timezone for testing is platform-specific, we use
-# explicit timezone offsets in all tests.
-
-parseTest("Tuesday at 09:04am on Dec 15, 2015 +0",
-    "dddd at hh:mmtt on MMM d, yyyy z", "2015-12-15T09:04:00+00:00", 348)
-# ANSIC       = "Mon Jan _2 15:04:05 2006"
-parseTest("Thu Jan 12 15:04:05 2006 +0", "ddd MMM dd HH:mm:ss yyyy z",
-    "2006-01-12T15:04:05+00:00", 11)
-# UnixDate    = "Mon Jan _2 15:04:05 MST 2006"
-parseTest("Thu Jan 12 15:04:05 2006 +0", "ddd MMM dd HH:mm:ss yyyy z",
-    "2006-01-12T15:04:05+00:00", 11)
-# RubyDate    = "Mon Jan 02 15:04:05 -0700 2006"
-parseTest("Mon Feb 29 15:04:05 -07:00 2016 +0", "ddd MMM dd HH:mm:ss zzz yyyy z",
-    "2016-02-29T15:04:05+00:00", 59) # leap day
-# RFC822      = "02 Jan 06 15:04 MST"
-parseTest("12 Jan 16 15:04 +0", "dd MMM yy HH:mm z",
-    "2016-01-12T15:04:00+00:00", 11)
-# RFC822Z     = "02 Jan 06 15:04 -0700" # RFC822 with numeric zone
-parseTest("01 Mar 16 15:04 -07:00", "dd MMM yy HH:mm zzz",
-    "2016-03-01T22:04:00+00:00", 60) # day after february in leap year
-# RFC850      = "Monday, 02-Jan-06 15:04:05 MST"
-parseTest("Monday, 12-Jan-06 15:04:05 +0", "dddd, dd-MMM-yy HH:mm:ss z",
-    "2006-01-12T15:04:05+00:00", 11)
-# RFC1123     = "Mon, 02 Jan 2006 15:04:05 MST"
-parseTest("Sun, 01 Mar 2015 15:04:05 +0", "ddd, dd MMM yyyy HH:mm:ss z",
-    "2015-03-01T15:04:05+00:00", 59) # day after february in non-leap year
-# RFC1123Z    = "Mon, 02 Jan 2006 15:04:05 -0700" # RFC1123 with numeric zone
-parseTest("Thu, 12 Jan 2006 15:04:05 -07:00", "ddd, dd MMM yyyy HH:mm:ss zzz",
-    "2006-01-12T22:04:05+00:00", 11)
-# RFC3339     = "2006-01-02T15:04:05Z07:00"
-parseTest("2006-01-12T15:04:05Z-07:00", "yyyy-MM-ddTHH:mm:ssZzzz",
-    "2006-01-12T22:04:05+00:00", 11)
-parseTest("2006-01-12T15:04:05Z-07:00", "yyyy-MM-dd'T'HH:mm:ss'Z'zzz",
-    "2006-01-12T22:04:05+00:00", 11)
-# RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"
-parseTest("2006-01-12T15:04:05.999999999Z-07:00",
-    "yyyy-MM-ddTHH:mm:ss.999999999Zzzz", "2006-01-12T22:04:05+00:00", 11)
-for tzFormat in ["z", "zz", "zzz"]:
-  # formatting timezone as 'Z' for UTC
-  parseTest("2001-01-12T22:04:05Z", "yyyy-MM-dd'T'HH:mm:ss" & tzFormat,
-      "2001-01-12T22:04:05+00:00", 11)
-# Kitchen     = "3:04PM"
-parseTestTimeOnly("3:04PM", "h:mmtt", "15:04:00")
-#when not defined(testing):
-#  echo "Kitchen: " & $s.parse(f)
-#  var ti = timeToTimeInfo(getTime())
-#  echo "Todays date after decoding: ", ti
-#  var tint = timeToTimeInterval(getTime())
-#  echo "Todays date after decoding to interval: ", tint
-
 # checking dayOfWeek matches known days
-doAssert getDayOfWeek(21, 9, 1900) == dFri
-doAssert getDayOfWeek(1, 1, 1970) == dThu
-doAssert getDayOfWeek(21, 9, 1970) == dMon
-doAssert getDayOfWeek(1, 1, 2000) == dSat
-doAssert getDayOfWeek(1, 1, 2021) == dFri
-# Julian tests
-doAssert getDayOfWeekJulian(21, 9, 1900) == dFri
-doAssert getDayOfWeekJulian(21, 9, 1970) == dMon
-doAssert getDayOfWeekJulian(1, 1, 2000) == dSat
-doAssert getDayOfWeekJulian(1, 1, 2021) == dFri
-
-# toSeconds tests with GM timezone
-let t4L = getGMTime(fromSeconds(876124714))
-doAssert toSeconds(toTime(t4L)) == 876124714
-doAssert toSeconds(toTime(t4L)) + t4L.timezone.float == toSeconds(toTime(t4))
+doAssert getDayOfWeek(01, mJan, 0000) == dSat
+doAssert getDayOfWeek(01, mJan, -0023) == dSat
+doAssert getDayOfWeek(21, mSep, 1900) == dFri
+doAssert getDayOfWeek(01, mJan, 1970) == dThu
+doAssert getDayOfWeek(21, mSep, 1970) == dMon
+doAssert getDayOfWeek(01, mJan, 2000) == dSat
+doAssert getDayOfWeek(01, mJan, 2021) == dFri
+
+# toUnix tests with GM timezone
+let t4L = fromUnix(876124714).utc
+doAssert toUnix(toTime(t4L)) == 876124714
+doAssert toUnix(toTime(t4L)) + t4L.utcOffset == toUnix(toTime(t4))
 
 # adding intervals
 var
-  a1L = toSeconds(toTime(t4L + initInterval(hours = 1))) + t4L.timezone.float
-  a1G = toSeconds(toTime(t4)) + 60.0 * 60.0
+  a1L = toUnix(toTime(t4L + initInterval(hours = 1))) + t4L.utcOffset
+  a1G = toUnix(toTime(t4)) + 60 * 60
 doAssert a1L == a1G
 
 # subtracting intervals
-a1L = toSeconds(toTime(t4L - initInterval(hours = 1))) + t4L.timezone.float
-a1G = toSeconds(toTime(t4)) - (60.0 * 60.0)
+a1L = toUnix(toTime(t4L - initInterval(hours = 1))) + t4L.utcOffset
+a1G = toUnix(toTime(t4)) - (60 * 60)
 doAssert a1L == a1G
 
 # add/subtract TimeIntervals and Time/TimeInfo
@@ -143,45 +78,16 @@ doAssert ti1 == getTime()
 ti1 += 1.days
 doAssert ti1 == getTime() + 1.days
 
-# overflow of TimeIntervals on initalisation
-doAssert initInterval(milliseconds = 25000) == initInterval(seconds = 25)
-doAssert initInterval(seconds = 65) == initInterval(seconds = 5, minutes = 1)
-doAssert initInterval(hours = 25) == initInterval(hours = 1, days = 1)
-doAssert initInterval(months = 13) == initInterval(months = 1, years = 1)
-
 # Bug with adding a day to a Time
 let day = 24.hours
 let tomorrow = getTime() + day
 doAssert tomorrow - getTime() == 60*60*24
 
-doAssert milliseconds(1000 * 60) == minutes(1)
-doAssert milliseconds(1000 * 60 * 60) == hours(1)
-doAssert milliseconds(1000 * 60 * 60 * 24) == days(1)
-doAssert seconds(60 * 60) == hours(1)
-doAssert seconds(60 * 60 * 24) == days(1)
-doAssert seconds(60 * 60 + 65) == (hours(1) + minutes(1) + seconds(5))
-
-# Bug with parse not setting DST properly if the current local DST differs from
-# the date being parsed. Need to test parse dates both in and out of DST. We
-# are testing that be relying on the fact that tranforming a TimeInfo to a Time
-# and back again will correctly set the DST value. With the incorrect parse
-# behavior this will introduce a one hour offset from the named time and the
-# parsed time if the DST value differs between the current time and the date we
-# are parsing.
-#
-# Unfortunately these tests depend on the locale of the system in which they
-# are run. They will not be meaningful when run in a locale without DST. They
-# also assume that Jan. 1 and Jun. 1 will have differing isDST values.
-let dstT1 = parse("2016-01-01 00:00:00", "yyyy-MM-dd HH:mm:ss")
-let dstT2 = parse("2016-06-01 00:00:00", "yyyy-MM-dd HH:mm:ss")
-doAssert dstT1 == getLocalTime(toTime(dstT1))
-doAssert dstT2 == getLocalTime(toTime(dstT2))
-
 # Comparison between Time objects should be detected by compiler
 # as 'noSideEffect'.
 proc cmpTimeNoSideEffect(t1: Time, t2: Time): bool {.noSideEffect.} =
   result = t1 == t2
-doAssert cmpTimeNoSideEffect(0.fromSeconds, 0.fromSeconds)
+doAssert cmpTimeNoSideEffect(0.fromUnix, 0.fromUnix)
 # Additionally `==` generic for seq[T] has explicit 'noSideEffect' pragma
 # so we can check above condition by comparing seq[Time] sequences
 let seqA: seq[Time] = @[]
@@ -195,49 +101,197 @@ for tz in [
     (-1800, "+0", "+00", "+00:30"), # half an hour
     (7200, "-2", "-02", "-02:00"), # positive
     (38700, "-10", "-10", "-10:45")]: # positive with three quaters hour
-  let ti = TimeInfo(monthday: 1, timezone: tz[0])
+  let ti = DateTime(month: mJan, monthday: 1, utcOffset: tz[0])
   doAssert ti.format("z") == tz[1]
   doAssert ti.format("zz") == tz[2]
   doAssert ti.format("zzz") == tz[3]
 
-block formatDst:
-  var ti = TimeInfo(monthday: 1, isDst: true)
-
-  # BST
-  ti.timezone = 0
-  doAssert ti.format("z") == "+1"
-  doAssert ti.format("zz") == "+01"
-  doAssert ti.format("zzz") == "+01:00"
-
-  # EDT
-  ti.timezone = 5 * 60 * 60 
-  doAssert ti.format("z") == "-4"
-  doAssert ti.format("zz") == "-04"
-  doAssert ti.format("zzz") == "-04:00"
-
-block dstTest:
-  let nonDst = TimeInfo(year: 2015, month: mJan, monthday: 01, yearday: 0,
-      weekday: dThu, hour: 00, minute: 00, second: 00, isDST: false, timezone: 0)
-  var dst = nonDst
-  dst.isDst = true
-  # note that both isDST == true and isDST == false are valid here because
-  # DST is in effect on January 1st in some southern parts of Australia.
-  # FIXME: Fails in UTC
-  # doAssert nonDst.toTime() - dst.toTime() == 3600
-  doAssert nonDst.format("z") == "+0"
-  doAssert dst.format("z") == "+1"
-
-  # parsing will set isDST in relation to the local time. We take a date in
-  # January and one in July to maximize the probability to hit one date with DST
-  # and one without on the local machine. However, this is not guaranteed.
-  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) == fromSeconds(1451962800)
-  doAssert toTime(parsedJul) == fromSeconds(1467342000)
-
 block countLeapYears:
   # 1920, 2004 and 2020 are leap years, and should be counted starting at the following year
   doAssert countLeapYears(1920) + 1 == countLeapYears(1921)
   doAssert countLeapYears(2004) + 1 == countLeapYears(2005)
-  doAssert countLeapYears(2020) + 1 == countLeapYears(2021)
\ No newline at end of file
+  doAssert countLeapYears(2020) + 1 == countLeapYears(2021)
+
+block timezoneConversion:
+  var l = now()
+  let u = l.utc
+  l = u.local
+
+  doAssert l.timezone == local()
+  doAssert u.timezone == utc()
+
+template parseTest(s, f, sExpected: string, ydExpected: int) =
+  let
+    parsed = s.parse(f, utc())
+    parsedStr = $parsed
+  check parsedStr == sExpected
+  if parsed.yearday != ydExpected:
+    echo s
+    echo parsed.repr
+    echo parsed.yearday, " exp: ", ydExpected
+  check(parsed.yearday == ydExpected)
+
+template parseTestTimeOnly(s, f, sExpected: string) =
+  check sExpected in $s.parse(f, utc())
+
+# because setting a specific timezone for testing is platform-specific, we use
+# explicit timezone offsets in all tests.
+template runTimezoneTests() =
+  parseTest("Tuesday at 09:04am on Dec 15, 2015 +0",
+      "dddd at hh:mmtt on MMM d, yyyy z", "2015-12-15T09:04:00+00:00", 348)
+  # ANSIC       = "Mon Jan _2 15:04:05 2006"
+  parseTest("Thu Jan 12 15:04:05 2006 +0", "ddd MMM dd HH:mm:ss yyyy z",
+      "2006-01-12T15:04:05+00:00", 11)
+  # UnixDate    = "Mon Jan _2 15:04:05 MST 2006"
+  parseTest("Thu Jan 12 15:04:05 2006 +0", "ddd MMM dd HH:mm:ss yyyy z",
+      "2006-01-12T15:04:05+00:00", 11)
+  # RubyDate    = "Mon Jan 02 15:04:05 -0700 2006"
+  parseTest("Mon Feb 29 15:04:05 -07:00 2016 +0", "ddd MMM dd HH:mm:ss zzz yyyy z",
+      "2016-02-29T15:04:05+00:00", 59) # leap day
+  # RFC822      = "02 Jan 06 15:04 MST"
+  parseTest("12 Jan 16 15:04 +0", "dd MMM yy HH:mm z",
+      "2016-01-12T15:04:00+00:00", 11)
+  # RFC822Z     = "02 Jan 06 15:04 -0700" # RFC822 with numeric zone
+  parseTest("01 Mar 16 15:04 -07:00", "dd MMM yy HH:mm zzz",
+      "2016-03-01T22:04:00+00:00", 60) # day after february in leap year
+  # RFC850      = "Monday, 02-Jan-06 15:04:05 MST"
+  parseTest("Monday, 12-Jan-06 15:04:05 +0", "dddd, dd-MMM-yy HH:mm:ss z",
+      "2006-01-12T15:04:05+00:00", 11)
+  # RFC1123     = "Mon, 02 Jan 2006 15:04:05 MST"
+  parseTest("Sun, 01 Mar 2015 15:04:05 +0", "ddd, dd MMM yyyy HH:mm:ss z",
+      "2015-03-01T15:04:05+00:00", 59) # day after february in non-leap year
+  # RFC1123Z    = "Mon, 02 Jan 2006 15:04:05 -0700" # RFC1123 with numeric zone
+  parseTest("Thu, 12 Jan 2006 15:04:05 -07:00", "ddd, dd MMM yyyy HH:mm:ss zzz",
+      "2006-01-12T22:04:05+00:00", 11)
+  # RFC3339     = "2006-01-02T15:04:05Z07:00"
+  parseTest("2006-01-12T15:04:05Z-07:00", "yyyy-MM-ddTHH:mm:ssZzzz",
+      "2006-01-12T22:04:05+00:00", 11)
+  parseTest("2006-01-12T15:04:05Z-07:00", "yyyy-MM-dd'T'HH:mm:ss'Z'zzz",
+      "2006-01-12T22:04:05+00:00", 11)
+  # RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"
+  parseTest("2006-01-12T15:04:05.999999999Z-07:00",
+      "yyyy-MM-ddTHH:mm:ss.999999999Zzzz", "2006-01-12T22:04:05+00:00", 11)
+  for tzFormat in ["z", "zz", "zzz"]:
+    # formatting timezone as 'Z' for UTC
+    parseTest("2001-01-12T22:04:05Z", "yyyy-MM-dd'T'HH:mm:ss" & tzFormat,
+        "2001-01-12T22:04:05+00:00", 11)
+  # Kitchen     = "3:04PM"
+  parseTestTimeOnly("3:04PM", "h:mmtt", "15:04:00")
+  #when not defined(testing):
+  #  echo "Kitchen: " & $s.parse(f)
+  #  var ti = timeToTimeInfo(getTime())
+  #  echo "Todays date after decoding: ", ti
+  #  var tint = timeToTimeInterval(getTime())
+  #  echo "Todays date after decoding to interval: ", tint
+
+  # Bug with parse not setting DST properly if the current local DST differs from
+  # the date being parsed. Need to test parse dates both in and out of DST. We
+  # are testing that be relying on the fact that tranforming a TimeInfo to a Time
+  # and back again will correctly set the DST value. With the incorrect parse
+  # behavior this will introduce a one hour offset from the named time and the
+  # parsed time if the DST value differs between the current time and the date we
+  # are parsing.
+  let dstT1 = parse("2016-01-01 00:00:00", "yyyy-MM-dd HH:mm:ss")
+  let dstT2 = parse("2016-06-01 00:00:00", "yyyy-MM-dd HH:mm:ss")
+  check dstT1 == toTime(dstT1).local
+  check dstT2 == toTime(dstT2).local
+
+  block dstTest:
+    # parsing will set isDST in relation to the local time. We take a date in
+    # January and one in July to maximize the probability to hit one date with DST
+    # and one without on the local machine. However, this is not guaranteed.
+    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)
+
+suite "ttimes":
+
+  # Generate tests for multiple timezone files where available
+  # Set the TZ env var for each test
+  when defined(Linux) or defined(macosx):
+    const tz_dir = "/usr/share/zoneinfo"
+    const f = "yyyy-MM-dd HH:mm zzz"
+    
+    let orig_tz = getEnv("TZ")
+    var tz_cnt = 0
+    for tz_fn in walkFiles(tz_dir & "/*"):
+      if symlinkExists(tz_fn) or tz_fn.endsWith(".tab") or
+          tz_fn.endsWith(".list"):
+        continue
+
+      test "test for " & tz_fn:
+        tz_cnt.inc
+        putEnv("TZ", tz_fn)
+        runTimezoneTests()
+
+    test "enough timezone files tested":
+      check tz_cnt > 10
+
+    test "dst handling":
+      putEnv("TZ", "Europe/Stockholm")
+      # In case of an impossible time, the time is moved to after the impossible time period
+      check initDateTime(26, mMar, 2017, 02, 30, 00).format(f) == "2017-03-26 03:30 +02:00"
+      # In case of an ambiguous time, the earlier time is choosen
+      check initDateTime(29, mOct, 2017, 02, 00, 00).format(f) == "2017-10-29 02:00 +02:00"
+      # These are just dates on either side of the dst switch
+      check initDateTime(29, mOct, 2017, 01, 00, 00).format(f) == "2017-10-29 01:00 +02:00"
+      check initDateTime(29, mOct, 2017, 01, 00, 00).isDst
+      check initDateTime(29, mOct, 2017, 03, 01, 00).format(f) == "2017-10-29 03:01 +01:00"
+      check (not initDateTime(29, mOct, 2017, 03, 01, 00).isDst)
+      
+      check initDateTime(21, mOct, 2017, 01, 00, 00).format(f) == "2017-10-21 01:00 +02:00"
+
+    test "issue #6520":
+      putEnv("TZ", "Europe/Stockholm")
+      var local = fromUnix(1469275200).local
+      var utc = fromUnix(1469275200).utc
+
+      let claimedOffset = 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
+
+    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"
+
+    test "datetime before epoch":
+      check $fromUnix(-2147483648).utc == "1901-12-13T20:45:52+00:00"
+
+    test "adding/subtracting time across dst":
+      putenv("TZ", "Europe/Stockholm")
+
+      let dt1 = initDateTime(26, mMar, 2017, 03, 00, 00)
+      check $(dt1 - 1.seconds) == "2017-03-26T01:59:59+01:00"
+
+      var dt2 = initDateTime(29, mOct, 2017, 02, 59, 59)
+      check  $(dt2 + 1.seconds) == "2017-10-29T02:00:00+01:00"
+
+    putEnv("TZ", orig_tz)
+
+  else:
+    # not on Linux or macosx: run one parseTest only
+    test "parseTest":
+      runTimezoneTests()
+
+  test "isLeapYear":
+    check isLeapYear(2016)
+    check (not isLeapYear(2015))
+    check isLeapYear(2000)
+    check (not isLeapYear(1900))
+
+  test "subtract months":
+    var dt = initDateTime(1, mFeb, 2017, 00, 00, 00, utc())
+    check $(dt - 1.months) == "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"
+    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"
\ No newline at end of file