首页 > 解决方案 > 在 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

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

但是,鉴于jq上面显示的各种输入的输出,这种理解肯定是错误的。特别是,我不明白如何正确和可靠地将时间字符串解析为 GMT 时间戳,并且 b) 一旦完成,当传递到生成上面看到的输出数组时fromdatemktimenow和的输出如何分别不同。strptimestrf[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

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

strflocaltime,当给定时间戳时,似乎根据TZ.

不幸的是,这似乎意味着我首先需要将 TZ 设置Etc/Utc为才能fromdate正常运行,然后当我想打印本地时间时,我需要重新设置TZ为本地时区。

标签: timezonejq

解决方案


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

首先,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

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

第二,fromdate等同于strptime() | mktime

第三,strflocaltime将时区偏移(永久和 DST)应用于 unix 时间戳输入,但不适用于分解时间输入:

$ 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"

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

按顺序检查我原来的混淆顺序:

$ 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

上面的 (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?!

在这里,通过上面的 (3)strptime产生一个不通过 , 调整的故障时间。strflocaltime

$ 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?

strptime产生故障时间,mktime理论上应该将其转换为 unix-timestamp 时间,假设它是 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

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

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

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


推荐阅读