Beefy Boxes and Bandwidth Generously Provided by pair Networks
"be consistent"
 
PerlMonks  

Converting seconds to DD:HH:MM:SS

by McDarren (Abbot)
on Oct 08, 2005 at 02:48 UTC ( [id://498353]=perlquestion: print w/replies, xml ) Need Help??

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

Hi all,

Recently I found myself having to convert an integer value (representing a number of seconds) into DD:HH:MM:SS

After messing around with time, localtime and a few of the Date/Time manipulation modules for a while - I couldn't see any quick and easy way to do it. And by "quick and easy" I mean a simple one line call to a function or method.

So I asked a work colleague of mine (a very experienced Perl programmer) how he would do it, and he quickly came up with the following options:
#!/usr/bin/perl -w use strict; use Date::Calc qw(Delta_DHMS); my ($d, $h, $m, $s); my @secs = (1000,10000,100000,1000000,86400,86399,3600,3599,0,-1,31394 +2941); for (@secs) { ($d, $h, $m, $s) = &sec_to_dhms_silly($_); print "$d days, $h hours, $m minutes, $s seconds\n"; ($d, $h, $m, $s) = &sec_to_dhms_sensible($_); print "$d days, $h hours, $m minutes, $s seconds\n"; } sub sec_to_dhms_silly { my @epoch = (1970, 1, 1, 0,0,0); my @mytime = gmtime(shift); splice (@mytime, 6); # discard fields after 6th element (yea +r) $mytime[5] += 1900; # gmtime returns year - 1900 $mytime[4]++; # gmtime has zero based month @mytime = reverse @mytime; # Delta_DHMS expects args in reverse to + gmtime return Delta_DHMS(@epoch, @mytime); } sub sec_to_dhms_sensible { shift; my ($d, $h, $m, $s); $s = $_ % 60; $_ = ($_ - $s) / 60; $m = $_ % 60; $_ = ($_ - $m) / 60; $h = $_ % 24; $_ = ($_ - $h) / 24; $d = $_; return ($d, $h, $m, $s); }
In the meantime, I came up with my own solution (and I'm sure I'll get beaten up bigtime for this - but hey, I already had the DB handle open - so why not make use of it? ;)
sub sec_to_ddmmss { my $t = shift; return $dbh->selectrow_array("SELECT sec_to_time($t);"); }
Whilst my option may not win in the efficiency stakes, I would certainly argue that it's much simpler.

And so my question is: Is there a simpler way than any of the above?
I guess what I'm looking for is a module that supports a method similar to the mySQL SEC_TO_TIME function - but it doesn't seem to exist?

Thanks,
Update:Just to clarify - the number of seconds represents "time remaining", and it's not relative to any particular point in time. So it's not a calculation - merely a straight conversion (if that makes sense ;)

--Darren :)

Replies are listed 'Best First'.
Re: Converting seconds to DD:HH:MM:SS
by Tanktalus (Canon) on Oct 08, 2005 at 03:13 UTC

    The _sensible approach seems the most sane way to me. That said, there are a few things to "fix".

    First - what are the &'s doing there? Experienced perl programmer? As I said before, seeing that & is an indicator of low experience, not high experience, to me. It's an indicator of perl4 programming, which, granted, is a long time programming in perl, but does not equate to high amounts of good experience in modern perls. Unless, of course, it were used in a goto, or magic argument passing, or overriding prototypes. (Of course, the use of prototypes at all can be suspect.)

    The first two options both take a parameter. Then ignore it. Your version uses your parameter. That's already a plus for your version.

    And the sensible version clobbers $_. Bad. Maybe he meant to say:

    local $_ = shift;
    I can buy that. Except that he's not really using $_ for anything that $t wouldn't work for as a lexical variable.

    Finally, the subtraction is a bit funny. You get a funny lookin' number for the -1 time (-1 day plus 23 hours plus 59 minutes plus 59 seconds - technically correct, but may not be what you want). Perhaps what he meant was "$_ = int($_ / 60)". In that case, here's a slightly revamped sensible function:

    sub sec_to_dhms_sensible2 { use integer; local $_ = shift; my ($d, $h, $m, $s); $s = $_ % 60; $_ /= 60; $m = $_ % 60; $_ /= 60; $h = $_ % 24; $_ /= 24; $d = $_; return ($d, $h, $m, $s); }
    I didn't fix everything, but it's a bit cleaner to look at. The only difference in output is that this gives a seconds of -1 rather than -1d, +23:59:59. Note that the use integer; is block-scoped (I confirmed this in testing) so that it doesn't affect anything else in the file - you can use floating point elsewhere.

      Your version shows the correct date/time for 2147483648(=2**31)seconds. Date::Calc version blows out w/ "out of range" error, and gam3's sec_to_ddmmss() shows "24855:20:45:52" (instead of "24855:03:14:08").

Re: Converting seconds to DD:HH:MM:SS
by gam3 (Curate) on Oct 08, 2005 at 04:37 UTC
    This solution is simular to your database solution:
    sub seconds_to_dhms { my $t = shift; return int($t / 86400), (gmtime($t))[2, 1, 0]; }
    but it should be a bit faster.

    UPDATE: renamed function as per sauoq (I had copied it directly from the question.)

    -- gam3
    A picture is worth a thousand words, but takes 200K.
      sub sec_to_ddmmss { my $t = shift; return int($t / 86400), (gmtime($t))[2, 1, 0]; }

      This is clever, but I'd like to see it commented because at first glance, it might appear erroneous. It should be better named too. I'd probably call it seconds_to_dhms(). Yours is broken in two respects: 1) you missed 'hh' for hours and 2) the doubled letters imply that your return values are formatted to two digits.

      -sauoq
      "My two cents aren't worth a dime.";
      
