Beefy Boxes and Bandwidth Generously Provided by pair Networks
There's more than one way to do things

Time to seconds

by argus (Acolyte)
on Nov 15, 2001 at 19:20 UTC ( #125583=perlquestion: print w/replies, xml ) Need Help??
argus has asked for the wisdom of the Perl Monks concerning the following question:

I have searched and searched but can't figure this one out.

I have a time (5H3M17S) and want to convert it into just seconds. I can do the reverse seconds to a formatted time, but am having problmes in reverse.

Note: If there are no seconds then only minutes will be displayed. If there are no hours, then they will not be displayed. Cryptic, huh? IAW: If a time value does not exsist it will not show up.

This is to take the output of newer dig queries who display the TTL (time to live) in hhmmss rather than just seconds.


Replies are listed 'Best First'.
Re: Time to seconds
by Masem (Monsignor) on Nov 15, 2001 at 19:26 UTC
    my $time = "5H3M17S"; $time =~ /((\d+)H)?((\d+)M)?((\d+)S)?/; my $secs = 3600*$1 + 60*$3 + $5;

    Dr. Michael K. Neylon - || "You've left the lens cap of your mind on again, Pinky" - The Brain
    "I can see my house from here!"
    It's not what you know, but knowing how to find it if you don't know that's important

      I haven't golfed in a while, so here's Masem's code in one line:
      (my $secs = "5H3M17S") =~ s/((\d+)H)?((\d+)M)?((\d+)S)?/3600*$1 + 60*$ +3 + $5/e;
      And just to be different:
      use strict; my $time = "5H3M17S"; my $secs = 0; my $exponent = 0; map { $secs += $_ * 60 ** $exponent++ } reverse(split(/[HMS]/, $time)) +;


      Hmm donuts^H^H^H^H^H^H golf..... ;o)



      Brother Frankus.


