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
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.
- Bodies of ``for`` loops now get their own scope:
.. code-block:: nim
```nim
# now compiles:
for i in 0..4:
let i = i + 1
echo i
```
- The parsing rules of ``if`` expressions were changed so that multiple
statements are allowed in the branches. We found few code examples that
now fail because of this change, but here is one:
.. code-block:: nim
```nim
t[ti] = if exp_negative: '-' else: '+'; inc(ti)
```
This now needs to be written as:
.. code-block:: nim
```nim
t[ti] = (if exp_negative: '-' else: '+'); inc(ti)
```
- To make Nim even more robust the system iterators ``..`` and ``countup``
now only accept a single generic type ``T``. This means the following code
doesn't die with an "out of range" error anymore:
.. code-block:: nim
```nim
var b = 5.Natural
var a = -5
for i in a..b:
echo i
```
- ``formatFloat``/``formatBiggestFloat`` now support formatting floats with zero
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
called [strformat](https://nim-lang.org/docs/strformat.html) has been added
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 =
case c
of '\0': result = "\\0"
of '\x01'..'\x1F', '\x80'..'\xFF': result = "\\x" & strutils.toHex(ord(c), 2)
of '\0': result = "\\x00" # not "\\0" to avoid ambiguous cases like "\\012".
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
else: result = c & ""

View File

@@ -433,6 +433,8 @@ Modules for JS backend
* `jsffi <jsffi.html>`_
Types and macros for easier interaction with JavaScript.
* `asyncjs <asyncjs.html>`_
Types and macros for writing asynchronous procedures in JavaScript.
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 {.
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>".}

View File

@@ -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

View File

@@ -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>",

View File

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

View File

@@ -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="

View File

@@ -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

View File

@@ -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

View File

@@ -291,6 +291,8 @@ when not defined(JS):
## echo fmod(-2.5, 0.3) ## -0.1
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: float64): float64 {.importc: "Math.floor", 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 dummy = oid.time
bigEndian32(addr(tmp), addr(dummy))
result = Time(tmp)
result = fromUnix(tmp)
when not defined(testing) and isMainModule:
let xo = genOid()

View File

@@ -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)

View File

@@ -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:

View File

@@ -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.}

View File

@@ -42,7 +42,7 @@ An expression like ``fmt"{key} is {value:arg} {{z}}"`` is transformed into:
format(key, temp)
format(" is ", temp)
format(value, arg, temp)
format("{z}", temp)
format(" {z}", temp)
temp
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]
The brackets ([]) indicate an optional element.
The square brackets ``[]`` indicate an optional element.
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.
``-`` Indicates that a sign should be used only for
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.
================= ====================================================
If the '#' character is present, integers use the 'alternate form' for formatting.
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]
pem_password_cb* = proc(buf: cstring, size, rwflag: cint, userdata: pointer): cint {.cdecl.}
{.deprecated: [PSSL: SslPtr, PSSL_CTX: SslCtx, PBIO: BIO].}
const
@@ -432,6 +434,12 @@ proc ErrClearError*(){.cdecl, dynlib: DLLUtilName, importc: "ERR_clear_error".}
proc ErrFreeStrings*(){.cdecl, dynlib: DLLUtilName, importc: "ERR_free_strings".}
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:
discard
else:

View File

@@ -579,9 +579,9 @@ else:
var event = newSelectEvent()
selector.registerEvent(event, 1)
discard selector.select(0)
event.setEvent()
event.trigger()
var rc1 = selector.select(0)
event.setEvent()
event.trigger()
var rc2 = selector.select(0)
var rc3 = selector.select(0)
assert(len(rc1) == 1 and len(rc2) == 1 and len(rc3) == 0)
@@ -611,7 +611,7 @@ else:
var event = newSelectEvent()
for i in 0..high(thr):
createThread(thr[i], event_wait_thread, event)
event.setEvent()
event.trigger()
joinThreads(thr)
assert(counter == 1)
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 """
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
doAssert fromUnix(2147483647).utc.yearday == 18
block localTimezoneTest:
# check if timezone is properly set during Time to TimeInfo conversion
doAssert fromSeconds(2147483647).getLocalTime().timezone == getTimezone()
block localTime:
var local = now()
let utc = local.utc
doAssert local.toTime == utc.toTime
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 a = fromUnix(1_000_000_000)
let b = fromUnix(1_500_000_000)
doAssert b - a == 500_000_000
let x = $timeString.parse(fmt).toTime().getGMTime()
doAssert x == timeStringGmt or x == timeStringGmt2
# 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"

View File

@@ -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
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
# 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))
# 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)
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/segfaults"
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
; should live here