日期之间的天数:计数
在我的上一篇文章中,我们开始探索日期计算,首先验证用户指定的给定日期,然后探讨了 GNU date 如何提供一些巧妙的数学计算功能,但也存在一些固有的局限性,最显著的局限性是它并非在所有 Linux 和 UNIX 系统上都可用。
因此,在这里,让我们继续开发,添加日期计算本身。对于此脚本,目标是回答指定日期和当前日期之间已经过去了多少天的问题。必要的算法在更广泛的“日期 A 到日期 B”问题中具有明显的应用,但让我们将其推迟到以后再说。
还要记住,我们讨论的是日期,而不是时间,因此它不是计算 24 小时的增量,而只是天数。因此,在晚上 11:59 运行完成的脚本会得到与凌晨 12:01 运行不同的结果,仅仅相差几秒钟。
首先,让我们获取当前的月份、日期和年份。我们可以使用一个巧妙的 eval
技巧来做到这一点
eval $(date "+thismon=%m;thisday=%d;thisyear=%Y;dayofyear=%j")
这会将 thismon
实例化为当前月份,thisday
实例化为当前月份的日期,thisyear
实例化为当前年份,dayofyear
实例化为当前日期在当前年份中的数字天数,所有这些都在一行代码中完成——简洁。
用户指定的开始日期已经分解为月份的日期、年份的月份和年份,因此一旦确定了当前的月份、日期和年份,等式就分为四个部分
-
已经过去了多少年 * 365。
-
开始年份还剩下多少天。
-
自今年年初以来已经过去了多少天。
-
补偿过渡期间的闰年。
第一个测试演示了此计算中的细微差别,因为如果我们要计算自例如 1996 年 11 月 15 日到今天 2014 年 6 月 3 日的天数,我们不想计算 365*(2014–1996),因为开始年份和结束年份都将是错误的。实际上,更好的计算方法是使用这个公式
365 * (今年 – 开始年份 – 2)
但是,这也不对。如果开始日期是 2014 年 6 月 1 日,结束日期是 2014 年 6 月 3 日会发生什么?这应该产生零值,如果开始日期是 2013 年 3 月 1 日,即使 2014–2013=1,也会产生零值。这是我对这段代码的第一次尝试
if [ $(( $thisyear - $startyear )) -gt 2 ] ; then
elapsed=$(( $thisyear - $startyear - 2 ))
basedays=$(( elapsed * 365 ))
else
basedays=0
fi
echo "$basedays days transpired between end of $startyear \
and beginning of this year"
现在这没有考虑到闰年,是吗?因此,相反,让我们尝试用不同的方法来利用 isleap
函数
if [ $(( $thisyear - $startyear )) -gt 2 ] ; then
# loop from year to year, counting years and adding +1
# for leaps as needed
theyear=$startyear
while [ $theyear -ne $thisyear ] ; do
isleap $theyear
if [ -n "$leapyear" ] ; then
elapsed=$(( $elapsed + 1 ))
echo "(adding 1 day to account for $theyear being a leap)"
fi
elapsed=$(( $elapsed + 365 ))
theyear=$(( $theyear + 1 ))
done
fi
快速而简单。当我以 1993 年作为开始年份运行此代码块时,它告诉我
(adding 1 day to account for 1996 being a leap year)
(adding 1 day to account for 2000 being a leap year)
(adding 1 day to account for 2004 being a leap year)
(adding 1 day to account for 2008 being a leap year)
(adding 1 day to account for 2012 being a leap year)
7670 days transpired between end of 1993 and beginning of this year
对于算法的第二步,计算从指定的开始日期到该特定年份结束的天数,嗯,这也有一个边缘情况,即它是当前年份。如果不是,则可以通过将之前每个月的天数加上开始日期的月份中的天数相加,然后从该特定年份的总天数中减去它(因为我们必须考虑闰年,这意味着我们必须考虑日期是否发生在 2 月 29 日之前或之后),或者我们可以做相同的基本等式,但将指定日期之后的天数相加。在后一种情况下,我们可以通过跟踪是否包含二月来巧妙地处理闰日。
基本代码假设我们有一个包含 12 个月中每个月的天数的数组,或者其他计算该值的方法。实际上,原始脚本包含以下代码片段
case $mon in
1|3|5|7|8|10|12 ) dim=31 ;; # most common value
4|6|9|11 ) dim=30 ;;
2 ) dim=29 ;; # is it a leap year?
* ) dim=-1 ;; # unknown month
esac
一个合乎逻辑的方法是将此转换为一个可以执行双重任务的简短函数。这可以通过将其包装在 function daysInMonth {
和 }
中轻松完成。
这样,就剩下遍历剩余的月份了,但要注意如果 month = 2(2 月),我们需要进行的闰年计算。该程序将二月始终设置为 29 天,因此如果不是闰年,我们需要减去一天来补偿
if [ $thisyear -ne $startyear ] ; then
monthsleft=$(( $startmon + 1 ))
daysleftinyear=0
while [ $monthsleft -le 12 ] ; do
if [ $monthsleft -eq 2 ] ; then # February. leapyear?
isleap $startyear
if [ -n "$leapyear" ] ; then
daysleftinyear=$(( $daysleftinyear + 1 )) # feb 29!
fi
fi
daysInMonth $monthsleft
daysleftinyear=$(( $daysleftinyear + $dim ))
monthsleft=$(( $monthsleft + 1 ))
done
else
daysleftinyear=0 # same year so no calculation needed
fi
最后一部分是计算开始日期的月份还剩下多少天,再次担心那些讨厌的闰年。这仅在开始年份与当前年份不同且开始月份与当前月份不同时才必要。在我们在同一年份的情况下,正如您将看到的,我们可以使用“年份中的第几天”并以不同的方式进行计算。
这是代码块
if [ $startyear -ne $thisyear -a $startmon -ne $thismon ] ; then
daysInMonth $startmon
if [ $startmon -eq 2 ] ; then # edge case: February
isleap $startyear
if [ -z "$leapyear" ] ; then
dim=$(( $dim - 1 )) # dim = days in month
fi
fi
daysleftinmon=$(( $dim - $startday ))
echo "calculated $daysleftinmon days left in the startmon"
fi
我们有一些有用的变量现在需要添加到“elapsed”变量中:daysleftinyear
是开始年份还剩下多少天,dayofyear
是当前日期在当前年份中的天数(例如,6 月 3 日是第 154 天)。为了清晰起见,我这样添加它
echo calculated $daysleftinyear days left in the specified year
elapsed=$(( $elapsed + daysleftinyear ))
# and, finally, the number of days into the current year
elapsed=$(( $elapsed + $dayofyear ))
echo "Calculated that $startmon/$startday/$startyear \
was $elapsed days ago."
这样,让我们用一些不同的输入来测试脚本
$ sh daysago.sh 8 3 1980
The date you specified -- 8-3-1980 -- is valid. Continuing...
12419 days transpired between end of 1980 and beginning of this year
calculated 28 days left in the startmon
calculated 122 days left in the specified year
Calculated that 8/3/1980 was 12695 days ago.
$ sh daysago.sh 6 3 2004
The date you specified -- 6-3-2004 -- is valid. Continuing...
3653 days transpired between end of 2004 and beginning of this year
calculated 184 days left in the specified year
Calculated that 6/3/2004 was 3991 days ago.
嗯...365*10 = 3650。加上闰年的几天,这似乎是错误的,不是吗?好像多了一年还是什么?更糟糕的是,看看如果我正好回到两年前会发生什么
$ sh daysago.sh 6 3 2012
The date you specified -- 6-3-2012 -- is valid. Continuing...
0 days transpired between end of 2012 and beginning of this year
calculated 184 days left in the specified year
Calculated that 6/3/2012 was 338 days ago.
肯定有些地方出错了。那应该是 2*365。但事实并非如此。呸。呸。在我的下一篇文章中,我们将深入研究并尝试找出问题所在!