From 742c7e1179728b8fc95a8652f90ae3d5df84fe8c Mon Sep 17 00:00:00 2001 From: Konstantin Molchanov Date: Tue, 21 Mar 2017 13:50:43 +0400 Subject: [PATCH 1/9] JS: Times: Add timezone prop to TimeInfo. --- lib/pure/times.nim | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/pure/times.nim b/lib/pure/times.nim index 82fc9ad41f03b..fe35c404c6cb4 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -598,6 +598,7 @@ elif defined(JS): result.year = t.getFullYear() result.weekday = weekDays[t.getDay()] result.yearday = 0 + result.timezone = getTimezone() proc getGMTime(t: Time): TimeInfo = result.second = t.getUTCSeconds() From 3ebffb2a006dc306084b304cdfb5f7cfd74b659f Mon Sep 17 00:00:00 2001 From: Konstantin Molchanov Date: Sun, 26 Mar 2017 23:50:02 +0400 Subject: [PATCH 2/9] Times: JS: Remove implicit UTC convesion. The conversion would produce incorrect timestamp. --- lib/pure/times.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pure/times.nim b/lib/pure/times.nim index fe35c404c6cb4..f85c7f4b1ae94 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -619,7 +619,7 @@ elif defined(JS): result.setMonth(ord(timeInfo.month)) result.setFullYear(timeInfo.year) result.setDate(timeInfo.monthday) - result.setSeconds(timeInfo.second + timeInfo.timezone) + result.setSeconds(timeInfo.second) proc `-` (a, b: Time): int64 = return a.getTime() - b.getTime() From bef86f55ceefb1f2ea3fb95a1a5112530707bd46 Mon Sep 17 00:00:00 2001 From: Konstantin Molchanov Date: Mon, 27 Mar 2017 00:14:48 +0400 Subject: [PATCH 3/9] Times: JS: Add yearday to TimeInfo. Add yearday calculation to getLocalTime and getGMTime, so that yearday is not 0 for TimeInfo instances under JS backend. Yearday 0 has no sense and contradicts the behaviour under C backend, where yearday is an int from 1 to 365, i.e. cannot be 0 even theoretically. --- lib/pure/times.nim | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/pure/times.nim b/lib/pure/times.nim index fe35c404c6cb4..2b7c22145261e 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -597,9 +597,12 @@ elif defined(JS): result.month = Month(t.getMonth()) result.year = t.getFullYear() result.weekday = weekDays[t.getDay()] - result.yearday = 0 result.timezone = getTimezone() + result.yearday = result.monthday - 1 + for month in mJan.. Date: Mon, 27 Mar 2017 21:14:02 +0400 Subject: [PATCH 4/9] Tests: Times: JS: Add test for yearday attribute. --- tests/js/ttimes.nim | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 tests/js/ttimes.nim diff --git a/tests/js/ttimes.nim b/tests/js/ttimes.nim new file mode 100644 index 0000000000000..a39ebe0b3bb15 --- /dev/null +++ b/tests/js/ttimes.nim @@ -0,0 +1,13 @@ +# test times module with js +discard """ + action: run +""" + +import times + +# $ date --date='@2147483647' +# 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 == 0 From 41e83f7a34f11ea09accaa7d8667ccbabeb0eccf Mon Sep 17 00:00:00 2001 From: Konstantin Molchanov Date: Mon, 27 Mar 2017 21:28:31 +0400 Subject: [PATCH 5/9] Tests: Times: JS: Fix test. --- tests/js/ttimes.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/js/ttimes.nim b/tests/js/ttimes.nim index a39ebe0b3bb15..644e9670afc8a 100644 --- a/tests/js/ttimes.nim +++ b/tests/js/ttimes.nim @@ -10,4 +10,4 @@ import times block yeardayTest: # check if yearday attribute is properly set on TimeInfo creation - doAssert fromSeconds(2147483647).getGMTime().yearday == 0 + doAssert fromSeconds(2147483647).getGMTime().yearday == 18 From cc9d282348bf878a0a68d64c0c0df80253639aed Mon Sep 17 00:00:00 2001 From: Konstantin Molchanov Date: Mon, 27 Mar 2017 22:01:37 +0400 Subject: [PATCH 6/9] Tests: Times: JS: Local timezone assignment during Time to TimeInfo conversion. --- tests/js/ttimes.nim | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/js/ttimes.nim b/tests/js/ttimes.nim index 644e9670afc8a..c444932900698 100644 --- a/tests/js/ttimes.nim +++ b/tests/js/ttimes.nim @@ -11,3 +11,7 @@ import times block yeardayTest: # check if yearday attribute is properly set on TimeInfo creation doAssert fromSeconds(2147483647).getGMTime().yearday == 18 + +block timezoneTest: + # check if timezone is properly set durint Time to TimeInfo conversion + doAssert fromSeconds(2147483647).getLocalTime().timezone == getTimezone() From 35cdb42e020bbb569f3fba5f94c9fc70709a7836 Mon Sep 17 00:00:00 2001 From: Konstantin Molchanov Date: Mon, 27 Mar 2017 22:08:43 +0400 Subject: [PATCH 7/9] Tests: Times: JS: Add test for timestamp persistence. --- tests/js/ttimes.nim | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/js/ttimes.nim b/tests/js/ttimes.nim index c444932900698..6420c81483092 100644 --- a/tests/js/ttimes.nim +++ b/tests/js/ttimes.nim @@ -12,6 +12,14 @@ block yeardayTest: # check if yearday attribute is properly set on TimeInfo creation doAssert fromSeconds(2147483647).getGMTime().yearday == 18 -block timezoneTest: +block localTimezoneTest: # check if timezone is properly set durint Time to TimeInfo conversion doAssert fromSeconds(2147483647).getLocalTime().timezone == getTimezone() + +block timestampPersistenceTest: + # check if timestamp persists during TimeInfo to Time conversion + const + testString = "2017-03-21T12:34:56+04:00" + fmt = "yyyy-MM-dd'T'HH:mm:sszzz" + + doAssert $testString.parse(fmt) == testString From a9044117e9af0d65f9a532a19c1ba44636533559 Mon Sep 17 00:00:00 2001 From: Konstantin Molchanov Date: Wed, 29 Mar 2017 16:40:52 +0400 Subject: [PATCH 8/9] Stdlib: Times: Use JS's "new Date" to convert TimeInfo to Time. To use JS's Date creation from string, I moved the TimeInfo formatting code above the toTime proc declaration. Also, I changed the argument type for newDate from string to cstring for it to work. --- lib/pure/times.nim | 513 ++++++++++++++++++++++----------------------- 1 file changed, 253 insertions(+), 260 deletions(-) diff --git a/lib/pure/times.nim b/lib/pure/times.nim index 867800eef817d..1b088c0acc301 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -385,266 +385,6 @@ proc `miliseconds=`*(t: var TimeInterval, milliseconds: int) {.deprecated.} = ## version. t.milliseconds = milliseconds -when not defined(JS): - proc epochTime*(): float {.rtl, extern: "nt$1", tags: [TimeEffect].} - ## gets time after the UNIX epoch (1970) in seconds. It is a float - ## because sub-second resolution is likely to be supported (depending - ## on the hardware/OS). - - proc cpuTime*(): float {.rtl, extern: "nt$1", tags: [TimeEffect].} - ## gets time spent that the CPU spent to run the current process in - ## seconds. This may be more useful for benchmarking than ``epochTime``. - ## However, it may measure the real time instead (depending on the OS). - ## The value of the result has no meaning. - ## To generate useful timing values, take the difference between - ## the results of two ``cpuTime`` calls: - ## - ## .. code-block:: nim - ## var t0 = cpuTime() - ## doWork() - ## echo "CPU time [s] ", cpuTime() - t0 - -when not defined(JS): - # C wrapper: - when defined(freebsd) or defined(netbsd) or defined(openbsd) or - defined(macosx): - type - StructTM {.importc: "struct tm", final.} = object - second {.importc: "tm_sec".}, - minute {.importc: "tm_min".}, - hour {.importc: "tm_hour".}, - monthday {.importc: "tm_mday".}, - month {.importc: "tm_mon".}, - year {.importc: "tm_year".}, - weekday {.importc: "tm_wday".}, - yearday {.importc: "tm_yday".}, - isdst {.importc: "tm_isdst".}: cint - gmtoff {.importc: "tm_gmtoff".}: clong - else: - type - StructTM {.importc: "struct tm", final.} = object - second {.importc: "tm_sec".}, - minute {.importc: "tm_min".}, - hour {.importc: "tm_hour".}, - monthday {.importc: "tm_mday".}, - month {.importc: "tm_mon".}, - year {.importc: "tm_year".}, - weekday {.importc: "tm_wday".}, - yearday {.importc: "tm_yday".}, - isdst {.importc: "tm_isdst".}: cint - type - TimeInfoPtr = ptr StructTM - Clock {.importc: "clock_t".} = distinct int - - when not defined(windows): - # This is not ANSI C, but common enough - proc timegm(t: StructTM): Time {. - importc: "timegm", header: "", tags: [].} - - proc localtime(timer: ptr Time): TimeInfoPtr {. - importc: "localtime", header: "", tags: [].} - proc gmtime(timer: ptr Time): TimeInfoPtr {. - importc: "gmtime", header: "", tags: [].} - proc timec(timer: ptr Time): Time {. - importc: "time", header: "", tags: [].} - proc mktime(t: StructTM): Time {. - importc: "mktime", header: "", tags: [].} - proc getClock(): Clock {.importc: "clock", header: "", tags: [TimeEffect].} - proc difftime(a, b: Time): float {.importc: "difftime", header: "", - tags: [].} - - var - clocksPerSec {.importc: "CLOCKS_PER_SEC", nodecl.}: int - - # our own procs on top of that: - proc tmToTimeInfo(tm: StructTM, local: bool): TimeInfo = - const - weekDays: array[0..6, WeekDay] = [ - dSun, dMon, dTue, dWed, dThu, dFri, dSat] - TimeInfo(second: int(tm.second), - minute: int(tm.minute), - hour: int(tm.hour), - monthday: int(tm.monthday), - month: Month(tm.month), - year: tm.year + 1900'i32, - weekday: weekDays[int(tm.weekday)], - yearday: int(tm.yearday), - isDST: tm.isdst > 0, - timezone: if local: getTimezone() else: 0 - ) - - - proc timeInfoToTM(t: TimeInfo): StructTM = - const - weekDays: array[WeekDay, int8] = [1'i8,2'i8,3'i8,4'i8,5'i8,6'i8,0'i8] - result.second = t.second - result.minute = t.minute - result.hour = t.hour - result.monthday = t.monthday - result.month = ord(t.month) - result.year = cint(t.year - 1900) - result.weekday = weekDays[t.weekday] - result.yearday = t.yearday - result.isdst = if t.isDST: 1 else: 0 - - when not defined(useNimRtl): - proc `-` (a, b: Time): int64 = - return toBiggestInt(difftime(a, b)) - - proc getStartMilsecs(): int = - #echo "clocks per sec: ", clocksPerSec, "clock: ", int(getClock()) - #return getClock() div (clocksPerSec div 1000) - when defined(macosx): - result = toInt(toFloat(int(getClock())) / (toFloat(clocksPerSec) / 1000.0)) - else: - result = int(getClock()) div (clocksPerSec div 1000) - when false: - var a: Timeval - posix_gettimeofday(a) - result = a.tv_sec * 1000'i64 + a.tv_usec div 1000'i64 - #echo "result: ", result - - proc getTime(): Time = return timec(nil) - proc getLocalTime(t: Time): TimeInfo = - var a = t - let lt = localtime(addr(a)) - assert(not lt.isNil) - result = tmToTimeInfo(lt[], true) - # copying is needed anyway to provide reentrancity; thus - # the conversion is not expensive - - proc getGMTime(t: Time): TimeInfo = - var a = t - result = tmToTimeInfo(gmtime(addr(a))[], false) - # copying is needed anyway to provide reentrancity; thus - # the conversion is not expensive - - proc toTime(timeInfo: TimeInfo): Time = - var cTimeInfo = timeInfo # for C++ we have to make a copy - # because the header of mktime is broken in my version of libc - - result = mktime(timeInfoToTM(cTimeInfo)) - # mktime is defined to interpret the input as local time. As timeInfoToTM - # does ignore the timezone, we need to adjust this here. - result = Time(TimeImpl(result) - getTimezone() + timeInfo.timezone) - - proc timeInfoToTime(timeInfo: TimeInfo): Time = toTime(timeInfo) - - const - epochDiff = 116444736000000000'i64 - rateDiff = 10000000'i64 # 100 nsecs - - proc unixTimeToWinTime*(t: Time): int64 = - ## converts a UNIX `Time` (``time_t``) to a Windows file time - result = int64(t) * rateDiff + epochDiff - - proc winTimeToUnixTime*(t: int64): Time = - ## converts a Windows time to a UNIX `Time` (``time_t``) - result = Time((t - epochDiff) div rateDiff) - - proc getTimezone(): int = - when defined(freebsd) or defined(netbsd) or defined(openbsd): - var a = timec(nil) - let lt = localtime(addr(a)) - # BSD stores in `gmtoff` offset east of UTC in seconds, - # but posix systems using west of UTC in seconds - return -(lt.gmtoff) - else: - return timezone - - proc fromSeconds(since1970: float): Time = Time(since1970) - - proc toSeconds(time: Time): float = float(time) - - when not defined(useNimRtl): - proc epochTime(): float = - when defined(posix): - var a: Timeval - posix_gettimeofday(a) - result = toFloat(a.tv_sec) + toFloat(a.tv_usec)*0.00_0001 - elif defined(windows): - var f: winlean.FILETIME - getSystemTimeAsFileTime(f) - var i64 = rdFileTime(f) - epochDiff - var secs = i64 div rateDiff - var subsecs = i64 mod rateDiff - result = toFloat(int(secs)) + toFloat(int(subsecs)) * 0.0000001 - else: - {.error: "unknown OS".} - - proc cpuTime(): float = - result = toFloat(int(getClock())) / toFloat(clocksPerSec) - -elif defined(JS): - proc newDate(): Time {.importc: "new Date".} - proc internGetTime(): Time {.importc: "new Date", tags: [].} - - proc newDate(value: float): Time {.importc: "new Date".} - proc newDate(value: string): Time {.importc: "new Date".} - proc getTime(): Time = - # Warning: This is something different in JS. - return newDate() - - const - weekDays: array[0..6, WeekDay] = [ - dSun, dMon, dTue, dWed, dThu, dFri, dSat] - - proc getLocalTime(t: Time): TimeInfo = - result.second = t.getSeconds() - result.minute = t.getMinutes() - result.hour = t.getHours() - result.monthday = t.getDate() - result.month = Month(t.getMonth()) - result.year = t.getFullYear() - result.weekday = weekDays[t.getDay()] - result.timezone = getTimezone() - - result.yearday = result.monthday - 1 - for month in mJan..", tags: [].} + + proc localtime(timer: ptr Time): TimeInfoPtr {. + importc: "localtime", header: "", tags: [].} + proc gmtime(timer: ptr Time): TimeInfoPtr {. + importc: "gmtime", header: "", tags: [].} + proc timec(timer: ptr Time): Time {. + importc: "time", header: "", tags: [].} + proc mktime(t: StructTM): Time {. + importc: "mktime", header: "", tags: [].} + proc getClock(): Clock {.importc: "clock", header: "", tags: [TimeEffect].} + proc difftime(a, b: Time): float {.importc: "difftime", header: "", + tags: [].} + + var + clocksPerSec {.importc: "CLOCKS_PER_SEC", nodecl.}: int + + # our own procs on top of that: + proc tmToTimeInfo(tm: StructTM, local: bool): TimeInfo = + const + weekDays: array[0..6, WeekDay] = [ + dSun, dMon, dTue, dWed, dThu, dFri, dSat] + TimeInfo(second: int(tm.second), + minute: int(tm.minute), + hour: int(tm.hour), + monthday: int(tm.monthday), + month: Month(tm.month), + year: tm.year + 1900'i32, + weekday: weekDays[int(tm.weekday)], + yearday: int(tm.yearday), + isDST: tm.isdst > 0, + timezone: if local: getTimezone() else: 0 + ) + + + proc timeInfoToTM(t: TimeInfo): StructTM = + const + weekDays: array[WeekDay, int8] = [1'i8,2'i8,3'i8,4'i8,5'i8,6'i8,0'i8] + result.second = t.second + result.minute = t.minute + result.hour = t.hour + result.monthday = t.monthday + result.month = ord(t.month) + result.year = cint(t.year - 1900) + result.weekday = weekDays[t.weekday] + result.yearday = t.yearday + result.isdst = if t.isDST: 1 else: 0 + + when not defined(useNimRtl): + proc `-` (a, b: Time): int64 = + return toBiggestInt(difftime(a, b)) + + proc getStartMilsecs(): int = + #echo "clocks per sec: ", clocksPerSec, "clock: ", int(getClock()) + #return getClock() div (clocksPerSec div 1000) + when defined(macosx): + result = toInt(toFloat(int(getClock())) / (toFloat(clocksPerSec) / 1000.0)) + else: + result = int(getClock()) div (clocksPerSec div 1000) + when false: + var a: Timeval + posix_gettimeofday(a) + result = a.tv_sec * 1000'i64 + a.tv_usec div 1000'i64 + #echo "result: ", result + + proc getTime(): Time = return timec(nil) + proc getLocalTime(t: Time): TimeInfo = + var a = t + let lt = localtime(addr(a)) + assert(not lt.isNil) + result = tmToTimeInfo(lt[], true) + # copying is needed anyway to provide reentrancity; thus + # the conversion is not expensive + + proc getGMTime(t: Time): TimeInfo = + var a = t + result = tmToTimeInfo(gmtime(addr(a))[], false) + # copying is needed anyway to provide reentrancity; thus + # the conversion is not expensive + + proc toTime(timeInfo: TimeInfo): Time = + var cTimeInfo = timeInfo # for C++ we have to make a copy + # because the header of mktime is broken in my version of libc + + result = mktime(timeInfoToTM(cTimeInfo)) + # mktime is defined to interpret the input as local time. As timeInfoToTM + # does ignore the timezone, we need to adjust this here. + result = Time(TimeImpl(result) - getTimezone() + timeInfo.timezone) + + proc timeInfoToTime(timeInfo: TimeInfo): Time = toTime(timeInfo) + + const + epochDiff = 116444736000000000'i64 + rateDiff = 10000000'i64 # 100 nsecs + + proc unixTimeToWinTime*(t: Time): int64 = + ## converts a UNIX `Time` (``time_t``) to a Windows file time + result = int64(t) * rateDiff + epochDiff + + proc winTimeToUnixTime*(t: int64): Time = + ## converts a Windows time to a UNIX `Time` (``time_t``) + result = Time((t - epochDiff) div rateDiff) + + proc getTimezone(): int = + when defined(freebsd) or defined(netbsd) or defined(openbsd): + var a = timec(nil) + let lt = localtime(addr(a)) + # BSD stores in `gmtoff` offset east of UTC in seconds, + # but posix systems using west of UTC in seconds + return -(lt.gmtoff) + else: + return timezone + + proc fromSeconds(since1970: float): Time = Time(since1970) + + proc toSeconds(time: Time): float = float(time) + + when not defined(useNimRtl): + proc epochTime(): float = + when defined(posix): + var a: Timeval + posix_gettimeofday(a) + result = toFloat(a.tv_sec) + toFloat(a.tv_usec)*0.00_0001 + elif defined(windows): + var f: winlean.FILETIME + getSystemTimeAsFileTime(f) + var i64 = rdFileTime(f) - epochDiff + var secs = i64 div rateDiff + var subsecs = i64 mod rateDiff + result = toFloat(int(secs)) + toFloat(int(subsecs)) * 0.0000001 + else: + {.error: "unknown OS".} + + proc cpuTime(): float = + result = toFloat(int(getClock())) / toFloat(clocksPerSec) + +elif defined(JS): + proc newDate(): Time {.importc: "new Date".} + proc internGetTime(): Time {.importc: "new Date", tags: [].} + + proc newDate(value: float): Time {.importc: "new Date".} + proc newDate(value: cstring): Time {.importc: "new Date".} + proc getTime(): Time = + # Warning: This is something different in JS. + return newDate() + + const + weekDays: array[0..6, WeekDay] = [ + dSun, dMon, dTue, dWed, dThu, dFri, dSat] + + proc getLocalTime(t: Time): TimeInfo = + result.second = t.getSeconds() + result.minute = t.getMinutes() + result.hour = t.getHours() + result.monthday = t.getDate() + result.month = Month(t.getMonth()) + result.year = t.getFullYear() + result.weekday = weekDays[t.getDay()] + result.timezone = getTimezone() + + result.yearday = result.monthday - 1 + for month in mJan.. Date: Wed, 29 Mar 2017 16:42:48 +0400 Subject: [PATCH 9/9] Tests: JS: Times: Fix text so that it works in timezones other then UTC+4. `parse` returns TimeInfo with the local timezone, which may not be the same as the one in the original string. To compare the moments encoded in the original string and returned by `parse`, we normalize them to UTC. --- tests/js/ttimes.nim | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/js/ttimes.nim b/tests/js/ttimes.nim index 6420c81483092..20ba142450104 100644 --- a/tests/js/ttimes.nim +++ b/tests/js/ttimes.nim @@ -13,13 +13,14 @@ block yeardayTest: doAssert fromSeconds(2147483647).getGMTime().yearday == 18 block localTimezoneTest: - # check if timezone is properly set durint Time to TimeInfo conversion + # check if timezone is properly set during Time to TimeInfo conversion doAssert fromSeconds(2147483647).getLocalTime().timezone == getTimezone() block timestampPersistenceTest: # check if timestamp persists during TimeInfo to Time conversion const - testString = "2017-03-21T12:34:56+04:00" + timeString = "2017-03-21T12:34:56+03:00" + timeStringGmt = "2017-03-21T09:34:56+00:00" fmt = "yyyy-MM-dd'T'HH:mm:sszzz" - doAssert $testString.parse(fmt) == testString + doAssert $timeString.parse(fmt).toTime().getGMTime() == timeStringGmt