【问题标题】:How to find the difference in days between two dates?如何找到两个日期之间的天数差异?
【发布时间】:2011-06-24 05:23:31
【问题描述】:

A="2002-20-10"
B="2003-22-11"

如何求两个日期的天数差?

【问题讨论】:

  • 如下所述,但您的日期是错误的:它们必须是 yyyy-mm-dd
  • 许多答案使用 (GNU-)date 的 -d 选项。我想补充一点,这不是 POSIX 日期的一部分,因此不太便携。只要 OP 不在 Solaris 等 Unix 发行版上工作,而只在“通用 Linux”上工作,他或她应该是好的。一个相应的标签将有助于恕我直言。

标签: bash shell date


【解决方案1】:

bash 方式 - 将日期转换为 %y%m%d 格式,然后您可以直接从命令行执行此操作:

echo $(( ($(date --date="031122" +%s) - $(date --date="021020" +%s) )/(60*60*24) ))

【讨论】:

  • 这不一定需要日期为%y%m%d 格式。例如。 gnu date 将接受 OP 使用的%Y-%m-%d 格式。一般来说,ISO-8601 是一个不错的选择(OP 的格式就是这样一种格式)。最好避免使用 2 位数年份的格式。
  • 与今天的不同之处,不妨使用days_diff=$(( (`date -d $B +%s` - `date -d "00:00" +%s`) / (24*3600) ))。请注意,$days_diff 将是一个整数(即没有小数)
  • 这取决于安装的date 的变体。它适用于 GNU date。它不适用于 POSIX date。对于大多数读者来说,这可能不是问题,但值得注意。
  • 如果你真的想要 POSIX 可移植性,请检查:unix.stackexchange.com/a/7220/43835
  • 只是为了避免混淆:OP 的格式是 not %Y-%m-%d,直到我们介绍 August 2.0 和 October 2.0 OP 的格式是 %Y-%d-%m。相关xkcd:xkcd.com/1179
【解决方案2】:

如果你有 GNU date,它允许打印任意日期的表示(-d 选项)。 在这种情况下,将日期转换为自 EPOCH 以来的秒数,然后除以 24*3600。

使用 GNU 日期的示例(来自 https://stackoverflow.com/a/9008871/215713):

let DIFF=($(date +%s -d 20210131)-$(date +%s -d 20210101))/86400
echo $DIFF
30

这也适用于其他格式的日期,例如“2021-01-31”。

其他答案建议了不需要 GNU 日期的方法。

【讨论】:

  • @Tree:我相信除了自己实现之外,没有可移植的方法来减去日期——这并不难,唯一感兴趣的是闰年。但现在每个人都想减去日期,看起来:看看unix.stackexchange.com/questions/1825/… :)
  • 看在lazyweb的份上,我想这就是user332325的意思:echo "( `date -d $B +%s` - `date -d $A +%s`) / (24*3600)" | bc -l
  • 在这种情况下便携是什么意思?
  • @htellez 可以在多个平台(windows、linux 等)上运行的东西
  • 对于基于此答案的 bash 一个班轮,请参阅:stackoverflow.com/a/49728059/1485527
【解决方案3】:

tl;dr

date_diff=$(( ($(date -d "2015-03-11 UTC" +%s) - $(date -d "2015-03-05 UTC" +%s)) / (60*60*24) ))

小心!这里的许多 bash 解决方案都因跨越夏令时开始日期(如果适用)的日期范围而被破坏。这是因为 $(( math )) 构造对结果值执行“地板”/截断操作,仅返回整数。让我举例说明:

DST 于今年 3 月 8 日在美国开始,所以让我们使用跨越该日期的日期范围:

start_ts=$(date -d "2015-03-05" '+%s')
end_ts=$(date -d "2015-03-11" '+%s')

让我们看看我们用双括号得到了什么:

echo $(( ( end_ts - start_ts )/(60*60*24) ))

返回“5”。

使用“bc”更准确地执行此操作会给我们带来不同的结果:

echo "scale=2; ( $end_ts - $start_ts )/(60*60*24)" | bc

返回 '5.95' - 缺少的 0.05 是 DST 切换丢失的小时数。

那么这应该如何正确完成呢?
我建议改用这个:

printf "%.0f" $(echo "scale=2; ( $end_ts - $start_ts )/(60*60*24)" | bc)

在这里,'printf' 对由 'bc' 计算的更准确的结果进行四舍五入,为我们提供了正确的日期范围 '6'。

编辑:在下面@hank-schultz 的评论中突出显示答案,这是我最近一直在使用的:

date_diff=$(( ($(date -d "2015-03-11 UTC" +%s) - $(date -d "2015-03-05 UTC" +%s) )/(60*60*24) ))

