Perl模块:将子日期间隔分成子间隔?

时间:2016-12-08 13:06:08

标签: perl date datetime intervals

让我说我有:

dateA : sometime in the past(ex: 2013-01-01T00:00:00)(yyyy-mm-dd)
dateB :now
intervalLength: from dateA - now

如果间隔长度大于30天,我需要生成一个数组,其中每个位置将包含一个具有每个子区间的开始和结束的对象:

intervalLength[0]: {start:2013-01-01T00:00:00, end:2013-01-30T00:00:00}
intervalLength[1]: {start:2013-01-30T00:00:01, end:2013-02-28T00:00:00}
...
intervalLength[n]: "last interval/rest" < 30days

所以基本上我需要一个可以将日期间隔分成更小的子区间的模块。理想情况下,我可以通过传递2个日期和所需的间隔最大长度作为参数来调用方法。

1 个答案:

答案 0 :(得分:1)

您可以使用DateTimeDateTime::Duration执行此操作,但也可以使用Time::Piece轻松实现。关键是你需要自己进行计算。

sub get_interval {
    my ( $start, $end ) = @_;

    # to get a 30 day timespan we need to add 29
    # 01. to 30. = 1 + 29
    my $twenty_nine_days = DateTime::Duration->new( days => 29 );

    my @return;
    while ( ( my $step = $start + $twenty_nine_days ) < $end ) {
        push @return, { start => $start->ymd, end => $step->ymd };
        $start = $step;
    }
    push @return, { start => $start->ymd, end => $end->ymd };

    return \@return;
}

此函数接受两个DateTime对象,并返回与您显示的示例类似的结构。它的工作原理是在循环中将29天添加到开始日期,直到超过结束日期。

我们需要使用29天而不是30天,因为我们需要30天的间隔,但开始日期已经是第一天,所以它是29。

让我们试试这个电话:

use Data::Printer;
p get_interval(
    DateTime->new( year => 2016, month => 1,  day => 1 ),
    DateTime->new( year => 2016, month => 12, day => 8 )
);

输出使用Data::Printer

\ [
    [0]  {
        end     "2016-01-30",
        start   "2016-01-01"
    },
    [1]  {
        end     "2016-02-28",
        start   "2016-01-30"
    },
    [2]  {
        end     "2016-03-28",
        start   "2016-02-28"
    },
    [3]  {
        end     "2016-04-26",
        start   "2016-03-28"
    },
    [4]  {
        end     "2016-05-25",
        start   "2016-04-26"
    },
    [5]  {
        end     "2016-06-23",
        start   "2016-05-25"
    },
    [6]  {
        end     "2016-07-22",
        start   "2016-06-23"
    },
    [7]  {
        end     "2016-08-20",
        start   "2016-07-22"
    },
    [8]  {
        end     "2016-09-18",
        start   "2016-08-20"
    },
    [9]  {
        end     "2016-10-17",
        start   "2016-09-18"
    },
    [10] {
        end     "2016-11-15",
        start   "2016-10-17"
    },
    [11] {
        end     "2016-12-08",
        start   "2016-11-15"
    }
]

我也写了一些单元测试。它们并不完整,但它们提供了一个很好的起点。

use strict;
use warnings;
use feature 'say';
use Data::Printer;
use Test::More;
use Test::Deep;
use DateTime;

sub get_date {
    my ( $year, $month, $day ) = @_;
    return DateTime->new( year => $year, month => $month, day => $day );
}

cmp_deeply get_interval( get_date( 2013, 1, 1 ), get_date( 2013, 2, 20 ) ),
  [
    { start => '2013-01-01', end => '2013-01-30', },
    { start => '2013-01-30', end => '2013-02-20', },
  ],
  'one and a bit intervals';

cmp_deeply get_interval( get_date( 2013, 1, 5 ), get_date( 2013, 1, 7 ) ),
  [ { start => '2013-01-05', end => '2013-01-07', }, ],
  'three days in one month';

cmp_deeply get_interval( get_date( 2013, 1, 30 ), get_date( 2013, 2, 1 ) ),
  [ { start => '2013-01-30', end => '2013-02-01', }, ],
  'three days over two month';

cmp_deeply get_interval( get_date( 2013, 1, 01 ), get_date( 2014, 1, 31 ) ),
  [
    { start => '2013-01-01', end => '2013-01-30', },
    { start => '2013-01-30', end => '2013-02-28', },
    { start => '2013-02-28', end => '2013-03-29', },
    { start => '2013-03-29', end => '2013-04-27', },
    { start => '2013-04-27', end => '2013-05-26', },
    { start => '2013-05-26', end => '2013-06-24', },
    { start => '2013-06-24', end => '2013-07-23', },
    { start => '2013-07-23', end => '2013-08-21', },
    { start => '2013-08-21', end => '2013-09-19', },
    { start => '2013-09-19', end => '2013-10-18', },
    { start => '2013-10-18', end => '2013-11-16', },
    { start => '2013-11-16', end => '2013-12-15', },
    { start => '2013-12-15', end => '2014-01-13', },
    { start => '2014-01-13', end => '2014-01-31', },
  ],
  'three days over two month';

# what happens if start > end?

done_testing;

sub get_interval {
    my ( $start, $end ) = @_;

    # to get a 30 day timespan we need to add 29
    # 01. to 30. = 1 + 29
    my $twenty_nine_days = DateTime::Duration->new( days => 29 );

    my @return;
    while ( ( my $step = $start + $twenty_nine_days ) < $end ) {
        push @return, { start => $start->ymd, end => $step->ymd };
        $start = $step;
    }
    push @return, { start => $start->ymd, end => $end->ymd };

    return \@return;
}