Beefy Boxes and Bandwidth Generously Provided by pair Networks
No such thing as a small change
 
PerlMonks  

Is there an easier way?

by Sherlock (Deacon)
on May 01, 2001 at 02:41 UTC ( [id://76799]=perlquestion: print w/replies, xml ) Need Help??

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

I just got done writing a method to compare two dates. The purpose of the function is to return 1 if the first date is greater than the second (further in the future) and 0 otherwise. The two dates are passed to a subroutine in the following format:
[M]M/[D]D/YYYY
The elements within [] are optional characters. For example, 1/1/2001 would be legal, as would 12/12/2001, but not, 12/12/01. The year requires all four digits.

This is the function I came up with, but I just feel that there must be an easier way to accomplish this task.
sub isGreater { my $currDate = $_[0]; my $oldDate = $_[1]; my $currMonth = substr($currDate, 0, index($currDate, "\/")); $currDate = substr($currDate, index($currDate, "\/")+1); my $oldMonth = substr($oldDate, 0, index($oldDate, "\/")); $oldDate = substr($oldDate, index($oldDate, "\/")+1); my $currDay = substr($currDate, 0, index($currDate, "\/")); $currDate = substr($currDate, index($currDate, "\/")+1); my $oldDay = substr($oldDate, 0, index($oldDate, "\/")); $oldDate = substr($oldDate, index($oldDate, "\/")+1); my $currYear = $currDate; my $oldYear = $oldDate; if ( $currYear > $oldYear ) { return 1; } elsif ( $currMonth > $oldMonth ) { return 1; } elsif ( $currDay > $oldDay ) { return 1; } return 0; }
As you can see, I'm simply grabbing all the characters up to a "/" and then I go back and cut off the front of the date up to (and including that "/"). Simply put, I grab the month from $oldDate, store it into $oldMonth, and then modify $oldDate to go from MM/DD/YYYY to DD/YYYY. For example, if $oldDate contained 1/12/2001, $oldMonth would contain 1 and $oldDate would contain 12/2001.

This method works fine (as far as I can tell - I'm sure it could use more testing), but is there an easier way to pull this off? I'm not overly familiar with Perl and I wonder if there is a built in function to do something quite similar to what I'm doing here.

Thanks,
- Sherlock

Replies are listed 'Best First'.
Re: Is there an easier way?
by lhoward (Vicar) on May 01, 2001 at 03:25 UTC
    Personally I prefer to convert all dates to an "offset from an epoch" and do all operations on them that way. Only converting back to "display format" when I'm done.

    Keeping in line with your basic logic I'd probably parse as follows:

    sub isGreater{ my $currDate = shift; my $oldDate = shift; my ($currMonth,$currDay,$currYear) = split /\//,$currDate; my ($oldMonth,$oldDay,$oldYear) = split /\//,$oldDate; return $currYear <=> $oldYear or $currMonth <=> $oldMonth or $currDa +y <=> $oldDay; }
Re: Is there an easier way?
by Beatnik (Parson) on May 01, 2001 at 03:09 UTC
    /me points to Date::Calc's Delta_Days($year1,$month1,$day1, $year2,$month2,$day2); function

    Greetz
    Beatnik
    ... Quidquid perl dictum sit, altum viditur.
      I found what you pointed me at and it looks good, but the part of the code I was more concerned with was the string parsing portion in the beginning. I wasn't too worried about the date comparison stuff at the end - although, as you pointed out, that could definately be made simpler, too.

      Thanks,
      - Sherlock
I'd use something like this...
by cLive ;-) (Prior) on May 01, 2001 at 03:55 UTC
    #!/usr/bin/perl use strict; my $first_date = '12/14/2000'; my $second_date = '04/18/2001'; print isGreatest($first_date,$second_date); sub isGreatest { # strip data into arrays my @date_1 = split /\//, $_[0]; my @date_2 = split /\//, $_[1]; # compare ordered date strings my $result = sprintf("%04d%02d%02d",@date_1[2,0,1]) <=> sprintf("%04d%02d%02d",@date_2[2,0,1]); # $result is -1, 0 or 1, so amend for output # to return only 0 or 1; return ($result + $result**2)/2; }

    (s)printf is your ally, but use it wisely. Read the (s)printf tutorial elsewhere in the monastry for a deeper insight.

    cLive ;-)

    Update: interesting thought chipmunk, never thought of that. d'oh!

      # compare ordered date strings my $result = sprintf("%04d%02d%02d",@date_1[2,0,1]) <=> sprintf("%04d%02d%02d",@date_2[2,0,1]); # $result is -1, 0 or 1, so amend for output # to return only 0 or 1; return ($result + $result**2)/2;
      Hmm... That's a neat trick with exponentiation to convert -1 to 0, but wouldn't this be much simpler?
      # compare ordered date strings return sprintf("%04d%02d%02d",@date_1[2,0,1]) > sprintf("%04d%02d%02d",@date_2[2,0,1]);
Re: Is there an easier way?
by Starky (Chaplain) on May 01, 2001 at 09:31 UTC
    While your earlier comments indicate that you're more interested in parsing than the mechanics of date comparison (and for that the regex is just what you're looking for), it's worth mentioning that converting the dates to julian dates (the total number of days or seconds or whatever since some date a long time ago) makes comparisons easy and intuitive. And, based on the comments of the author of Date::Calc, if you need to compare a very large number of dates, you would probably see some good performance gains from using Time::JulianDay over Date::Calc. Here's how you'd do it:
    #!/usr/bin/perl
    use Time::JulianDay;
    my $date0 = '5/2/2000';
    my $date1 = '12/12/2001';
    print &check_dates($date0,$date1) ? "$date0 > $date1\n" : "$date0 < $date1\n";
    sub check_dates {
            my ($date0,$date1) = @_;
            if ($date0 =~ m|\d{1,2}/\d{1,2}/\d{4}| && $date1 =~ m|\d{1,2}/\d{1,2}/\d{4}|) {
                    $date0 =~ m|(\d{1,2})/(\d{1,2})/(\d{4})|;
                    my $jd0 = julian_day($3,$2,$1);
                    $date1 =~ m|(\d{1,2})/(\d{1,2})/(\d{4})|;
                    my $jd1 = julian_day($3,$2,$1);
                    return $jd0 > $jd1 ? 1 : 0;
            }
    }
    
    Hope this helps!
Re: Is there an easier way?
by ColonelPanic (Friar) on May 01, 2001 at 03:33 UTC
    A regex would probably be preferred:
    $date =~ m|(\d{1,2})/(\d{1,2})/(\d{4})|; ($month,$day,$year) = ($1,$2,$3);
    This code is untested.

    Update: Doh! A very stupid error was made on my part. See Merlyn's helpful comment below.

    When's the last time you used duct tape on a duct? --Larry Wall
      $date =~ m|(\d{1,2})/(\d{1,2})/(\d{4})|; ($month,$day,$year) = ($1,$2,$3);
      Augh. Use of $1 there without checking for the success of the match. Good way to uncover a bug or a security hole. Never. Don't.

      At a bare minimum, add "or die" to the first line. Then I'll be happy.

      -- Randal L. Schwartz, Perl hacker

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://76799]
Approved by root
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others admiring the Monastery: (3)
As of 2024-04-19 19:32 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found