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

Howdy Monks!

Here it is, the final version of the date conversion script...and now that it works it is truly "happiness".

I added some user interface niceties, eliminated two of the 5 regexes (I know more could go too), fixed the rest of the problems it was having, and turned it in to my instructor.

So now that it is done and handed in, this is where the real fun begins. How would you have done any of it differently? Or maybe all of it differently? UPDATE - we weren't allowed to use any modules - only our own code

BTW - I just wanted to say to you all that in my brief membership here at PM I have learned more Perl than I have in any class at any time, and for that I thank you all. Without your valuable assistance I might never have been able to write this script the way it turned out. Thanks!

ps - I actually _like_ Perl now :-)

update - to trace this script to its birth, go to this post

#!usr/bin/perl #Script to parse dates with the following formats: #Apr 8 1984, Apr 08 84, 4/8/84, 04/08/84, 08 Apr 1984 use warnings; use strict; #blah blah blah once print "\n\n\n\n"; print "Date Converter, v1.0\n"; print "this program assumes that a year from 10 to 99 is in the 1900's +\n"; print "and that a year from 00 to 09 is in the 2000's\n"; print "and will loop until you type 'q' to break out of it.\n"; #loop script forever, atbe while (1) { #declare my vars and make sure they're empty my ( $MM, $DD, $YY, $YYYY ); #take in date or q at command line and make text lowercase print "please enter a date\n"; chomp (my $date = <>); if ($date eq "q") { exit(); } $date = lc ($date); #run date entered thru series of regex filters, #and subsequently perform perly goodness on them. #send result to output subroutine if ($date =~ /[a-zA-Z]{3}\s\d{1,2}\s\d{2,4}/) #parse Apr 08 84 or Apr +8 1984 { my @dateparts = split (/\s/ , $date); $MM = $dateparts [0]; $DD = $dateparts [1]; $YY = $dateparts [2]; if (10 <= $YY && $YY <= 99) {$YYYY = $YY + 1900} elsif (0 <= $YY && $YY <= 9) {$YYYY = $YY + 2000} else {$YYYY = $YY} output ($MM, $DD, $YYYY); } elsif ($date =~ /\d{1,2}\/\d{1,2}\/\d{2}/) #parse 04/08/84 or 4/8/84 { my @dateparts = split (/\// , $date); $MM = $dateparts [0]; $DD = $dateparts [1]; $YY = $dateparts [2]; if (10 <= $YY && $YY <= 99) {$YYYY = $YY + 1900} elsif (0 <= $YY && $YY <= 9) {$YYYY = $YY + 2000} output ($MM, $DD, $YYYY); } elsif ($date =~ /\d{1,2}\s([a-zA-Z]{3})\s\d{4}/) #parse 08 Apr 1984 { my @dateparts = split (/\s/ , $date); $DD = $dateparts [0]; $MM = $dateparts [1]; $YYYY = $dateparts [2]; output ($MM, $DD, $YYYY); } else #contingency plan { print "\nyour entry is not in a format recognized by this script.\ +n"; print "please use only the following:\nApr 8 1984, Apr 08 84, 4/8/ +84, 04/08/84, 08 Apr 1984, or 'q' to quit.\n\n"; } #output subroutine #take in $MM $DD $YYYY, parse $MM with %months #and print "fullmonth day, year" to STDOUT sub output { my @outputdates = @_; my %months = ( jan => "January", feb => "February", mar => "March", apr => "April", may => "May", jun => "June", jul => "July", aug => "August", sep => "September", oct => "October", nov => "November", dec => "December", 1 => "January", 2 => "February", 3 => "March", 4 => "April", 5 => "May", 6 => "June", 7 => "July", 8 => "August", 9 => "September", 10 => "October", 11 => "November", 12 => "December", '01' => "January", '02' => "February", '03' => "March", '04' => "April", '05' => "May", '06' => "June", '07' => "July", '08' => "August", '09' => "September" ); print "$months{$outputdates[0]} $outputdates[1], $outputdates[2]\n +\n"; } }

