在 jq 中可靠地解析日期字符串

Cla*_*ius 3 timezone jq

总体目标:将 GMT 中的字符串解析为时间,jq并将格式化的时间和该时间的差异输出到“现在”。但是,jqs(版本 1.6,Debian 测试)时区处理对我来说似乎很困惑:

$ jq --version
jq-1.6
$ date
Sa 4. Jul 19:36:08 BST 2020
$ echo '""' | jq 'now | strftime("%H:%M")'
"18:36"        // OK, strftime is supposed to give GMT
$ echo '""' | jq 'now | strflocaltime("%H:%M")'
"19:36"        // also OK, British Summer time is one hour ahead, strflocaltime should give local time
$ echo '"2020-07-04T18:14:12Z"' | jq 'strptime("%Y-%m-%dT%H:%M:%SZ") | strftime("%H:%M")'
"18:14"        // strptime parses GMT, so this is fine
$ echo '"2020-07-04T18:14:12Z"' | jq 'strptime("%Y-%m-%dT%H:%M:%SZ") | strflocaltime("%H:%M")'
"18:14"        // but why is this not 19:14?!
$ echo '"2020-07-04T18:14:12Z"' | jq 'strptime("%Y-%m-%dT%H:%M:%SZ") | mktime | strftime("%H:%M")'
"19:14"        // and why does "mktime" change things around?
$ echo '"2020-07-04T18:14:12Z"' | jq 'strptime("%Y-%m-%dT%H:%M:%SZ") | mktime | strflocaltime("%H:%M")'
"20:14"       // and why does strflocaltime kick in after, but not before mktime?
$ echo '"2020-07-04T18:14:12Z"' | jq 'fromdate | strftime("%H:%M")'
"19:14"       // I thought fromdate was synonymous to strptime?
$ echo '"2020-07-04T18:14:12Z"' | jq 'fromdate | strflocaltime("%H:%M")'
"20:14"       // I suppose this is the same issue as above with mktime
Run Code Online (Sandbox Code Playgroud)

更长的版本:我正在使用 API 来显示附近火车站的到达时间,特别是我想显示接下来的几列火车以及他们将离开的分钟数。我想用来jq解析该数据。数据包含格式的时间字符串"2020-07-04T18:14:12Z"。我的理解是,两者fromdatestrptimeinjq都应该将数据解析为 GMT 时间戳(来自手册页:“在所有情况下,这些内置函数都专门处理 UTC 时间。”,手册页似乎可以互换使用 GMT 和 UTC)和任何操作在jq使用 UTC范围内,如果使用,则只有最终输出在本地时区strflocaltime

但是,鉴于jq上面显示的各种输入的输出,这种理解肯定是错误的。特别是,我不明白如何正确可靠地将时间字符串解析为 GMT 时间戳,并且 b) 一旦完成,当传入以生成上述输出数组时fromdatemktimenow和的输出strptime分别有何不同strf[local]time

编辑:使用前两个答案中的信息进一步播放,似乎主要问题是fromdate夏令时的应用(或不应用)取决于TZ环境变量的设置:

$ TZ=BST jq -n '"2020-07-05T07:38:57Z" | fromdate'
1593934737
$ TZ=Etc/UTC jq -n '"2020-07-05T07:38:57Z" | fromdate'
1593934737
$ TZ=Europe/London jq -n '"2020-07-05T07:38:57Z" | fromdate'
1593938337
$ TZ=Asia/Tokyo jq -n '"2020-07-05T07:38:57Z" | fromdate'
1593934737
$ TZ=America/Los_Angeles jq -n '"2020-07-05T07:38:57Z" | fromdate'
1593938337
$ TZ=Asia/Kathmandu jq -n '"2020-07-05T07:38:57Z" | fromdate'
1593934737
$ unset TZ; jq -n '"2020-07-05T07:38:57Z" | fromdate'
1593938337
Run Code Online (Sandbox Code Playgroud)

请注意,伦敦、洛杉矶和未设置的 TZ 获得与东京、加德满都、UTC 和(我认为格式错误?)BST 不同的 Unix 纪元时间戳。我相信这不应该发生,因为时间戳应该与时区无关。不幸的是,目前它似乎无视永久时区偏移(东京和加德满都给出与 UTC 相同的结果,两者都没有 DST)但它确实考虑了 DST,除非在不遵守 DST 的时区中运行。

strflocaltime,当给定时间戳时,似乎根据 的当前值应用永久和 DST 时区校正TZ

不幸的是这似乎暗示,我首先需要设置TZ,以Etc/Utc获得fromdate正确,然后表现时,我想打印本地时间,我需要重新设置TZ为本地时区。

Cla*_*ius 5

