我经常想进行一些快速的日期计算,例如:


这两个日期之间有什么区别?
这个另一个日期之后n周的日期是什么?

我通常打开日历并计算天数,但是我认为应该有一个程序/脚本可用于进行这类计算。有什么建议吗?

评论

另请参见UNIX中的工具,以在GNU日期不可用时减去日期。

#1 楼

使用GNU date(1),“日期后的n周”很容易:

$ date -d 'now + 3 weeks'
Tue Dec  6 23:58:04 EST 2011
$ date -d 'Aug 4 + 3 weeks'
Thu Aug 25 00:00:00 EST 2011
$ date -d 'Jan 1 1982 + 11 weeks'
Fri Mar 19 00:00:00 EST 1982


我不知道一种简单的方法来计算两个日期之间的差,但是您可以使用shell函数在date(1)周围包裹一些逻辑。

datediff() {
    d1=$(date -d "" +%s)
    d2=$(date -d "" +%s)
    echo $(( (d1 - d2) / 86400 )) days
}
$ datediff '1 Nov' '1 Aug'
91 days


如果希望以其他方式进行日期计算,请交换d1d2,或者变得有点幻想,不要紧。此外,如果在此时间间隔内有非DST到DST的过渡,则一天中只有23小时长;您可以在总数上加½天作为补偿。

echo $(( (((d1-d2) > 0 ? (d1-d2) : (d2-d1)) + 43200) / 86400 )) days


评论


我正在尝试实现“添加n分钟”,我的问题是我在可变的timeStudy中记录了分钟数,而找不到在3(代替您的代码)中插入它的方法。有什么帮助吗?

–汤马索(Tommaso Seneci)
20 Mar 23 '20 at 19:55

@TommasoSeneci:date -d“ now + $ {timeStudy}分钟”-使用双引号而不是单引号可以在字符串中进行变量扩展。

– camh
20-3-23在21:59



太好了,问题解决了!!

–汤马索(Tommaso Seneci)
20 Mar 24 '20 at 12:15

#2 楼

对于一组便携式工具,请尝试我自己的dateutils。您的两个示例可以归结为一类:
ddiff 2011-11-15 2012-04-11
=>
  148

或几周又几天:
ddiff 2011-11-15 2012-04-11 -f '%w %d'
=>
  21 1


dadd 2011-11-15 21w
=>
  2012-04-10

关于Ubuntu安装的重要说明:
这些非常相同的dateutils可作为Ubuntu软件包提供,因此可以通过sudo apt install dateutils安装。
但是,命令之前必须带有dateutils。前缀,如dateutils.ddiff 2019-03-28 2019-05-16
中所示,它们也可以在Fedora和Fedora EPEL中用于带有sudo dnf install dateutils的RHEL或CentOS。在Fedora中,这些软件包不需要前缀,而是使用长名称-例如,datediffdateadd代替ddiffdadd

评论


为您的工具+1(尽管dateadd -i'%m%d%Y'01012015 +1似乎无效,它无限期地挂在那里...如果日期规范用字符分隔,则确实有效)字符...任何想法出什么事了吗?)

–don_crissti
15年11月22日在2:17

@don_crissti解析器无法区分仅数字的日期和持续时间,它已在当前主文件中固定(d0008f98)

–hroptatyr
2015年11月23日下午6:21

您是否有窗口的dateutils二进制分配?

–莫什
17年2月12日在4:18

@mosh不,我没有尝试的设施。

–hroptatyr
17年2月12日在8:21

这些非常相同的dateutils可作为Ubuntu软件包使用,因此可以通过sudo apt install dateutils安装。但是,命令必须在dateutils之前。如dateutils.ddiff中的前缀2019-03-28 2019-05-16

– Serge Stroobandt
19年5月16日在9:27

#3 楼

用于计算我在地球上行走的天数的python示例:

$ python
>>> from datetime import date as D
>>> print (D.today() - D(1980, 6, 14)).days
11476


