Reputation: 2102
Lets say I have:
dateA : sometime in the past(ex: 2013-01-01T00:00:00)(yyyy-mm-dd)
dateB :now
intervalLength: from dateA - now
If the interval length greater than 30 days i need to generate an array where each position would hold an object with start and end of every subinterval:
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
So basically I need a module that can break a date interval into smaller sub intervals. Ideally a method which I can call by passing 2 dates and required interval max length as arguments.
Upvotes: 0
Views: 148
Reputation: 54323
You can do that with DateTime and DateTime::Duration, but it should be easy to implement with Time::Piece as well. The point is that you need to do the calculating yourself.
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;
}
This function takes two DateTime objects and returns a structure similar to the example you showed. It works by adding 29 days to the start date in a loop until the end date is exceeded.
We need to use 29 days and not 30 days because we want an interval of 30 days, but the start date is already the first day, so it's 29.
Let's try with this call:
use Data::Printer;
p get_interval(
DateTime->new( year => 2016, month => 1, day => 1 ),
DateTime->new( year => 2016, month => 12, day => 8 )
);
And the output using 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"
}
]
I've written some unit tests as well. They are by no means complete, but they give a good starting point.
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;
}
Upvotes: 1