Re: Time to seconds
by tadman (Prior) on Nov 15, 2001 at 20:07 UTC
    Everyone is always quick with the regex, but in this case, I think you are painting yourself into a corner when you go with that approach. Just because you can, doesn't mean you should and all that.

    So, here's a solution which is really straight-up, and it converts both ways.

    Sample input:
    3h4m 11040 3h4m 1h2m3s 3723 1h2m3s 1h5s 3605 1h5s 3m4s 184 3m4s 1d2h3m4s 93784 1d2h3m4s 3x -1 2m2m -1 23h59m59s 86399 23h59m59s
    The conversion routines:
    my @time_values = qw [ d h m s ]; my %time_value = ( d => 86400, h => 3600, m => 60, s => 1 ); sub string_to_time { my ($time) = @_; my $value = 0; foreach my $letter (@time_values) { if ($time =~ s/(\d+)$letter//) { $value += $1 * $time_value{$letter}; } } return -1 if length $time; return $value; } sub time_to_string { my ($time) = @_; my $value = ''; foreach my $letter (@time_values) { my $time_value = $time_value{$letter}; if ($time > $time_value) { $value .= int($time/$time_value).$letter; $time %= $time_value; } } return $value; }
    And a quick test-harness for comparisons with other routines:
    #!/usr/bin/perl -w use strict; my @test = qw [ 3h4m 1h2m3s 1h5s 3m4s 1d2h3m4s 3x 2m2m 23h59m59s ]; foreach (@test) { my $s2t = string_to_time($_); my $t2s = time_to_string($s2t); printf ("%-15s %-10s %-10s\n", $_, $s2t, $t2s); }
    A couple of notes:
    • I'm not sure if you want to use -1 as an "invalid" return. You might want 0, or some other trigger, if you are working with values from 1969, for example. Maybe you have some really, really old DNS data that predates ARPANET.
    • You can add new letters to the spec, and it will convert accordingly. I'm not sure how your tool displays really, really long values, such as months, or years, especially since these are subjective things. There is no ISO standard month length.
    • These could be compacted, of course, even to the point of golf.

      Perl has the best regex I know of and two lines IME is more maintainable than two subroutines.
      For trivial munging it seems sensible to use regexes, otherwise you code could end up looking like <shudder> Java.. :-S.

      The results and error conditions you providecould easily be added to a regex, without compromising robustness or readability

      Abigail has a raft of regexes that are both eye watering and mind expanding ;^)

      Brother Frankus.


        Perl does have a very fine regex, as demonstrated below by BrentDax, which is what I was thinking of doing initially, forgetting, at the time, that feature of split().

        No matter how you slice it, though, regex or otherwise, you should put the code into a sub-routine. At least, anyone who is concerned about maintainability would make sure to do that. Having a compact regex is not a license to cut and paste it all over your code, wherever it is required.

        If you're afraid of this code looking like Java, well, that isn't too hard to fix:
        my %time_value = ( d => 86400, h => 3600, m => 60, s => 1 ); sub string_to_time { my ($time) = @_; my $value = 0; map { $value += $1*$time_value{$_} if ($time =~ s/(\d+)$_//) } + sort keys %time_value; return ($value && !length $time)? $value : undef; } sub time_to_string { my ($time) = @_; my $value = ''; (defined $time && $time > 0) || return undef; map { $value .= int($time/$time_value{$_}).$_ and $time %= $ti +me_value{$_} if ($time > $time_value{$_})} sort keys %time_value; return $value; }
        So much for readability and maintainability though.
      A suggestion:
      my @time_values = qw [ d h m s ]; my %time_value = ( d => 86400, h => 3600, m => 60, s => 1 );
      The array is not necessary, since it can be generated from keys %time_value. That way you don't have to keep the two data structures in sync.


        This is true to some extent, but in this case, I wanted to make sure that the entries were processed in the proper order, being from highest to lowest. That they happen to sort both numerically and alphabetically is indeed curious, however the addition of 'y' for years would throw that out of whack, and is a style of programming best reserved for Golf.

        The behaviour of 'keys %time_value' is not always in the order they were added, at least under some historical versions of Perl. Best to avoid making assumptions, I suppose. As much as I don't like maintaining inter-related structures, such as these, I didn't want to make a sort_by_value routine even more.
      That's a very nice, robust, solution.

      For an error, here are two suggestions: return undef, or throw an exception.

      You can even "use warnings" to specify which you want.

      For a totally different approach, which doesn't apply well to this format, one time in a date thing I used July 4 1776 as an error return code. A famous date is easy to recognise as not a random (e.g. bad arithmetic or stray pointer) but still outside the expected range.


Re: Time to seconds
by broquaint (Abbot) on Nov 15, 2001 at 19:28 UTC
    This sounds like a simple case of string munging
    use strict; my $seconds = 0; my $timestr = '5H3M17S'; my ($hr,$min,$sec) = $timestr =~ /^(?:(\d+)H)?(?:(\d)+M)?(\d+)S$/; $seconds += $hr * 60 * 60 if defined $hr; $seconds += $min * 60 if defined $min; $seconds += $sec; print "seconds left to going live - $seconds";
    Pretty simple stuff really, just grab what's there, do a few multiplications and your set.


      If you also want to test for validity, you might want to replace the regexp search
      $timestr =~ /^(?:(\d+)H)?(?:(\d)+M)?(\d+)S$/;
      $timestr =~ /^(?:(?:(\d+)H)?(\d)+M)?(\d+)S$/;.

      The difference is that the first will accept 10H5S, while the second won't. (And you'll be testing that the match succeeds in real code, of course).

      Except that this doesn't work when there are no seconds.
      I.E. $timestr = '3M';

      However, the first one does.

      Thank you both, sometimes the simplest things elude us. :(

Re: Time to seconds
by BrentDax (Hermit) on Nov 15, 2001 at 23:48 UTC
    my %time=reverse split(/([HMS])/, '5H3M17S'); no warnings 'uninitialized'; my $time=$time{H}*60*60+$time{M}*60+$time{S};

    --Brent Dax
    There is no sig.

      Bravo! (Just what I was thinking).

      However, as an alternative to using no warnings 'uninitialized' (always appear as though you thought of everything), try OR-ing the hash values with 0, like this:

      my %time=reverse split(/([HMS])/, '5H3M17S'); my $time = (( (($time{H}||0) * 60) + ($time{M}||0)) * 60) + ($time{S}| +|0);


        I have to disagree. I think no warnings 'uninitialized' is a much more elegant way to avoid those idiotic warnings. For one thing, they shouldn't be warnings in the *first* place. In this example, the expression that triggers the warning is:
        60 * undef
        Why should this cause a warning? The behavior is well-defined, documented, rational, and well understood. I don't think perl should be warning me about this since it is a feature of Perl!

        Your solution (nothing personal, its a common workaround) converts the above expression to:

        60 * (undef || 0)
        Which doesn't spew out a warning... The question is why not? If the first one gives me a "Use of uninitialized value in multiplication" error, why wouldn't this give me a "Use of uninitialized value in logical OR" error? Its not like undef in boolean context is more well-behaved than in numeric context1. Why should one throw a warning and the other not... Either both should trigger warnings and we sprinkle lots of defined() calls everywhere, or neither should.

        Adding gratuitous logic to avoid spurious warnings doesn't agree with me. Better just to turn those specific warnings off and be done with it.

        1I know thats splitting hairs, but the fact that it is splitting hairs is kind of my point anyway.


        I would do that on a system without warnings; however, it's always a good thing to be explicit about such things. use warnings 'uninitialized' makes my intent--ducking the 'Use of uninitialized value in multiplication' warnings--more clear than sticking a bunch of default values in. Besides, it's easier to type than "shift-backslash-backslash-unshift-zero" x 3. :^)

        --Brent Dax
        There is no sig.

Re: Time to seconds
by BlueLines (Hermit) on Nov 16, 2001 at 00:40 UTC
    Umm, the regexen are all nice, but why not use Time::Local to handle the time part?
    use Time::Local; my ($hours,$minutes,$seconds) = (1,2,3); #insert your regex here print timelocal($seconds,$minutes,$hours,1,0,70);


    Disclaimer: This post may contain inaccurate information, be habit forming, cause atomic warfare between peaceful countries, speed up male pattern baldness, interfere with your cable reception, exile you from certain third world countries, ruin your marriage, and generally spoil your day. No batteries included, no strings attached, your mileage may vary.
      The result of timelocal will depend on the local time zone. For example, when I ran your code it printed 18123 because I'm at GMT-0500. Of course, that can be avoided by using gmtime insead. The following code prints the expected 3723:
      use Time::Local; my ($hours, $minutes, $seconds) = (1, 2, 3); #insert your regex here print timegm($seconds, $minutes, $hours, 1, 0, 70);
      Unfortunately, even that's not a complete solution, because the epoch is not Jan 1, 1970 on all platforms. On the Macintosh, for example, it's Jan 1, 1904, and the output is 2082830523.

      There's still a way to do it with timelocal, though:

      use Time::Local; my ($hours, $minutes, $seconds) = (1, 2, 3); #insert your regex here print timelocal($seconds, $minutes, $hours, 1, 0, 100) - timelocal(0, 0, 0, 1, 0, 100);
      The choice of year is relatively arbitrary, but note that you mustn't pick a time of year that will be affected by daylight savings.

      So, you can solve this problem using Time::Local, you just have to be careful about how you do it.

      I thought of Time::Loacla at first, but the code had to be as portable as possible. Some of the machines that it might run on might not have that module installed. I hated useing Getopts::Long, but it seems generally standard around here and I am in a time crunch. I might recode it later. See the Mass Domian Name Lookup for how I used it.

        Do you mean Time::Local? If so then you don't have a problem as the module is part of the standard Perl distribution and will be installed everywhere where Perl is. If it isn't installed then there is something wrong with the Perl installation and you may have far more serious problems :)


        "The first rule of Perl club is you don't talk about Perl club."

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://125583]
Approved by root
and all is quiet...

How do I use this? | Other CB clients
Other Users?
Others pondering the Monastery: (5)
As of 2017-01-24 03:28 GMT
Find Nodes?
    Voting Booth?
    Do you watch meteor showers?

    Results (201 votes). Check out past polls.