I've searched through CPAN for an appropriate module, but to no avail. Assuming that I have a hash whose keys are in YYYY-MM format, I need to find the gaps in the date ranges and return those gaps. I've written some code that appears to work, but it seems like overkill. Boundary conditions are a nightmare and the logic gets a bit funky in places.

I'm posting the code, in case an alternative doesn't exist, but I'm hoping to find a simpler solution. I think my solution is as simple as I can reasonably make it, but a module that's been tested more robustly would be much appreciated.

Side note: &find_gaps should return a reference to an array containing all dates that should be there, but aren't.

use strict; use warnings; use Data::Dumper; my @temp = ( [ qw( testthis 2000-4 2000-2 2000-1 2000-12 2001-1 2002-1 +2 1999-12 ) ], # bad [ qw( testthis 1999-1 1999-2 1999-3 1999-4 1999-5 1999-6 +1999-7 1999-8 1999-9 1999-10 1999-11 1999-12 2001-3 ) ], # bad [ qw( 1999-1 1999-2 1999-3 1999-4 1999-5 1999-6 1999-7 19 +99-8 1999-9 1999-10 1999-11 1999-12 2000-1 ) ], # good [ qw( 1998-12 1999-1 ) ], # good [ qw( 2004-1 ) ], # good [ qw( 2004-12 ) ], # good [ qw( 2004-12 2004-11 ) ], # good [ qw( testthis 2004-12 2004-9 2004-11 ) ] ); # bad foreach ( @temp ) { my %dates = map { $_, '' } @$_; if ( exists $dates{ 'testthis' } ) { delete $dates{ 'testthis' }; print Dumper( \%dates ); my ( $missing ) = find_gaps( \%dates ); print Dumper( $missing ), '-' x 20 ,"\n"; } } # # Above is my test code. Below is the code in question # sub find_gaps { my $dates = shift; my %year; # Create hash with years as keys and values being a list with # first month and last month in that year range foreach my $date ( keys %$dates ) { my $this_year = substr $date, 0, 4; next if exists $year{ $this_year }; $year{ $this_year } = get_range( $this_year, \%$dates ); } my @years = sort keys %year; my $gap = 0; my ( @missing, @present ); # Year span is greater than number of years if ( scalar @years > 1 and ( ( $years[ -1 ] - $years[ 0 ] ) ) != ( + scalar @years ) - 1 ) { for my $year ( $years[ 0 ] .. $years[ -1 ] ) { if ( ! grep { /$year/ } @years ) { for ( 1 .. 12 ) { push @missing, "$year-$_"; } # next month } } # next year } # Gap in months? for my $year_index ( 0 .. $#years ) { my $curr_year = $years[ $year_index ]; my $first_month = $year{ $curr_year }->[ 0 ]; my $last_month = $year{ $curr_year }->[ 1 ]; next if $year_index == 0 and $first_month == 12; next if $year_index == $#years and $first_month == 1 and ! $la +st_month; my ( $first, $last ); # first year, but more than one year if ( $year_index == 0 and $#years != 0 ) { ( $first, $last ) = ( $first_month, 12 ); # first year # first and only year } elsif ( $year_index == 0 ) { ( $first, $last ) = ( $first_month, $last_month ? $last_mo +nth : $first_month ); #last year } elsif ( $year_index == $#years ) { ( $first, $last ) = ( 1, $last_month ? $last_month : $firs +t_month ); # last year # intervening years } else { ( $first, $last ) = ( 1, 12 ); } for my $month ( $first .. $last ) { if ( ! exists $dates->{ $curr_year."-".$month } ) { push @missing, $curr_year."-".$month; } } # next $month } # next $year_index return \@missing; } sub get_range { my ( $year, $dates ) = @_; my @months; # If the %date has the same year, grab the month @months = sort { $a <=> $b } map { substr( $_, 5 ) } grep { substr( $_, 0, 4 ) == $year } keys %$dates; # Return range if more than one month, else return the month $#months > 0 ? [ $months[ 0 ], $months[ -1 ] ] : [ $months[ 0 ], 0 + ]; }

Cheers,
Ovid

Join the Perlmonks Setiathome Group or just click on the the link and check out our stats.


In reply to Finding gaps in date ranges by Ovid

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post, it's "PerlMonks-approved HTML":



  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, details, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, summary, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.