0

What I'm trying to do is, given start and end dates in the format YYYY-MM-DD, write a function that will return key/value pairs of dates in that format representing the beginning and end of the intervening months with the caveat that the first pair will start with whatever the start date is, and the last pair will end with whatever the end date is. I haven't been able to find quite the solution I need, though I imagine Date::Manip or Date::Calc can do the job.

For example, if the call looked like:

&get_date_pairs('2014-08-18', '2014-10-17');

then the hash the function returned would look like:

%hash = (
        2014-08-18 => 2014-08-31,
        2014-09-01 => 2014-09-30,
        2014-10-01 => 2014-10-17,

);
sir_gelato
  • 310
  • 2
  • 9
  • What is the application for this? This is something that you could hardcode fairly easily--set up data structures with numbers of days per month, etc.--but if there is some bigger problem that is going to be solved by this, it might be more appropriate to do things a different way. btw, the hash structure is a little odd... an array of arrays might be more intuitive for this kind of data. – i alarmed alien Oct 07 '14 at 21:40
  • 3
    Side note: In general, [don't call functions with an ampersand](http://stackoverflow.com/q/1347396) (`&foo`) unless you have a good reason to. – ThisSuitIsBlackNot Oct 07 '14 at 21:42
  • I'm hitting an API to get reports for all data between very specific ranges, and the API will only return months (or fractions of a month) at a time. An array of arrays would be fine, I just need pairs of start/end dates returned in some easily-parsed format. – sir_gelato Oct 07 '14 at 21:42

2 Answers2

2

Using Time::Piece:

use strict;
use warnings;

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

my $start = '2014-08-18';
my $end   = '2014-10-17';
my $fmt   = '%Y-%m-%d';

# Normalized to Noon to avoid DST
my $month_start = Time::Piece->strptime( $start, $fmt ) + 12 * ONE_HOUR;
my $period_end  = Time::Piece->strptime( $end,   $fmt );

while (1) {
    print $month_start->strftime($fmt), ' - ';

    my $month_end = $month_start + ONE_DAY * ( $month_start->month_last_day - $month_start->mday );

    # End of Cycle if current End of Month is greater than or equal to End Date
    if ( $month_end > $period_end ) {
        print $end, "\n";
        last;
    }

    # Print End of Month and begin cycle for next month
    print $month_end->strftime($fmt), "\n";
    $month_start = $month_end + ONE_DAY;
}

Outputs:

2014-08-18 - 2014-08-31
2014-09-01 - 2014-09-30
2014-10-01 - 2014-10-17
Miller
  • 34,962
  • 4
  • 39
  • 60
  • 1
    Might be simpler to use the `month_last_day` method: `perl -MTime::Piece -wE '$t = localtime; say $t->month_last_day'` spits out 31 since it's (now) October. – ThisSuitIsBlackNot Oct 07 '14 at 22:04
  • @ThisSuitIsBlackNot Thanks for pointing that out. I looked for that function in the docs, but couldn't find it when I composed my solution. Code simplified to use that instead now. – Miller Oct 08 '14 at 00:31
0

You didn't say what problem you were having, so I'm guessing you are asking for an algorithm to achieve what you want.

  1. Set $current_date to $start_date.
  2. Loop:
    1. Set $end_of_month to the last day of the month of $current_date.
    2. If $end_date is less than or equal to the $end_of_month,
      1. Set element $current_date of the hash to $end_date.
      2. Exit loop.
    3. Set element $current_date of the hash to $end_of_month.
    4. Set $current_date to $end_of_month.
    5. Add one day to $current_date.

I use DateTime object (usually constructed by DateTime::Format::Strptime) when I need to work with dates and times, but the Date::Calc should be up to the task too. I don't know anything about Date::Manip.

ikegami
  • 367,544
  • 15
  • 269
  • 518