只要您始终从较晚的日期中减去较早的日期,这也应该是闰秒安全的,因为闰秒只会增加差异 - 截断有效地向下舍入到正确的结果。

【讨论】:

  • 如果相反,您为开始和结束指定时区(并确保它们相同),那么您也不再有这个问题,就像这样:echo $(( ($(date --date="2015-03-11 UTC" +%s) - $(date --date="2015-03-05 UTC" +%s) )/(60*60*24) )),它返回6,而不是 5。
  • 啊,其中一位回答者想到了其他人在变体中重复的基本解决方案的不准确性。竖起大拇指!闰秒呢?它是闰秒安全的吗?如果在特定时间运行,软件会返回一个天结果的先例,参考problems associated with the leap second。
  • 糟糕,您作为示例给出的“错误”计算在此处正确返回 6(Ubuntu 15.04 AMD64,GNU 日期版本 8.23)。也许您的示例与时区有关?我的时区是“欧洲/巴黎”。
  • 在 MSYS 上使用 GNU date 2.0 进行“错误”计算的结果相同,时区相同。
  • @StéphaneGourichon,您所在时区的夏令时更改似乎发生在 2015 年 3 月 29 日 - 因此请尝试包含该日期范围。
【解决方案4】:

在python中

$python -c "from datetime import date; print (date(2003,11,22)-date(2002,10,20)).days"
398

【讨论】:

  • @mouviciel 不是真的。 Python 并不总是存在。
  • @mc0e 在这种情况下,什么都不会出现
  • @Anubis OP 将标签指定为bash 和shell,所以我认为我们可以假设我们在谈论*nix 系统。在这样的系统中,echo 和 date 可靠存在,但 python 不存在。
  • @mc0e 是的,这是有道理的。尽管大多数 *nix 系统都可以使用 python2,但对于低端设备(嵌入式等),我们只能使用主要工具才能生存。
【解决方案5】:

这对我有用:

A="2002-10-20"
B="2003-11-22"
echo $(( ($(date -d $B +%s) - $(date -d $A +%s)) / 86400 )) days

打印

398 days

发生了什么?

  1. 在A和B中提供有效的时间字符串
  2. 使用date -d 处理时间字符串
  3. 使用date %s 将时间字符串转换为自 1970 年以来的秒数(unix 纪元)
  4. 使用bash parameter expansion 减去秒数
  5. 除以每天的秒数 (86400=60*60*24) 得到不同的天数
  6. ! DST 不予考虑!请在unix.stackexchange 上查看此答案!

【讨论】:

  • 此答案不适用于带有 Bash 5.1 的 macOS:bash: ( - ) / 86400 : syntax error: operand expected (error token is ") / 86400 ")
  • 很抱歉听到这个消息。也许将空格删除到)/86400 可以解决它吗?
  • 抱歉,我似乎粘贴了错误的错误信息。真正的问题是在 macOS 上,date -d 设置内核的夏令时值。它不接受该位置的日期。
  • 也许改用-jf?如果可行,我会将该变体添加到我的答案中。最初我发布了我的答案,因为大多数其他人没有解释他们为什么使用某些参数。此外,我在链接中引用的有关 DST 的信息并不突出。所以我的主张不是提供独立于平台的解决方案。但我们可以共同努力实现这一目标。
  • 关于 MacOS 和 gdate:coreutils 在Homebrew。只需安装brew install coreutils 即可获得gdate。也许值得一试。
【解决方案6】:

为了您的方便,这里是 MacOS X / *BSD 版本。

$ A="2002-20-10"; B="2003-22-11";
$ echo $(((`date -jf %Y-%d-%m "$B" +%s` - `date -jf %Y-%d-%m "$A" +%s`)/86400))

快乐!

【讨论】:

  • -bash: (-)/86400: 语法错误:操作数预期(错误标记是“)/86400”)
  • @cavalcade - 那是因为你还没有完成A="2002-20-10"; B="2003-22-11"
  • 谢谢,尽管它适用于这种特殊情况,但我建议使用echo $(((`date -jf "%Y-%d-%m" "$B" +%s` - `date -jf "%Y-%d-%m" "$A" +%s`)/86400)),即将格式和变量都包含在双引号中,否则可能会在不同的日期格式上失败(例如%b %d, %Y/@ 987654325@)
  • @RAM237 这就是我不注意得到的结果headsmack 很好,谢谢
  • ... Y-d-m?我的天啊。我以为我永远不会看到这一天。
【解决方案7】:

如果选项 -d 在您的系统中有效,这是另一种方法。需要注意的是,由于我考虑了每年 365 天,因此它不会考虑闰年。

