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 - mneylon-pm@masemware.com
||
"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
| [reply] [d/l] |
|
|
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))
+;
-Matt | [reply] [d/l] [select] |
|
|
| [reply] [d/l] |
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.
| [reply] [d/l] [select] |
|
|
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.
¤
| [reply] |
|
|
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.
| [reply] [d/l] |
|
|
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.
—John | [reply] [d/l] |
|
|
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.
| [reply] |
|
|
|
|
|
|
|
|
|
|
|
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.
—John
| [reply] |
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.
HTH
broquaint | [reply] [d/l] |
|
|
| [reply] [d/l] [select] |
|
|
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. :(
| [reply] |
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};
=cut
--Brent Dax
There is no sig. | [reply] [d/l] |
|
|
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);
dmm
| [reply] [d/l] [select] |
|
|
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.
-Blake
| [reply] [d/l] [select] |
|
|
|
|
|
|
|
| [reply] [d/l] |
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);
BlueLines
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. | [reply] [d/l] |
|
|
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. | [reply] [d/l] [select] |
|
|
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.
| [reply] |
|
|
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 :)
--
<http://www.dave.org.uk>
"The first rule of Perl club is you don't talk about
Perl club."
| [reply] |