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

Dear wise perlmonks, I am getting desperate with this subroutine I am writing: It takes an array containing hours minutes seconds, then it compares the current time to that time and calculates how long it still takes in seconds till that time has arrived. Unfortunately I have not been able to calculate hours correctly because that keeps being wrong and my brain has melted away:(. Or does anyone know a module that could do this for me? Cheers and thank you very much:) Rob

sub timeleft { $hour = (localtime(time))[2]; $minutes = (localtime(time))[1]; $seconds = (localtime(time))[0]; #print "$minutes"; print " Time now is $hour:$minutes:$seconds "; print "Time remaining until $_[0]:$_[1]:$_[2] = "; if ($_[2] == $seconds) {$seconds = 0} if ($_[2] > $seconds) {$seconds = $_[2] - $seconds;} #correct if ($_[2] < $seconds) {$seconds = (60- $seconds) + $_[2]; #correct $spareminute = 1;} #minuteklopt $minutes -= $spareminute; #print "$minutes"; if ($_ [1] == $minutes ) {$minutes = 0; } if ($_ [1] > $minutes ) {$minutes = $_[1] - $minutes; } #correct if ($_ [1] < $minutes ) {$minutes = (60 - $minutes) + $_[1]; #correct $sparehour + 1; } $minuteprint = $minutes; $minutes *= 60; $hour -= $sparehour; if ($_[0] == $hour) {$hour =0;} if ($_[0] > $hour) {$hour =$_[0] - $hour;} if ($_[0] < $hour) {$hour = (24 - $hour) + $_[0] ;} $hourprint = $hour; $hour *= 60; $hour *= 60; print "$hourprint: $minuteprint: $seconds "; $baas = $hour + $minute + $seconds; #$baas = ($hour * 60 * 60) + ($minutes * 60) + $seconds; return $baas }

Replies are listed 'Best First'.
Re: Timeleft subroutine
by kennethk (Abbot) on Aug 29, 2012 at 19:42 UTC
    When I have to do math, I use DateTime, which includes subtraction. If you want to do the math yourself, it is likely easier to keep it in epoch format, and then only convert for display.

    With regards to your code, $sparehour + 1; is a no-op; you probably meant $sparehour = 1;. This would have been flagged by warnings -- see Use strict warnings and diagnostics or die.

    Also note that, since you are not using lexical variables (declared with my) your values for $spareminute and $sparehour will leak between calls. You can fix this by just replacing the assignments w/ modifications of the original variables, a la: $minutes -= 1; and $hour -= 1;.

    Finally, you have a one-off typo on your return value: you have $minute in place of $minutes in my $baas = $hour + $minute + $seconds;. This would have been caught by strict, and looks like a bug that happened during debugging.


    #11929 First ask yourself `How would I do this without a computer?' Then have the computer do it the same way.

Re: Timeleft subroutine
by philiprbrenan (Monk) on Aug 29, 2012 at 19:50 UTC

    May I suggest that you convert both times to seconds before you try to compare the times as this makes the comparison much easier?

    use feature ":5.14"; use warnings FATAL => qw(all); use strict; sub timeLeft {my ($Hours, $Minutes, $Seconds) = @_; my ($hours, $minutes, $seconds) = @{[localtime(time)]}[2, 1, 0]; my $currentTimeInSeconds = $hours*3600+$minutes*60+$seconds; my $targetTimeInSeconds = $Hours*3600+$Minutes*60+$Seconds; $targetTimeInSeconds > $currentTimeInSeconds ? $targetTimeInSeconds +- $currentTimeInSeconds : undef } say timeLeft(qw(20 50 00));

    Produces:

    158
    
Re: Timeleft subroutine
by dasgar (Priest) on Aug 29, 2012 at 20:34 UTC

    For data and time calculations, I've found that Date::Calc has been able to meet my needs. In your case, Delta_DHMS or Delta_YMDHMS might the functions from Date::Calc would probably be what you're looking for.

      Thanks everyone:), I downloaded Date::Calc, I have been looking for one like that, I could use philip brenan's version of timeleft very well.

      Date::Calc is almost always overkill, but I also always use it for all date/time work because it always seems to have the functions that I need.

      Bill
Re: Timeleft subroutine
by Kenosis (Priest) on Aug 29, 2012 at 21:10 UTC

    Here's another option:

    use Modern::Perl; say timeLeft( 14, 30, 00 ); sub timeLeft { my ( @time, $nowScs, $thenScs ) = ( localtime(time) )[ 2, 1, 0 ]; for ( 0 .. 2 ) { $nowScs += $time[ 2 - $_ ] * 60**$_; $thenScs += $_[ 2 - $_ ] * 60**$_; } $thenScs - $nowScs; }

    Output:

    1203

    Interesting to script, but not too readable...

    Update: Added @time.

Re: Timeleft subroutine
by Marshall (Canon) on Aug 30, 2012 at 13:01 UTC
    For the interface, I would be thinking about seconds as a return value from timeleft(). Perhaps like something below or maybe not? Your idea has some problems, like what happens at midnight?

    In general I would design the subs to work in seconds from the "epoch" date/time and have some conversion routines - Yes, there are exceptions, but I don't know enough about your app.

    There are also some Perl modules: Date::Time and Date::Calc that work well.

    #!/usr/bin/perl -w use strict; use Time::Local; # a CORE function (no install needed) # $time = timelocal($sec,$min,$hour,$mday,$mon,$year); print "current time is: ".localtime(),"\n"; print "seconds left until 8/30/2012 5:32 AM\n"; print seconds_left(2012,8,30,5,32,0), "\n"; sub seconds_left { my ($year, $month, $dayOfMonth, $hour, $minutes, $seconds) = @_; my $expiration_seconds = timelocal($seconds, $minutes, $hour, $dayOfMonth, $month-1, $year); my $current_seconds = time(); return ($expiration_seconds - $current_seconds); } __END__ current time is: Thu Aug 30 05:31:30 2012 seconds left until 8/30/2012 5:32 AM 30
    This whole time/date stuff can get complicated. I mean like what happens when there is a leap year? What happens when there is a time switch from "normal time" to "daylight savings time"? I guess this year (2012) we had a "leap second".

    In general, I would advise that your program work with GMT or what is now called UTC time - yes there is slight difference between these terms - but if you know enough to argue, then you already understand it well enough that we don't have to. Use UTC/GMT internally and convert to "local time" as needed.