我想在这里开始建立一个答案,结合不同的块:

首先,mktime在采用“分解时间结构”时考虑 DST 但不考虑其他时区信息:

$ TZ=Etc/Utc jq -n '[2020,6,5,7,38,57,0,186] | mktime'
1593934737
$ TZ=Europe/London jq -n '[2020,6,5,7,38,57,0,186] | mktime'
1593938337
$ TZ=America/Los_Angeles jq -n '[2020,6,5,7,38,57,0,186] | mktime'
1593938337
$ TZ=Asia/Tokyo jq -n '[2020,6,5,7,38,57,0,186] | mktime'
1593934737
$ TZ=Asia/Kathmandu jq -n '[2020,6,5,7,38,57,0,186] | mktime'
1593934737
$ unset TZ; jq -n '[2020,6,5,7,38,57,0,186] | mktime'
1593938337
Run Code Online (Sandbox Code Playgroud)

请注意,仅有的两个输出是 15939 34 737 或 15939 38 337,它们的差正好是 3600。

其次,fromdatestrptime() | mktime.

第三,strflocaltime将时区偏移量(永久和 DST)应用于 unix-timestamp 输入,但不适用于故障时间输入:

$ TZ='Europe/London' jq -n '[2020,6,5,7,38,57,0,186] | strflocaltime("%H:%M")'
"07:38"
$ TZ='Asia/Tokyo' jq -n '[2020,6,5,7,38,57,0,186] | strflocaltime("%H:%M")'
"07:38"
$ TZ='Europe/London' jq -n '1593934737 | strflocaltime("%H:%M")'
"08:38"
$ TZ='Asia/Tokyo' jq -n '1593934737 | strflocaltime("%H:%M")'
"16:38"
Run Code Online (Sandbox Code Playgroud)

第四,now产生一个 unix-timestamp 输出,它会受到strflocaltime's 调整的影响。

按顺序回顾我最初引起混淆的序列:

$ echo '""' | jq 'now | strftime("%H:%M")'
"18:36"        // OK, strftime is supposed to give GMT
$ echo '""' | jq 'now | strflocaltime("%H:%M")'
"19:36"        // also OK, British Summer time is one hour ahead, strflocaltime should give local time
Run Code Online (Sandbox Code Playgroud)

这由上面的 (3) 和 (4) 解释:now生成一个 unix 时间戳,将其strflocaltime调整为本地时间。

$ echo '"2020-07-04T18:14:12Z"' | jq 'strptime("%Y-%m-%dT%H:%M:%SZ") | strftime("%H:%M")'
"18:14"        // strptime parses GMT, so this is fine
$ echo '"2020-07-04T18:14:12Z"' | jq 'strptime("%Y-%m-%dT%H:%M:%SZ") | strflocaltime("%H:%M")'
"18:14"        // but why is this not 19:14?!
Run Code Online (Sandbox Code Playgroud)

在这里,strptime产生了一个不被strflocaltime上述(3)调整的击穿时间。

$ echo '"2020-07-04T18:14:12Z"' | jq 'strptime("%Y-%m-%dT%H:%M:%SZ") | mktime | strftime("%H:%M")'
"19:14"        // and why does "mktime" change things around?
$ echo '"2020-07-04T18:14:12Z"' | jq 'strptime("%Y-%m-%dT%H:%M:%SZ") | mktime | strflocaltime("%H:%M")'
"20:14"       // and why does strflocaltime kick in after, but not before mktime?
Run Code Online (Sandbox Code Playgroud)

strptime产生故障时间,mktime理论上应该将其转换为 Unix 时间戳时间,假设它是 UTC,但mktime错误地应用了一个小时的 DST 偏移量(通过上面的(1)),导致strftime产生(意外正确的)本地时间和strflocaltime– 纠正永久和 DST 偏移(通过上面的 (3)) – 再给出一个(总共两个)小时的偏移。

$ echo '"2020-07-04T18:14:12Z"' | jq 'fromdate | strftime("%H:%M")'
"19:14"       // I thought fromdate was synonymous to strptime?
$ echo '"2020-07-04T18:14:12Z"' | jq 'fromdate | strflocaltime("%H:%M")'
"20:14"       // I suppose this is the same issue as above with mktime
Run Code Online (Sandbox Code Playgroud)

这只是内部fromdate使用的 (2) 的结果mktime

在 master 分支(a17dd32)上编译最新提交,不再出现此问题,因为mktime不再应用一小时偏移量。这可能是由于提交3c5b1419

作为临时解决方法,我们可以通过mktimewith:获得偏移量jq -n 'now | gmtime | mktime - (now | trunc)'。从任何出现的 中减去此偏移量fromdate将可靠地产生 UTC 时间戳。