Merge branch 'devel' of github.com:nim-lang/Nim into devel

This commit is contained in:
Araq 2017-12-21 10:03:56 +01:00
commit 218679fc95
24 changed files with 1363 additions and 1041 deletions

View file

@ -39,36 +39,37 @@
what to return if the environment variable does not exist. what to return if the environment variable does not exist.
- Bodies of ``for`` loops now get their own scope: - Bodies of ``for`` loops now get their own scope:
.. code-block:: nim ```nim
# now compiles: # now compiles:
for i in 0..4: for i in 0..4:
let i = i + 1 let i = i + 1
echo i echo i
```
- The parsing rules of ``if`` expressions were changed so that multiple - The parsing rules of ``if`` expressions were changed so that multiple
statements are allowed in the branches. We found few code examples that statements are allowed in the branches. We found few code examples that
now fail because of this change, but here is one: now fail because of this change, but here is one:
.. code-block:: nim ```nim
t[ti] = if exp_negative: '-' else: '+'; inc(ti) t[ti] = if exp_negative: '-' else: '+'; inc(ti)
```
This now needs to be written as: This now needs to be written as:
.. code-block:: nim ```nim
t[ti] = (if exp_negative: '-' else: '+'); inc(ti) t[ti] = (if exp_negative: '-' else: '+'); inc(ti)
```
- To make Nim even more robust the system iterators ``..`` and ``countup`` - To make Nim even more robust the system iterators ``..`` and ``countup``
now only accept a single generic type ``T``. This means the following code now only accept a single generic type ``T``. This means the following code
doesn't die with an "out of range" error anymore: doesn't die with an "out of range" error anymore:
.. code-block:: nim ```nim
var b = 5.Natural var b = 5.Natural
var a = -5 var a = -5
for i in a..b: for i in a..b:
echo i echo i
```
- ``formatFloat``/``formatBiggestFloat`` now support formatting floats with zero - ``formatFloat``/``formatBiggestFloat`` now support formatting floats with zero
precision digits. The previous ``precision = 0`` behavior (default formatting) precision digits. The previous ``precision = 0`` behavior (default formatting)
@ -139,3 +140,6 @@ This now needs to be written as:
- For string formatting / interpolation a new module - For string formatting / interpolation a new module
called [strformat](https://nim-lang.org/docs/strformat.html) has been added called [strformat](https://nim-lang.org/docs/strformat.html) has been added
to the stdlib. to the stdlib.
- codegenDecl pragma now works for the JavaScript backend. It returns an empty string for
function return type placeholders.
- Asynchronous programming for the JavaScript backend using the `asyncjs` module.

View file

@ -175,8 +175,17 @@ proc put(g: var TSrcGen, kind: TTokType, s: string) =
proc toNimChar(c: char): string = proc toNimChar(c: char): string =
case c case c
of '\0': result = "\\0" of '\0': result = "\\x00" # not "\\0" to avoid ambiguous cases like "\\012".
of '\x01'..'\x1F', '\x80'..'\xFF': result = "\\x" & strutils.toHex(ord(c), 2) of '\a': result = "\\a" # \x07
of '\b': result = "\\b" # \x08
of '\t': result = "\\t" # \x09
of '\L': result = "\\L" # \x0A
of '\v': result = "\\v" # \x0B
of '\f': result = "\\f" # \x0C
of '\c': result = "\\c" # \x0D
of '\e': result = "\\e" # \x1B
of '\x01'..'\x06', '\x0E'..'\x1A', '\x1C'..'\x1F', '\x80'..'\xFF':
result = "\\x" & strutils.toHex(ord(c), 2)
of '\'', '\"', '\\': result = '\\' & c of '\'', '\"', '\\': result = '\\' & c
else: result = c & "" else: result = c & ""

View file

@ -433,6 +433,8 @@ Modules for JS backend
* `jsffi <jsffi.html>`_ * `jsffi <jsffi.html>`_
Types and macros for easier interaction with JavaScript. Types and macros for easier interaction with JavaScript.
* `asyncjs <asyncjs.html>`_
Types and macros for writing asynchronous procedures in JavaScript.
Deprecated modules Deprecated modules
------------------ ------------------

110
lib/js/asyncjs.nim Normal file
View file

@ -0,0 +1,110 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2017 Nim Authors
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
## This module implements types and macros for writing asynchronous code
## for the JS backend. It provides tools for interaction with JavaScript async API-s
## and libraries, writing async procedures in Nim and converting callback-based code
## to promises.
##
## A Nim procedure is asynchronous when it includes the ``{.async.}`` pragma. It
## should always have a ``Future[T]`` return type or not have a return type at all.
## A ``Future[void]`` return type is assumed by default.
##
## This is roughly equivalent to the ``async`` keyword in JavaScript code.
##
## .. code-block:: nim
## proc loadGame(name: string): Future[Game] {.async.} =
## # code
##
## should be equivalent to
##
## .. code-block:: javascript
## async function loadGame(name) {
## // code
## }
##
## A call to an asynchronous procedure usually needs ``await`` to wait for
## the completion of the ``Future``.
##
## .. code-block:: nim
## var game = await loadGame(name)
##
## Often, you might work with callback-based API-s. You can wrap them with
## asynchronous procedures using promises and ``newPromise``:
##
## .. code-block:: nim
## proc loadGame(name: string): Future[Game] =
## var promise = newPromise() do (resolve: proc(response: Game)):
## cbBasedLoadGame(name) do (game: Game):
## resolve(game)
## return promise
##
## Forward definitions work properly, you just don't need to add the ``{.async.}`` pragma:
##
## .. code-block:: nim
## proc loadGame(name: string): Future[Game]
##
## JavaScript compatibility
## ~~~~~~~~~~~~~~~~~~~~~~~~~
##
## Nim currently generates `async/await` JavaScript code which is supported in modern
## EcmaScript and most modern versions of browsers, Node.js and Electron.
## If you need to use this module with older versions of JavaScript, you can
## use a tool that backports the resulting JavaScript code, as babel.
import jsffi
import macros
when not defined(js) and not defined(nimdoc) and not defined(nimsuggest):
{.fatal: "Module asyncjs is designed to be used with the JavaScript backend.".}
type
Future*[T] = ref object
future*: T
## Wraps the return type of an asynchronous procedure.
PromiseJs* {.importcpp: "Promise".} = ref object
## A JavaScript Promise
proc replaceReturn(node: var NimNode) =
var z = 0
for s in node:
var son = node[z]
if son.kind == nnkReturnStmt:
node[z] = nnkReturnStmt.newTree(nnkCall.newTree(ident("jsResolve"), son[0]))
elif son.kind == nnkAsgn and son[0].kind == nnkIdent and $son[0] == "result":
node[z] = nnkAsgn.newTree(son[0], nnkCall.newTree(ident("jsResolve"), son[1]))
else:
replaceReturn(son)
inc z
proc generateJsasync(arg: NimNode): NimNode =
assert arg.kind == nnkProcDef
result = arg
if arg.params[0].kind == nnkEmpty:
result.params[0] = nnkBracketExpr.newTree(ident("Future"), ident("void"))
var code = result.body
replaceReturn(code)
result.body = nnkStmtList.newTree()
var q = quote:
proc await[T](f: Future[T]): T {.importcpp: "(await #)".}
proc jsResolve[T](a: T): Future[T] {.importcpp: "#".}
result.body.add(q)
for child in code:
result.body.add(child)
result.pragma = quote:
{.codegenDecl: "async function $2($3)".}
macro async*(arg: untyped): untyped =
## Macro which converts normal procedures into
## javascript-compatible async procedures
generateJsasync(arg)
proc newPromise*[T](handler: proc(resolve: proc(response: T))): Future[T] {.importcpp: "(new Promise(#))".}
## A helper for wrapping callback-based functions
## into promises and async procedures

View file

@ -609,11 +609,12 @@ proc clock_nanosleep*(a1: ClockId, a2: cint, a3: var Timespec,
proc clock_settime*(a1: ClockId, a2: var Timespec): cint {. proc clock_settime*(a1: ClockId, a2: var Timespec): cint {.
importc, header: "<time.h>".} 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*(a1: var Time): cstring {.importc, header: "<time.h>".}
proc ctime_r*(a1: var Time, a2: cstring): 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 difftime*(a1, a2: Time): cdouble {.importc, header: "<time.h>".}
proc getdate*(a1: cstring): ptr Tm {.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*(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 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>".} proc localtime*(a1: var Time): ptr Tm {.importc, header: "<time.h>".}

View file

@ -12,8 +12,6 @@
# To be included from posix.nim! # To be included from posix.nim!
from times import Time
const const
hasSpawnH = not defined(haiku) # should exist for every Posix system nowadays hasSpawnH = not defined(haiku) # should exist for every Posix system nowadays
hasAioH = defined(linux) hasAioH = defined(linux)
@ -40,13 +38,15 @@ type
const SIG_HOLD* = cast[SigHandler](2) const SIG_HOLD* = cast[SigHandler](2)
type type
Time* {.importc: "time_t", header: "<time.h>".} = distinct clong
Timespec* {.importc: "struct timespec", Timespec* {.importc: "struct timespec",
header: "<time.h>", final, pure.} = object ## struct timespec header: "<time.h>", final, pure.} = object ## struct timespec
tv_sec*: Time ## Seconds. tv_sec*: Time ## Seconds.
tv_nsec*: clong ## Nanoseconds. tv_nsec*: clong ## Nanoseconds.
Dirent* {.importc: "struct dirent", 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_ino*: Ino
d_off*: Off d_off*: Off
d_reclen*: cushort d_reclen*: cushort

View file

@ -9,8 +9,6 @@
{.deadCodeElim:on.} {.deadCodeElim:on.}
from times import Time
const const
hasSpawnH = not defined(haiku) # should exist for every Posix system nowadays hasSpawnH = not defined(haiku) # should exist for every Posix system nowadays
hasAioH = defined(linux) hasAioH = defined(linux)
@ -36,6 +34,8 @@ type
{.deprecated: [TSocketHandle: SocketHandle].} {.deprecated: [TSocketHandle: SocketHandle].}
type type
Time* {.importc: "time_t", header: "<time.h>".} = distinct int
Timespec* {.importc: "struct timespec", Timespec* {.importc: "struct timespec",
header: "<time.h>", final, pure.} = object ## struct timespec header: "<time.h>", final, pure.} = object ## struct timespec
tv_sec*: Time ## Seconds. tv_sec*: Time ## Seconds.
@ -209,24 +209,24 @@ type
st_gid*: Gid ## Group ID of file. st_gid*: Gid ## Group ID of file.
st_rdev*: Dev ## Device ID (if file is character or block special). st_rdev*: Dev ## Device ID (if file is character or block special).
st_size*: Off ## For regular files, the file size in bytes. st_size*: Off ## For regular files, the file size in bytes.
## For symbolic links, the length in bytes of the ## For symbolic links, the length in bytes of the
## pathname contained in the symbolic link. ## pathname contained in the symbolic link.
## For a shared memory object, the length in bytes. ## For a shared memory object, the length in bytes.
## For a typed 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 ## For other file types, the use of this field is
## unspecified. ## unspecified.
when defined(macosx) or defined(android): when defined(macosx) or defined(android):
st_atime*: Time ## Time of last access. st_atime*: Time ## Time of last access.
st_mtime*: Time ## Time of last data modification. st_mtime*: Time ## Time of last data modification.
st_ctime*: Time ## Time of last status change. st_ctime*: Time ## Time of last status change.
else: else:
st_atim*: Timespec ## Time of last access. st_atim*: Timespec ## Time of last access.
st_mtim*: Timespec ## Time of last data modification. st_mtim*: Timespec ## Time of last data modification.
st_ctim*: Timespec ## Time of last status change. st_ctim*: Timespec ## Time of last status change.
st_blksize*: Blksize ## A file system-specific preferred I/O block size st_blksize*: Blksize ## A file system-specific preferred I/O block size
## for this object. In some file system types, this ## for this object. In some file system types, this
## may vary from file to file. ## may vary from file to file.
st_blocks*: Blkcnt ## Number of blocks allocated for this object. st_blocks*: Blkcnt ## Number of blocks allocated for this object.
Statvfs* {.importc: "struct statvfs", header: "<sys/statvfs.h>", Statvfs* {.importc: "struct statvfs", header: "<sys/statvfs.h>",

View file

@ -168,18 +168,20 @@ type
timers*: HeapQueue[tuple[finishAt: float, fut: Future[void]]] timers*: HeapQueue[tuple[finishAt: float, fut: Future[void]]]
callbacks*: Deque[proc ()] callbacks*: Deque[proc ()]
proc processTimers(p: PDispatcherBase) {.inline.} = proc processTimers(p: PDispatcherBase; didSomeWork: var bool) {.inline.} =
#Process just part if timers at a step #Process just part if timers at a step
var count = p.timers.len var count = p.timers.len
let t = epochTime() let t = epochTime()
while count > 0 and t >= p.timers[0].finishAt: while count > 0 and t >= p.timers[0].finishAt:
p.timers.pop().fut.complete() p.timers.pop().fut.complete()
dec count dec count
didSomeWork = true
proc processPendingCallbacks(p: PDispatcherBase) = proc processPendingCallbacks(p: PDispatcherBase; didSomeWork: var bool) =
while p.callbacks.len > 0: while p.callbacks.len > 0:
var cb = p.callbacks.popFirst() var cb = p.callbacks.popFirst()
cb() cb()
didSomeWork = true
proc adjustedTimeout(p: PDispatcherBase, timeout: int): int {.inline.} = proc adjustedTimeout(p: PDispatcherBase, timeout: int): int {.inline.} =
# If dispatcher has active timers this proc returns the timeout # If dispatcher has active timers this proc returns the timeout
@ -284,14 +286,13 @@ when defined(windows) or defined(nimdoc):
let p = getGlobalDispatcher() let p = getGlobalDispatcher()
p.handles.len != 0 or p.timers.len != 0 or p.callbacks.len != 0 p.handles.len != 0 or p.timers.len != 0 or p.callbacks.len != 0
proc poll*(timeout = 500) = proc runOnce(timeout = 500): bool =
## Waits for completion events and processes them. Raises ``ValueError``
## if there are no pending operations.
let p = getGlobalDispatcher() let p = getGlobalDispatcher()
if p.handles.len == 0 and p.timers.len == 0 and p.callbacks.len == 0: if p.handles.len == 0 and p.timers.len == 0 and p.callbacks.len == 0:
raise newException(ValueError, raise newException(ValueError,
"No handles or timers registered in dispatcher.") "No handles or timers registered in dispatcher.")
result = false
if p.handles.len != 0: if p.handles.len != 0:
let at = p.adjustedTimeout(timeout) let at = p.adjustedTimeout(timeout)
var llTimeout = var llTimeout =
@ -304,6 +305,7 @@ when defined(windows) or defined(nimdoc):
let res = getQueuedCompletionStatus(p.ioPort, let res = getQueuedCompletionStatus(p.ioPort,
addr lpNumberOfBytesTransferred, addr lpCompletionKey, addr lpNumberOfBytesTransferred, addr lpCompletionKey,
cast[ptr POVERLAPPED](addr customOverlapped), llTimeout).bool cast[ptr POVERLAPPED](addr customOverlapped), llTimeout).bool
result = true
# http://stackoverflow.com/a/12277264/492186 # http://stackoverflow.com/a/12277264/492186
# TODO: http://www.serverframework.com/handling-multiple-pending-socket-read-and-write-operations.html # TODO: http://www.serverframework.com/handling-multiple-pending-socket-read-and-write-operations.html
@ -333,13 +335,14 @@ when defined(windows) or defined(nimdoc):
else: else:
if errCode.int32 == WAIT_TIMEOUT: if errCode.int32 == WAIT_TIMEOUT:
# Timed out # Timed out
discard result = false
else: raiseOSError(errCode) else: raiseOSError(errCode)
# Timer processing. # Timer processing.
processTimers(p) processTimers(p, result)
# Callback queue processing # Callback queue processing
processPendingCallbacks(p) processPendingCallbacks(p, result)
var acceptEx: WSAPROC_ACCEPTEX var acceptEx: WSAPROC_ACCEPTEX
var connectEx: WSAPROC_CONNECTEX var connectEx: WSAPROC_CONNECTEX
@ -1202,7 +1205,7 @@ else:
# descriptor was unregistered in callback via `unregister()`. # descriptor was unregistered in callback via `unregister()`.
discard discard
proc poll*(timeout = 500) = proc runOnce(timeout = 500): bool =
let p = getGlobalDispatcher() let p = getGlobalDispatcher()
when ioselSupportedPlatform: when ioselSupportedPlatform:
let customSet = {Event.Timer, Event.Signal, Event.Process, let customSet = {Event.Timer, Event.Signal, Event.Process,
@ -1212,6 +1215,7 @@ else:
raise newException(ValueError, raise newException(ValueError,
"No handles or timers registered in dispatcher.") "No handles or timers registered in dispatcher.")
result = false
if not p.selector.isEmpty(): if not p.selector.isEmpty():
var keys: array[64, ReadyKey] var keys: array[64, ReadyKey]
var count = p.selector.selectInto(p.adjustedTimeout(timeout), keys) var count = p.selector.selectInto(p.adjustedTimeout(timeout), keys)
@ -1224,20 +1228,24 @@ else:
if Event.Read in events or events == {Event.Error}: if Event.Read in events or events == {Event.Error}:
processBasicCallbacks(fd, readList) processBasicCallbacks(fd, readList)
result = true
if Event.Write in events or events == {Event.Error}: if Event.Write in events or events == {Event.Error}:
processBasicCallbacks(fd, writeList) processBasicCallbacks(fd, writeList)
result = true
if Event.User in events or events == {Event.Error}: if Event.User in events or events == {Event.Error}:
processBasicCallbacks(fd, readList) processBasicCallbacks(fd, readList)
custom = true custom = true
if rLength == 0: if rLength == 0:
p.selector.unregister(fd) p.selector.unregister(fd)
result = true
when ioselSupportedPlatform: when ioselSupportedPlatform:
if (customSet * events) != {}: if (customSet * events) != {}:
custom = true custom = true
processCustomCallbacks(fd) processCustomCallbacks(fd)
result = true
# because state `data` can be modified in callback we need to update # because state `data` can be modified in callback we need to update
# descriptor events with currently registered callbacks. # descriptor events with currently registered callbacks.
@ -1249,9 +1257,9 @@ else:
p.selector.updateHandle(SocketHandle(fd), newEvents) p.selector.updateHandle(SocketHandle(fd), newEvents)
# Timer processing. # Timer processing.
processTimers(p) processTimers(p, result)
# Callback queue processing # Callback queue processing
processPendingCallbacks(p) processPendingCallbacks(p, result)
proc recv*(socket: AsyncFD, size: int, proc recv*(socket: AsyncFD, size: int,
flags = {SocketFlag.SafeDisconn}): Future[string] = flags = {SocketFlag.SafeDisconn}): Future[string] =
@ -1474,6 +1482,19 @@ else:
data.readList.add(cb) data.readList.add(cb)
p.selector.registerEvent(SelectEvent(ev), data) p.selector.registerEvent(SelectEvent(ev), data)
proc drain*(timeout = 500) =
## Waits for completion events and processes them. Raises ``ValueError``
## if there are no pending operations. In contrast to ``poll`` this
## processes as many events as are available.
if runOnce(timeout):
while runOnce(0): discard
proc poll*(timeout = 500) =
## Waits for completion events and processes them. Raises ``ValueError``
## if there are no pending operations. This runs the underlying OS
## `epoll`:idx: or `kqueue`:idx: primitive only once.
discard runOnce()
# Common procedures between current and upcoming asyncdispatch # Common procedures between current and upcoming asyncdispatch
include includes.asynccommon include includes.asynccommon

View file

@ -51,7 +51,7 @@ proc setCookie*(key, value: string, domain = "", path = "",
if secure: result.add("; Secure") if secure: result.add("; Secure")
if httpOnly: result.add("; HttpOnly") if httpOnly: result.add("; HttpOnly")
proc setCookie*(key, value: string, expires: TimeInfo, proc setCookie*(key, value: string, expires: DateTime,
domain = "", path = "", noName = false, domain = "", path = "", noName = false,
secure = false, httpOnly = false): string = secure = false, httpOnly = false): string =
## Creates a command in the format of ## Creates a command in the format of
@ -63,9 +63,9 @@ proc setCookie*(key, value: string, expires: TimeInfo,
noname, secure, httpOnly) noname, secure, httpOnly)
when isMainModule: 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): when not defined(testing):
echo cookie echo cookie
let start = "Set-Cookie: test=value; Expires=" let start = "Set-Cookie: test=value; Expires="

View file

@ -277,15 +277,16 @@ proc registerTimer*[T](s: Selector[T], timeout: int, oneshot: bool,
var events = {Event.Timer} var events = {Event.Timer}
var epv = EpollEvent(events: EPOLLIN or EPOLLRDHUP) var epv = EpollEvent(events: EPOLLIN or EPOLLRDHUP)
epv.data.u64 = fdi.uint epv.data.u64 = fdi.uint
if oneshot: 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_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 new_ts.it_value.tv_nsec = (timeout %% 1_000) * 1_000_000
incl(events, Event.Oneshot) incl(events, Event.Oneshot)
epv.events = epv.events or EPOLLONESHOT epv.events = epv.events or EPOLLONESHOT
else: 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_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_sec = new_ts.it_interval.tv_sec
new_ts.it_value.tv_nsec = new_ts.it_interval.tv_nsec new_ts.it_value.tv_nsec = new_ts.it_interval.tv_nsec

View file

@ -452,10 +452,10 @@ proc selectInto*[T](s: Selector[T], timeout: int,
if timeout != -1: if timeout != -1:
if timeout >= 1000: 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 tv.tv_nsec = (timeout %% 1_000) * 1_000_000
else: else:
tv.tv_sec = 0.Time tv.tv_sec = posix.Time(0)
tv.tv_nsec = timeout * 1_000_000 tv.tv_nsec = timeout * 1_000_000
else: else:
ptv = nil ptv = nil

View file

@ -291,6 +291,8 @@ when not defined(JS):
## echo fmod(-2.5, 0.3) ## -0.1 ## echo fmod(-2.5, 0.3) ## -0.1
else: else:
proc trunc*(x: float32): float32 {.importc: "Math.trunc", nodecl.}
proc trunc*(x: float64): float64 {.importc: "Math.trunc", nodecl.}
proc floor*(x: float32): float32 {.importc: "Math.floor", nodecl.} proc floor*(x: float32): float32 {.importc: "Math.floor", nodecl.}
proc floor*(x: float64): float64 {.importc: "Math.floor", nodecl.} proc floor*(x: float64): float64 {.importc: "Math.floor", nodecl.}
proc ceil*(x: float32): float32 {.importc: "Math.ceil", nodecl.} proc ceil*(x: float32): float32 {.importc: "Math.ceil", nodecl.}

View file

@ -88,7 +88,7 @@ proc generatedTime*(oid: Oid): Time =
var tmp: int32 var tmp: int32
var dummy = oid.time var dummy = oid.time
bigEndian32(addr(tmp), addr(dummy)) bigEndian32(addr(tmp), addr(dummy))
result = Time(tmp) result = fromUnix(tmp)
when not defined(testing) and isMainModule: when not defined(testing) and isMainModule:
let xo = genOid() let xo = genOid()

View file

@ -173,33 +173,33 @@ proc findExe*(exe: string, followSymlinks: bool = true;
return x return x
result = "" 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. ## Returns the `file`'s last modification time.
when defined(posix): when defined(posix):
var res: Stat var res: Stat
if stat(file, res) < 0'i32: raiseOSError(osLastError()) if stat(file, res) < 0'i32: raiseOSError(osLastError())
return res.st_mtime return fromUnix(res.st_mtime.int64)
else: else:
var f: WIN32_FIND_DATA var f: WIN32_FIND_DATA
var h = findFirstFile(file, f) var h = findFirstFile(file, f)
if h == -1'i32: raiseOSError(osLastError()) if h == -1'i32: raiseOSError(osLastError())
result = winTimeToUnixTime(rdFileTime(f.ftLastWriteTime)) result = fromUnix(winTimeToUnixTime(rdFileTime(f.ftLastWriteTime)).int64)
findClose(h) 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. ## Returns the `file`'s last read or write access time.
when defined(posix): when defined(posix):
var res: Stat var res: Stat
if stat(file, res) < 0'i32: raiseOSError(osLastError()) if stat(file, res) < 0'i32: raiseOSError(osLastError())
return res.st_atime return fromUnix(res.st_atime.int64)
else: else:
var f: WIN32_FIND_DATA var f: WIN32_FIND_DATA
var h = findFirstFile(file, f) var h = findFirstFile(file, f)
if h == -1'i32: raiseOSError(osLastError()) if h == -1'i32: raiseOSError(osLastError())
result = winTimeToUnixTime(rdFileTime(f.ftLastAccessTime)) result = fromUnix(winTimeToUnixTime(rdFileTime(f.ftLastAccessTime)).int64)
findClose(h) 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. ## Returns the `file`'s creation time.
## ##
## **Note:** Under POSIX OS's, the returned time may actually be the time at ## **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): when defined(posix):
var res: Stat var res: Stat
if stat(file, res) < 0'i32: raiseOSError(osLastError()) if stat(file, res) < 0'i32: raiseOSError(osLastError())
return res.st_ctime return fromUnix(res.st_ctime.int64)
else: else:
var f: WIN32_FIND_DATA var f: WIN32_FIND_DATA
var h = findFirstFile(file, f) var h = findFirstFile(file, f)
if h == -1'i32: raiseOSError(osLastError()) if h == -1'i32: raiseOSError(osLastError())
result = winTimeToUnixTime(rdFileTime(f.ftCreationTime)) result = fromUnix(winTimeToUnixTime(rdFileTime(f.ftCreationTime)).int64)
findClose(h) findClose(h)
proc fileNewer*(a, b: string): bool {.rtl, extern: "nos$1".} = 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)) winlean.sleep(int32(milsecs))
else: else:
var a, b: Timespec 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 a.tv_nsec = (milsecs mod 1000) * 1000 * 1000
discard posix.nanosleep(a, b) discard posix.nanosleep(a, b)
@ -1481,16 +1481,17 @@ type
size*: BiggestInt # Size of file. size*: BiggestInt # Size of file.
permissions*: set[FilePermission] # File permissions permissions*: set[FilePermission] # File permissions
linkCount*: BiggestInt # Number of hard links the file object has. linkCount*: BiggestInt # Number of hard links the file object has.
lastAccessTime*: Time # Time file was last accessed. lastAccessTime*: times.Time # Time file was last accessed.
lastWriteTime*: Time # Time file was last modified/written to. lastWriteTime*: times.Time # Time file was last modified/written to.
creationTime*: Time # Time file was created. Not supported on all systems! creationTime*: times.Time # Time file was created. Not supported on all systems!
template rawToFormalFileInfo(rawInfo, path, formalInfo): untyped = template rawToFormalFileInfo(rawInfo, path, formalInfo): untyped =
## Transforms the native file info structure into the one nim uses. ## Transforms the native file info structure into the one nim uses.
## 'rawInfo' is either a 'TBY_HANDLE_FILE_INFORMATION' structure on Windows, ## 'rawInfo' is either a 'TBY_HANDLE_FILE_INFORMATION' structure on Windows,
## or a 'Stat' structure on posix ## or a 'Stat' structure on posix
when defined(Windows): 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) template merge(a, b): untyped = a or (b shl 32)
formalInfo.id.device = rawInfo.dwVolumeSerialNumber formalInfo.id.device = rawInfo.dwVolumeSerialNumber
formalInfo.id.file = merge(rawInfo.nFileIndexLow, rawInfo.nFileIndexHigh) 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.id = (rawInfo.st_dev, rawInfo.st_ino)
formalInfo.size = rawInfo.st_size formalInfo.size = rawInfo.st_size
formalInfo.linkCount = rawInfo.st_Nlink.BiggestInt formalInfo.linkCount = rawInfo.st_Nlink.BiggestInt
formalInfo.lastAccessTime = rawInfo.st_atime formalInfo.lastAccessTime = fromUnix(rawInfo.st_atime.int64)
formalInfo.lastWriteTime = rawInfo.st_mtime formalInfo.lastWriteTime = fromUnix(rawInfo.st_mtime.int64)
formalInfo.creationTime = rawInfo.st_ctime formalInfo.creationTime = fromUnix(rawInfo.st_ctime.int64)
result.permissions = {} result.permissions = {}
checkAndIncludeMode(S_IRUSR, fpUserRead) checkAndIncludeMode(S_IRUSR, fpUserRead)

View file

@ -1060,10 +1060,10 @@ elif not defined(useNimRtl):
var tmspec: Timespec var tmspec: Timespec
if timeout >= 1000: 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 tmspec.tv_nsec = (timeout %% 1_000) * 1_000_000
else: else:
tmspec.tv_sec = 0.Time tmspec.tv_sec = posix.Time(0)
tmspec.tv_nsec = (timeout * 1_000_000) tmspec.tv_nsec = (timeout * 1_000_000)
try: try:
@ -1109,20 +1109,20 @@ elif not defined(useNimRtl):
var b: Timespec var b: Timespec
b.tv_sec = e.tv_sec b.tv_sec = e.tv_sec
b.tv_nsec = e.tv_nsec 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: if e.tv_nsec >= s.tv_nsec:
e.tv_nsec -= s.tv_nsec e.tv_nsec -= s.tv_nsec
else: else:
if e.tv_sec == 0.Time: if e.tv_sec == posix.Time(0):
raise newException(ValueError, "System time was modified") raise newException(ValueError, "System time was modified")
else: else:
diff = s.tv_nsec - e.tv_nsec diff = s.tv_nsec - e.tv_nsec
e.tv_nsec = 1_000_000_000 - diff 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: if t.tv_nsec >= e.tv_nsec:
t.tv_nsec -= e.tv_nsec t.tv_nsec -= e.tv_nsec
else: 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 diff = e.tv_nsec - t.tv_nsec
t.tv_nsec = 1_000_000_000 - diff t.tv_nsec = 1_000_000_000 - diff
s.tv_sec = b.tv_sec s.tv_sec = b.tv_sec
@ -1154,10 +1154,10 @@ elif not defined(useNimRtl):
raiseOSError(osLastError()) raiseOSError(osLastError())
if timeout >= 1000: 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 tmspec.tv_nsec = (timeout %% 1_000) * 1_000_000
else: else:
tmspec.tv_sec = 0.Time tmspec.tv_sec = posix.Time(0)
tmspec.tv_nsec = (timeout * 1_000_000) tmspec.tv_nsec = (timeout * 1_000_000)
try: try:

View file

@ -190,12 +190,8 @@ when not defined(nimscript):
proc randomize*() {.benign.} = proc randomize*() {.benign.} =
## Initializes the random number generator with a "random" ## Initializes the random number generator with a "random"
## number, i.e. a tickcount. Note: Does not work for NimScript. ## number, i.e. a tickcount. Note: Does not work for NimScript.
when defined(JS): let time = int64(times.epochTime() * 1_000_000_000)
proc getMil(t: Time): int {.importcpp: "getTime", nodecl.} randomize(time)
randomize(getMil times.getTime())
else:
let time = int64(times.epochTime() * 1_000_000_000)
randomize(time)
{.pop.} {.pop.}

View file

@ -42,7 +42,7 @@ An expression like ``fmt"{key} is {value:arg} {{z}}"`` is transformed into:
format(key, temp) format(key, temp)
format(" is ", temp) format(" is ", temp)
format(value, arg, temp) format(value, arg, temp)
format("{z}", temp) format(" {z}", temp)
temp temp
Parts of the string that are enclosed in the curly braces are interpreted Parts of the string that are enclosed in the curly braces are interpreted
@ -94,7 +94,7 @@ The general form of a standard format specifier is::
[[fill]align][sign][#][0][minimumwidth][.precision][type] [[fill]align][sign][#][0][minimumwidth][.precision][type]
The brackets ([]) indicate an optional element. The square brackets ``[]`` indicate an optional element.
The optional align flag can be one of the following: The optional align flag can be one of the following:
@ -126,8 +126,9 @@ The 'sign' option is only valid for numeric types, and can be one of the followi
positive as well as negative numbers. positive as well as negative numbers.
``-`` Indicates that a sign should be used only for ``-`` Indicates that a sign should be used only for
negative numbers (this is the default behavior). negative numbers (this is the default behavior).
`` `` (space) Indicates that a leading space should be used on (space) Indicates that a leading space should be used on
positive numbers. positive numbers.
================= ====================================================
If the '#' character is present, integers use the 'alternate form' for formatting. If the '#' character is present, integers use the 'alternate form' for formatting.
This means that binary, octal, and hexadecimal output will be prefixed This means that binary, octal, and hexadecimal output will be prefixed

File diff suppressed because it is too large Load diff

View file

@ -64,6 +64,8 @@ type
des_key_schedule* = array[1..16, des_ks_struct] des_key_schedule* = array[1..16, des_ks_struct]
pem_password_cb* = proc(buf: cstring, size, rwflag: cint, userdata: pointer): cint {.cdecl.}
{.deprecated: [PSSL: SslPtr, PSSL_CTX: SslCtx, PBIO: BIO].} {.deprecated: [PSSL: SslPtr, PSSL_CTX: SslCtx, PBIO: BIO].}
const const
@ -432,6 +434,12 @@ proc ErrClearError*(){.cdecl, dynlib: DLLUtilName, importc: "ERR_clear_error".}
proc ErrFreeStrings*(){.cdecl, dynlib: DLLUtilName, importc: "ERR_free_strings".} proc ErrFreeStrings*(){.cdecl, dynlib: DLLUtilName, importc: "ERR_free_strings".}
proc ErrRemoveState*(pid: cInt){.cdecl, dynlib: DLLUtilName, importc: "ERR_remove_state".} proc ErrRemoveState*(pid: cInt){.cdecl, dynlib: DLLUtilName, importc: "ERR_remove_state".}
proc PEM_read_bio_RSA_PUBKEY*(bp: BIO, x: ptr PRSA, pw: pem_password_cb, u: pointer): PRSA {.cdecl,
dynlib: DLLSSLName, importc.}
proc RSA_verify*(kind: cint, origMsg: pointer, origMsgLen: cuint, signature: pointer,
signatureLen: cuint, rsa: PRSA): cint {.cdecl, dynlib: DLLSSLName, importc.}
when true: when true:
discard discard
else: else:

View file

@ -579,9 +579,9 @@ else:
var event = newSelectEvent() var event = newSelectEvent()
selector.registerEvent(event, 1) selector.registerEvent(event, 1)
discard selector.select(0) discard selector.select(0)
event.setEvent() event.trigger()
var rc1 = selector.select(0) var rc1 = selector.select(0)
event.setEvent() event.trigger()
var rc2 = selector.select(0) var rc2 = selector.select(0)
var rc3 = selector.select(0) var rc3 = selector.select(0)
assert(len(rc1) == 1 and len(rc2) == 1 and len(rc3) == 0) assert(len(rc1) == 1 and len(rc2) == 1 and len(rc3) == 0)
@ -611,7 +611,7 @@ else:
var event = newSelectEvent() var event = newSelectEvent()
for i in 0..high(thr): for i in 0..high(thr):
createThread(thr[i], event_wait_thread, event) createThread(thr[i], event_wait_thread, event)
event.setEvent() event.trigger()
joinThreads(thr) joinThreads(thr)
assert(counter == 1) assert(counter == 1)
result = true result = true

26
tests/js/tasync.nim Normal file
View file

@ -0,0 +1,26 @@
discard """
disabled: true
output: '''
0
x
'''
"""
import asyncjs
# demonstrate forward definition
# for js
proc y(e: int): Future[string]
proc x(e: int) {.async.} =
var s = await y(e)
echo s
proc y(e: int): Future[string] {.async.} =
echo 0
return "x"
discard x(2)

View file

@ -1,4 +1,3 @@
# test times module with js
discard """ discard """
action: run action: run
""" """
@ -9,21 +8,37 @@ import times
# Tue 19 Jan 03:14:07 GMT 2038 # Tue 19 Jan 03:14:07 GMT 2038
block yeardayTest: block yeardayTest:
# check if yearday attribute is properly set on TimeInfo creation doAssert fromUnix(2147483647).utc.yearday == 18
doAssert fromSeconds(2147483647).getGMTime().yearday == 18
block localTimezoneTest: block localTime:
# check if timezone is properly set during Time to TimeInfo conversion var local = now()
doAssert fromSeconds(2147483647).getLocalTime().timezone == getTimezone() let utc = local.utc
doAssert local.toTime == utc.toTime
block timestampPersistenceTest: let a = fromUnix(1_000_000_000)
# check if timestamp persists during TimeInfo to Time conversion let b = fromUnix(1_500_000_000)
const doAssert b - a == 500_000_000
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() # Because we can't change the timezone JS uses, we define a simple static timezone for testing.
doAssert x == timeStringGmt or x == timeStringGmt2
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"

View file

@ -1,16 +1,17 @@
# test the new time module # test the new time module
discard """ discard """
file: "ttimes.nim" file: "ttimes.nim"
action: "run" output: '''[Suite] ttimes
'''
""" """
import import
times, strutils times, os, strutils, unittest
# $ date --date='@2147483647' # $ date --date='@2147483647'
# Tue 19 Jan 03:14:07 GMT 2038 # 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) let actual = t.format(format)
if actual != expected: if actual != expected:
echo "Formatting failure!" echo "Formatting failure!"
@ -18,7 +19,7 @@ proc checkFormat(t: TimeInfo, format, expected: string) =
echo "actual : ", actual echo "actual : ", actual
doAssert false 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 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("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" & 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") 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" & 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", " 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") "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") t4.checkFormat("M MM MMM MMMM", "10 10 Oct October")
# Interval tests # Interval tests
(t4 - initInterval(years = 2)).checkFormat("yyyy", "1995") (t4 - initInterval(years = 2)).checkFormat("yyyy", "1995")
(t4 - initInterval(years = 7, minutes = 34, seconds = 24)).checkFormat("yyyy mm ss", "1990 24 10") (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 # checking dayOfWeek matches known days
doAssert getDayOfWeek(21, 9, 1900) == dFri doAssert getDayOfWeek(01, mJan, 0000) == dSat
doAssert getDayOfWeek(1, 1, 1970) == dThu doAssert getDayOfWeek(01, mJan, -0023) == dSat
doAssert getDayOfWeek(21, 9, 1970) == dMon doAssert getDayOfWeek(21, mSep, 1900) == dFri
doAssert getDayOfWeek(1, 1, 2000) == dSat doAssert getDayOfWeek(01, mJan, 1970) == dThu
doAssert getDayOfWeek(1, 1, 2021) == dFri doAssert getDayOfWeek(21, mSep, 1970) == dMon
# Julian tests doAssert getDayOfWeek(01, mJan, 2000) == dSat
doAssert getDayOfWeekJulian(21, 9, 1900) == dFri doAssert getDayOfWeek(01, mJan, 2021) == dFri
doAssert getDayOfWeekJulian(21, 9, 1970) == dMon
doAssert getDayOfWeekJulian(1, 1, 2000) == dSat
doAssert getDayOfWeekJulian(1, 1, 2021) == dFri
# toSeconds tests with GM timezone # toUnix tests with GM timezone
let t4L = getGMTime(fromSeconds(876124714)) let t4L = fromUnix(876124714).utc
doAssert toSeconds(toTime(t4L)) == 876124714 doAssert toUnix(toTime(t4L)) == 876124714
doAssert toSeconds(toTime(t4L)) + t4L.timezone.float == toSeconds(toTime(t4)) doAssert toUnix(toTime(t4L)) + t4L.utcOffset == toUnix(toTime(t4))
# adding intervals # adding intervals
var var
a1L = toSeconds(toTime(t4L + initInterval(hours = 1))) + t4L.timezone.float a1L = toUnix(toTime(t4L + initInterval(hours = 1))) + t4L.utcOffset
a1G = toSeconds(toTime(t4)) + 60.0 * 60.0 a1G = toUnix(toTime(t4)) + 60 * 60
doAssert a1L == a1G doAssert a1L == a1G
# subtracting intervals # subtracting intervals
a1L = toSeconds(toTime(t4L - initInterval(hours = 1))) + t4L.timezone.float a1L = toUnix(toTime(t4L - initInterval(hours = 1))) + t4L.utcOffset
a1G = toSeconds(toTime(t4)) - (60.0 * 60.0) a1G = toUnix(toTime(t4)) - (60 * 60)
doAssert a1L == a1G doAssert a1L == a1G
# add/subtract TimeIntervals and Time/TimeInfo # add/subtract TimeIntervals and Time/TimeInfo
@ -143,45 +78,16 @@ doAssert ti1 == getTime()
ti1 += 1.days ti1 += 1.days
doAssert ti1 == getTime() + 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 # Bug with adding a day to a Time
let day = 24.hours let day = 24.hours
let tomorrow = getTime() + day let tomorrow = getTime() + day
doAssert tomorrow - getTime() == 60*60*24 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 # Comparison between Time objects should be detected by compiler
# as 'noSideEffect'. # as 'noSideEffect'.
proc cmpTimeNoSideEffect(t1: Time, t2: Time): bool {.noSideEffect.} = proc cmpTimeNoSideEffect(t1: Time, t2: Time): bool {.noSideEffect.} =
result = t1 == t2 result = t1 == t2
doAssert cmpTimeNoSideEffect(0.fromSeconds, 0.fromSeconds) doAssert cmpTimeNoSideEffect(0.fromUnix, 0.fromUnix)
# Additionally `==` generic for seq[T] has explicit 'noSideEffect' pragma # Additionally `==` generic for seq[T] has explicit 'noSideEffect' pragma
# so we can check above condition by comparing seq[Time] sequences # so we can check above condition by comparing seq[Time] sequences
let seqA: seq[Time] = @[] let seqA: seq[Time] = @[]
@ -195,49 +101,197 @@ for tz in [
(-1800, "+0", "+00", "+00:30"), # half an hour (-1800, "+0", "+00", "+00:30"), # half an hour
(7200, "-2", "-02", "-02:00"), # positive (7200, "-2", "-02", "-02:00"), # positive
(38700, "-10", "-10", "-10:45")]: # positive with three quaters hour (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("z") == tz[1]
doAssert ti.format("zz") == tz[2] doAssert ti.format("zz") == tz[2]
doAssert ti.format("zzz") == tz[3] 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: block countLeapYears:
# 1920, 2004 and 2020 are leap years, and should be counted starting at the following year # 1920, 2004 and 2020 are leap years, and should be counted starting at the following year
doAssert countLeapYears(1920) + 1 == countLeapYears(1921) doAssert countLeapYears(1920) + 1 == countLeapYears(1921)
doAssert countLeapYears(2004) + 1 == countLeapYears(2005) doAssert countLeapYears(2004) + 1 == countLeapYears(2005)
doAssert countLeapYears(2020) + 1 == countLeapYears(2021) 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"

View file

@ -67,7 +67,7 @@ srcdoc2: "pure/collections/heapqueue"
srcdoc2: "pure/fenv;impure/rdstdin;pure/strformat" srcdoc2: "pure/fenv;impure/rdstdin;pure/strformat"
srcdoc2: "pure/segfaults" srcdoc2: "pure/segfaults"
srcdoc2: "pure/basic2d;pure/basic3d;pure/mersenne;pure/coro;pure/httpcore" srcdoc2: "pure/basic2d;pure/basic3d;pure/mersenne;pure/coro;pure/httpcore"
srcdoc2: "pure/bitops;pure/nimtracker;pure/punycode;pure/volatile" srcdoc2: "pure/bitops;pure/nimtracker;pure/punycode;pure/volatile;js/asyncjs"
; Note: everything under 'webdoc' doesn't get listed in the index, so wrappers ; Note: everything under 'webdoc' doesn't get listed in the index, so wrappers
; should live here ; should live here