解决方案

解决方案

如果我从当前日期中减去时间量,GNU 日期会直观地工作:

date '+%F %R'; date '+%F %R' --date='- 1 hour'
2021-04-19 15:35
2021-04-19 14:35

但是,当我使用日期作为操作数时,结果是意外的:

$ 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

如何date解释这个$date - 1 hour表达式?

答案1

简而言之:--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

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

$ date +'%F %T %Z' -d '+ 5 hours 2021-01-01 12:00:00'
2021-01-01 17:00:00 CET

(因为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 纳秒),
日期:新时间 = 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 中欧夏令时

手册页说:

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

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

答案2

-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

时区

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

$ date ; date -u
Mon 19 Apr 2021 12:43:04 PM WAT
Mon 19 Apr 2021 11:43:04 AM UTC

假设您的当地时间是 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

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

这意味着仅当包含时区时“时间点”才是完整的。

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

日期字符串

当您使用日期时,2000/1/2 03:04:05您没有指定时区。
这就是为什么该命令date将 next- 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

假设您的当地时间是 WAT(并且不会因 DST 而改变,没有夏令时)。我不能告诉你,因为你没有使用%zNor %Z。时区更改将-d值从+1(WAT) 更改为-1,两小时差。

此外,您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

确认您的结果。

最后,+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

不会+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

答案3

许多其他日期处理工具将解析和日期算术作为单独的步骤进行。以 perl 为例:

perl -sE '
    use Time::Piece;
    use Time::Seconds;

    $t = Time::Piece->strptime($timestamp, $format);
    say $t->cdate;

    $s = $t + ONE_HOUR;
    say $s->cdate;

    $s = $t - ONE_HOUR;
    say $s->cdate;
' -- -timestamp='2000/1/2 03:04:05' -format='%Y/%m/%d %T'
Sun Jan  2 03:04:05 2000
Sun Jan  2 04:04:05 2000
Sun Jan  2 02:04:05 2000

相关内容