评论


万一有人希望它像一个命令一样工作,而不是输入交互式解释器:ychaouche @ ychaouche-PC〜$ python -c“ from datetime import date as d; print(d.today()-d( 1980,6,14))。days“ 12813 ychaouche @ ychaouche-PC〜$

–ychaouche
15年7月14日在14:56



这对我有用python3 -c“从datetime导入日期作为d;打印(d.today()-d(2016,1,9))”不需要最后天

– Kiran K Telukunta
16-10-14在5:18



#4 楼

我通常更喜欢将时间/日期设置为unix utime格式(自新纪元(七十年代开始以来的秒数,UTC))。这样,它总是归结为简单的减法或秒的加法。

通常的问题是将日期/时间转换为这种格式。

在shell环境/脚本中您可以使用date '+%s'来获取它。
在撰写本文时,当前时间是1321358027

与2011-11-04(我的生日)进行比较,date '+%s' -d 2011-11-04产生1320361200。减去:expr 1321358027 - 1320361200给出996827秒,即expr 996827 / 86400 = 11天前。

使用标准库,例如从C,PHP或Perl转换为utime(1320361200格式)到日期非常简单。 。
对于GNU date,可以在-d之前加上@参数,以指示“自大纪元以来的秒数”格式。

#5 楼

这是在使用date -d "$death_date - $y years - $m months - $d days"获取出生日期(用于家谱)时出现的。该命令是错误的。月的长度不尽相同,所以(date + offset) - offset != date。年龄(以年/月/日为单位)是从出生日期算起的度量。

$ date --utc -d 'mar 28 1867 +72years +11months +2days'
Fri Mar  1 00:00:00 UTC 1940

$ date --utc -d 'mar 1 1940 -72years -11months -2days'
Sat Mar 30 00:00:00 UTC 1867
# (2 days later than our starting point)


日期在两种情况下均提供正确的输出,但在第二种情况下你问错了问题。重要的是一年中的+/- 11封面的11个月,然后加上/减去天数。例如:

$ date --utc -d 'mar 31 1939  -1month'
Fri Mar  3 00:00:00 UTC 1939
$ date --utc -d 'mar 31 1940  -1month' # leap year
Sat Mar  2 00:00:00 UTC 1940
$ date --utc -d 'jan 31 1940  +1month' # leap year
Sat Mar  2 00:00:00 UTC 1940


要使减法成为加法的逆运算,则必须颠倒运算的顺序。加数会增加年份,然后增加几个月,然后增加几天。如果减法使用相反的顺序,那么您将回到起点。如果不是,那么您就不会这样做,如果天数偏移量跨越了另一个长度月份中的月份边界。

如果您需要从结束日期和年龄倒退,则可以这样做与date的多个调用。首先减去日期,然后减去月份,然后减去年份。 (由于think年会更改2月的长度,因此我不认为将年和月合并在单个date调用中是不安全的。)

#6 楼

如果图形工具适合您,我衷心推荐qalculate(一个强调单位转换的计算器,它带有GTK和KDE接口IIRC)。您可以在其中说出例如

days(1900-05-21, 1900-01-01)


以获取日期之间的天数(140,因为1900年不是a年),但是您当然也可以这样做时间相同:

17:12:45 − 08:45:12


产生8.4591667小时,或者,如果将输出设置为时间格式,则产生8:27:33

评论


很好,甚至更多,因为qalculate确实具有CLI。尝试使用qalc,然后再帮助几天。

– Sparhawk
2014年7月4日在12:19

qcalc示例:qalc -t'days(1900-05-21,1900-01-01)'-> 140

– sierrasdetandil
19 Mar 25 '19在15:39

#7 楼

计算同一日历年的两个日期之间差异的另一种方法是:

date_difference.sh
1  #!/bin/bash
2  DATEfirstnum=`date -d "2014/5/14" +"%j"`
3  DATElastnum=`date -d "12/31/14" +"%j"`
4  DAYSdif=$(($DATElastnum - $DATEfirstnum))
5  echo "$DAYSdif"