Replies are listed 'Best First'.
Re: Final date conversion happiness script
by Zaxo (Archbishop) on Jan 13, 2004 at 00:41 UTC

    Good work, it's been a pleasure seeing you develop this. Now the assignment is in, I'll show you how I'd do it. The basic principle I'll use is to get the input into some fixed order as soon as possible.

    You could simplify it a bit by making split more ambitious, splitting on either whitespace or '/', my @dateparts = split '/|\s+', $date; Now decide if we have a named month second (08 Apr 1984), and reorder if so, @dateparts[0,1] = @dateparts[1,0] if $dateparts[1] =~ /^[A-Z]/; Lets collect the year logic in one place,

    $dateparts[2] += ($dateparts[2] > 99) ? 0 : ($dateparts[2] > 9) ? 1900 : 2000;
    Now sanity check the month exists $months{$dateparts[0]} or die 'Bad Month!'; It would be good to check the day against the number of days in the month - an exercise for you. Don't forget leap years ;-) We now have put the dateparts array in a set format and we can do what we like with it,
    print $months{$dateparts[0]}, ' ', 0+$dateparts[1], ', ', dateparts[2];
    For real work, I'd shove the numerical forms into a localtime-like array and call POSIX::strftime() on it for string formatting.

    After Compline,
    Zaxo

Re: Final date conversion happiness script
by ysth (Canon) on Jan 12, 2004 at 23:51 UTC
    I think the biggest thing I would have approached differently (other than using a CPAN module to do date work) is to divide the problem into parts and have code to perform each part.
    # input: chomp(my $date = <>); # parse, return numeric year,month,day or empty list on error if (my ($y,$m,$d) = split_date($date)) { # print out in Month dd yy format print "Date is: ", format_date($y,$m,$d), "\n"; } else { print "invalid date: $date; please try again\n"; }
    It doesn't make as much sense to have part of your conversion done in the main code and part done in a subroutine. Ditto for printing an error message in the main code or the successful formatting in a sub.

    Update: make comment agree with code re: order of returned values; said parsing but meant conversion

      I think the biggest thing I would have approached differently (other than using a CPAN module to do date work)

      I think we are heading into the part of the class where we can do just that...gotta get that foundation stuff outta the way tho

      is to divide the problem into parts and have code to perform each part.

      Yea - I recognize that I did this script in a sort of "conversant" style (or some other word that makes more sense) in order to help me understand how to make it work. I'm just now starting to get more comfortable with dividing things into discrete functions, and subroutines. Now I just have to put that more into practice.

Re: Final date conversion happiness script
by Roger (Parson) on Jan 13, 2004 at 00:59 UTC
    OK, here's another variant without using modules.

    #!/usr/local/bin/perl -w use strict; use warnings; # $time = str2time($date); my @date = ( 'Apr 8 1984', 'apr 08 84', '4/8/84', '04/08/84', '08 Apr 1984', 'April 8 1984', '8 april 1984'); my @months = qw/ January February March April May June July August September October November December /; # build month name lookup hash my %months = map { ($months[$_] => $_, substr($months[$_], 0, 3) => $_) } 0..9; foreach (@date) { my ($year, $month, $day) = parse_date($_); printf "%s %d %d\n", $months[$month], $day, $year; } sub parse_date { my $date_str = shift; my ($year, $month, $day); if ($date_str =~ /\//) { # validate date string die "Invalid date format" if $date_str !~ /[\/0..9]/; # in mm/dd/[cc]yy format ($month, $day, $year) = split /\//, $date_str; # normalization $year = normalize_year($year); $month -= 1; # zero based month, get rid of leading 0 $day += 0; # get rid of leading 0 } else { # in alpha month, numeric day format ($month, $day, $year) = split /\s+/, $date_str; # normalization $year = normalize_year($year); ($month, $day) = ($day, $month) if $month =~ /\d/; $day += 0; $month = ucfirst $month; die "Invalid month" if !exists $months{$month}; $month = $months{$month}; } return ($year, $month, $day); } sub normalize_year { my $year = shift; return length($year) == 2 ? $year > 30 ? $year + 1900 : $year + 2000 : $year; }
    And the output -
    April 8 1984 April 8 1984 April 8 1984 April 8 1984 April 8 1984 April 8 1984 April 8 1984
Re: Final date conversion happiness script
by Roger (Parson) on Jan 13, 2004 at 00:17 UTC
    #!/usr/local/bin/perl -w use strict; use warnings; use Date::Parse; # date strings in various format my @date = ( 'Apr 8 1984', 'Apr 08 84', '4/8/84', '04/08/84', '08 Apr 1984' ); my @months = qw/ January February March April May June July August September October November December /; foreach (@date) { my ($day,$month,$year) = (strptime $_)[3,4,5]; printf "%s %d %d\n", $months[$month], $day, $year + 1900; }

    And the output -
    April 8 1984 April 8 1984 April 8 1984 April 8 1984 April 8 1984
      Ah, yes...I am really looking forward to being allowed to use modules in this class!!! Thanks for the code, btw.