您遇到的主要与系统的时区设置有关。由于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。