第1行向外壳声明要使用的解释器。
第2行将date之外的值分配给变量
DATEfirstnum。 -d标志以时间格式显示字符串,格式为
2014年5月14日,+"%j"告诉date将输出格式格式化为一年中的第一天(1-365)。
3与第2行相同,但日期不同,并且字符串的格式不同,2014年12月31日。
第4行将值DAYSdif分配给两个
天的差额。
第5行显示DAYSdif的值。

这适用于date的GNU版本,但不适用于PC-BSD / FreeBSD版本。我从端口树安装了coreutils,并改用了命令/usr/local/bin/gdate

评论


该脚本将无法运行。最后一行有一个错字,变量赋值周围有空格,因此bash尝试运行一个带有两个参数的名为DATEfirst name的程序。尝试以下操作:DATEfirstnum = $(date -d“ $ 1” +%s)DATElastnum = $(date -d“ $ 2” +%s)此外,此脚本将无法计算两个不同年份之间的差额。 +%j代表一年中的某天(001..366),因此./date_difference.sh 12/31/2001 12/30/2014输出-1。正如其他答案所指出的那样,您需要将两个日期都转换为自1970-01-01 00:00:00 UTC以来的秒数。

–六
15年2月28日在13:29

您不需要$在算术表达式内:$((DATElastnum-DATEfirstnum))也将起作用。

–俄罗斯
18年6月2日在14:26

#8 楼

借助dannas解决方案,可以使用以下代码在一行中完成此操作:

python -c "from datetime import date as d; print(d.today() - d(2016, 7, 26))"


(在Python 2.x和Python 3中均可使用)。

#9 楼

我经常使用SQL进行日期计算。例如MySQL,PostgreSQL或SQLite:

bash-4.2$ mysql <<< "select datediff(current_date,'1980-06-14')"
datediff(current_date,'1980-06-14')
11477

bash-4.2$ psql <<< "select current_date-'1980-06-14'"
 ?column? 
----------
    11477
(1 row)

bash-4.2$ sqlite2 <<< "select julianday('now')-julianday('1980-06-14');"
11477.3524537035


其他时候,我只是对JavaScript有心情。例如SpiderMonkey,WebKit,Seed或Node.js:

bash-4.2$ js -e 'print((new Date()-new Date(1980,5,14))/1000/60/60/24)'
11477.477526192131

bash-4.2$ jsc-1 -e 'print((new Date()-new Date(1980,5,14))/1000/60/60/24)'
11477.47757960648

bash-4.2$ seed -e '(new Date()-new Date(1980,5,14))/1000/60/60/24'
11477.4776318287

bash-4.2$ node -pe '(new Date()-new Date(1980,5,14))/1000/60/60/24'
11624.520061481482


(当将月份传递给JavaScript Date对象的构造函数时要小心。从0开始。)

#10 楼

date和bash可以进行日期差异(显示OS X选项)。将后一个日期放在第一位。

echo $((($(date -jf%D "04/03/16" +%s) - $(date -jf%D "03/02/16" +%s)) / 86400))
# 31


评论


-jf%D OSX日期选项代表什么?

–SebMa
20年6月29日在12:47

#11 楼

还有结合了GNU日期的GNU单位时间计算:

$ gunits $(gdate +%s)sec-$(gdate +%s -d -1234day)sec 'yr;mo;d;hr;min;s'
        3 yr + 4 mo + 16 d + 12 hr + 37 min + 26.751072 s
$ gunits $(gdate +%s -d '2015-1-2 3:45:00')sec-$(gdate +%s -d '2013-5-6 7:43:21')sec 'yr;mo;d;hr;min;s'
        1 yr + 7 mo + 27 d + 13 hr + 49 min + 26.206759 s


(Linux中的单位是单位,gdate是日期)

#12 楼

github:gist上的datediff.sh

