夏令时影响 mktime 的结果

daylight saving time influences the result of mktime

我在尝试获取准确的 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 '{="" d=sprintf(20[=11=]); 
                            print mktime(d)+lap-ts }'  file

结果输出 GPS 时间(< 1 小时)

1210922327
1210922443
1210922480

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

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

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

1210925927
1210926043
1210926080

提前致谢

您遇到的问题主要与您系统的时区设置有关。由于 Daylight Saving Time, your time-zone shifted one hour which causes the discrepancy. A very nice post of detecting DST, can be found 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: 来避免 DST 补偿吗 由于 OP 需要 GPS 时间,即自 1980-01-06T00:00:00 以来的总秒数,因此您基本上减去两次。因此,如果两者都是在没有 DST 校正的情况下在同一个 TZ 中计算的,那么您总是会得到正确的结果。有两种方法可以做到这一点:

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

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

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

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

    两者都会产生以下输出。

    1210925927
    1210926043
    1210926080
    

    在这两种情况下,您都无需担心您的系统时区和所有与夏令时相关的繁琐信息。

  • 使用 mktime 禁用 DST 校正:将 DST 条目添加到 datespec 部分时 mktime,你可以告诉系统是否总是在夏令时工作,或者让系统自己解决。后者是你想要的。 datespecYYYY MM DD HH MM SS [DST] 形式的字符串。这也降低了它:

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

mktime awk 4.2.0 以上版本的文档:

mktime(datespec [, utc-flag ]) Turn datespec into a timestamp in the same form as is returned by systime(). It is similar to the function of the same name in ISO C. The argument, datespec, is a string of the form "YYYY MM DD HH MM SS [DST]". The string consists of six or seven numbers representing, respectively, the full year including century, the month from 1 to 12, the day of the month from 1 to 31, the hour of the day from 0 to 23, the minute from 0 to 59, the second from 0 to 60,55 and an optional daylight-savings flag.

The values of these numbers need not be within the ranges specified; for example, an hour of -1 means 1 hour before midnight. The origin-zero Gregorian calendar is assumed, with year 0 preceding year 1 and year -1 preceding year 0. If utc-flag is present and is either non-zero or non-null, the time is assumed to be in the UTC time zone; otherwise, the time is assumed to be in the local time zone. If the DST daylight-savings flag is positive, the time is assumed to be daylight savings time; if zero, the time is assumed to be standard time; and if negative (the default), mktime() attempts to determine whether daylight savings time is in effect for the specified time.

If datespec does not contain enough elements or if the resulting time is out of range, mktime() returns -1.