日期之间的天数?
细心的读者会知道,我正在对我广受欢迎的Wicked Cool Shell Scripts一书进行重大修订,该书将于今年晚些时候出版。尽管这本已有十年历史的书中的大多数脚本仍然是最新且有价值的,但有些脚本肯定已经过时或已被新技术或实用程序所取代。不用担心——这就是我进行更新的原因。
我将要添加的一个脚本是一个复杂的脚本,我将在我的Linux 杂志专栏中开发它:daysago。该脚本将接受过去指定的日期,并告诉您该日期与当前日期和时间之间已经过去了多少天。
您可能会认为这相当复杂,确实如此,但不是您可能想的那样。由于 Linux 系统存储和操作日期的方式,实际计算非常容易。挑战在于解析输入。
本书的第一部分包含一个有用的脚本实用程序库,然而,其中一个恰好是我们想要的——这不是巧合!
有效日期?处理日期这样复杂的事情的最简单方法是将工作推给用户。有几种不同的策略可以做到这一点,但现在让我们偷懒一下,提示用户输入月份、日期和年份,要求输入数字值。然后,我们需要检查它是否有效。
验证用户指定的日期非常简单,直到我们遇到闰年的问题。我们习惯于认为每四年是闰年,但公式比这复杂得多,可以用这组规则概括
-
能被 4 整除的年份是闰年,除非...
-
该年份也能被 100 整除,除非...
-
该年份能被 400 整除,在这种情况下,它是闰年。
这够复杂了吗?当然,如果我们只关注过去几十年的闰年,那没什么大不了的,但不可避免的是,有人会尝试类似 2 月 29 日,1776 年这样的日期,在这种情况下,我们需要知道它是否有效。
或者,我们可以偷懒。
由于我喜欢对事情采取偷懒的解决方案(记住,我不是在这里编写生产代码,我是在演示概念),让我们通过使用 Linux cal
命令来作弊。因为它允许我们指定月份/年份,我们可以通过要求显示 2/1776 来移交 1776 年是否有 2 月 29 日的问题
$ cal 2 1776
February 1776
Su Mo Tu We Th Fr Sa
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29
看起来 1776 年确实是闰年。难怪他们有时间在夏天来临之前起草宪法,夏天让费城变得太热,没人能工作!
为了将此命令变成脚本,一个简单的 grep
并测试非零结果就可以解决问题
mon=$1; day=$2; year=$3
if [ $mon -eq 2 -a $day -eq 29 ] ; then
echo checking for feb 29 : was $3 a leap year?
leapyear=$(cal 2 $year | grep '29')
if [ ! -z "$leapyear" ] ; then
echo "Yes, $year was a leapyear, so February 29, $year \
is a valid date."
else
echo "Oops, $year wasn't a leapyear, so February only \
had 28 days."
fi
fi
让我们运行一些快速测试,看看会发生什么
$ sh valid-date.sh 2 29 1777
checking for feb 29 : was 1777 a leap year?
Oops, 1777 wasn't a leapyear, so February only had 28 days.
$ sh valid-date.sh 2 29 1776
checking for feb 29 : was 1776 a leap year?
Yes, 1776 was a leapyear, so February 29, 1776 is a valid date.
这很有道理,而且使用 cal
进行此特定测试肯定很容易。
我们仍然需要封装“九月、四月、六月和十一月是 30 天”的信息,这也很容易通过一个相当紧凑的 case 语句来完成
case $mon in
1|3|5|7|8|10|12 ) dim=31 ;; # most common value
4|6|9|11 ) dim=30 ;;
2 ) dim=29 ;; # possible leap year?
* ) dim=-1 ;; # unknown month
esac
在这种情况下,我们设置的变量是“月份中的天数”或 dim
(不是指发条橙,我的影迷读者)。这使得检查除 2 月 29 日之外的所有日期作为可能的日期变得容易,如这个简单的条件所示
if [ $day -lt 0 -o $day -gt $dim ] ; then
echo "Invalid date: Month #$mon has $dim days, so day \
$day is impossible."
fi
当然,有很多不同的方法可以做到这一点,但是因为大多数月份都有 31 天,所以,再次,我正在寻找捷径!
混合在一起并稍微调整输出,我们现在可以测试以正确的月、日、年格式指定的任何日期的有效性
$ sh valid-date.sh 2 29 2013
The date you specified -- 2-29-2013 -- is valid. Continuing...
$ sh valid-date.sh 1 33 2013
Invalid date: Month #1 has 31 days, so day 33 is impossible.
$ sh valid-date.sh 2 29 2013
2013 wasn't a leapyear, so February only had 28 days.
啊,一切都运行良好。
我们开始时决定将所有日期格式化问题都推给用户,但我们仍然需要做一些基本的测试,至少是这个测试
if [ $# -ne 3 ] ; then
echo "Usage: $(basename $0) mon day year"
echo " with just numerical values (ex: 7 7 1776)"
exit 1
fi
是的,这个月的脚本并不光彩——这就是脚本编写者的生活。
有了有效日期,就有一种趋势是使用类似 GNU date
的东西进行数学运算(参见 GNU date
边栏),但这有一些固有的局限性,其中最不重要的局限性是它不适用于 1970 年之前的任何日期。
本月我将在这里停止,但下个月,我们将采用我们验证的日期,看看是否有公式可以快速计算从那时到现在的天数!
GNUdate
如果您的系统上安装了 GNU date(尝试 date --version
),那么这个脚本项目的后半部分将变得非常容易,因为您可以轻松计算 1970 年 1 月 1 日到指定日期之间的秒数。例如
date '+%s' -d 2011-11-04
很容易从一个日期中减去另一个日期,然后除以 86400 以将秒转换为天数——对于 1970 年 1 月 1 日之后的日期。