Beefy Boxes and Bandwidth Generously Provided by pair Networks
Problems? Is your data what you think it is?
 
PerlMonks  

perl basic count days between two dates

by scripter87 (Novice)
on Oct 02, 2013 at 19:20 UTC ( [id://1056668]=perlquestion: print w/replies, xml ) Need Help??

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

Hi guys I am very new to perl and programming in general. I have a question about getting the amount of days between two dates. I do not want to use any modules- (just A LOT of code). Here is my code so far any advice for me.? I cant get my head around trying to get the right number of days in different years- as you will see in the code

#!/usr/bin/perl use strict; use warnings; print "please enter day of birth: "; my $d = <STDIN>; print "please enter month of birth: "; my $m = <STDIN>; print "please enter year of birth: "; my $y = <STDIN>; chomp($d, $m,$y);#strip newlines print "please enter current day: "; my $cd = <STDIN>; print "please enter current month: "; my $cm = <STDIN>; print "please enter current year: "; my $cy = <STDIN>; chomp($cd, $cm,$cy); my$days=0; if ($y>$cy){ warn "Birth Year Cannot be after the current year."; } elsif ($y==$cy){ $days= $days + NumberOfDaysBetween($d,$cd ,$m,$cm ); print "$days"; } else { my$years= ($cy-$y) * 365; $days= $days + NumberOfDaysBetween($d,31,$m,12) + NumberOfDaysBetween( +1,$cd,1,$cm) + $years; print "war war$days"; } sub NumberOfDaysBetween { my$daysBetween; my$startDay=shift; my$endDay=shift; my$sMon=shift; my$eMon=shift; if ($y <= $cy){ if ($sMon > $eMon || $sMon == $eMon&& $startDay > $endDay){ print "invalid input"; } elsif ($sMon == $eMon && $startDay<= $endDay){ $daysBetween = $endDay- $startDay; } else { $daysBetween =((&NumberOfDaysInMonth($sMon))-($startDay-1)) + +$endDay + &cumulativeDaysInMonths($sMon,$eMon); } } return $daysBetween; } sub NumberOfDaysInMonth { my $mon=shift; my$numOfDaysInMonth; if ($mon == 2) { $numOfDaysInMonth = 28; } elsif ($mon == 9|| $mon == 4|| $mon == 6|| $mon == 11) { $numOfDaysInMonth = 30; } else { $numOfDaysInMonth = 31; } return $numOfDaysInMonth; } sub cumulativeDaysInMonths { my $sMonth=shift; my $eMonth=shift; my$daysRange=0; $eMonth--; while ($eMonth>$sMonth){ $daysRange=$daysRange + NumberOfDaysInMonth($eMonth); $eMonth-- } return$daysRange; }

Replies are listed 'Best First'.
Re: perl basic count days between two dates
by marinersk (Priest) on Oct 02, 2013 at 19:43 UTC
    There are modules that will do this for you. I acknowledge you indicate you do not wish to use a module.

    So the first step is to figure out the algorithm you wish to use. Grab pencil and paper (or a whiteboard for you youngins), and start trying to figure out how you would do the math by hand.

    Then try to write code that would do the same thing you did by hand.

    The number of things you are likely to overlook or get wrong are controllable in this example, but high enough to reward your use of a Module where someone else (or several someone elses) took the time to iron out the bugs for you.

    But if you just gotta write the code yourself, Step 1 is to figure out how to do it by hand.

    P.S. I would suggest researching Leap Year Rules. Assuming your needs do not extend to earlier than Friday, October 15, 1582 in Catholic countries, 1698 or 1752 in principalities headed by Protestants, or 1918 in Russia, a shortcut to use only the modern computation should suffice.

    In that computation, the base assumption is that every year has 365 days unless it is declared to be a leap year, in which case it has 366.

    More specifically, February would have 29 days rather than the hard-coded 28 you presume in your code.

    Research the Leap Year Rules, and you should be able to devise a subroutine which can determine if any given year is a leap year.

    From there, you should have all the tools you need to compute number of days between dates, given sufficient forethought to the construction of either your computational algorithms, or your foreach loops -- or some combination thereof.

    Update: Okay, fine, here's one example of the many things a module could do for you that you would have to grind through to do by hand:

      There's always more than one way to do it .. I like to switch things around and test for the least likely outcome first .. check to see if the year is divisible by 400, then 100, and finally 4. I think it also makes the code a little more transparent, but everyone's got their own style ..

      #!/usr/bin/perl use strict; use warnings; my %testData = ( 1900 => 0, 1904 => 1, 1972 => 1, 1973 => 0, 1999 => 0, 2000 => 1, 2001 => 0, 2004 => 1 ); { foreach my $this ( keys %testData ) { if ( $testData{$this} == leapYear($this) ) { print "Correct - $this year " . ( $testData{$this} ? "is" : "is not" ) . " a leap year.\n"; } else { print "Error with $this entry ..\n"; } } } sub leapYear { my $year = shift; if ( $year % 400 ) { if ( $year % 100 ) { # 3. Simple case: it's a leap year if it's divisible by 4 return ( $year % 4 ) ? 0 : 1; } else { # 2. If it's divisible by 100: it's not a leap year. return 0; } } else { # 1. If it's divisible by 400: it's a leap year. return 1; } }

      I've numbered the comments to make it a little clearer which result might be returned first, although this could be made even clearer by reversing the meaning of the conditionals so that the routine looks like this:

      sub leapYear { my $year = shift; if ( $year % 400 == 0 ) { # 1. If it's divisible by 400: it's a leap year. return 1; } else { if ( $year % 100 == 0 ) { # 2. If it's divisible by 100: it's not a leap year. return 0; } else { # 3. Simple case: it's a leap year if it's divisible by 4 return ( $year % 4 ) ? 0 : 1; } } }

      Alex / talexb / Toronto

      Thanks PJ. We owe you so much. Groklaw -- RIP -- 2003 to 2013.

Re: perl basic count days between two dates
by davido (Cardinal) on Oct 02, 2013 at 22:08 UTC

    • ...perl basic..."
    • ...I am very new to perl...
    • ...and programming in general.
    • ...question about getting the amount of days between two dates.
    • ...any advice for me.?

    We were all new to Perl at some point, and all new to programming in general. Nothing wrong with that. But this is not a "basic" problem. However, Perl does provide tools to help you avoid becoming an expert in date calculations. My advice: If you're not using the power of Perl's modules (core and CPAN), you're not programming Perl. You're just using its syntax and interpreter.

    Your technique is neglecting leap years, isn't it?

    Here is a CPANy version:

    use strict; use warnings; use DateTime; use IO::Prompt::Tiny qw( prompt ); # Get a DateTime object for birthday and now based on user input. my( $bday, $now ) = map { DateTime->new( prompt_date( $_ ) ) } qw( Birth Today's ); die "Birth date must be in the past.\n" unless $bday < $now; # Perform the math. print "\nYou are ", $bday->delta_days($now)->in_units('days'), " days +old.\n"; # Prompting: pass date entry type, receive a day=>__, month=>__, year= +>__ hash. sub prompt_date { print shift, " date entry:\n"; return map { $_ => prompt "\tPlease enter $_:" } qw( day month year +); }

    A sample run:

    Birth date entry: Please enter day: 27 Please enter month: 9 Please enter year: 1954 Today's date entry: Please enter day: 02 Please enter month: 10 Please enter year: 2013 You are 21555 days old.

    (I used the birth date of a famous individual in the Perl community.)

    Date calculations are very hard to get right. At minimum we have to deal with leap years. There could be other challenges that I'm not aware of, and since I don't want to become an expert in nuances of the Gregorian Calendar, I offload as much of that burden as possible to the DateTime module.

    Prompting with a message isn't all that hard, but IO::Prompt::Tiny seems more convenient than a bunch of print statements, <STDIN> invocations, and chomp, so I used that module too.

    Once we've handled the prompting and the date manipulation, there's not much left to do except a quick sanity check, and printing the result of the calculation. Keep in mind that there are countless problem domains in the universe. As a programmer you will be asked to become an expert on some of them. But it would be impossible to become an expert on all of them. Using well-tested modules that solve problems such that you don't need to become an expert on a peripheral topic will enable you to focus more on becoming a great programmer, and less on becoming a great date calculator, genome researcher, and so on. There's no shame in using available tools to make your life as a programmer easier. In fact, it's smart; you become more effective, and your code becomes more robust.

    A little more terse:

    use strict; use warnings; use DateTime; use IO::Prompt::Tiny qw( prompt ); # Get a DateTime object for birthday and now based on user input. my( $bday, $now ) = map { print "$_ date entry:\n"; DateTime->new( map { $_ => prompt "\tPlease enter $_:" } qw(day mont +h year) ) } qw(Birth Today's); die "Birth date must be in the past.\n" unless $bday < $now; # Perform the math. print "\nYou are ", $bday->delta_days($now)->in_units('days'), " days +old.\n";

    Dave

Re: perl basic count days between two dates
by dasgar (Priest) on Oct 02, 2013 at 22:34 UTC

    For this kind of task, I personally would just use Date::Calc.

    If you really want to write your own code, you might want to check out Dave Rolsky's Date, Times, Perl, and You presentation from YAPC::NA 2012. He discusses a lot of the challenges that one needs to keep in mind when dealing with dates and times (time zones, daylight savings time, leap seconds, etc.). If you're just doing this as a learning exercise, you might be willing to ignore some of the issues that Dave discusses in his presentation. However, if you're trying to create production code, you might want to consider leveraging the large amount of work others have put into the existing date and time modules.

Re: perl basic count days between two dates
by hdb (Monsignor) on Oct 02, 2013 at 20:09 UTC

    Even if you do not want to use a module, pick one that does the job and compare your code against the module's. There are so many things one can do wrong with date math that it is not worth writing one's own code.

      Thanks guys I am aware of the leap year but I did not want to further confuse myself so for now I am just trying to ease myself in to understanding. I added this sub routine but it only keeps 365 so even if put a date of 2 years e.g. 1-jan-2000 to 1-jan-2002 it still only gives 365 as the number of days.

      sub isFullYear{ my$startDay=shift; my$endDay=shift; my$sMon=shift; my$eMon=shift; my$fullYear; if ( NumberOfDaysBetween($startDay,$endDay ,$endDay,$eMon) == 365){ $fullYear=365; } return $fullYear; }

      this is the call for the sub in the main

      my$years= ($cy-$y) * isFullYear($d,$cd ,$m,$cm); $days= $days + NumberOfDaysBetween($d,31,$m,12) + NumberOfDaysBetween( +1,$cd,1,$cm) + $years; print "$days";

        Ok, this code:

        my$fullYear; if ( NumberOfDaysBetween($startDay,$endDay ,$endDay,$eMon) == 365){ $fullYear=365; } return $fullYear; }

        What happens when NumberOfDaysBetween() is not 365? $fullYear is undefined. Since you have warnings enabled you should see a corresponding warning message. It will still evaluate to 0, but initializing the variable "my $fullYear=0;" is better.

        Also, why call NumberOfDaysBetween() with $endDay twice as parameter? Shouldn't there be a $sMon instead?

        But if you want to program yourself you surely would prefer finding the bugs yourself instead of us just telling you what to do. So here is how:

        It is very fortunate that you are already using subroutines. You first want to make sure that your subroutines are correct. Before you can check what is wrong with the subroutine FullYear you have to be sure that NumberOfDaysBetween really does what you expect it to do. So lets do that:

        After the line "use warnings;" at the beginning of your program add the following lines:

        print "1,1,1,1 == ",NumberOfDaysBetween(1,1,1,1),"\n"; print "1,1,5,1 == ",NumberOfDaysBetween(1,1,5,1),"\n"; print "1,1,1,2 == ",NumberOfDaysBetween(1,1,1,2),"\n"; print "8,3,3,8 == ",NumberOfDaysBetween(8,3,3,8),"\n"; exit(0);

        Now call your script. Is the output as you expect it? If yes, lets be sure and put in some other values and check these too. If you are finally convinced that NumberOfDaysBetween is correct, then do the same with isFullYear(). And so on, until there is nothing left to test

        But what happens when you find unexpected results? Then you should add print-statements in that subroutine where you print out the values of variables. Or simply check which path the program takes in an if-clause. For example:

        print "daysBetween=",$daysBetween,"\n";

        directly after that variable was set would tell you if the program executed the line before and what the result was. Start the program and check if the output you are getting is as you expected. Put in as many print statements as you need, the only downside is that you have to delete them again after you found the bugs

        That is everything you need to know at the moment. Should you later have more complex data structures like arrays and hashes I recommend you take a look at the module Data::Dumper to print out those. Also you should know that there is a debugger built into perl. Instead of putting in all those print statements you can step through the program line by line and print out variables while the programm is running without having to change one line of code. But for the moment print-statements will do.

Re: perl basic count days between two dates
by mtmcc (Hermit) on Oct 02, 2013 at 21:42 UTC
    It seems to me that this is a thinking problem more than a perl problem.

    You have three options:

    1. Use CPAN modules (see above).

    2. Learn from CPAN modules to solve your problem (see above).

    3. Think about the solution, and then implement it in perl.

    There is an answer, and someone has already figured it out. But this is a thinking problem, not really a coding problem. I wish you well.

Re: perl basic count days between two dates
by Anonymous Monk on Oct 02, 2013 at 23:14 UTC
    You already have Time::Local installed. It is a core module.

      And that is why its use is encouraged by the FAQ.

Re: perl basic count days between two dates
by Anonymous Monk on Oct 03, 2013 at 09:34 UTC

    I feel old...

    use strict; use warnings; my $n = 0; my @YMDAY = map $n += $_, qw<0 0 31 28 31 30 31 30 31 31 30 31 30 31>; my $today = days(2013, 10, 3); my $bday = days(1970, 3, 9); printf "You are %d days old.\n", $today - $bday; sub days { my ($year, $month, $day) = @_; my $days = $year * 365 + $YMDAY[$month] + $day - 1; $days-- if $month <= 2 and !($year % 4) && $year % 100 || !($year % 400); $year++, $days-- if $year < 0; $days + int($year / 4) - int($year / 100) + int($year / 400); } __END__ You are 15914 days old.
Re: perl basic count days between two dates ( strptime %Y-%j )
by Anonymous Monk on Oct 02, 2013 at 23:50 UTC

Log In?
Username:
Password:

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

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

    No recent polls found