Hello,
I am tried to do this:
I tried doing the first part using below:
import times
let
dtStr = "2019/06/06 18:17:43 +00:00"
dt = parse(dtStr, "YYYY/MM/dd HH:mm:ss zzz")
echo dt
echo dt.timezone
echo dt.utcOffset
But it prints:
2019-06-06T14:17:43-04:00
LOCAL
14400
I understand why that happens.. because as mentioned in https://nim-lang.github.io/Nim/times#parse%2Cstring%2CTimeFormat%2CTimezone%2CDateTimeLocale :
If a UTC offset was parsed, the result will be converted to the zone timezone.
But then the original time zone (which was UTC: +00:00) is lost!
How do I parse that original time zone to a TimeZone variable from "2019/06/06 18:17:43 +00:00"?
Thanks.
there's utc proc for what you want
import times
let
dtStr = "2019/06/06 18:17:43 +00:00"
dt = parse(dtStr, "YYYY/MM/dd HH:mm:ss zzz").utc
echo dt
Based on the reply from times stdlib maintainer, what I want to do is not possible using that stdlib.
But he does have his timezones package on Nimble that helped me out. Here is the solution.
import times, strutils, timezones
let
remoteDtStr = "2019/06/06 18:17:43 +00:00"
remoteZoneStr = remoteDtStr.split(' ')[2]
# https://gitter.im/nim-lang/Nim?at=5cf98056bf4cbd167c412705
remoteZone = tz(remoteZoneStr)
localDtStr = "2019/06/06:18:17:43"
localDt = parse(localDtStr, "YYYY/MM/dd:HH:mm:ss")
localDtInRemoteZone = localDt.inZone(remoteZone)
echo localDt
echo localDtInRemoteZone
Outputs:
2019-06-06T18:17:43-04:00
2019-06-06T22:17:43+00:00
Shameless plug: https://github.com/treeform/chrono
"+00:00" its an timezone offset, not a timezone or a dst part.
I struggled with timezones for a long time, then one day I said enough is enough and wrote my own timezone library. And now I know how they work 100%. I have slain one of my big dragons.
import chrono
block:
let
dtStr = "2019/06/06 18:17:43 +00:00"
cal = parseCalendar("{year/4}/{month/2}/{day/2} {hour/2}:{minute/2}:{second/2} {offsetDir}{offsetHour/2}:{offsetMinute/2}", dtStr)
echo cal
echo cal.tzName # blank there is no timezone
echo cal.tzOffset # 0
block:
let
dtStr = "2019/06/06 18:17:43 -07:00"
cal = parseCalendar("{year/4}/{month/2}/{day/2} {hour/2}:{minute/2}:{second/2} {offsetDir}{offsetHour/2}:{offsetMinute/2}", dtStr)
echo cal #2019-06-06T18:17:43-07:00
echo cal.tzName # blank there is no timezone
echo cal.tzOffset # -25200.0 in seconds
The dtStr is what I get by parsing something else. I don't know the target timezone beforehand. The dtStr example just happens to be UTC.. the timezone in it could even be +01:30.. I don't know that beforehand.
I see, what you want is the datetime as it is with its original timezone.
The last resort is to have manual splitting like using strscans or pegs.
"+00:00" its an timezone offset, not a timezone or a dst part.
Thanks. I did not know that difference earlier.
The challenge is not with parsing the date/time string.. it's with getting a TimeZone type (from times module) variable by parsing the time zone offset portion of the input date/time string.
(once I get the timezone variable, I can using the inZone proc from times to convert a different time/date string to the timezone parsed from the first time/date string.)
I looked at the chrono module, and it looks like it's a replacement of the stdlib times. I noticed that your library also has a TimeZone type which is different that one in times.
For now the timezones library serves my purpose as its tz proc returns a TimeZone type value that's compatible with that type in times.
The last resort is to have manual splitting like using strscans or pegs.
Parsing the date/time string is not an issue.. the parse from times is wonderful. I just wished it retained the original time zone in the date/time string and not do an implicit inZone(local()) conversion for all parsed times.
@GULPF Is this something that can be fixed? If parse is used to parse a string with the time zone offset specified, then it should always return the DateTime in the specified timezone. User can always do a .local() on that returned value to get the local time from that.
What is your use case? Like @treeform says, "+01:00" is a UTC offset and not the full timezone. Even if you extract the UTC offset as a timezone, you cannot reliably use that offset to convert other dates to the users timezone.
Is this something that can be fixed?
I thought about implementing it like you want when I originally implemented Timezone, since it would be more powerful. The reason I didn't (other than to avoid a breaking change) was that it would be misleading. It would look like parse parsed the users timezone, even though that's not possible.
something like this
import times, strutils, sugar
let
dtStr = "2019/06/06 18:17:43 +02:00"
dt = parse(dtStr, "YYYY/MM/dd HH:mm:ss zzz")
myM10 = newTimezone("M-10",
(x: Time) => ZonedTime(utcOffset: Hours.convert(Seconds, 10), time: x),
(x: Time) => ZonedTime(utcOffset: Hours.convert(Seconds, 10), time: x))
dtOffset = -1 * dtStr.rsplit(maxsplit=1)[1].split(":", maxsplit=1)[0].parseInt
dtTz = newTimezone("DtTz",
(x: Time) => ZonedTime(utcOffset: Hours.convert(Seconds, dtOffset), time: x),
(x: Time) => ZonedTime(utcOffset: Hours.convert(Seconds, dtOffset), time: x))
dtOrig = dt.inZone(dtTz)
dtMy = dt.inZone(myM10)
echo dt
echo dt.timezone
echo dt.utcOffset
echo "========"
echo dtOrig
echo dtOrig.timezone
echo dtOrig.utcOffset
echo "========"
echo dtMy
echo dtMy.timezone
echo dtMy.utcOffset
note:
IMO, the current times module is more pleasant to work with compared to earlier version, kudos for the dev :3
Thanks. I'll try out your snippet later today.
What's confusing to me is that I tried about the same approach as yours but without using the sugar =>, and it didn't work because it failed to compile because the newTimeZone needed gcsafe proc args.
This fails: http://ix.io/1L6u/nim
From my discussion on gitter: https://gitter.im/nim-lang/Nim?at=5cf9d0e7f3a60a79a468a3da
I'll then also try out this suggestion by @GULPF: https://gitter.im/nim-lang/Nim?at=5cfa28266fc5846bab7808e1
I am failing to understand the basic requirements of how to make gcsafe procs, but I need to generate that offset int at run time from an outside proc.
What's confusing to me is that I tried about the same approach as yours but without using the sugar =>, and it didn't work because it failed to compile because the newTimeZone needed gcsafe proc args.
You're capturing string which is not gc-safe, in my case I just get the integer value of offset and convert accordingly using convert proc.
You can avoid capturing gc reffed entity by getting the necessary value instead of capturing the whole ref object there.
After some thought, I agree to have parse proc to retain original offset, because we need to preserve (arbitrary) information we have, but on the contrary, we have control of what timezone/offset we want to process in our app.
I just realized something (along the same lines of what you said)!
I was doing zoneStr.zoneStrToOffset() inside that gcsafe proc. But just moving that proc call outside it and passing it just the value of that call worked in my original snippet!
After some thought, I agree to have parse proc to retain original offset, because we need to preserve (arbitrary) information we have
I don't understand why the implicit local() inzoning is done if the input string specifies the time zone explicitly (if user hasn't provided the time zone in the string and then if that default local() is done, that's understood).
If user really always wants to do local(), appending .local() to the parse output is quite concise.
WDYT? @GULPF?
I don't understand why the implicit local() inzoning is done if the input string specifies the time zone explicitly
I think the problem is in stdlib in C only provided gmtime and localtime https://en.cppreference.com/w/c/chrono
CMIIW, since I haven't seen how parse implemented so I just guessed that.