为什么 GNU 日期的`-`(减号)解释与指定日期时的直观解释不同?

Mar*_*cus 12 linux gnu date

如果我从当前日期中减去一个时间量,GNU 日期的工作方式很直观:

date '+%F %R'; date '+%F %R' --date='- 1 hour'
2021-04-19 15:35
2021-04-19 14:35
Run Code Online (Sandbox Code Playgroud)

但是,当我使用日期作为操作数时,结果出乎意料:

$ date '+%F %R' --date='2000/1/2 03:04:05 - 1 hour'
2000-01-02 06:04

$ date '+%F %R' --date='2000/1/2 03:04:05 + 1 hour ago'
2000-01-02 02:04
Run Code Online (Sandbox Code Playgroud)

怎样date理解$date - 1 hour表达?

ilk*_*chu 19

简而言之:--date除非您指定时区,否则您给出的日期是当地时间+/- NNN。只有在此之后的任何东西,即使它只是hour被视为相对修饰符。所以- 1 hour并不意味着从给定的时间中减去一小时,而是指定时间在时区 UTC-01 中,然后再增加一小时。


我认为应该适用于您正在尝试的内容,要么在偏移量之前明确给出时区,要么将偏移量放在首位,以免与时区混淆。

在这里,使用中欧夏令时时区 (CEST) 和今天的日期,并%Z添加到输出中以显示时区。(你也可以%z用来输出数字时区,或者+0200在这里。)

$ date +'%F %T %Z' -d '2021-04-19 12:00:00 CEST + 5 hours'
2021-04-19 17:00:00 CEST
$ date +'%F %T %Z' -d '+ 5 hours 2021-04-19 12:00:00'
2021-04-19 17:00:00 CEST
Run Code Online (Sandbox Code Playgroud)

当然,对于问题中的一月日期,像 CEST 这样的夏季时区将是无效的。但是重新排列两者仍然有效,您给出的时间只是当时的当地时间。

$ date +'%F %T %Z' -d '+ 5 hours 2021-01-01 12:00:00'
2021-01-01 17:00:00 CET
Run Code Online (Sandbox Code Playgroud)

(因为2021-10-31 02:30:00我得到了 CET,即使那个时间也存在于 CEST 中......)

(有关如何解释各种输入的更多示例,请参阅此答案的旧版本。)


根据@muru 对另一个问题的回答,我们还可以使用该--debug选项让程序实际告诉我们它做了什么。注意第二行和第三行:

$ date --debug +'%F %T %Z' -d '2021-04-19 12:00:00 - 1 小时'
日期:解析日期部分:(YMD)2021-04-19
日期:解析时间部分:12:00:00 TZ=-01:00
日期:解析相关部分:+1 小时
日期:输入时区:-01:00(从解析的日期/时间字符串设置)
日期:使用指定时间作为起始值:'12:00:00'
日期:开始日期/时间:'(YMD) 2021-04-19 12:00:00 TZ=-01:00'
日期:'(YMD) 2021-04-19 12:00:00 TZ=-01:00' = 1618837200 纪元-秒
日期:时间调整后(+1 小时,+0 分钟,+0 秒,+0 ns),
日期:新时间 = 1618840800 纪元秒
日期:输出时区:+01:00(从 TZ="Europe/Berlin" 环境值设置)
日期:最终:1618840800.000000000(纪元秒)
日期:最终:(YMD) 2021-04-19 14:00:00 (UTC0)
日期:最终:(YMD)2021-04-19 16:00:00(输出时区 TZ=+01:00)
2021-04-19 16:00:00 CEST

手册页说:

日期字符串格式比此处轻松记录的要复杂 [...]

这确实看起来很贴切。更全面的文档在信息页面或在线:https : //www.gnu.org/software/coreutils/manual/html_node/Date-input-formats.html


ImH*_*ere 7

-d

是的,当 -d 值很简单时,它的效果很容易理解。
是的,a-d '- 1 hour'会将时间抵消 1 小时。

$ date ; date -d '- 1 hour'
Mon 19 Apr 2021 07:58:52 AM EDT
Mon 19 Apr 2021 06:58:52 AM EDT
Run Code Online (Sandbox Code Playgroud)

时区

date生成的字符串指定了“时间点”。
默认情况下,“时间点”以本地时间写入。但它也可以写成 UTC0:

$ date ; date -u
Mon 19 Apr 2021 12:43:04 PM WAT
Mon 19 Apr 2021 11:43:04 AM UTC
Run Code Online (Sandbox Code Playgroud)

假设您的当地时间是 WAT - 西非时间 (WAT)。

当然,相同的“时间点”也可能发生在其他时区:

$ TZ=Asia/Kolkata date -d "Mon 19 Apr 2021 12:43:04 PM WAT"
Mon 19 Apr 2021 05:13:04 PM IST
Run Code Online (Sandbox Code Playgroud)

请注意,-d值内的内容正是上面打印的日期。

这意味着“时间点”仅在包含时区时才完成。

这就是为什么打印带有%z%Z添加的日期是个好主意的原因。从%F %R这个意义上说,格式是不完整的,最好使用+'%F %R %z'.

日期字符串

当您使用日期时,2000/1/2 03:04:05您没有指定时区。
这就是为什么该命令date将下一个- 1表示时区的原因:

$ date -u -d '2000/1/2 03:04:05'; date -u -d '2000/1/2 03:04:05 - 1'
Sun 02 Jan 2000 03:04:05 AM WAT
Sun 02 Jan 2000 05:04:05 AM WAT
Run Code Online (Sandbox Code Playgroud)

那是假设您的当地时间是 WAT(并且不会因夏令时而改变,没有夏令时)。我不知道,因为你没有使用%z也没有%Z。时区更改将-d值从+1(WAT)更改为-12 小时时差。

此外,您hour在那之后写道,这是一个额外的小时:

$ date -d '2000/1/2 03:04:05'; date -d '2000/1/2 03:04:05 - 1 hour'
Sun 02 Jan 2000 03:04:05 AM WAT
Sun 02 Jan 2000 06:04:05 AM WAT
Run Code Online (Sandbox Code Playgroud)

确认您的结果。

最后,+1:

$ date -d '2000/1/2 03:04:05 - 1 hour'; date -d '2000/1/2 03:04:05 + 1 hour ago'
Sun 02 Jan 2000 06:04:05 AM WAT
Sun 02 Jan 2000 02:04:05 AM WAT
Run Code Online (Sandbox Code Playgroud)

+1不改变时间值(因为它是在WAT,或1,已经),但hour ago移位的时间1小时背面(从3到2)。

解决方案

将时区放在-d时间值中。
或者,将相关部分放在首位- 1 hour

$ date -d '2000/1/2 03:04:05 +0100 - 1 hour'
  Sun 02 Jan 2000 02:04:05 AM WAT


$ date -d '-1 hour 2000/1/2 03:04:05'
  Sun 02 Jan 2000 02:04:05 AM WAT
Run Code Online (Sandbox Code Playgroud)