#!/bin/bash
#Simplest calculator two dates difference. By default in days

# Usage:
# ./datediff.sh first_date second_date [-(s|m|h|d) | --(seconds|minutes|hours|days)]

first_date=$(date -d "" "+%s")
second_date=$(date -d "" "+%s")

case "" in
"--seconds" | "-s") period=1;;
"--minutes" | "-m") period=60;;
"--hours" | "-h") period=$((60*60));;
"--days" | "-d" | "") period=$((60*60*24));;
esac

datediff=$(( ($first_date - $second_date)/($period) ))
echo $datediff


#13 楼

camh的答案可以解决大部分问题,但是我们可以进行改进以处理舍入,时区等问题,此外,我们还可以提供一些额外的精度和选择单位的能力:

datediff() {
#convert dates to decimal seconds since 1970-01-01 00:00:00 UTC
date1seconds=$(date +%s.%N -d "$date1")
date2seconds=$(date +%s.%N -d "$date2")

#Calculate time difference in various time units
timeseconds=$(printf "%0.8f\n" $(bc <<<"scale=9; ($date2sec-$date1sec)"))
timeminutes=$(printf "%0.8f\n" $(bc <<<"scale=9; ($date2sec-$date1sec)/60"))
  timehours=$(printf "%0.8f\n" $(bc <<<"scale=9; ($date2sec-$date1sec)/3600"))
   timedays=$(printf "%0.8f\n" $(bc <<<"scale=9; ($date2sec-$date1sec)/86400"))
  timeweeks=$(printf "%0.8f\n" $(bc <<<"scale=9; ($date2sec-$date1sec)/604800"))
}


-d告诉date我们提供了要转换的日期时间。 +%s.%N从1970-01-01 00:00:00 UTC将日期时间更改为seconds.nanoseconds。 bc计算两个数字之间的差,并且除法因子得到不同的单位。如果需要,printf在小数点前添加0(bc不需要),并确保舍入到最接近的值(bc只是截断)。您也可以使用awk

现在让我们用一个时髦的测试用例来运行它,

date1='Tue Jul  9 10:18:04.031 PST 2020'
date2='Wed May  8 15:19:34.447 CDT 2019'
datediff "$date1" "$date2"

echo $timeseconds seconds
-36971909.584000000 seconds

echo $timeminutes minutes
-616198.493066667 minutes

echo $timehours hours
-10269.974884444 hours

echo $timedays days
-427.915620185 days

echo $timeweeks weeks
-61.130802884 weeks


请注意,由于一个月或一年的长度并不总是同样,这里没有一个“正确”的答案,尽管现在可以使用365.24天作为合理的近似值。

#14 楼

这是一个比较旧的线程,但是我发现了一些有趣的东西。我被困在带有BusyBox(v1.19)的嵌入式系统上,对超级方便的now - 10 days标记在那里失败感到有些失望。实际上,-d选项提供的输入格式集非常有限。

Recognized TIME formats:
    hh:mm[:ss]
    [YYYY.]MM.DD-hh:mm[:ss]
    YYYY-MM-DD hh:mm[:ss]
    [[[[[YY]YY]MM]DD]hh]mm[.ss]


但是,可以对输入日期进行一些数学运算。

鉴于这些格式,我发现将日期的显示日期设置为零会给您上个月的最后一天:

$ date -d 2020.03.00-12:00
Sat Feb 29 12:00:00 EST 2020 


输入负数会更进一步:

$ date -d 2020.03.-10-12:00
Wed Feb 19 12:00:00 EST 2020


谁能确认这是否也适用于GNU日期? (尽管now - 10 days更好)

#15 楼

date -d特定于GNU日期,未由POSIX定义:

https://pubs.opengroup.org/onlinepubs/9699919799/utilities/date.html

已经提到过,这可能更适合于
正确的编程语言。例如PHP:

 <?php
