【问题标题】:daylight saving time influences the result of mktime夏令时影响 mktime 的结果
【发布时间】:2018-11-02 09:58:28
【问题描述】:

我在尝试获取准确的 GPS 时间时遇到问题。看起来问题与代码中使用的命令 mktime 有关。 为了获得正确的 GPS 时间,我在 UTC 中添加了 -2 小时。

但目的是有一个代码来自动计算 GPS 时间,而不是以 UTC-2 手动更改为例

输入文件 GMT 时间(当地时间 > 3 小时)

18/05/21 08:18:29
18/05/21 08:20:25
18/05/21 08:21:02

当我运行下面的代码时,减少了 1 小时:

gawk -F'[/: ]' -v ts=$(date -d'01/06/1980' +%s)  \
                -v lap=18 '{$1="" d=sprintf(20$0); 
                            print mktime(d)+lap-ts }'  file

结果输出 GPS TIME (

1210922327
1210922443
1210922480

当我运行下面的代码时,得到了准确的 GPS 时间。

gawk -F'[/: ]' -v ts=$(TZ=UTC-2 date -d'1/6/1980 0:00' +%s)  \
                -v lap=18 '{$1="" d=sprintf(20$0); 
                            print mktime(d)+lap-ts }' file

结果输出 GPS TIME(准确时间:OUTPUT DESIRED)

1210925927
1210926043
1210926080

提前致谢

【问题讨论】:

  • 你想达到什么目的? mktime 需要“YYYY MM DD hh mm ss”形式的输入。所以一切都是空格分隔的,没有特殊字符,如“-”或“/”或“T”或“。”或“:”
  • Hi kvantor 在输入文件中是 mktime 的信息 (18/05/21 08:18:29)
  • 嗨 OXXO,正如我在之前的评论中指出的那样,mktime 无法解释这种格式,您需要将其从 18/05/21 08:18:29 转换为 2018 05 21 08 18 29 只有这样您才能收到自 @ 以来的正确秒数987654332@。你可以做类似d=$0; gsub(/[/:]/," ",d); mktime("20"d);
  •  夏令时?
  • mktime(datespec [, utc-flag ]) :: datespec 的形式为"YYYY MM DD HH MM SS [DST]" 如果DST 夏令时标志为正,则假定时间为夏令时;如果为零,则假定时间为标准时间;如果否定(默认),mktime() 会尝试确定夏令时是否在指定时间生效。 所以我建议在其后面添加一个额外的0。

标签: awk timezone dst


【解决方案1】:

您遇到的主要与系统的时区设置有关。由于Daylight Saving Time,您的时区偏移了一小时,这导致了差异。一个非常好的检测 DST 的帖子,可以找到 here。复制其示例,我们可以证明对于时区 TZ=Europe/Stockholm,时区会根据日期更改为夏令时:

$ TZ=Europe/Stockholm date +%Z # CET or CEST depending of when its run
$ TZ=Europe/Stockholm date --date=20170101 +%Z # CET
$ TZ=Europe/Stockholm date --date=20170601 +%Z # CEST
$ TZ=CET date --date=20170101 +%Z # CET
$ TZ=CET date --date=20170601 +%Z # CEST, note that its auto adjusted to CEST

因此它肯定会对从 1970-01-01T00:00:00 UTC 给出的纪元时间产生影响。通过zdump,我们可以看到 DST 何时生效:

$ zdump -v /usr/share/zoneinfo/Europe/Stockholm | grep 2018
/usr/share/zoneinfo/Europe/Stockholm  Sun Mar 25 00:59:59 2018 UTC = Sun Mar 25 01:59:59 2018 CET isdst=0 gmtoff=3600
/usr/share/zoneinfo/Europe/Stockholm  Sun Mar 25 01:00:00 2018 UTC = Sun Mar 25 03:00:00 2018 CEST isdst=1 gmtoff=7200
/usr/share/zoneinfo/Europe/Stockholm  Sun Oct 28 00:59:59 2018 UTC = Sun Oct 28 02:59:59 2018 CEST isdst=1 gmtoff=7200
/usr/share/zoneinfo/Europe/Stockholm  Sun Oct 28 01:00:00 2018 UTC = Sun Oct 28 02:00:00 2018 CET isdst=0 gmtoff=3600

这在纪元时间被视为:

$ TZ=Europe/Stockholm date -d "2018-03-25 01:59:59" +%s
1521939599
$ TZ=Europe/Stockholm date -d "2018-03-25 03:00:00" +%s
1521939600
$ TZ=Europe/Stockholm date -d "2018-03-25 02:00:00" +%s
date: invalid date ‘2018-03-25 02:00:00’

