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/ );
+}

Reply via email to