用日期和减法减去时间


18

SE网络上的所有其他问题都涉及以下情形:假定日期为nowQ)或仅指定日期(Q)。

我想做的是提供一个日期和时间,然后从中减去一个时间。
这是我首先尝试的方法:

date -d "2018-12-10 00:00:00 - 5 hours - 20 minutes - 5 seconds"

结果是2018-12-10 06:39:55-它增加了7个小时。然后减去20:05分钟。

在阅读man和的info第和页之后date,我认为我已经用以下方法修复了该问题:

date -d "2018-12-10T00:00:00 - 5 hours - 20 minutes - 5 seconds"

但是,同样的结果。从哪儿可以到达7个小时?

我也尝试过其他约会,因为我想也许那天我们有7200 leap秒,谁知道哈哈。但是结果相同。

还有更多示例:

$ date -d "2018-12-16T00:00:00 - 24 hours" +%Y-%m-%d_%H:%M:%S
2018-12-17_02:00:00

$ date -d "2019-01-19T05:00:00 - 2 hours - 5 minutes" +%Y-%m-%d_%H:%M:%S
2019-01-19_08:55:00

但是,这里变得有趣了。如果我省略输入时间,则可以正常工作:

$ date -d "2018-12-16 - 24 hours" +%Y-%m-%d_%H:%M:%S
2018-12-15_00:00:00

$ date -d "2019-01-19 - 2 hours - 5 minutes" +%Y-%m-%d_%H:%M:%S
2019-01-18_21:55:00

$ date --version
date (GNU coreutils) 8.30

我想念什么?

更新:Z在末尾添加了一个,它改变了行为:

$ date -d "2019-01-19T05:00:00Z - 2 hours" +%Y-%m-%d_%H:%M:%S
2019-01-19_04:00:00

我还是很困惑。在GNU信息页面上没有太多关于此的内容关于日期内容。

我猜这是一个时区问题,但引用ISO 8601的Calendar Wiki

如果未使用时间表示形式给出UTC关系信息,则该时间假定为本地时间。

这就是我想要的。我的当地时间也已正确设置。我不确定为什么在我提供日期时间并想从中减去一些东西的简单情况下,日期根本不会与时区混淆。它不应该首先从日期字符串中减去小时吗?即使它确实先将其转换为日期,然后进行了减法,但是如果我没有减去任何减法,我也会得到我想要的:

$ date -d "2019-01-19T05:00:00" +%Y-%m-%d_%H:%M:%S
2019-01-19_05:00:00

因此,如果这确实是一个时区问题,那么疯狂来自何处?


Answers:


20

最后一个示例应该已经为您澄清了一些事情:timezones

$ TZ=UTC date -d "2019-01-19T05:00:00Z - 2 hours" +%Y-%m-%d_%H:%M:%S
2019-01-19_03:00:00
$ TZ=Asia/Colombo date -d "2019-01-19T05:00:00Z - 2 hours" +%Y-%m-%d_%H:%M:%S 
2019-01-19_08:30:00

由于输出明显随时区变化,我怀疑对于未指定时区的时间字符串采用了一些非显而易见的默认值。测试几个值,似乎是UTC-05:00,尽管我不确定那是什么。

$ TZ=UTC date -d "2019-01-19T05:00:00 - 2 hours" +%Y-%m-%d_%H:%M:%S%Z
2019-01-19_08:00:00UTC
$ TZ=UTC date -d "2019-01-19T05:00:00Z - 2 hours" +%Y-%m-%d_%H:%M:%S%Z
2019-01-19_03:00:00UTC
$ TZ=UTC date -d "2019-01-19T05:00:00" +%Y-%m-%d_%H:%M:%S%Z           
2019-01-19_05:00:00UTC

仅在执行日期算术时使用。


看来这里的问题是,- 2 hours不是取算术,但作为一个时区符

