Merge branch 'devel' into coroutines
This commit is contained in:
commit
24ad2cb392
89 changed files with 2829 additions and 1405 deletions
|
|
@ -124,7 +124,8 @@ export Port, SocketFlag
|
|||
##
|
||||
## * The effect system (``raises: []``) does not work with async procedures.
|
||||
## * Can't await in a ``except`` body
|
||||
|
||||
## * Forward declarations for async procs are broken,
|
||||
## link includes workaround: https://github.com/nim-lang/Nim/issues/3182.
|
||||
|
||||
# TODO: Check if yielded future is nil and throw a more meaningful exception
|
||||
|
||||
|
|
@ -1412,12 +1413,12 @@ proc getName(node: NimNode): string {.compileTime.} =
|
|||
else:
|
||||
error("Unknown name.")
|
||||
|
||||
macro async*(prc: stmt): stmt {.immediate.} =
|
||||
## Macro which processes async procedures into the appropriate
|
||||
## iterators and yield statements.
|
||||
proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} =
|
||||
## This macro transforms a single procedure into a closure iterator.
|
||||
## The ``async`` macro supports a stmtList holding multiple async procedures.
|
||||
if prc.kind notin {nnkProcDef, nnkLambda}:
|
||||
error("Cannot transform this node kind into an async proc." &
|
||||
" Proc definition or lambda node expected.")
|
||||
error("Cannot transform this node kind into an async proc." &
|
||||
" Proc definition or lambda node expected.")
|
||||
|
||||
hint("Processing " & prc[0].getName & " as an async proc.")
|
||||
|
||||
|
|
@ -1504,9 +1505,19 @@ macro async*(prc: stmt): stmt {.immediate.} =
|
|||
result[6] = outerProcBody
|
||||
|
||||
#echo(treeRepr(result))
|
||||
if prc[0].getName == "getAsync":
|
||||
if prc[0].getName == "hubConnectionLoop":
|
||||
echo(toStrLit(result))
|
||||
|
||||
macro async*(prc: stmt): stmt {.immediate.} =
|
||||
## Macro which processes async procedures into the appropriate
|
||||
## iterators and yield statements.
|
||||
if prc.kind == nnkStmtList:
|
||||
for oneProc in prc:
|
||||
result = newStmtList()
|
||||
result.add asyncSingleProc(oneProc)
|
||||
else:
|
||||
result = asyncSingleProc(prc)
|
||||
|
||||
proc recvLine*(socket: AsyncFD): Future[string] {.async.} =
|
||||
## Reads a line of data from ``socket``. Returned future will complete once
|
||||
## a full line is read or an error occurs.
|
||||
|
|
|
|||
|
|
@ -126,8 +126,11 @@ proc parseHeader(line: string): tuple[key, value: string] =
|
|||
var i = 0
|
||||
i = line.parseUntil(result.key, ':')
|
||||
inc(i) # skip :
|
||||
i += line.skipWhiteSpace(i)
|
||||
i += line.parseUntil(result.value, {'\c', '\L'}, i)
|
||||
if i < len(line):
|
||||
i += line.skipWhiteSpace(i)
|
||||
i += line.parseUntil(result.value, {'\c', '\L'}, i)
|
||||
else:
|
||||
result.value = ""
|
||||
|
||||
proc parseProtocol(protocol: string): tuple[orig: string, major, minor: int] =
|
||||
var i = protocol.skipIgnoreCase("HTTP/")
|
||||
|
|
|
|||
|
|
@ -226,6 +226,10 @@ proc `$`*[A, B](t: Table[A, B]): string =
|
|||
## The `$` operator for hash tables.
|
||||
dollarImpl()
|
||||
|
||||
proc hasKey*[A, B](t: TableRef[A, B], key: A): bool =
|
||||
## returns true iff `key` is in the table `t`.
|
||||
result = t[].hasKey(key)
|
||||
|
||||
template equalsImpl() =
|
||||
if s.counter == t.counter:
|
||||
# different insertion orders mean different 'data' seqs, so we have
|
||||
|
|
@ -293,10 +297,6 @@ proc hasKeyOrPut*[A, B](t: var TableRef[A, B], key: A, val: B): bool =
|
|||
## returns true iff `key` is in the table, otherwise inserts `value`.
|
||||
t[].hasKeyOrPut(key, val)
|
||||
|
||||
proc hasKey*[A, B](t: TableRef[A, B], key: A): bool =
|
||||
## returns true iff `key` is in the table `t`.
|
||||
result = t[].hasKey(key)
|
||||
|
||||
proc contains*[A, B](t: TableRef[A, B], key: A): bool =
|
||||
## alias of `hasKey` for use with the `in` operator.
|
||||
return hasKey[A, B](t, key)
|
||||
|
|
|
|||
|
|
@ -402,7 +402,7 @@ proc request*(url: string, httpMethod: string, extraHeaders = "",
|
|||
|
||||
headers.add(" HTTP/1.1\c\L")
|
||||
|
||||
add(headers, "Host: " & r.hostname & "\c\L")
|
||||
add(headers, "Host: " & parseUri(url).hostname & "\c\L")
|
||||
if userAgent != "":
|
||||
add(headers, "User-Agent: " & userAgent & "\c\L")
|
||||
if proxy != nil and proxy.auth != "":
|
||||
|
|
|
|||
|
|
@ -608,29 +608,29 @@ proc newJArray*(): JsonNode =
|
|||
proc getStr*(n: JsonNode, default: string = ""): string =
|
||||
## Retrieves the string value of a `JString JsonNode`.
|
||||
##
|
||||
## Returns ``default`` if ``n`` is not a ``JString``.
|
||||
if n.kind != JString: return default
|
||||
## Returns ``default`` if ``n`` is not a ``JString``, or if ``n`` is nil.
|
||||
if n.isNil or n.kind != JString: return default
|
||||
else: return n.str
|
||||
|
||||
proc getNum*(n: JsonNode, default: BiggestInt = 0): BiggestInt =
|
||||
## Retrieves the int value of a `JInt JsonNode`.
|
||||
##
|
||||
## Returns ``default`` if ``n`` is not a ``JInt``.
|
||||
if n.kind != JInt: return default
|
||||
## Returns ``default`` if ``n`` is not a ``JInt``, or if ``n`` is nil.
|
||||
if n.isNil or n.kind != JInt: return default
|
||||
else: return n.num
|
||||
|
||||
proc getFNum*(n: JsonNode, default: float = 0.0): float =
|
||||
## Retrieves the float value of a `JFloat JsonNode`.
|
||||
##
|
||||
## Returns ``default`` if ``n`` is not a ``JFloat``.
|
||||
if n.kind != JFloat: return default
|
||||
## Returns ``default`` if ``n`` is not a ``JFloat``, or if ``n`` is nil.
|
||||
if n.isNil or n.kind != JFloat: return default
|
||||
else: return n.fnum
|
||||
|
||||
proc getBVal*(n: JsonNode, default: bool = false): bool =
|
||||
## Retrieves the bool value of a `JBool JsonNode`.
|
||||
##
|
||||
## Returns ``default`` if ``n`` is not a ``JBool``.
|
||||
if n.kind != JBool: return default
|
||||
## Returns ``default`` if ``n`` is not a ``JBool``, or if ``n`` is nil.
|
||||
if n.isNil or n.kind != JBool: return default
|
||||
else: return n.bval
|
||||
|
||||
proc getFields*(n: JsonNode,
|
||||
|
|
@ -638,15 +638,15 @@ proc getFields*(n: JsonNode,
|
|||
seq[tuple[key: string, val: JsonNode]] =
|
||||
## Retrieves the key, value pairs of a `JObject JsonNode`.
|
||||
##
|
||||
## Returns ``default`` if ``n`` is not a ``JObject``.
|
||||
if n.kind != JObject: return default
|
||||
## Returns ``default`` if ``n`` is not a ``JObject``, or if ``n`` is nil.
|
||||
if n.isNil or n.kind != JObject: return default
|
||||
else: return n.fields
|
||||
|
||||
proc getElems*(n: JsonNode, default: seq[JsonNode] = @[]): seq[JsonNode] =
|
||||
## Retrieves the int value of a `JArray JsonNode`.
|
||||
##
|
||||
## Returns ``default`` if ``n`` is not a ``JArray``.
|
||||
if n.kind != JArray: return default
|
||||
## Returns ``default`` if ``n`` is not a ``JArray``, or if ``n`` is nil.
|
||||
if n.isNil or n.kind != JArray: return default
|
||||
else: return n.elems
|
||||
|
||||
proc `%`*(s: string): JsonNode =
|
||||
|
|
|
|||
|
|
@ -427,10 +427,12 @@ proc `^`*[T](x, y: T): T =
|
|||
var (x, y) = (x, y)
|
||||
result = 1
|
||||
|
||||
while y != 0:
|
||||
while true:
|
||||
if (y and 1) != 0:
|
||||
result *= x
|
||||
y = y shr 1
|
||||
if y == 0:
|
||||
break
|
||||
x *= x
|
||||
|
||||
proc gcd*[T](x, y: T): T =
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@
|
|||
##
|
||||
## This module provides support for `memory mapped files`:idx:
|
||||
## (Posix's `mmap`:idx:) on the different operating systems.
|
||||
##
|
||||
## It also provides some fast iterators over lines in text files (or
|
||||
## other "line-like", variable length, delimited records).
|
||||
|
||||
when defined(windows):
|
||||
import winlean
|
||||
|
|
@ -245,3 +248,96 @@ proc close*(f: var MemFile) =
|
|||
|
||||
if error: raiseOSError(lastErr)
|
||||
|
||||
type MemSlice* = object ## represent slice of a MemFile for iteration over delimited lines/records
|
||||
data*: pointer
|
||||
size*: int
|
||||
|
||||
proc c_memcpy(a, b: pointer, n: int) {.importc: "memcpy", header: "<string.h>".}
|
||||
|
||||
proc `$`*(ms: MemSlice): string {.inline.} =
|
||||
## Return a Nim string built from a MemSlice.
|
||||
var buf = newString(ms.size)
|
||||
c_memcpy(addr(buf[0]), ms.data, ms.size)
|
||||
buf[ms.size] = '\0'
|
||||
result = buf
|
||||
|
||||
iterator memSlices*(mfile: MemFile, delim='\l', eat='\r'): MemSlice {.inline.} =
|
||||
## Iterates over [optional `eat`] `delim`-delimited slices in MemFile `mfile`.
|
||||
##
|
||||
## Default parameters parse lines ending in either Unix(\\l) or Windows(\\r\\l)
|
||||
## style on on a line-by-line basis. I.e., not every line needs the same ending.
|
||||
## Unlike readLine(File) & lines(File), archaic MacOS9 \\r-delimited lines
|
||||
## are not supported as a third option for each line. Such archaic MacOS9
|
||||
## files can be handled by passing delim='\\r', eat='\\0', though.
|
||||
##
|
||||
## Delimiters are not part of the returned slice. A final, unterminated line
|
||||
## or record is returned just like any other.
|
||||
##
|
||||
## Non-default delimiters can be passed to allow iteration over other sorts
|
||||
## of "line-like" variable length records. Pass eat='\\0' to be strictly
|
||||
## `delim`-delimited. (Eating an optional prefix equal to '\\0' is not
|
||||
## supported.)
|
||||
##
|
||||
## This zero copy, memchr-limited interface is probably the fastest way to
|
||||
## iterate over line-like records in a file. However, returned (data,size)
|
||||
## objects are not Nim strings, bounds checked Nim arrays, or even terminated
|
||||
## C strings. So, care is required to access the data (e.g., think C mem*
|
||||
## functions, not str* functions). Example:
|
||||
##
|
||||
## .. code-block:: nim
|
||||
## var count = 0
|
||||
## for slice in memSlices(memfiles.open("foo")):
|
||||
## if slice.size > 0 and cast[cstring](slice.data)[0] != '#':
|
||||
## inc(count)
|
||||
## echo count
|
||||
|
||||
proc c_memchr(cstr: pointer, c: char, n: csize): pointer {.
|
||||
importc: "memchr", header: "<string.h>" .}
|
||||
proc `-!`(p, q: pointer): int {.inline.} = return cast[int](p) -% cast[int](q)
|
||||
var ms: MemSlice
|
||||
var ending: pointer
|
||||
ms.data = mfile.mem
|
||||
var remaining = mfile.size
|
||||
while remaining > 0:
|
||||
ending = c_memchr(ms.data, delim, remaining)
|
||||
if ending == nil: # unterminated final slice
|
||||
ms.size = remaining # Weird case..check eat?
|
||||
yield ms
|
||||
break
|
||||
ms.size = ending -! ms.data # delim is NOT included
|
||||
if eat != '\0' and ms.size > 0 and cast[cstring](ms.data)[ms.size - 1] == eat:
|
||||
dec(ms.size) # trim pre-delim char
|
||||
yield ms
|
||||
ms.data = cast[pointer](cast[int](ending) +% 1) # skip delim
|
||||
remaining = mfile.size - (ms.data -! mfile.mem)
|
||||
|
||||
iterator lines*(mfile: MemFile, buf: var TaintedString, delim='\l', eat='\r'): TaintedString {.inline.} =
|
||||
## Replace contents of passed buffer with each new line, like
|
||||
## `readLine(File) <system.html#readLine,File,TaintedString>`_.
|
||||
## `delim`, `eat`, and delimiting logic is exactly as for
|
||||
## `memSlices <#memSlices>`_, but Nim strings are returned. Example:
|
||||
##
|
||||
## .. code-block:: nim
|
||||
## var buffer: TaintedString = ""
|
||||
## for line in lines(memfiles.open("foo"), buffer):
|
||||
## echo line
|
||||
|
||||
for ms in memSlices(mfile, delim, eat):
|
||||
buf.setLen(ms.size)
|
||||
c_memcpy(addr(buf[0]), ms.data, ms.size)
|
||||
buf[ms.size] = '\0'
|
||||
yield buf
|
||||
|
||||
iterator lines*(mfile: MemFile, delim='\l', eat='\r'): TaintedString {.inline.} =
|
||||
## Return each line in a file as a Nim string, like
|
||||
## `lines(File) <system.html#lines.i,File>`_.
|
||||
## `delim`, `eat`, and delimiting logic is exactly as for
|
||||
## `memSlices <#memSlices>`_, but Nim strings are returned. Example:
|
||||
##
|
||||
## .. code-block:: nim
|
||||
## for line in lines(memfiles.open("foo")):
|
||||
## echo line
|
||||
|
||||
var buf = TaintedString(newStringOfCap(80))
|
||||
for line in lines(mfile, buf, delim, eat):
|
||||
yield buf
|
||||
|
|
|
|||
|
|
@ -1244,7 +1244,14 @@ iterator walkFiles*(pattern: string): string {.tags: [ReadDirEffect].} =
|
|||
while true:
|
||||
if not skipFindData(f) and
|
||||
(f.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) == 0'i32:
|
||||
yield splitFile(pattern).dir / extractFilename(getFilename(f))
|
||||
# Windows bug/gotcha: 't*.nim' matches 'tfoo.nims' -.- so we check
|
||||
# that the file extensions have the same length ...
|
||||
let ff = getFilename(f)
|
||||
let dotPos = searchExtPos(pattern)
|
||||
let idx = ff.len - pattern.len + dotPos
|
||||
if dotPos < 0 or idx >= ff.len or ff[idx] == '.' or
|
||||
pattern[dotPos+1] == '*':
|
||||
yield splitFile(pattern).dir / extractFilename(ff)
|
||||
if findNextFile(res, f) == 0'i32: break
|
||||
findClose(res)
|
||||
else: # here we use glob
|
||||
|
|
|
|||
|
|
@ -168,9 +168,8 @@ proc cmpIgnoreStyle*(a, b: string): int {.noSideEffect,
|
|||
inc(i)
|
||||
inc(j)
|
||||
|
||||
{.pop.}
|
||||
|
||||
proc strip*(s: string, leading = true, trailing = true, chars: set[char] = Whitespace): string
|
||||
proc strip*(s: string, leading = true, trailing = true, chars: set[char] = Whitespace): string
|
||||
{.noSideEffect, rtl, extern: "nsuStrip".} =
|
||||
## Strips `chars` from `s` and returns the resulting string.
|
||||
##
|
||||
|
|
@ -1396,6 +1395,62 @@ proc format*(formatstr: string, a: varargs[string, `$`]): string {.noSideEffect,
|
|||
|
||||
{.pop.}
|
||||
|
||||
proc removeSuffix*(s: var string, chars: set[char] = Newlines) {.
|
||||
rtl, extern: "nsuRemoveSuffixCharSet".} =
|
||||
## Removes the first matching character from the string (in-place) given a
|
||||
## set of characters. If the set of characters is only equal to `Newlines`
|
||||
## then it will remove both the newline and return feed.
|
||||
## .. code-block:: nim
|
||||
## var
|
||||
## userInput = "Hello World!\r\n"
|
||||
## otherInput = "Hello!?!"
|
||||
## userInput.removeSuffix
|
||||
## userInput == "Hello World!"
|
||||
## userInput.removeSuffix({'!', '?'})
|
||||
## userInput == "Hello World"
|
||||
## otherInput.removeSuffix({'!', '?'})
|
||||
## otherInput == "Hello!?"
|
||||
|
||||
var last = len(s) - 1
|
||||
|
||||
if chars == Newlines:
|
||||
if s[last] == '\10':
|
||||
last -= 1
|
||||
|
||||
if s[last] == '\13':
|
||||
last -= 1
|
||||
|
||||
else:
|
||||
if s[last] in chars:
|
||||
last -= 1
|
||||
|
||||
s.setLen(last + 1)
|
||||
|
||||
proc removeSuffix*(s: var string, c: char) {.rtl, extern: "nsuRemoveSuffixChar".} =
|
||||
## Removes a single character (in-place) from a string.
|
||||
## .. code-block:: nim
|
||||
## var
|
||||
## table = "users"
|
||||
## table.removeSuffix('s')
|
||||
## table == "user"
|
||||
removeSuffix(s, chars = {c})
|
||||
|
||||
proc removeSuffix*(s: var string, suffix: string) {.
|
||||
rtl, extern: "nsuRemoveSuffixString".} =
|
||||
## Remove the first matching suffix (in-place) from a string.
|
||||
## .. code-block:: nim
|
||||
## var
|
||||
## answers = "yeses"
|
||||
## answers.removeSuffix("es")
|
||||
## answers == "yes"
|
||||
|
||||
var newLen = s.len
|
||||
|
||||
if s.endsWith(suffix):
|
||||
newLen -= len(suffix)
|
||||
|
||||
s.setLen(newLen)
|
||||
|
||||
when isMainModule:
|
||||
doAssert align("abc", 4) == " abc"
|
||||
doAssert align("a", 0) == "a"
|
||||
|
|
|
|||
|
|
@ -26,11 +26,6 @@ type
|
|||
WeekDay* = enum ## represents a weekday
|
||||
dMon, dTue, dWed, dThu, dFri, dSat, dSun
|
||||
|
||||
when not defined(JS):
|
||||
var
|
||||
timezone {.importc, header: "<time.h>".}: int
|
||||
tzname {.importc, header: "<time.h>" .}: array[0..1, cstring]
|
||||
|
||||
when defined(posix) and not defined(JS):
|
||||
type
|
||||
TimeImpl {.importc: "time_t", header: "<time.h>".} = int
|
||||
|
|
@ -49,6 +44,9 @@ when defined(posix) and not defined(JS):
|
|||
proc posix_gettimeofday(tp: var Timeval, unused: pointer = nil) {.
|
||||
importc: "gettimeofday", header: "<sys/time.h>".}
|
||||
|
||||
var
|
||||
timezone {.importc, header: "<time.h>".}: int
|
||||
tzname {.importc, header: "<time.h>" .}: array[0..1, cstring]
|
||||
# we also need tzset() to make sure that tzname is initialized
|
||||
proc tzset() {.importc, header: "<time.h>".}
|
||||
# calling tzset() implicitly to initialize tzname data.
|
||||
|
|
@ -60,12 +58,20 @@ elif defined(windows):
|
|||
when defined(vcc):
|
||||
# newest version of Visual C++ defines time_t to be of 64 bits
|
||||
type TimeImpl {.importc: "time_t", header: "<time.h>".} = int64
|
||||
# visual c's c runtime exposes these under a different name
|
||||
var
|
||||
timezone {.importc: "_timezone", header: "<time.h>".}: int
|
||||
tzname {.importc: "_tzname", header: "<time.h>"}: array[0..1, cstring]
|
||||
else:
|
||||
type TimeImpl {.importc: "time_t", header: "<time.h>".} = int32
|
||||
var
|
||||
timezone {.importc, header: "<time.h>".}: int
|
||||
tzname {.importc, header: "<time.h>" .}: array[0..1, cstring]
|
||||
|
||||
type
|
||||
Time* = distinct TimeImpl
|
||||
|
||||
|
||||
elif defined(JS):
|
||||
type
|
||||
Time* {.importc.} = object
|
||||
|
|
@ -542,11 +548,9 @@ elif defined(JS):
|
|||
## get the milliseconds from the start of the program
|
||||
return int(getTime() - startMilsecs)
|
||||
|
||||
proc valueOf(time: Time): float {.importcpp: "getTime", tags:[]}
|
||||
proc fromSeconds(since1970: float): Time = result = newDate(since1970 * 1000)
|
||||
|
||||
proc fromSeconds(since1970: float): Time = result = newDate(since1970)
|
||||
|
||||
proc toSeconds(time: Time): float = result = time.valueOf() / 1000
|
||||
proc toSeconds(time: Time): float = result = time.getTime() / 1000
|
||||
|
||||
proc getTimezone(): int = result = newDate().getTimezoneOffset()
|
||||
|
||||
|
|
@ -1046,6 +1050,120 @@ proc parse*(value, layout: string): TimeInfo =
|
|||
info.weekday = getLocalTime(timeInfoToTime(info)).weekday
|
||||
return info
|
||||
|
||||
# Leap year calculations are adapted from:
|
||||
# from http://www.codeproject.com/Articles/7358/Ultra-fast-Algorithms-for-Working-with-Leap-Years
|
||||
# The dayOfTheWeek procs are adapated from:
|
||||
# from http://stason.org/TULARC/society/calendars/2-5-What-day-of-the-week-was-2-August-1953.html
|
||||
|
||||
# Note: for leap years, start date is assumed to be 1 AD.
|
||||
# counts the number of leap years up to January 1st of a given year.
|
||||
# Keep in mind that if specified year is a leap year, the leap day
|
||||
# has not happened before January 1st of that year.
|
||||
proc countLeapYears(yearSpan: int): int =
|
||||
(((yearSpan - 1) / 4) - ((yearSpan - 1) / 100) + ((yearSpan - 1)/400)).int
|
||||
|
||||
proc countDays(yearSpan: int): int =
|
||||
(yearSpan - 1) * 365 + countLeapYears(yearSpan)
|
||||
|
||||
proc countYears(daySpan: int): int =
|
||||
# counts the number of years spanned by a given number of days.
|
||||
((daySpan - countLeapYears(daySpan div 365)) div 365)
|
||||
|
||||
proc countYearsAndDays(daySpan: int): tuple[years: int, days: int] =
|
||||
# counts the number of years spanned by a given number of days and the remainder as days.
|
||||
let days = daySpan - countLeapYears(daySpan div 365)
|
||||
result.years = days div 365
|
||||
result.days = days mod 365
|
||||
|
||||
const
|
||||
secondsInMin = 60
|
||||
secondsInHour = 60*60
|
||||
secondsInDay = 60*60*24
|
||||
epochStartYear = 1970
|
||||
|
||||
proc getDayOfWeek*(day, month, year: int): WeekDay =
|
||||
## Returns the day of the week enum from day, month and year.
|
||||
# Day & month start from one.
|
||||
let
|
||||
a = (14 - month) div 12
|
||||
y = year - a
|
||||
m = month + (12*a) - 2
|
||||
d = (day + y + (y div 4) - (y div 100) + (y div 400) + (31*m) div 12) mod 7
|
||||
# The value of d is 0 for a Sunday, 1 for a Monday, 2 for a Tuesday, etc. so we must correct
|
||||
# for the WeekDay type.
|
||||
if d == 0: return dSun
|
||||
result = (d-1).WeekDay
|
||||
|
||||
proc getDayOfWeekJulian*(day, month, year: int): WeekDay =
|
||||
## Returns the day of the week enum from day, month and year, according to the Julian calender.
|
||||
# Day & month start from one.
|
||||
let
|
||||
a = (14 - month) div 12
|
||||
y = year - a
|
||||
m = month + (12*a) - 2
|
||||
d = (5 + day + y + (y div 4) + (31*m) div 12) mod 7
|
||||
result = d.WeekDay
|
||||
|
||||
proc timeToTimeInfo*(t: Time): TimeInfo =
|
||||
## Converts a Time to TimeInfo.
|
||||
let
|
||||
secs = t.toSeconds().int
|
||||
daysSinceEpoch = secs div secondsInDay
|
||||
(yearsSinceEpoch, daysRemaining) = countYearsAndDays(daysSinceEpoch)
|
||||
daySeconds = secs mod secondsInDay
|
||||
|
||||
y = yearsSinceEpoch + epochStartYear
|
||||
|
||||
var
|
||||
mon = mJan
|
||||
days = daysRemaining
|
||||
daysInMonth = getDaysInMonth(mon, y)
|
||||
|
||||
# calculate month and day remainder
|
||||
while days > daysInMonth and mon <= mDec:
|
||||
days -= daysInMonth
|
||||
mon.inc
|
||||
daysInMonth = getDaysInMonth(mon, y)
|
||||
|
||||
let
|
||||
yd = daysRemaining
|
||||
m = mon # month is zero indexed enum
|
||||
md = days
|
||||
# NB: month is zero indexed but dayOfWeek expects 1 indexed.
|
||||
wd = getDayOfWeek(days, mon.int + 1, y).Weekday
|
||||
h = daySeconds div secondsInHour + 1
|
||||
mi = (daySeconds mod secondsInHour) div secondsInMin
|
||||
s = daySeconds mod secondsInMin
|
||||
result = TimeInfo(year: y, yearday: yd, month: m, monthday: md, weekday: wd, hour: h, minute: mi, second: s)
|
||||
|
||||
proc timeToTimeInterval*(t: Time): TimeInterval =
|
||||
## Converts a Time to a TimeInterval.
|
||||
|
||||
let
|
||||
secs = t.toSeconds().int
|
||||
daysSinceEpoch = secs div secondsInDay
|
||||
(yearsSinceEpoch, daysRemaining) = countYearsAndDays(daysSinceEpoch)
|
||||
daySeconds = secs mod secondsInDay
|
||||
|
||||
result.years = yearsSinceEpoch + epochStartYear
|
||||
|
||||
var
|
||||
mon = mJan
|
||||
days = daysRemaining
|
||||
daysInMonth = getDaysInMonth(mon, result.years)
|
||||
|
||||
# calculate month and day remainder
|
||||
while days > daysInMonth and mon <= mDec:
|
||||
days -= daysInMonth
|
||||
mon.inc
|
||||
daysInMonth = getDaysInMonth(mon, result.years)
|
||||
|
||||
result.months = mon.int + 1 # month is 1 indexed int
|
||||
result.days = days
|
||||
result.hours = daySeconds div secondsInHour + 1
|
||||
result.minutes = (daySeconds mod secondsInHour) div secondsInMin
|
||||
result.seconds = daySeconds mod secondsInMin
|
||||
# Milliseconds not available from Time
|
||||
|
||||
when isMainModule:
|
||||
# $ date --date='@2147483647'
|
||||
|
|
@ -1066,12 +1184,13 @@ when isMainModule:
|
|||
" ss t tt y yy yyy yyyy yyyyy z zz zzz 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 UTC"
|
||||
|
||||
when not defined(JS) and sizeof(Time) == 8:
|
||||
var t3 = getGMTime(fromSeconds(889067643645)) # Fri 7 Jun 19:20:45 BST 30143
|
||||
assert t3.format("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 ZZZ") ==
|
||||
"7 07 Fri Friday 6 06 18 18 20 20 6 06 Jun June 45 45 P PM 3 43 143 0143 30143 0 00 00:00 UTC"
|
||||
assert t3.format(":,[]()-/") == ":,[]()-/"
|
||||
when not defined(JS):
|
||||
when sizeof(Time) == 8:
|
||||
var t3 = getGMTime(fromSeconds(889067643645)) # Fri 7 Jun 19:20:45 BST 30143
|
||||
assert t3.format("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 ZZZ") ==
|
||||
"7 07 Fri Friday 6 06 18 18 20 20 6 06 Jun June 45 45 P PM 3 43 143 0143 30143 0 00 00:00 UTC"
|
||||
assert t3.format(":,[]()-/") == ":,[]()-/"
|
||||
|
||||
var t4 = getGMTime(fromSeconds(876124714)) # Mon 6 Oct 08:58:34 BST 1997
|
||||
assert t4.format("M MM MMM MMMM") == "10 10 Oct October"
|
||||
|
|
@ -1131,3 +1250,18 @@ when isMainModule:
|
|||
assert "15:04:00" in $s.parse(f)
|
||||
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
|
||||
assert getDayOfWeek(21, 9, 1900) == dFri
|
||||
assert getDayOfWeek(1, 1, 1970) == dThu
|
||||
assert getDayOfWeek(21, 9, 1970) == dMon
|
||||
assert getDayOfWeek(1, 1, 2000) == dSat
|
||||
assert getDayOfWeek(1, 1, 2021) == dFri
|
||||
# Julian tests
|
||||
assert getDayOfWeekJulian(21, 9, 1900) == dFri
|
||||
assert getDayOfWeekJulian(21, 9, 1970) == dMon
|
||||
assert getDayOfWeekJulian(1, 1, 2000) == dSat
|
||||
assert getDayOfWeekJulian(1, 1, 2021) == dFri
|
||||
|
|
|
|||
|
|
@ -285,7 +285,7 @@ macro check*(conditions: stmt): stmt {.immediate.} =
|
|||
|
||||
result = getAst(rewrite(checked, checked.lineinfo, checked.toStrLit))
|
||||
|
||||
template require*(conditions: stmt): stmt {.immediate, dirty.} =
|
||||
template require*(conditions: stmt): stmt {.immediate.} =
|
||||
## Same as `check` except any failed test causes the program to quit
|
||||
## immediately. Any teardown statements are not executed and the failed
|
||||
## test output is not generated.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue