【问题标题】:Perl module: break date interval into sub intervals?Perl模块:将日期间隔分成子间隔?
【发布时间】:2016-12-08 13:06:07
【问题描述】:

假设我有:

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 个日期和所需的间隔最大长度作为参数来调用它。

【问题讨论】:

  • 到目前为止,您已经尝试过...?
  • @JohnDoe 你是什么意思?我要求参考一个模块。重点是不必从头开始编写解决方案,因为我会担心异常情况......我尝试过的是在 cpanm 上搜索失败......
  • 因为在大多数情况下,此类问题会被标记为requesting a library suggestion 的题外话。
  • 所以你不想要一个实际的间隔(如 duration),但你希望你的间隔由两个日期表示。一个是开始,一个是结束。而且它们应该总是到月底,所以如果间隔跨越多个月,你就有几个。没有现成的模块,但我认为 DateTime 最适合构建它。另外,你在乎时间吗?因为结尾是2013-01-30T23:59:59 而不是2013-01-30T00:00:00(假设你在一月份住的地方当然只有30天)。 :)
  • @simbabque 由两个日期表示的间隔为真。不是月底,结束日期应该始终是开始日期(无论它可能是)+ 30 天。原因是 Moves App API 限制您一次最多请求 31 天的数据(每次服务器调用)。因此,我正在创建一个脚本,该脚本每天在 00:00:00 运行,从 API 中提取新数据。给定用户第一次发生这种情况时,数据可能存在于一年多前的 Moves API 上,因此我必须分解服务器请求。

标签: perl date datetime intervals


【解决方案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;
}

【讨论】:

  • 哇,我对一个模块的引用很满意 :) 你构建了这个不错的子。非常感谢先生。
  • @AlExTsm 好吧,我很无聊。 :P
猜你喜欢
  • 2021-10-13
  • 1970-01-01
  • 1970-01-01
  • 2016-10-06
  • 2022-01-22
  • 2015-03-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多