Re: Converting seconds to DD:HH:MM:SS
by jkeenan1 (Deacon) on Oct 08, 2005 at 03:12 UTC
    Recently I found myself having to convert an integer value (representing a number of seconds) into DD:HH:MM:SS
    Number of seconds ... since when?

    This question is relevant, because your colleague's solution uses Date::Calc::Delta_DHMS(), whose documentation describes it as follows:

    This function returns the difference in days, hours, minutes and seconds between the two given dates with times.
    If your problem really involves the difference between two points in time, then your colleague's solution is a valid hack ... though I suspect other monks could come up with a shorter solution. But if it does not involve a difference, then I don't see why localtime could not have been used.

    Another point, before trying to compare the efficiency of your colleague's solution vs your own (which certainly looks simple), write some tests to make sure each is doing what you intend.

    Jim Keenan

Re: Converting seconds to DD:HH:MM:SS
by gloryhack (Deacon) on Oct 08, 2005 at 05:26 UTC
    If you want to write as few lines as possible, you might try:
    #!/usr/bin/perl use strict; use warnings; use Date::Calc qw(Delta_DHMS Time_to_Date); my @secs = qw(1000 10000 100000 1000000 86400 86399 3600 3599 0 31394 +2941 -2941 -86399); for (@secs) { printf("%s%02d:%02d:%02d:%02d\n", $_ < 0 ? '-' : '', Delta_DHMS(Time +_to_Date(0), Time_to_Date(abs($_)))); }
    Quick 'n' easy, and negative "time remaining" will still print out nicely.

    Edit: Or, as a subroutine:

    sub time_remaining { my $input_seconds = shift; return sprintf(("%s%02d:%02d:%02d:%02d", $input_seconds < 0 ? '-' : +'', Delta_DHMS(Time_to_Date(0), Time_to_Date(abs($input_seconds)))); }
Re: Converting seconds to DD:HH:MM:SS
by pg (Canon) on Oct 08, 2005 at 03:22 UTC
    "In the meantime, I came up with my own solution (and I'm sure I'll get beaten up bigtime for this - but hey, I already had the DB handle open - so why not make use of it? ;)"

    Certainly not a bad idea with a database application. The only thing you need to be aware of is that, the time that the database gives is not always the same as what the OS has. This might easily confuse people.

Re: Converting seconds to DD:HH:MM:SS
by GrandFather (Saint) on Oct 08, 2005 at 06:27 UTC

    If you want compact:

    my @fields = ($secs % 60, ($secs /= 60) % 60, ($secs /= 60) % 24, int +($secs / 24)); print join ':', reverse @fields;

    However my Perl legalese is not up to assuring you that the calculation will be done in the correct order for all Perl implementations. :)


    Perl is Huffman encoded by design.
Re: Converting seconds to DD:HH:MM:SS
by snowhare (Friar) on Oct 08, 2005 at 14:56 UTC

    Never ignore the power of CPAN

    use Date::Manip; my $t = 234234324; my ($d, $h, $m, $s) = Delta_Format("+${t}seconds",'exact','%dh','%hv' +,'%mv','%sv');
