Closed p5pRT closed 20 years ago
Example where Time::Local fails miserably: -- # timelocal fials for example at nonexistant date: # dd.mm.yyyy hh:mm:ss # 30.02.2001 00:00:00 use Time::Local;
print scalar(localtime(timelocal(0\,0\,0\,30\,1\,101)))\, "\n"; -- If I run this I get: Fri Mar 2 00:00:00 2001 which is wrong.
Feburary 30th?
The range checks in Time::Local are a bit primitive...
croak "Day '$_[3]' out of range 1..31" if $_[3] > 31 || $_[3] \< 1;
This could be beefed up very easily.
Jarkko and I exchanged some mail about this when he was banging on Time::Piece. I'd like to suggest there are two distinct flavors of error possible. One is something like February 30\, above\, which will never be valid. One might expect a fair amount of computation to detect logical inconsistencies such as this. The other is reaching a date that *would* be valid\, but is too large or too small to fit in the prevailing time_t. One would like to detect this\, but it should cost much less than a full-blown consistency check on each component. If one is willing to assume responsibility for keeping the components consistent\, it should be possible (via some interface) to avoid the extra overhead -- jpl
Would much else be needed than just...
@Days_In_Month = ( [31\,28\,31\,30\,31\,30\,31\,31\,30\,31\,30\,31]\, [31\,29\,31\,30\,31\,30\,31\,31\,30\,31\,30\,31]\, );
my $is_leap = ($year % 4==0) and ($year % 400 == 0 or $year % 100 != 0); my $max_day = $Days_In_Month[$is_leap][$mon];
croak "Day '$mday' out of range 1..$max_day" unless 1 \< $mday && $mday \<= $max_day;
to beef up the month-day check?
The other is reaching a date that *would* be valid\, but is too large or too small to fit in the prevailing time_t.
That's easy\, too.
# Can I get the max size of time_t from some better source? # POSIX? Config? my @Time_Bounds = ( [localtime(2**31 - 1)]\, [localtime(-2**31)]\, );
unless( $Time_Bounds[0][5] \< $year && $year \< $Time_Bounds[1][5] ) { # exercise for the reader }
I suppose I should be patching this.
A little less primitive. I'm not sure its worth the effort to take care of leap years correctly\, but it wouldn't be difficult:
You appear to have forgotten the -1 for September and November.
But why not just:
$md = (31\, 29\, 31\, 30\, 31\, 30\, 31\, 30\, 30\, 31\, 30\, 31)[$month];
?
@@ -151\,8 +160\,8 @@ January 1\, 1970). This value can be positive or negative.
It is worth drawing particular attention to the expected ranges for -the values provided. While the day of the month is expected to be in -the range 1..31\, the month should be in the range 0..11.
+the values provided. The value for the day of the month is the actual day +(ie 1..31)\, while the month is the number of month since January ( 0..11).
That should be:
while the month is the number of months since January (0..11).
with an s added and a space removed.
Ronald
Because August has 31 days?
$md = (31\, 29\, 31\, 30\, 31\, 30\, 31\, 31\, 30\, 31\, 30\, 31)[$month]; # J F M A M J J A S O N D
Chris
You achieve the effect of covering both error conditions in a single call\, letting localtime handle the requisite hassles...
D
At 11:56 AM -0400 5/22/01\, Horsley Tom wrote:
As an alternate direction for all this: You could just always run the output from timelocal back through localtime and see if you get the same values\, if not\, warn about them (that should take care of all possible errors\, at least it seems like it should :-).
?
But why not just: |
---|
$md = (31\, 29\, 31\, 30\, 31\, 30\, 31\, 30\, 30\, 31\, 30\, 31)[$month]; |
Because mine is more self documenting? Because mine is easier to modify if someone decides they want to worry about leap years? Because I didn't think of it? The third one is probably the most truthful. ;-)
| with an s added and a space removed.
eeeks.. Yes. Bad fingers.
-spp
9 and 11 are October and December. :)
Ronald
What has 2**31 to do with it? Plenty of platforms support wider ranges. And negative time_t values are distinctly nonportable.
And in any case\, there's no reason why Perl should be limited by the size limits of the underlying platform.
Mike Guy
More generous limits are always welcome. At the root of my concern is a bunch of date manipulation routines (a couple included at the end). Given that the midnight sub establishes its date components using localtime\, I was surprised to have timelocal croak:
Can't handle date (0\, 0\, 0\, 11\, 6\, 142)
There's nothing syntactically wrong with the components\, so there's no (easy) way to tell it's going to cause timelocal to croak. I'd prefer not to pay the overhead of having the components checked\, and I *really* dislike the need to do an eval to catch the croak. -- jpl
# Just adding 24 * 60 * 60 seconds doesn't always get you # to the next day (as we discovered when daylight saving time ended). # The safe thing to do is start from a known time\, overcompensate\, # then restore to midnight\, for consistency.
sub midnight { my $time = $_[0]; my ($sec\, $min\, $hour\, $mday\, $mon\, $year\, $wday) = localtime($time); timelocal(0\, 0\, 0\, $mday\, $mon\, $year); }
sub day_before { my $time = midnight($_[0]); midnight($time - 12 * 60 * 60); }
sub day_after { my $time = midnight($_[0]); midnight($time + 36 * 60 * 60); }
*cough* That's what the "Can I get the max size of time_t from some better source?" comment was about.
Given how much work Time::Local has to do normally\, adding in a some O(1) validations at the beginning won't alter performance. I'd almost guarantee it.
Can't handle date (0\, 0\, 0\, 11\, 6\, 142)
There's nothing syntactically wrong with the components\, so there's no (easy) way to tell it's going to cause timelocal to croak.
and I *really* dislike the need to do an eval to catch the croak.
Aside from changing the world over to 64bit time_t\, or having Time::Local roll all its own date handling routines (which would *really* kill performance) how do we handle this other than dying/throwing an exception?
For many machines\, the universe ends in the wee hours of the morning on Tuesday\, January 19th\, 2038. We would have to write a whole new time handling system for perl completely independent of the standard C library to change this. This may be useful\, but its a bit of an overkill when we'll get the same effect by just waiting for people to upgrade time_t.
$Config{'intsize'} would probably be a good guess.
Graham.
Hmmm\, anyone actually running on a platform where "print scalar localtime(2**48)" gives something meaningful? What's $Config{intsize} say? It still doesn't tell us if time_t is signed or unsigned. :(
Alternatively\, Time::Local can make some intellegent guesses by creeping up the size of time (0\, 2**31-1\, then 2**31\, then 2**32-1\, 2**32\, 2**47-1\, etc...) as well as the lower bound (0\, (-2**31)-1\, etc...) making sure it always gets a higher (or lower) result.
Does all this strike anyone as overkill?
If you really need to know then presumably same techniques Configure used to find intsize can be applied to time_t.
Alternatively\, Time::Local can make some intellegent guesses by creeping up the size of time (0\, 2**31-1\, then 2**31\, then 2**32-1\, 2**32\, 2**47-1\, etc...) as well as the lower bound (0\, (-2**31)-1\, etc...) making sure it always gets a higher (or lower) result.
Does all this strike anyone as overkill?
Yup.
My personal hangup with Time::Local has more to do with craoking than with the level of checking. If there were an interface\, presumable shared by timelocal()\, that would return undef instead of croaking\, then it wouldn't be necessary to jump through hoops to survive dirty data or stretched limits. -- jpl
So instead of this complicated code:
my $time = eval { timelocal(@time); }; if( $@ ) { print "timelocal failed because $@"; ...do some sort of recovery... }
you want the much simpler:
my $time = timelocal(...); unless( defined $time ) { print "timelocal failed for some reason"; ...do some sort of recovery... }
;)
I thought the eval overhead might matter. Michael is right\, the overhead of timelocal itself pretty much dominates. I'll stop worrying. -- jpl
Benchmark: running evaled\, inline\, each for at least 10 CPU seconds... evaled: 11 wallclock secs (10.45 usr + 0.03 sys = 10.48 CPU) @ 9093.99/s (n=95305) inline: 11 wallclock secs (10.36 usr + 0.04 sys = 10.40 CPU) @ 9357.21/s (n=97315)
Migrated from rt.perl.org#7029 (status was 'resolved')
Searchable as RT7029$