如您所见,对于 TZ=Europe/Stockholm,时间 2018-03-25T02:00:00 不存在,而其他两个时间仅为 1 秒分开。


总结,这一切是什么意思:它本质上意味着您的系统会自动补偿 DST,除非您的 TZ 是 UTC。这在所有与日期相关的命令上都起作用,例如 systime()、date 甚至是 Awk 的 mktime()。


我们能否通过awk 避免夏令时补偿: 由于 OP 需要 GPS 时间,即自 1980-01-06T00:00:00 以来的总秒数,因此您基本上减去了两次。因此,如果两者都在同一个 TZ 中计算而没有 DST 校正,您总是会得到正确的结果。有两种方法可以做到这一点:

  • 在特定时区执行您的命令: 通过强制系统在单个 TZ(例如 UTC、UTC+2、...)中工作,将不会有 DST 校正。对于 OP 的问题,感兴趣的 TZ 是 UTC。

    $ TZ="UTC" awk 'BEGIN { ts  = mktime("1980 01 06 00 00 00") }
                    { datespec="20"$0; gsub(/[/:]/," ",datespec);
                      print mktime(datespec) + lap - ts
                    }' lap=18 file
    

    或从awk 4.20 开始,您可以告诉mktime() 使用UTC 标志假定日期为UTC。 (mktime(datespec [, utc-flag ]))

    $ awk 'BEGIN { ts  = mktime("1980 01 06 00 00 00",1) }
           { datespec="20"$0; gsub(/[/:]/," ",datespec);
             print mktime(datespec,1) + lap - ts
           }' lap=18 file
    

    两者都会产生以下输出。

    1210925927
    1210926043
    1210926080
    

    在这两种情况下,您都无需担心您的系统时区以及与夏令时相关的所有内容。

  • 使用 mktime 禁用 DST 校正:将 DST 条目添加到 mktime 的 datespec 部分时,您可以告诉系统是否始终在 DST 中工作或让系统自己算出来。后者是您不想要的。 datespec 是 YYYY MM DD HH MM SS [DST] 形式的字符串。这也降低了它:

    $ awk 'BEGIN { ts  = mktime("1980 01 06 00 00 00 0") }
           { datespec="20"$0; gsub(/[/:]/," ",datespec);
             print mktime(datespec" 0") + lap - ts
           }' lap=18 file
    

mktime awk 4.2.0 以上的文档:

mktime(datespec [, utc-flag ]) 将datespec 转换为与systime() 返回的格式相同的时间戳。它类似于 ISO C 中的同名函数。参数datespec 是"YYYY MM DD HH MM SS [DST]" 形式的字符串。该字符串由六个或七个数字组成,分别代表包括世纪在内的全年,从 1 到 12 的月份,从 1 到 31 的月份的日期,从 0 到 23 的小时,从 0 到 23 的分钟59,从 0 到 60,55 的第二个和一个可选的夏令时标志。

这些数字的值不必在指定的范围内;例如,一小时 -1 表示午夜前 1 小时。假定原点为零的公历,第 0 年在第 1 年之前,第 -1 年在第 0 年之前。如果utc-flag 存在并且非零或非空,则假定时间为UTC 时区;否则,时间假定为本地时区。 如果DST夏令时标志为正,则假定时间为夏令时;如果为零,则假定时间为标准时间;如果为负(默认值),mktime() 会尝试确定夏令时是否在指定时间内有效。

如果datespec 不包含足够的元素或者如果结果时间超出范围,则mktime() 返回-1。

【讨论】:

  • Kvantour,令人惊叹的解释。 tks 很多,我已经在我的输出中修改了结果,因为我注意到一个额外的数字现在结果与您的代码的输出匹配。再次感谢广泛的信息和出色的代码。
  • @OXXO 我已经重写了帖子,相信解决方案就在那里。我希望这有帮助。请删除此处的一些 cmets,因为它们不再对帖子做出贡献,并且您提出的所有问题都已解决。
  • 您好 Kvantour,这是一个很好的关于夏令时的解释,以及它对 mktime 功能的影响,非常感谢您分享这些信息,对其他用户非常有用。我使用该选项强制系统在单个 TZ 中工作,它现在可以完美运行。再次感谢您提供非常有用的帖子。
猜你喜欢
  • 2014-04-28
  • 2013-06-27
  • 1970-01-01
  • 2010-09-18
  • 2011-02-10
  • 2012-02-10
  • 2015-11-10
  • 2011-06-07
  • 1970-01-01
相关资源
最近更新 更多