在命令行中进行日期运算,第一部分
如果您曾经使用过电子表格,您可能使用过或见过用于进行日期运算的函数——换句话说,取一个日期并加上一些天数或月份以获得一个新的日期,或者取两个日期并找出它们之间的天数。同样的事情可以使用简单的date命令从命令行完成,可能还需要 Bash 算术运算的一些帮助。
对于进行日期运算,要理解的date命令的两个最重要的特性是+FORMAT选项和--date选项。格式化选项允许您以不同的方式格式化日期,或者仅获取日期的某些部分,例如年份、月份、日期等等。
$ date '+%Y'
2018
$ date '+%m'
09
$ date '+%d'
16
有几十个格式化选项;请参阅 date(1) 手册页以了解更多信息。
第二个选项,--date(或-d) 选项,允许您指定要使用的日期,而不是当前日期。
$ date --date 'March 1, 2015' "+%Y"
2015
$ date --date='March 1, 2015' "+%m"
03
$ date -d 'March 1, 2015' "+%d"
01
该--date选项的有趣之处在于,它允许您以多种方式指定日期字符串。例如,给定上述“2015 年 3 月 1 日”的日期,您可以计算相对于它的日期。
$ date --date 'March 1, 2015 +7 days'
Sun Mar 8 00:00:00 MST 2015
$ date --date 'March 1, 2015 -1 year'
Sat Mar 1 00:00:00 MST 2014
$ date --date 'March 1, 2015 +12 days +12 hours +15 minutes'
Fri Mar 13 12:15:00 MST 2015
您还可以使用引用过去或未来一周中某天的字符串(相对于当前日期)。
$ date --date 'last Monday'
Mon Sep 10 00:00:00 MST 2018
$ date --date 'next Monday'
Mon Sep 17 00:00:00 MST 2018
但是请注意,组合日期和诸如“下周一”之类的内容不起作用。 date(1) 手册页提供了关于您可以指定的日期字符串类型的更多信息,但是 GNU Info date 页面有更完整的文档。
现在您已经了解了进行日期运算的基础知识,让我们用它来做一些事情。这个例子来自我最近在整理 Linux Journal 的在线档案时编写的一些脚本。我们每个月都会出版一本新刊。每期都有一个期号,比上一期多一期。在为脚本指定参数时,期号很容易处理,但读者倾向于更多地考虑月份和年份。因此,在几个地方,我需要从期号生成月份和年份。
为了不使示例复杂化,让我们忽略 LJ 出版历史中的几次中断。因此,让我们使用 2018 年 3 月的第 284 期作为起点,并进行所有相对于它的计算。该脚本接受一个或多个期号,并打印出与该期号对应的月份和年份。
$ cat date1.sh
1 for inum in $*
2 do
3 [[ "$inum" =~ ^[0-9]{1,4}$ ]] || { echo "Invalid issue number: $inum"; continue; }
4
5 month=$(date --date="March 1, 2018 +$((inum - 284)) month" +'%B')
6 year=$(date --date="March 1, 2018 +$((inum - 284)) month" +'%Y')
7 printf "Issue %d is from %s, %s\n" $inum $month $year
8 done
$ bash date1.sh 284 285
Issue 284 is from March, 2018
Issue 285 is from April, 2018
第 5 行和第 6 行使用 Bash 的算术表达式语法计算自第 284 期以来经过的期数。$((inum - 284))。然后,该结果用于将那么多月添加到第一期的日期上,从而给出相关期的日期。然后date命令的格式选项提供了月份和年份(%B和%Y格式说明符,分别)。接下来,这些值用于打印出该期的月份和年份。只是以防您想知道,第 1000 期将于 2077 年 11 月发行。
在进行日期运算时,一件可能会让您困惑的事情是,如果您在月末附近使用日期来添加或减去月份,您可能会得到意想不到的结果。这是因为 date
命令在添加月份时,对每个月的长度使用相同的值,而忽略了并非所有月份的长度都相同的事实。因此,例如,看看如果您将上面脚本中的参考日期从月初更改为月末会发生什么。
$ cat date2.sh
5 month=$(date --date="March 31, 2018 +$((inum - 284)) month" +'%B')
6 year=$(date --date="March 31, 2018 +$((inum - 284)) month" +'%Y')
$ bash date2.sh 284 285
Issue 284 is from March, 2018
Issue 285 is from May, 2018
这错误地将第 285 期标记为五月而不是四月。如果您自己运行有效的date命令,您可以验证这一点。
$ date --date="March 31, 2018 +1 month"
Tue May 1 00:00:00 MST 2018
由于四月只有 30 天,您最终会跳到五月一日。
我上面提到的另一种常见的日期运算类型是计算两个日期之间的差值。不幸的是,date命令不支持直接这样做。为此,您需要将相关日期转换为自 epoch 以来的秒数(使用%s格式说明符),然后减去这些值,并除以一天的秒数 (86,400) 以获得中间的天数。
$ cat date3.sh
start_date="March 1, 2018"
end_date="March 31, 2018"
sdate=$(date --date="$start_date" '+%s')
edate=$(date --date="$end_date" '+%s')
days=$(( (edate - sdate) / 86400 ))
echo "$days days between $start_date and $end_date"
$ bash date3.sh
30 days between March 1, 2018 and March 31, 2018
在本文的第二部分中,我计划讨论如何获得相对于特定日期的特定前一天(正如我上面指出的,这不能直接用date命令完成)。换句话说,我希望能够做一些有效地执行以下操作的事情。
$ date --date 'March 1, 2015 previous Monday' # won't work
date: invalid date ‘March 1 2015 previous Monday’