This patch started out as just adding some tests (taken from DT::TZ::Alias) but I
uncovered several issues.
These currently accepted offset formats should be rejected as they are ambiguous:
'111', '+111', '-111',
'1:11', '+1:11', '-1:11',
'11111', '+11111', '-11111',
'111:11', '+111:11', '-111:11',
'1:1111', '+1:1111', '-1:1111',
'1:11:11', '+1:11:11', '-1:11:11',
'1111:11', '+1111:11', '-1111:11',
'11:1111', '+11:1111', '-11:1111',
- the hours value of an offset must be specified as 2 digits
- the colon separator must be used between both hours/minutes and minutes/seconds or
not at all
The algorithm for turning an offset string into a number of seconds was taking a
modulus of the hours value. This could lead to developer surprise. The hours value
has been limited to 99 (what fits into 2 digits), minutes limited to 59, and seconds
limited to 59.
- offset string formats are now validated and undef is returned for bad formats
- the modulus of the hours value is removed
The algorithm for turning a number of seconds into an offset string was producing
floating point values. These were not being seen because of the sprintf format but
could be a problem in the future.
- internal floating point values are now truncated
- the input number of seconds is now limited to what can actually be formated into a
valid offset string
- the output of offset_as_seconds() passed to offset_as_string() will return the
original input (and vice versa)
The 'name' key of OffsetOnly objects was not being normalized. Two objects with
identical values but different 'names' could be created.
- the 'name' value is now normalized
Also:
- added more regression tests
- some minor cosmetic changes for clarity
Comments?
-J
--
Index: lib/DateTime/TimeZone.pm
===================================================================
RCS file: /cvsroot/perl-date-time/modules/DateTime-TimeZone/lib/DateTime/TimeZone.pm,v
retrieving revision 1.84
diff -u -r1.84 TimeZone.pm
--- lib/DateTime/TimeZone.pm 10 Aug 2003 13:44:48 -0000 1.84
+++ lib/DateTime/TimeZone.pm 19 Aug 2003 10:28:53 -0000
@@ -346,12 +346,16 @@
return 0 if $offset eq '0';
- return undef unless $offset =~ /^([\+\-])?(\d\d?):?(\d\d)(?::?(\d\d))?$/;
+ return undef unless $offset =~ /^([\+\-])?(\d\d)(:?)(\d\d)(?:\3(\d\d))?$/;
+
+ my ( $sign, $hours, $minutes, $seconds ) = ( $1, $2, $4, $5 );
- my ( $sign, $hours, $minutes, $seconds ) = ( $1, $2, $3, $4 );
$sign = '+' unless defined $sign;
+ return undef unless $hours >= 0 && $hours <= 99;
+ return undef unless $minutes >= 0 && $minutes <= 59;
+ return undef unless ! defined( $seconds ) || ( $seconds >= 0 && $seconds <= 59 );
- my $total = ($hours * 60 * 60) + ($minutes * 60);
+ my $total = $hours * 3600 + $minutes * 60;
$total += $seconds if $seconds;
$total *= -1 if $sign eq '-';
@@ -363,17 +367,17 @@
my $offset = shift;
return undef unless defined $offset;
+ return undef unless $offset >= -359999 && $offset <= 359999;
my $sign = $offset < 0 ? '-' : '+';
$offset = abs($offset);
- my $hours = $offset / ( 60 * 60 );
- $hours = $hours % 24;
-
- my $mins = ( $offset % ( 60 * 60 ) ) / 60;
-
- my $secs = $offset % 60;
+ my $hours = int( $offset / 3600 );
+ $offset %= 3600;
+ my $mins = int( $offset / 60 );
+ $offset %= 60;
+ my $secs = int( $offset );
return ( $secs ?
sprintf( '%s%02d%02d%02d', $sign, $hours, $mins, $secs ) :
@@ -569,10 +573,12 @@
Given an offset as a string, this returns the number of seconds
represented by the offset as a positive or negative number.
+Returns C<undef> if $offset is not in the range C<-99:59:59> to C<+99:59:59>.
=item * offset_as_string( $offset )
Given an offset as a number, this returns the offset as a string.
+Returns C<undef> if $offset is not in the range C<-359999> to C<359999>.
=back
Index: lib/DateTime/TimeZone/OffsetOnly.pm
===================================================================
RCS file:
/cvsroot/perl-date-time/modules/DateTime-TimeZone/lib/DateTime/TimeZone/OffsetOnly.pm,v
retrieving revision 1.18
diff -u -r1.18 OffsetOnly.pm
--- lib/DateTime/TimeZone/OffsetOnly.pm 13 Jun 2003 17:50:57 -0000 1.18
+++ lib/DateTime/TimeZone/OffsetOnly.pm 19 Aug 2003 10:28:53 -0000
@@ -3,7 +3,7 @@
use strict;
use vars qw ($VERSION);
-$VERSION = 0.01;
+$VERSION = 0.02;
use DateTime::TimeZone;
use base 'DateTime::TimeZone';
@@ -24,7 +24,10 @@
return DateTime::TimeZone::UTC->new unless $offset;
- my $self = { name => $p{offset}, offset => $offset };
+ my $self = {
+ name => DateTime::TimeZone::offset_as_string( $offset ),
+ offset => $offset,
+ };
return bless $self, $class;
}
Index: t/05offset.t
===================================================================
RCS file: /cvsroot/perl-date-time/modules/DateTime-TimeZone/t/05offset.t,v
retrieving revision 1.2
diff -u -r1.2 05offset.t
--- t/05offset.t 7 May 2003 17:08:14 -0000 1.2
+++ t/05offset.t 19 Aug 2003 10:28:53 -0000
@@ -4,7 +4,7 @@
use DateTime::TimeZone;
-use Test::More tests => 9;
+use Test::More tests => 28;
is(DateTime::TimeZone::offset_as_string(0), "+0000",
"offset_as_string does the right thing on 0");
@@ -16,14 +16,54 @@
"offset_as_string works on positive half hours");
is(DateTime::TimeZone::offset_as_string(-5400), "-0130",
"offset_as_string works on negative half hours");
-
is(DateTime::TimeZone::offset_as_string(20700), "+0545",
"offset_as_string works on positive 15min zones");
is(DateTime::TimeZone::offset_as_string(-20700), "-0545",
"offset_as_string works on negative 15min zones");
+is(DateTime::TimeZone::offset_as_string(359999), "+995959",
+ "offset_as_string max value");
+is(DateTime::TimeZone::offset_as_string(-359999), "-995959",
+ "offset_as_string min value");
+is(DateTime::TimeZone::offset_as_string(360000), undef,
+ "offset_as_string exceeded max value");
+is(DateTime::TimeZone::offset_as_string(-360000), undef,
+ "offset_as_string exceeded min value");
+
+my @offset_seconds = qw(
+ 0
+ 3600
+ -3600
+ 5400
+ -5400
+ 20700
+ -20700
+ 359999
+ -359999
+);
+
+my @offset_strings = qw(
+ +0100
+ -0100
+ +0130
+ -0130
+ +0545
+ -0545
+ +995959
+ -995959
+);
-is(DateTime::TimeZone::offset_as_string(86400), "+0000",
- "offset_as_string rolls over properly on one full day of seconds");
-is(DateTime::TimeZone::offset_as_string(86400 + 3600), "+0100",
- "offset_as_string rolls over properly on one day + 1 hour of seconds");
+foreach ( @offset_seconds ) {
+ is( DateTime::TimeZone::offset_as_seconds(
+ DateTime::TimeZone::offset_as_string( $_ )
+ ),
+ $_, "n -> offset_as_string -> offset_as_seconds = n "
+ );
+}
+foreach ( @offset_strings ) {
+ is( DateTime::TimeZone::offset_as_string(
+ DateTime::TimeZone::offset_as_seconds( $_ )
+ ),
+ $_, "n -> offset_as_seconds -> offset_as_string= n "
+ );
+}
Index: t/07offset-only.t
===================================================================
RCS file: /cvsroot/perl-date-time/modules/DateTime-TimeZone/t/07offset-only.t,v
retrieving revision 1.3
diff -u -r1.3 07offset-only.t
--- t/07offset-only.t 7 May 2003 17:08:14 -0000 1.3
+++ t/07offset-only.t 19 Aug 2003 10:28:53 -0000
@@ -9,7 +9,7 @@
BEGIN { require 'check_datetime_version.pl' }
-plan tests => 2;
+plan tests => 180;
eval { DateTime::TimeZone::OffsetOnly->new( offset => 'bad' ) };
is( $@, "Invalid offset: bad\n",
@@ -17,3 +17,92 @@
my $off = DateTime::TimeZone::OffsetOnly->new( offset => '-0100' );
is( $off->name, '-0100', 'name is -0100' );
+
+my @good_offsets = (
+ [ '0', 'UTC' ],
+ [ '0000', 'UTC' ],
+ [ '000000', 'UTC' ],
+ [ '1000', '+1000' ],
+ [ '100001', '+100001' ],
+ [ '10:00:02', '+100002' ],
+ [ '+0000', 'UTC' ],
+ [ '+000000', 'UTC' ],
+ [ '+000001', '+000001' ],
+ [ '+00:00:02', '+000002' ],
+ [ '-0000', 'UTC' ],
+ [ '-000000', 'UTC' ],
+ [ '-000001', '-000001' ],
+ [ '-00:00:02', '-000002' ],
+ [ '+9959', '+9959' ],
+ [ '+995959', '+995959' ],
+ [ '+99:59:58', '+995958' ],
+ [ '-9959', '-9959' ],
+ [ '-995959', '-995959' ],
+ [ '-99:59:58', '-995958' ],
+);
+
+my @bad_offsets = (
+ '+0', '-0',
+ '1', '+1', '-1',
+ '1:', '+1:', '-1:',
+ ':1', '+:1', '-:1',
+ '11', '+11', '-11',
+ '11:', '+11:', '-11:',
+ '1:1', '+1:1', '-1:1',
+ ':11', '+:11', '-:11',
+ '111', '+111', '-111',
+ '111:', '+111:', '-111:',
+ '11:1', '+11:1', '-11:1',
+ '1:11', '+1:11', '-1:11',
+ ':111', '+:111', '-:111',
+ ':11:1', '+:11:1', '-:11:1',
+ '1:11:', '+1:11:', '-1:11:',
+ '1111:', '+1111:', '-1111:',
+ '111:1', '+111:1', '-111:1',
+ '1:111', '+1:111', '-1:111',
+ ':1111', '+:1111', '-:1111',
+ ':11:11', '+:11:11', '-:11:11',
+ '1:11:1', '+1:11:1', '-1:11:1',
+ '11:11:', '+11:11:', '-11:11:',
+ '11111', '+11111', '-11111',
+ '11111:', '+11111:', '-11111:',
+ '1111:1', '+1111:1', '-1111:1',
+ '111:11', '+111:11', '-111:11',
+ '11:111', '+11:111', '-11:111',
+ '1:1111', '+1:1111', '-1:1111',
+ ':11111', '+:11111', '-:11111',
+ ':11:111', '+:11:111', '-:11:111',
+ '1:11:11', '+1:11:11', '-1:11:11',
+ '11:11:1', '+11:11:1', '-11:11:1',
+ '111:11:', '+111:11:', '-111:11:',
+ '111111:', '+111111:', '-111111:',
+ '11111:1', '+11111:1', '-11111:1',
+ '1111:11', '+1111:11', '-1111:11',
+ '111:111', '+111:111', '-111:111',
+ '11:1111', '+11:1111', '-11:1111',
+ '1:11111', '+1:11111', '-1:11111',
+ ':111111', '+:111111', '-:111111',
+ ':11:1111', '+:11:1111', '-:11:1111',
+ '1:11:111', '+1:11:111', '-1:11:111',
+ '111:11:1', '+111:11:1', '-111:11:1',
+ '1111:11:', '+1111:11:', '-1111:11:',
+ '1111111', '+1111111', '-1111111',
+ '00:60', '+00:60', '-00:60',
+ '00:99', '+00:99', '-00:99',
+ '00:60:00', '+00:60:00', '-00:60:00',
+ '00:99:00', '+00:99:00', '-00:99:00',
+ '00:00:60', '+00:00:60', '-00:00:60',
+ '00:00:99', '+00:00:99', '-00:00:99',
+ '00:60:60', '+00:60:60', '-00:60:60',
+ '00:99:99', '+00:99:99', '-00:99:99',
+);
+
+foreach ( @good_offsets ) {
+ my $off = DateTime::TimeZone::OffsetOnly->new( offset => $_->[0] );
+ is( $off->name, $_->[1] );
+}
+
+foreach ( @bad_offsets ) {
+ eval{ DateTime::TimeZone::OffsetOnly->new( offset => $_ ) };
+ like( $@, qr/Invalid offset/ );
+}