【问题标题】:List of days between start and end dates开始日期和结束日期之间的天数列表
【发布时间】:2025-05-28 02:35:01
【问题描述】:

我试图在 Perl 中获取开始日期和结束日期之间的每个单独的日期。我最初使用以下代码完成了 7 天:

use Time::ParseDate;  
my $newdate;
my $newtime;
my @dates = ();

my $newtime->[0] = parsedate($start_datetime);
my $newdate->[0] = strftime("%Y-%m-%d",localtime($newtime->[0]));
push(@dates, $newdate->[0]);
my $newtime->[1] = parsedate($newdate->[0]) + (1 * 24 * 60 * 60);
my $newdate->[1] = strftime("%Y-%m-%d",localtime($newtime->[1]));
push(@dates, $newdate->[1]);
my $newtime->[2] = parsedate($newdate->[1]) + (1 * 24 * 60 * 60);
my $newdate->[2] = strftime("%Y-%m-%d",localtime($newtime->[2]));
push(@dates, $newdate->[2]);
my $newtime->[3] = parsedate($newdate->[2]) + (1 * 24 * 60 * 60);
my $newdate->[3] = strftime("%Y-%m-%d",localtime($newtime->[3]));
push(@dates, $newdate->[3]);
my $newtime->[4] = parsedate($newdate->[3]) + (1 * 24 * 60 * 60);
my $newdate->[4] = strftime("%Y-%m-%d",localtime($newtime->[4]));
push(@dates, $newdate->[4]);
my $newtime->[5] = parsedate($newdate->[4]) + (1 * 24 * 60 * 60);
my $newdate->[5] = strftime("%Y-%m-%d",localtime($newtime->[5]));
push(@dates, $newdate->[5]);
my $newtime->[6] = parsedate($newdate->[5]) + (1 * 24 * 60 * 60);
my $newdate->[6] = strftime("%Y-%m-%d",localtime($newtime->[6]));
push(@dates, $newdate->[6]);
my $newtime->[7] = parsedate($newdate->[6]) + (1 * 24 * 60 * 60);
my $newdate->[7] = strftime("%Y-%m-%d",localtime($newtime->[7]));
push(@dates, $newdate->[7]);

但意识到它应该能够工作任意天数。 所以我尝试了下面的循环,但它对我来说不太有效

my $newdate;
my $newtime;
my @dates = ();
my $start_date = substr($start_datetime, 0, 10) ; 
$start_date =~ s/-//gi;  
my $end_date = substr($end_datetime, 0, 10) ;
$end_date =~ s/-//gi;

my $days = yyyymmdd_to_rata_die($end_date) - yyyymmdd_to_rata_die($start_date);
my $newtime->[0] = parsedate($xml->{action_content}->{start_date});
my $newdate->[0] = strftime("%Y-%m-%d",localtime($newtime->[0]));
push(@dates, $newdate->[0]);
for(my $x=1;$x<$days;$x++)
{
  my $newtime->[$x] = parsedate($newdate->[$x-1]) + (1 * 24 * 60 * 60);
  my $newdate->[$x] = strftime("%Y-%m-%d",localtime($newtime->[$x]));
  push(@dates, $newdate->[$x]);
}

我从 * 上的一个问题中得到了下面的函数

sub yyyymmdd_to_rata_die
{
    use integer;
    my ( $y, $m, $d ) = $_[0] =~ /\A([0-9]{4})([0-9]{2})([0-9]{2})\z/
    or return;

    my $adj;

    if ( $m <= 2 ) 
    {
        $y -= ( $adj = ( 14 - $m ) / 12 );
        $m += 12 * $adj;
    }
    elsif ( $m > 14 ) 
    {
        $y += ( $adj = ( $m - 3 ) / 12 );
        $m -= 12 * $adj;
    }

    $d += ( $m * 367 - 1094 ) / 12 + $y % 100 * 1461 / 4 + ( $y / 100 * 36524 + $y / 400 ) - 306;
}

任何帮助将不胜感激

编辑: $start_datetime = "2014-09-01 00:00:00"; $end_datetime = "2014-09-07 23:59:59";

我需要开始和结束日期之间的所有日子,即。
2014-09-01
2014-09-02
2014-09-03
2014-09-04
2014-09-05
2014-09-06
2014-09-07