$o1 = date_create('2020-02-26');
$o2 = date_create('2020-02-16');
$o3 = date_diff($o2, $o1);
echo 'days: ', $o3->d, "\n";
 


结果:

days: 10


#16 楼

这是一个相当老的线程,但是我在BusyBox中发现了一些有趣的东西,至少与从已知日期开始添加和减去N个时间单位有关。但是,用另一种方法来确定两个已知日期之间的时间间隔并没有太多。

我被困在带有BusyBox的嵌入式系统上,超级方便的now - 10 days表示法在这里失败了。实际上,-d选项提供的输入格式集非常有限。

$ busybox date --help
BusyBox v1.19.0 (2019-07-14 10:52:19 UTC) multi-call binary.

Usage: date [OPTIONS] [+FMT] [TIME]

Display time (using +FMT), or set time

        [-s,--set] TIME Set time to TIME
        -u,--utc        Work in UTC (don't convert to local time)
        -R,--rfc-2822   Output RFC-2822 compliant date string
        -I[SPEC]        Output ISO-8601 compliant date string
                        SPEC='date' (default) for date only,
                        'hours', 'minutes', or 'seconds' for date and
                        time to the indicated precision
        -r,--reference FILE     Display last modification time of FILE
        -d,--date TIME  Display TIME, not 'now'
        -D FMT          Use FMT for -d TIME conversion

Recognized TIME formats:
        hh:mm[:ss]
        [YYYY.]MM.DD-hh:mm[:ss]
        YYYY-MM-DD hh:mm[:ss]
        [[[[[YY]YY]MM]DD]hh]mm[.ss]


但是,可以对输入日期进行一些数学运算。给定这些格式,我发现将输入日期的天数设置为零会给您上个月的最后一天:

$ date -d 2020.03.00-12:00
Sat Feb 29 12:00:00 EST 2020 


此外,输入负数会不断减去向后,我当然没想到:

$ date -d 2020.03.-10-12:00
Wed Feb 19 12:00:00 EST 2020


所以您可以在已知的日期做一些快速数学运算,以生成值以馈入BusyBox -d中的date选项呼叫:

#!/bin/bash
#Other shells not tested.

start_year=2020
start_month=1
start_day=20

offset_year=-2
offset_month=4
offset_day=5

new_year=$(( $start_year + $offset_year ))
new_month=$(( $start_month + $offset_month ))
new_day=$(( $start_day + $offset_day ))

new_date$( busybox date -d $new_year.$new_month.$new_day-12:00 +%Y-%m-%d )
echo $new_date


输出:

2018-05-25


谁能确认这是否也适用于GNU,BSD,或MacOS date?我当然很欣赏GNU now - 10 days的性能要好得多,但是我有兴趣使它尽可能地便携。我已经确认Android完全不同(两种方法都无法正常工作)。

#17 楼

对于简单的日子,我们可以使用命令date:
 $ dateA="2020-11-10"
$ dateB="2020-12-28"
$ echo "(`date -d $dateB +%s` - `date -d $dateA +%s`)/86400" | bc
48
 


-d, --date=STRING display time described by STRING
%s seconds since 1970-01-01 00:00:00 UTC

86400表示一天中的秒数

echo命令将仅使用所需的数学表达式构建一个字符串,然后bc对其进行解析。
对于请添加n周,我认为@camh的答案是一种很好的方法:
 $ dateA="2020-11-10"
$ date -d "$dateA + 3 weeks"
ter dez  1 00:00:00 -03 2020
 

此外,您可以几天更改weeks,月,年等

评论


是否有理由为什么要通过bc传递数据,而不是在shell中使用算术替换(使用$((... ...)))来计算结果?另外,您似乎只解决了一半的问题。

– Kusalananda♦
20 Nov 10在12:52



使用较早的命令替换``和bc的唯一原因是使其更简洁。我也更改了答案以涵盖第二个问题。谢谢你的提示。

–丹尼尔·罗莎(Daniel da Rosa)
20 Nov 11在13:57