# TZ=UTC date -d "2019-01-19T05:00:00 - 2 hours" +%Y-%m-%d_%H:%M:%S%Z --debug
date: parsed datetime part: (Y-M-D) 2019-01-19 05:00:00 UTC-02
date: parsed relative part: +1 hour(s)
date: input timezone: parsed date/time string (-02)
date: using specified time as starting value: '05:00:00'
date: starting date/time: '(Y-M-D) 2019-01-19 05:00:00 TZ=-02'
date: '(Y-M-D) 2019-01-19 05:00:00 TZ=-02' = 1547881200 epoch-seconds
date: after time adjustment (+1 hours, +0 minutes, +0 seconds, +0 ns),
date:     new time = 1547884800 epoch-seconds
date: timezone: TZ="UTC" environment value
date: final: 1547884800.000000000 (epoch-seconds)
date: final: (Y-M-D) 2019-01-19 08:00:00 (UTC)
date: final: (Y-M-D) 2019-01-19 08:00:00 (UTC+00)
2019-01-19_08:00:00UTC

因此,不仅没有进行任何算术运算,而且似乎在日光节约时间上进行了1小时调整,这对我们来说有点无聊的时间。

这也适用于:

# TZ=UTC date -d "2019-01-19T05:00:00 + 5:30 hours" +%Y-%m-%d_%H:%M:%S%Z --debug
date: parsed datetime part: (Y-M-D) 2019-01-19 05:00:00 UTC+05:30
date: parsed relative part: +1 hour(s)
date: input timezone: parsed date/time string (+05:30)
date: using specified time as starting value: '05:00:00'
date: starting date/time: '(Y-M-D) 2019-01-19 05:00:00 TZ=+05:30'
date: '(Y-M-D) 2019-01-19 05:00:00 TZ=+05:30' = 1547854200 epoch-seconds
date: after time adjustment (+1 hours, +0 minutes, +0 seconds, +0 ns),
date:     new time = 1547857800 epoch-seconds
date: timezone: TZ="UTC" environment value
date: final: 1547857800.000000000 (epoch-seconds)
date: final: (Y-M-D) 2019-01-19 00:30:00 (UTC)
date: final: (Y-M-D) 2019-01-19 00:30:00 (UTC+00)
2019-01-19_00:30:00UTC

调试多一点,解析似乎是:2019-01-19T05:00:00 - 2-2作为时区),和hours(= 1小时),有一个隐含的加法。看看是否使用分钟来变得更容易:

# TZ=UTC date -d "2019-01-19T05:00:00 - 2 minutes" +%Y-%m-%d_%H:%M:%S%Z --debug
date: parsed datetime part: (Y-M-D) 2019-01-19 05:00:00 UTC-02
date: parsed relative part: +1 minutes
date: input timezone: parsed date/time string (-02)
date: using specified time as starting value: '05:00:00'
date: starting date/time: '(Y-M-D) 2019-01-19 05:00:00 TZ=-02'
date: '(Y-M-D) 2019-01-19 05:00:00 TZ=-02' = 1547881200 epoch-seconds
date: after time adjustment (+0 hours, +1 minutes, +0 seconds, +0 ns),
date:     new time = 1547881260 epoch-seconds
date: timezone: TZ="UTC" environment value
date: final: 1547881260.000000000 (epoch-seconds)
date: final: (Y-M-D) 2019-01-19 07:01:00 (UTC)
date: final: (Y-M-D) 2019-01-19 07:01:00 (UTC+00)
2019-01-19_07:01:00UTC

因此,好吧,日期算术已经完成,而不仅仅是我们所要求的。¯\(ツ)/¯


1
@confetti确实可以做到;我认为此默认时区仅在加/减(比较TZ=UTC date -d "2019-01-19T05:00:00Z - 2 hours" +%Y-%m-%d_%H:%M:%Svs TZ=UTC date -d "2019-01-19T05:00:00 - 2 hours" +%Y-%m-%d_%H:%M:%S)时使用
Olorin

2
date根据ISO 8601标准,可能是可能的错误,如果未提供时区,则应假定使用本地时间(区域),而在算术运算的情况下则不这样做。但是对我来说很奇怪。
五彩纸屑

1
@confetti发现了问题:- 2 hours这里被当作时区说明符。
Olorin

