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

This code lists each date since January 1, 1896.

The form that these dates will be inputted to requires the format YYYYMMDD, thus the first through ninth MM and DD values were coded as literal strings with "0" in front.

I can tell that this flow is screaming for the use of hashes, subs, or improved loop structures, so fill me in on some good ways to improve.

Thanks!

#!/usr/bin/perl ############################################## ####### ii.pl ######################### ############################################## # This program lists all dates from 01/01/1896 # to 12/31/2009. # The list will be used to submit each date on # the wx station site in order to gather # the required data. ############################################## # COMMENTS COME AFTER THE LINE OF INTEREST use strict; # to ensure scoping obedience use warnings; # provides warnings when syntax is off use diagnostics; # helps use Number::Ops qw(:all); # for using 'isint()' operator when determining leap years my (@nonleaps, @leaps, @dates); # arrays to hold non leap years, leap years, and then all dates my @yrs = (1896 .. 2009); # all years in the period of record (POR) my @mos = ('01', '02', '03', '04', '05', '06', '07', '08', '09', 10, 11, 12); # literal strings must be used for months 1 through 9 because # the web form requires input of 8 characters only (YYYYMMDD) my @feb = ('01', '02', '03', '04', '05', '06', '07', '08', '09', 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28); # days in the month of feb on non leap years. Literal strings # used for the first 9 days for the same reason as above. my @leap = ('01', '02', '03', '04', '05', '06', '07', '08', '09', 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29); # feb days during leap years my @thirty = ('01', '02', '03', '04', '05', '06', '07', '08', '09', 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30); # for those months with 30 days my @thirtyone = ('01', '02', '03', '04', '05', '06', '07', '08', '09', 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31); # for those with 31 days #BEGIN FLOW CONTROL# #################### foreach my $yrs (@yrs) {# for each year foreach my $mos (@mos) {# for each month in each year if (isint($yrs/4) && $yrs != 1900) {# if it's a leap year if ($mos=='02') {# if it's feb in a leap year foreach my $leap (@leap) {# then add each day in feb to the date below my $date = "$yrs$mos$leap";# date is YYYYMMDD push (@dates, "$date\n"); # add the date to the a +rray } } elsif ($mos=='04' || $mos=='06' || $mos=='09' || $mos==11) {# if it's april, june, sept, or nov foreach my $thirty (@thirty) {# add thirty days to each month my $date = "$yrs$mos$thirty";# date is YYYYMMDD push (@dates, "$date\n"); # add the date to th +e array } } else {# if its any of the other months foreach my $thirtyone (@thirtyone) {# add 31 days my $date = "$yrs$mos$thirtyone"; push (@dates, "$date\n"); } } } else {# if it's not a leap year..(do the same, but 28 days in feb). +.. if ($mos=='02') { foreach my $feb (@feb) { my $date = "$yrs$mos$feb"; push (@dates, "$date\n"); } } elsif ($mos=='04' || $mos=='06' || $mos=='09' || $mos==11) { foreach my $thirty (@thirty) { my $date = "$yrs$mos$thirty"; push (@dates, "$date\n"); } } else { foreach my $thirtyone (@thirtyone) { my $date = "$yrs$mos$thirtyone"; push (@dates, "$date\n"); } } } } } print @dates; open (FH, ">dates.txt");# open a new file for output print FH @dates;# print the array of all dates to the file

Replies are listed 'Best First'.
Re: Improve my Code: Date List
by trwww (Priest) on Jun 18, 2009 at 01:16 UTC

    Thats some monster code for a simple task! Use this instead:

    use warnings; use strict; use DateTime; my $end = DateTime->new(year => 2009, month => 12, day => 31); my $date = DateTime->new(year => 1896, month => 1, day => 1); while ($date <= $end) { print $date->ymd, "\n"; $date->add( days => 1 ); }

    regards,

Re: Improve my Code: Date List
by jwkrahn (Abbot) on Jun 18, 2009 at 02:18 UTC

    This will probably work better:

    #!/usr/bin/perl use warnings; use strict; sub is_leap_year { ( $_[0] % 4 == 0 ) && ( $_[0] % 100 != 0 ) || ( $_[ +0] % 400 == 0 ) } sub end_of_month { my ( $yr, $mn ) = @_; return is_leap_year( $yr ) ? 29 : 28 if $mn == 2; return 30 if $mn == 4 || $mn == 6 || $mn == 9 || $mn == 11; return 31; } for my $year ( 1896 .. 2009 ) { for my $month ( 1 .. 12 ) { for my $day ( 1 .. end_of_month( $year, $month ) ) { printf "%04d%02d%02d\n", $year, $month, $day; } } }
Re: Improve my Code: Date List
by toolic (Bishop) on Jun 17, 2009 at 23:38 UTC
    All those dates screams for a search on CPAN, unless you want to re-invent wheels.
      I do want to reinvent wheels until I understand the wheel well enough to trust someone else's.
        why dont use already existing date modules Eg:- Date::Calc For instance the code can be simplified to
        use strict;
        use warnings;
        use Date::Calc qw(Today Add_Delta_Days);
        
        my $today = sprintf("%02d%02d%02d",Today());
        
        my $year = 1986;
        my $month = 1;
        my $day = 1;
        my $offset =0 ;
        my $print_date = 0;
        
        while ($today > $print_date){
                my ($new_year,$new_month,$new_day)= Add_Delta_Days($year,$month,$day, $offset);
                $print_date =  sprintf("%02d%02d%02d",$new_year,$new_month,$new_day);
                print "$print_date\n";
                $offset++;
        }
        
        
        Depending on your req you can alter where ever needed
        Cheers
Re: Improve my Code: Date List
by johngg (Canon) on Jun 18, 2009 at 23:00 UTC
    my @mos = ('01', '02', '03', '04', '05', '06', '07', '08', '09', 10, 11, 12); ... my @thirtyone = ('01', '02', '03', '04', '05', '06', '07', '08', '09', 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31);

    Leaving aside whether your approach is the right one, you could save yourself a lot of typing by learning about map and sprintf and by re-using what you have already built.

    my @mos = map { sprintf q{%02d}, $_ } 1 .. 12; my @feb = ( @mos, 13 .. 28 ); my @leap = ( @feb, 29 ); my @thirty = ( @leap, 30 ); my @thirtyone = ( @thirty, 31 );

    I hope this is of interest.

    Cheers,

    JohnGG

      Or learning about the range operator:

      my @mos = '01' .. '12';