date1yrs=`date -d "20100209" +%Y`
date1days=`date -d "20100209" +%j`
date2yrs=`date +%Y`
date2days=`date +%j`
diffyr=`expr $date2yrs - $date1yrs`
diffyr2days=`expr $diffyr \* 365`
diffdays=`expr $date2days - $date1days`
echo `expr $diffyr2days + $diffdays`

【讨论】:

    【解决方案8】:

    即使你没有 GNU date,你也可能已经安装了 Perl:

    use Time::Local;
    sub to_epoch {
      my ($t) = @_; 
      my ($y, $d, $m) = ($t =~ /(\d{4})-(\d{2})-(\d{2})/);
      return timelocal(0, 0, 0, $d+0, $m-1, $y-1900);
    }
    sub diff_days {
      my ($t1, $t2) = @_; 
      return (abs(to_epoch($t2) - to_epoch($t1))) / 86400;
    }
    print diff_days("2002-20-10", "2003-22-11"), "\n";
    

    这将返回 398.041666666667 -- 398 天零一小时,因为夏令时。


    这个问题再次出现在我的提要中。这是使用 Perl 捆绑模块的更简洁的方法

    days=$(perl -MDateTime -le '
        sub parse_date { 
            @f = split /-/, shift;
            return DateTime->new(year=>$f[0], month=>$f[2], day=>$f[1]); 
        }
        print parse_date(shift)->delta_days(parse_date(shift))->in_units("days");
    ' $A $B)
    echo $days   # => 398
    

    【讨论】:

      【解决方案9】:

      这是我使用 zsh 的工作方法。在 OSX 上测试:

      # Calculation dates
      ## A random old date
      START_DATE="2015-11-15"
      ## Today's date
      TODAY=$(date +%Y-%m-%d)
      
      # Import zsh date mod
      zmodload zsh/datetime
      
      # Calculate number of days
      DIFF=$(( ( $(strftime -r %Y-%m-%d $TODAY) - $(strftime -r %Y-%m-%d $START_DATE) ) / 86400 ))
      echo "Your value: " $DIFF
      

      结果:

      Your value:  1577
      

      基本上,我们使用strftime reverse (-r) 功能将日期字符串转换回时间戳,然后进行计算。

      【讨论】:

        【解决方案10】:

        使用mysql命令

        $ echo "select datediff('2013-06-20 18:12:54+08:00', '2013-05-30 18:12:54+08:00');"  | mysql -N
        

        结果:21

        注意:计算中仅使用值的日期部分

        参考:http://dev.mysql.com/doc/refman/5.6/en/date-and-time-functions.html#function_datediff

        【讨论】:

          【解决方案11】:

          这是我设法在 centos 7 上工作的最简单的方法:

          OLDDATE="2018-12-31"
          TODAY=$(date -d $(date +%Y-%m-%d) '+%s')
          LINUXDATE=$(date -d "$OLDDATE" '+%s')
          DIFFDAYS=$(( ($TODAY - $LINUXDATE) / (60*60*24) ))
          
          echo $DIFFDAYS
          

          【讨论】:

            【解决方案12】:

            我会在 Ruby 中提交另一个可能的解决方案。看起来它是迄今为止最小和最干净的一个:

            A=2003-12-11
            B=2002-10-10
            DIFF=$(ruby -rdate -e "puts Date.parse('$A') - Date.parse('$B')")
            echo $DIFF
            

            【讨论】:

            • 他正在 bash 中寻找方法。
            • 在 shell 本身中没有可移植的方法。所有替代方案都使用特定的外部程序(即 GNU date 或一些脚本语言),老实说,我认为 Ruby 是一个好的方法。该解决方案非常简短,不使用任何非标准库或其他依赖项。事实上,我认为安装 Ruby 的几率比安装 GNU date 的几率要高。
            【解决方案13】:

            另一个 Python 版本:

            python -c "from datetime import date; print date(2003, 11, 22).toordinal() - date(2002, 10, 20).toordinal()"
            

            【讨论】:

              【解决方案14】:

              使用http://cfajohnson.com/shell/ssr/ssr-scripts.tar.gz中的shell函数;它们可以在任何标准的 Unix shell 中工作。

              date1=2012-09-22
              date2=2013-01-31
              . date-funcs-sh
              _date2julian "$date1"
              jd1=$_DATE2JULIAN
              _date2julian "$date2"
              echo $(( _DATE2JULIAN - jd1 ))
              

              请参阅http://cfajohnson.com/shell/ssr/08-The-Dating-Game.shtml 的文档

              【讨论】:

                【解决方案15】:

                在 unix 上,您应该安装 GNU 日期。你不需要偏离 bash。这是考虑到几天的解决方案,只是为了展示步骤。它可以简化并扩展到完整日期。

                DATE=$(echo `date`)
                DATENOW=$(echo `date -d "$DATE" +%j`)
                DATECOMING=$(echo `date -d "20131220" +%j`)
                THEDAY=$(echo `expr $DATECOMING - $DATENOW`)
                
                echo $THEDAY 
                

                【讨论】:

                  【解决方案16】:

                  假设一个月是一年的 1/12:

                  #!/usr/bin/awk -f
                  function mktm(datespec) {
                    split(datespec, q, "-")
                    return q[1] * 365.25 + q[3] * 365.25 / 12 + q[2]
                  }
                  BEGIN {
                    printf "%d\n", mktm(ARGV[2]) - mktm(ARGV[1])
                  }
                  

                  【讨论】:

                    【解决方案17】:

                    命令行bash解决方案

                    echo $((($(date +%s -d 20210131)-$(date +%s -d 20210101))/86400)) days
                    

                    将打印

                    30 days
                    

                    【讨论】:

                      【解决方案18】:

                      试试这个:

                      perl -e 'use Date::Calc qw(Delta_Days); printf "%d\n", Delta_Days(2002,10,20,2003,11,22);'
                      

                      【讨论】:

                        【解决方案19】:

                        假设我们手动将 Oracle 数据库备份同步到第三磁盘。然后我们要删除该磁盘上的旧备份。所以这里有一个小 bash 脚本:

                        #!/bin/sh
                        
                        for backup_dir in {'/backup/cmsprd/local/backupset','/backup/cmsprd/local/autobackup','/backup/cfprd/backupset','/backup/cfprd/autobackup'}
                        do
                        
                            for f in `find $backup_dir -type d -regex '.*_.*_.*' -printf "%f\n"`
                            do
                        
                                f2=`echo $f | sed -e 's/_//g'`
                                days=$(((`date "+%s"` - `date -d "${f2}" "+%s"`)/86400))
                        
                                if [ $days -gt 30 ]; then
                                    rm -rf $backup_dir/$f
                                fi
                        
                            done
                        
                        done
                        

                        修改目录和保留期(“30 天”)以满足您的需要。

                        【讨论】:

                        • Oracle 使用 'YYYY_MM_DD' 之类的格式将备份集放入闪存恢复区,因此我们删除了将其传递给 'date -d' 的下划线
                        【解决方案20】:

                        对于 MacOS sierra(可能来自 Mac OS X yosemate),

                        从文件中获取纪元时间(从 1970 年开始的秒数),并将其保存到 var: old_dt=`date -j -r YOUR_FILE "+%s"`

                        获取当前时间的纪元时间 new_dt=`date -j "+%s"`

                        计算以上两个纪元时间的差异 (( diff = new_dt - old_dt ))

                        检查 diff 是否超过 23 天 (( new_dt - old_dt > (23*86400) )) && echo Is more than 23 days

                        【讨论】:

                          【解决方案21】:
                          echo $(date +%d/%h/%y) date_today
                          echo " The other date is 1970/01/01"
                          TODAY_DATE="1970-01-01"
                          BEFORE_DATE=$(date +%d%h%y)
                          echo "THE DIFFERNS IS " $(( ($(date -d $BEFORE_DATE +%s) - $(date -d $TODAY_DATE +%s)) )) SECOUND
                          

                          使用它来获取您今天的日期以及自您想要的日期以来的秒数。如果你想要它的天数,只需将它除以 86400

                          echo $(date +%d/%h/%y) date_today
                          echo " The other date is 1970/01/01"
                          TODAY_DATE="1970-01-01"
                          BEFORE_DATE=$(date +%d%h%y)
                          echo "THE DIFFERNS IS " $(( ($(date -d $BEFORE_DATE +%s) - $(date -d $TODAY_DATE +%s))/ 86400)) SECOUND
                          

                          【讨论】:

                            【解决方案22】:

                            在生成自签名证书时,我不想将到期时间硬编码为设定的天数,而是希望它们在Y2k38 problem 开始之前(2028 年 1 月 19 日)到期。但是,OpenSSL 只允许使用-days 设置过期时间,这是从当前日期算起的天数。 我最终使用了这个:

                            openssl req -newkey rsa -new -x509 \
                              -days $((( $((2**31)) - $(date +%s))/86400-1)) \
                              -nodes -out new.crt -keyout new.key -subj '/CN=SAML_SP/'
                            

                            【讨论】:

                              【解决方案23】:

                              对于 zsh(仅适用于同一年的日期)

                              echo $(($(date -d "today" +%j) - $(date -d "08 Jan 2021" +%j) )) days
                              

                              【讨论】:

                                猜你喜欢
                                • 2011-05-29
                                • 1970-01-01
                                • 1970-01-01
                                • 1970-01-01
                                • 2011-03-01
                                • 2011-03-20
                                • 1970-01-01
                                • 1970-01-01
                                • 2022-12-01
                                相关资源
                                最近更新 更多