2
哇。现在,以某种方式有意义。好吧,至少它能解释它。非常感谢您解决此问题。对我来说,听起来他们的解析器需要更新,因为显然使用这样的格式和空格,您不想通过-2小时指定时区,这肯定会造成混乱。如果他们不想更新解析器,那么至少手册应该对此有所说明。
五彩纸屑

1
今天我了解了约会的--debug选择!很好的解释。
Jeff Schaller

6

当您首先将输入日期转换为ISO 8601时,它可以正常工作:

$ date -d "$(date -Iseconds -d "2018-12-10 00:00:00") - 5 hours - 20 minutes - 5 seconds"
So 9. Dez 18:39:55 CET 2018

谢谢,确实可以,但是对此有什么解释吗?我向问题中添加了有关ISO 8601的更多信息,但我真的不知道在date没有提供任何减法的情况下会造成什么混乱,时区保持不变,并且一切都如预期而无需提供任何信息时区信息或转换。
五彩纸屑

我不能告诉你,对不起。如果有人可以回答这个问题,我会很高兴,因为我也想知道。
pLumo

我也很高兴,但是我现在会接受,因为它确实解决了我的问题!
五彩纸屑

3
之所以有效,是因为date -I它还输出时区说明符(例如2018-12-10T00:00:00+02:00),该时区说明符解决了Olorin的答案中
ilkkachu

6

TLDR:这不是错误。您刚刚发现的微妙但已记录的行为之一date。当使用进行时间算术运算时date,请使用与时区无关的格式(例如Unix时间),或者非常仔细地阅读文档以了解如何正确使用此命令。


GNU date使用您的系统设置(TZ环境变量,如果未设置,则为系统默认值)来确定-d/ --date选项提供的日期和+format参数报告的日期的时区。--date选项还允许您为其自己的选项参数覆盖时区,但不会覆盖的时区+format恕我直言,这就是造成混乱的根源。

考虑到我的时区是UTC-6,请比较以下命令:

$ date -d '1970-01-01 00:00:00' '+Normal: %F %T %:z%nUnix: %s'
Normal: 1970-01-01 00:00:00 -06:00
Unix: 21600
$ date -d '1970-01-01 00:00:00 UTC' '+Normal: %F %T %:z%nUnix: %s'
Normal: 1969-12-31 18:00:00 -06:00
Unix: 0
$ TZ='UTC0' date -d '1970-01-01 00:00:00 UTC' '+Normal: %F %T %:z%nUnix: %s'
Normal: 1970-01-01 00:00:00 +00:00
Unix: 0

第一个将我的时区用于-d+format。第二个使用UTC,-d但我的时区用于+format。第三个都使用UTC。

现在,比较以下简单操作:

$ date -d '1970-01-01 00:00:00 UTC +1 day' '+Normal: %F %T %:z%nUnix: %s'
Normal: 1970-01-01 18:00:00 -06:00
Unix: 86400
$ TZ='UTC0' date -d '1970-01-01 00:00:00 UTC +1 day' '+Normal: %F %T %:z%nUnix: %s'
Normal: 1970-01-02 00:00:00 +00:00
Unix: 86400

即使Unix时间告诉了我同样的事情,但由于我自己的时区,“正常”时间也有所不同。

如果我想执行相同的操作但只使用我的时区:

$ TZ='CST+6' date -d '1970-01-01 00:00:00 -06:00 +1 day' '+Normal: %F %T %:z%nUnix: %s'
Normal: 1970-01-02 00:00:00 -06:00
Unix: 108000

4
因提到纪元/ Unix时间而受批评,而imo是执行时间算术的唯一明智的方法
Rui F Ribeiro

1
TL; DR如果您只知道本地时间与祖鲁时间的时差(例如,美国西海岸是-8小时或-08:00),只需将其附加到输入日期时间...就可以了。示例:date -d '2019-02-28 14:05:36-08:00 +2 days 4 hours 3 seconds' +'local: %F %T'给出本地时间:2019-03-02 18:05:36
B层

1
@ BLayer是的,它在这种情况下可以正常工作。但是,想象一下使用给定脚本使用这种方法在不同时区的地方执行的情况。根据+format参数提供的信息,尽管从技术上讲是正确的,但输出可能看起来是错误的。例如,在我的系统上,相同的命令输出:(local: 2019-03-02 20:05:39如果我添加%:z+format,很明显信息是正确的,并且差异是由于时区造成的)。
nxnev

