dbmathis has asked for the wisdom of the Perl Monks concerning the following question:

Hello Monks,

I am working on a utility for work and I have run into a slight problem.

I have two dates that look like 2007-11-23 and 2008-04-12. What I need to do is increment by day starting with 2007-11-23 and then load an array with ever date between and including the two dates above.

How would I do this with the standard perl modules?

Thanks

David Mathis

So the end result would be an array with one date per element of the array like:
2007-11-23
2007-11-24
2007-11-25
...
...
...
2008-04-11
2008-04-12

I was able to figure this out using Date::Calc but this util will be on 50 other servers that I have no access to install the Date::Calc module.

  • Comment on Increment through date range by day with only standard modules.

Replies are listed 'Best First'.
Re: Increment through date range by day with only standard modules.
by marto (Cardinal) on Dec 12, 2008 at 19:56 UTC

    You could use PAR::Packer to make an executable containing all the modules you need.

    Cheers,

    Martin

Re: Increment through date range by day with only standard modules.
by toolic (Bishop) on Dec 12, 2008 at 20:03 UTC
    You should be able to just copy and paste from the source code of Date::Calc. It is pure Perl, and it only relies on other core modules.

    Update: Nevermind!

      Seems to have a lot of XS for being pure perl. :)

Re: Increment through date range by day with only standard modules.
by DStaal (Chaplain) on Dec 12, 2008 at 19:55 UTC

    General approach I'd use: Use Time::Local and localtime to alternate between epoc time and a more standard time format. (Add days in epoc time, convert to standard, use, then convert back to epoc at a specific time of day and start over.)

    I'll leave the actual code as an exercise. ;)

    use Time::Local; my $date_epoc = timelocal(0,0,12,23,11,2007); my $end_date = timelocal(0,0,12,12,4,2008); my @date_array; push @date_array, '2008-11-23'; while ( $date_epoc < $end_date ) { # Might want <= instead: I'd have +to check. $date_epoc = $date_epoc + 86400; my (undef, undef, undef, $day, $mon, $year) = localtime($date_epoc) +; push @date_array, "$year-$mon-$day"; $date_epoc = timelocal(0,0,12,$day,$mon,$year); }
      • You have an off-by-one error for the months.
      • The last line of your loop is a no-op.
      • @date_array is redundant.
      • A C-style for loop works great here.
      • strftime is most appropriate here.
      • 86400 is better written as 24*60*60. The latter is self-documenting.
      • I prefer to use timegm/gmtime instead of setting the time to noon.
      use strict; use warnings; use POSIX qw( strftime ); use Time::Local qw( timegm ); my ($y1,$m1,$d1) = (2007,23,11); my ($y2,$m2,$d2) = (2007, 4,12); my $date1 = timegm(0,0,0,$d1,$m1-1,$y1); my $date2 = timegm(0,0,0,$d2,$m2-1,$y2); my @dates; for (my $date=$date1; $date<=$date2; $date+=24*60*60) { push @dates, strftime('%Y-%m-%d', gmtime($date)); }
        1. Yes, I do. My Bad.
        2. No, it's not. It's actually vitally important: It handles daylight savings time, leap seconds, and other time oddities, by ignoring them and resetting things.
        3. Yes, it does. I just never think of them...
        4. strftime is nice, but since you need to separate the info out anyway, I figured we might as well leave it off.
        5. Good point.
        6. Again: The setting to noon helps make sure you don't have to worry about the oddities in computing time. (It's less likely to make a difference at noon.) I don't care what timezone you are in, just make sure you aren't using midnight, as you'll get errors!
        Thanks, I will look over this tonight. You both have been very helpful.
      Thanks, this approach makes sense. Thanks!
Re: Increment through date range by day with only standard modules.
by johngg (Canon) on Dec 12, 2008 at 21:57 UTC

    This solution doesn't use modules at all.

    use strict; use warnings; my $startDate = q{2007-11-23}; my $endDate = q{2008-04-12}; my $leap = isLeap( ( split m{-}, $startDate )[ 0 ] ); my @dates = ( $startDate ); while( $startDate ne $endDate ) { $startDate = incByOneDay( $startDate ); push @dates, $startDate; } print do{ local $" = qq{\n}; qq{@dates\n}; }; sub incByOneDay { my $raDaysInMonth = [ [ 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ], [ 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ], ]; my( $y, $m, $d ) = split m{-}, $_[ 0 ]; $d ++; $d = 1, $m ++ if $d > $raDaysInMonth->[ $leap ]->[ $m ]; $m = 1, $y ++, $leap = isLeap( $y ) if $m > 12; return sprintf q{%04d-%02d-%02d}, $y, $m, $d; } sub isLeap { my $year = shift; return 0 if $year % 4; return 1 if $year % 100; return 1 unless $year % 400; return 0; }

    I would probably use modules if starting from scratch but I had this code lying around anyway so I thought I'd adapt it.

    Cheers,

    JohnGG

      while( $startDate lt $endDate ) would be more forgiving than while( $startDate ne $endDate ) in case $endDate happened to be something like 2009-02-29.

        Well, not really, as we should be validating $startDate and $endDate before we even get to the stage of generating our date range array. However, I left that as an exercise for the reader :-)

        Cheers,

        JohnGG