【问题讨论】:

  • 请举例说明您需要的输入和对应的输出。
  • 您的代码表明您使用的是XML::Simple。如果你是,那么我强烈建议你转向别的东西,因为使用它绝非简单

标签: perl datetime


【解决方案1】:

我建议您使用Time::Piece 模块。它不需要安装,因为它从 Perl 5 版本 10 开始就是一个核心模块。

应该是这样的

use strict;
use warnings;

use Time::Piece;

print days_between('2014-01-01', '2014-12-01'), "\n";

sub days_between {
  my ($start, $end) = map Time::Piece->strptime($_, '%Y-%m-%d'), @_;
  ($end - $start)->days;
}

输出

334

更新

我很抱歉。我误解了您的要求,并认为您想要两个日期之间的 天数。该程序打印出一个列表,其中包含两个限制之间(包括)之间的所有日期。如果在标量上下文中调用,它将像以前一样提供列表中的 number 天。

use strict;
use warnings;

use Time::Piece;
use Time::Seconds qw/ ONE_DAY /;

printf "%d days:\n", scalar days_from_to('2014-09-01 00:00:00', '2014-09-07 23:59:59');

print "$_\n" for days_from_to('2014-09-01 00:00:00', '2014-09-07 23:59:59');

sub days_from_to {

   my @limits = map /(\d\d\d\d-\d\d-\d\d)/, @_;
   @limits = map Time::Piece->strptime($_, '%Y-%m-%d'), @limits;

   my @dates = ( $limits[0] );
   push @dates, $dates[-1] + ONE_DAY while $dates[-1] < $limits[-1];   

   map $_->ymd, @dates;
}

输出

7 days:
2014-09-01
2014-09-02
2014-09-03
2014-09-04
2014-09-05
2014-09-06
2014-09-07

更新

如果需要更关键的正则表达式来拒绝像0123-45-67 这样的字符串,那么您可以使用它

my $date_re = qr/(?:
   (?: (?:19|20)[0-9][0-9] ) -
   (?:
      (?: 0?[469] | 11 ) -
      (?: 0?[1-9] | [12][0-9] | 30 )
   |
      (?: 0?[13578] | 1[02] ) -
      (?: 0?[1-9] | [12][0-9] | 3[01] )
   |
      (?: 0?2 ) -
      (?: 0?[1-9] | [12][0-9] )
   )
)/x;

这将接受 1900 到 2099 之间的日期,并确保该月的日期对该月有效。唯一的附带条件是它允许任何一年的 2 月 29 日。

或者您可能更喜欢像这样使用Regexp::Common::time

use Regexp::Common qw/ time /;

my $date_re = $RE{time}{strftime}{-pat => '%Y-%m-%d'};

但这与 2 月 29 日有同样的问题。

子程序如下所示

sub days_from_to {

   my @limits = map /($date_re)/, @_;

   @limits = map Time::Piece->strptime($_, '%Y-%m-%d'), @limits;

   my @dates = ( $limits[0] );
   push @dates, $dates[-1] + ONE_DAY while $dates[-1] < $limits[-1];   

   map $_->ymd, @dates;
}

【讨论】:

  • @ladyT:不客气。请注意,正则表达式会删除除了看起来像日期的第一件事之外的所有内容。这意味着时间是不必要的,但是您可以传递整个日志行,甚至是多行文本,只要其中的第一个日期是您想要的。但要小心0000-99-99 之类的东西,它们会完全混淆它!
【解决方案2】:

使用Time::Piece 之类的东西可以让你的生活更轻松;

#!/usr/bin/perl

use strict;
use warnings;
use 5.010;

use Time::Piece;
use Time::Seconds;

my $fmt = '%Y-%m-%d';

my $start_date = '2014-09-08';
my $days = 30;

my @dates;
my $date = Time::Piece->strptime($start_date, $fmt);

foreach (1 .. $days) {
  push @dates, $date;
  $date += ONE_DAY;
}

say $_ for @dates;

更新:看起来Borodin's answer 使用非常相似的方法来处理您添加到问题中的输入和所需输出。

【讨论】:

  • 看起来参数是开始和结束日期,并且它们包括时间字段,如2014-09-07 23:59:59 所以strptime 将在字符串末尾引发一个垃圾警告,除非它被修剪。