@BLayer恕我直言,一种更好的通用方法是,正如我在回答中所说的那样,将相同的时区用于-d和(+format或)Unix时间,以避免意外的结果。
nxnev

1
@nxnev同意,如果您正在编写要发布/共享的脚本。开头的单词“如果您知道自己的时区”除了具有字面含义外,还应暗示随意/个人使用。仅仅依靠脆弱的东西来发布脚本的人可能不应该在脚本共享游戏中就知道自己的系统的tz。:)另一方面,我会/将在所有个人环境中使用自己的命令。
B层

4

GNU date确实支持简单的日期算术,尽管有时@sudodus的答案中所示的纪元时间计算有时更清晰(并且更易于移植)。

在时间戳中未指定时区时使用+/-会触发下一个匹配时区的尝试,然后再解析其他任何内容。

这是一种实现方法,使用“ ago”代替“-”:

$ date -d "2018-12-10 00:00:00 5 hours ago 20 minutes ago 5 seconds ago"
Sun Dec  9 18:39:55 GMT 2018

要么

$ date -d "2018-12-10 00:00:00Z -5 hours -20 minutes -5 seconds"
Sun Dec  9 18:39:55 GMT 2018

(尽管您不能随意使用“ Z”,但它在我的区域中有效,但这使其成为UTC / GMT区域时间戳-使用您自己的区域,或者通过附加${TZ:-$(date +%z)}到时间戳来使用%z /%Z 。)

在这些表格中添加额外的时间条款会调整时间:

  • “ 5小时前”减去5小时
  • “ 4小时”加(隐式)4小时
  • “因此3个小时”增加(明确)3个小时(旧版本不支持)

可以使用任何顺序的许多复杂调整(尽管相对和易变的术语,例如“ 14周,因此上周一”正在引起麻烦;-)

(这里也有另一个小陷阱,date它将始终给出一个有效日期,因此date -d "2019-01-31 1 month"给出了“ 2019-03-03”,就像“下个月”一样)

考虑到支持的各种时间和日期格式,时区解析必定是草率的:它可以是一个或多个字母的后缀,一个小时或一个小时:分钟的偏移量,一个名称“ America / Denver”(甚至文件名) (对于TZ变量)。

您的2018-12-10T00:00:00版本不起作用,因为“ T”只是一个定界符,而不是时区,在末尾添加“ Z”也可以使该工作正常(取决于所选区域的正确性)。

请参阅:https//www.gnu.org/software/tar/manual/html_node/Date-input-formats.html ,尤其是7.7节。


1
其他选项包括:date -d "2018-12-10 00:00:00 now -5 hourstoday0 hour代替now,指示date时间之后的令牌不是时区偏移量的任何内容。
斯特凡Chazelas

1
或者,为彻底起见,在日期时间之后通过对args重新排序不留下任何内容:date -d“ -5 hours 2018-12-10 00:00:00”`
B层

2

这个解决方案很容易理解,但是有点复杂,因此我将其显示为shellscript。

  • 转换为“自1970-01-01 00:00:00 UTC以来的秒数”
  • 加或减差
  • 使用最终date命令行转换回人类可读的格式

Shell脚本:

#!/bin/bash

startdate="2018-12-10 00:00:00"

ddif="0"          # days
diff="-5:-20:-5"  # hours:minutes:seconds

#-----------------------------------------------------------------------------

ss1970in=$(date -d "$startdate" "+%s")  # seconds since 1970-01-01 00:00:00 UTC
printf "%11s\n" "$ss1970in"

h=${diff%%:*}
m=${diff#*:}
m=${m%:*}
s=${diff##*:}
difs=$(( (((ddif*24+h)*60)+m)*60+s ))
printf "%11s\n" "$difs"

ss1970ut=$((ss1970in + difs))  # add/subtract the time difference
printf "%11s\n" "$ss1970ut"

date -d "@$ss1970ut" "+%Y-%m-%d %H:%M:%S"
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.