Re: Converting seconds to DD:HH:MM:SS
by davidrw (Prior) on Oct 08, 2005 at 13:02 UTC
    I came up with this ~one-line approach using Date::Calc and got it working, then i noticed in the pod and in gloryhack's post the Delta_DHMS method ... and in OP. i just totally missed the obvious.. anyways, here's a slighty-long-but-still-quick solution for the sake of TMTOWTDI (note it also blows up on the last one):
    #!/usr/bin/perl use strict; use Date::Calc qw(Time_to_Date Delta_Days); while(<DATA>){ warn join ":", map { sprintf "%02d", $_ } do { my @d = Time_to_Date($_); ( Delta_Days((Time_to_Date(0))[0..2], @d[0..2]), @d[3..5] ) }; } __DATA__ 1000 10000 100000 1000000 86400 86399 3600 3599 0 -1 313942941
Re: Converting seconds to DD:HH:MM:SS
by parv (Parson) on Oct 08, 2005 at 07:17 UTC
    I am curious to know the output from your DB for 2147483648.
Re: Converting seconds to DD:HH:MM:SS
by McDarren (Abbot) on Oct 10, 2005 at 01:29 UTC
    Many thanks for the replies and suggestions.

    As an exercise and a follow-up, I decided to throw them all together and see how they stacked up.

    (I should point out that this is the first time I've used Benchmark, and also the first time I've attempted to write test code such as this, so my methodology may be a bit off. Also, the only changes I made to the other Monks code snippets was to make them into a subroutine if they weren't already.)

    Here is the code I used: Which gave the following results:
    %contenders = ( 'cw_sensible' => { '86399' => '0 0 0 0', '86400' => '0 0 0 0', '1' => '0 0 0 0', '2147483648' => '0 0 0 0', '0' => '0 0 0 0', '-2147483648' => '0 0 0 0', '-1' => '0 0 0 0', 'code' => sub { "DUMMY" } }, 'snowhare' => { '86399' => '0 23 59 59', '86400' => '1 0 0 0', '1' => '0 0 0 1', '2147483648' => '24855 3 14 8', '0' => '0 0 0 0', '-2147483648' => '-24855 -3 -14 -8', '-1' => '0 0 0 -1', 'code' => sub { "DUMMY" } }, 'gam3' => { '86399' => '0 23 59 59', '86400' => '1 0 0 0', '1' => '0 0 0 1', '2147483648' => '24855 20 45 52', '0' => '0 0 0 0', '-2147483648' => '-24855 20 45 52', '-1' => '0 23 59 59', 'code' => sub { "DUMMY" } }, 'gloryhack' => { '86399' => '00:23:59:59', '86400' => '01:00:00:00', '1' => '00:00:00:01', '2147483648' => '', '0' => '00:00:00:00', '-2147483648' => '', '-1' => '-00:00:00:01', 'code' => sub { "DUMMY" } }, 'cw_silly' => { '86399' => '0 23 59 59', '86400' => '1 0 0 0', '1' => '0 0 0 1', '2147483648' => '-24855 -3 -14 -8', '0' => '0 0 0 0', '-2147483648' => '-24855 -3 -14 -8', '-1' => '0 0 0 -1', 'code' => sub { "DUMMY" } }, 'McDarren' => { '86399' => '23:59:59', '86400' => '24:00:00', '1' => '00:00:01', '2147483648' => '596523:14:08', '0' => '00:00:00', '-2147483648' => '-596523:14:08', '-1' => '-00:00:01', 'code' => sub { "DUMMY" } }, 'GrandFather' => { '86399' => '0:23:59:59', '86400' => '1:0:0:0', '1' => '0:0:0:1', '2147483648' => '24855:3:14:8', '0' => '0:0:0:0', '-2147483648' => '-24855:21:46:52', '-1' => '0:0:0:59', 'code' => sub { "DUMMY" } }, 'Tanktulas' => { '86399' => '0 23 59 59', '86400' => '1 0 0 0', '1' => '0 0 0 1', '2147483648' => '-24855 -3 -14 -8', '0' => '0 0 0 0', '-2147483648' => '-24855 -3 -14 -8', '-1' => '0 0 0 -1', 'code' => sub { "DUMMY" } }, 'davidrw' => { '86399' => '86399:86399:86399:86399', '86400' => '86400:86400:86400:86400', '1' => '01:01:01:01', '2147483648' => '', '0' => '00:00:00:00', '-2147483648' => '', '-1' => '', 'code' => sub { "DUMMY" } } ); Rate snowhare McDarren davidrw cw_sensible gloryhack c +w_silly GrandFather gam3 Tanktulas snowhare 510/s -- -83% -97% -98% -99% + -99% -99% -100% -100% McDarren 3040/s 497% -- -84% -86% -91% + -92% -95% -98% -98% davidrw 19472/s 3721% 540% -- -10% -43% + -47% -67% -85% -87% cw_sensible 21611/s 4141% 611% 11% -- -37% + -41% -63% -83% -86% gloryhack 34046/s 6581% 1020% 75% 58% -- + -7% -41% -74% -77% cw_silly 36645/s 7092% 1105% 88% 70% 8% + -- -37% -72% -76% GrandFather 58163/s 11315% 1813% 199% 169% 71% + 59% -- -55% -61% gam3 129947/s 25402% 4174% 567% 501% 282% + 255% 123% -- -14% Tanktulas 150334/s 29403% 4845% 672% 596% 342% + 310% 158% 16% --
    Comments

    Firstly, [id://snowhare]'s solution throws the following warning:
    Argument "exact" isn't numeric in numeric gt (>) at /usr/local/share/p +erl/5.8.4/Date/Manip.pm line 1985.
    I'm not sure what causes that, and I'm not inclined right now to go digging in the source of Date::Manip.

    Most of the others also threw uninitiallised value warnings when used with '0', but that's to be expected (of course, the DBI solution doesn't have this issue).

    Only two of the solutions (interestly, the two slowest) gave correct results for all values of @secs: [id://snowhare]'s and [id://McDarren|my own] (although mine only returns HH:MM:SS - no DD)

    A quick summary of each:
    • cw_sensible: Doesn't work at all (due to logic errors as pointed out by [id://Tanktalus])
    • [id://snowhare]: By far the slowest, but gives the correct results.
    • [id://gam3]: Quite quick, but fails on +/-(2^31).
    • [id://gloryhack]: Also fails on +/-(2^31). (This one didn't work at all at first - but then I changed the printf to sprintf and it did. Perhaps somebody can explain this to me?)
    • cw_silly: Failed on +(2^31).
    • [id://McDarren]: Works for all values, but one of the slowest.
    • [id://GrandFather]: Failed on -(2^31).
    • [id://Tanktalus]: By far the quickest, but fails on +(2^31).
    • [id://davidrw]: Doesn't seem to work at all.
    Cheers,
    --Darren :)

    Update:Adjusted <readmore> tags, as suggested by [id://GrandFather]

      (Initially i was somewhat surprised that Tanktalus's version did not result in the right value for 2^31. I think it may be for you might not have perl compiled w/ USE_64_BIT_INT.)

      A problem in the above benchmark is that not all the subroutines are returning the same type of result. I modified your above code ...

      • removed *_davidrw() from comparison as its result was quite odd;
      • removed *_snowhare() from comparison as it was the slowest (produced the same output as yours) ;
      • removed *_McDarren(), as i do not have mysql or equivalent DB installed currently;
      • edited cmpthese() somewhat;
      • made all the subs to return only a string;
      • grouped (non)module-using subs together.

      Last three versions still place in the same order as in your benchmark, and snowhare version is still the slowest. Big difference is that gloryhack version becomes quite faster than cw_silly, as opposed to gloryhack version being marginally slower in your benchmark.

      Below, comparison is shown, date calculation results hidden ...

      Rate cw_sensible cw_silly gloryhack GrandFather gam3 + Tanktalus cw_sensible 27248/s -- -46% -67% -73% -75% + -83% cw_silly 50161/s 84% -- -40% -49% -55% + -68% gloryhack 83435/s 206% 66% -- -16% -25% + -47% GrandFather 99258/s 264% 98% 19% -- -10% + -37% gam3 110623/s 306% 121% 33% 11% -- + -30% Tanktalus 157554/s 478% 214% 89% 59% 42% + --

      Nice summary, though I'd be inclined to only bury the code in the readmore tags. Everything else is likely to be of immediate interest - especially the benchmark results.


      Perl is Huffman encoded by design.
Re: Converting seconds to DD:HH:MM:SS
by blazar (Canon) on Dec 27, 2005 at 14:13 UTC
    Of the two date/time manipulation modules I've used most, i.e. precisely Date::Calc and DateTime I've come to think that they're pretty much complementary, i.e. I prefer one over the other depending on my actual needs. I even happened to use both at a time. Indeed I find the Delta_* subs to be quite handy...

Log In?
Username:
Password:

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

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

    No recent polls found