[rt.cpan.org #79845] [PATCH] sub-second precision and rounding

2012-10-01 Thread Dave Rolsky via RT
URL: https://rt.cpan.org/Ticket/Display.html?id=79845 

On Tue Sep 25 07:27:53 2012, MHASCH wrote:

 To demonstrate that the current rounding behaviour is not
 quite thought through, try:
 
   use DateTime 0.76;
   my $d = DateTime-new(
 year   =  2012,
 month  = 9,
 day=25,
 hour   =12,
 minute =39,
 second =59,
 nanosecond = 999876000,
 time_zone  = 'Europe/Berlin',
   );
   print $d-strftime('%H:%M:%S.%3N'), \n;
 
 This will print 12:39:59.1000 rather than 12:39:59.999.
 And no, please don't make it print 12:40:00.000 either.
 Rounding is just not the right thing to do by default.

There were a few RT tickets about this and I've thought about it a fair 
bit. I think your solution is right.

This gets really messy with a datetime like 2011-12-31T23:59:59.999

To round properly we'd have to round every single unit, including the 
year! There's really no way to do this in strftime and format_cldr 
anyway, since you can only dictate the formatting _per unit_, so you 
might not even be printing out all the units which need rounding.



[rt.cpan.org #79845] [PATCH] sub-second precision and rounding

2012-09-25 Thread Martin Becker via RT
URL: https://rt.cpan.org/Ticket/Display.html?id=79845 

And here is the patch.

-Martin
diff -rup DateTime-0.76.orig/Build.PL DateTime-0.76/Build.PL
--- DateTime-0.76.orig/Build.PL	2012-07-01 23:55:52.0 +0200
+++ DateTime-0.76/Build.PL	2012-09-25 12:34:19.0 +0200
@@ -31,7 +31,6 @@ my %module_build_args = (
 Carp = 0,
 DateTime::Locale = 0.41,
 DateTime::TimeZone = 1.09,
-Math::Round = 0,
 Params::Validate = 0.76,
 Scalar::Util = 0,
 XSLoader = 0,
diff -rup DateTime-0.76.orig/lib/DateTime.pm DateTime-0.76/lib/DateTime.pm
--- DateTime-0.76.orig/lib/DateTime.pm	2012-07-01 23:55:52.0 +0200
+++ DateTime-0.76/lib/DateTime.pm	2012-09-25 12:30:47.0 +0200
@@ -44,7 +44,7 @@ use DateTime::Duration;
 use DateTime::Helpers;
 use DateTime::Locale 0.41;
 use DateTime::TimeZone 1.09;
-use Math::Round qw( nearest round );
+use POSIX qw(floor);
 use Params::Validate 0.76
 qw( validate validate_pos UNDEF SCALAR BOOLEAN HASHREF OBJECT );
 
@@ -824,9 +824,9 @@ sub nanosecond {
 return $_[0]-{rd_nanosecs};
 }
 
-sub millisecond { round( $_[0]-{rd_nanosecs} / 100 ) }
+sub millisecond { floor( $_[0]-{rd_nanosecs} / 100 ) }
 
-sub microsecond { round( $_[0]-{rd_nanosecs} / 1000 ) }
+sub microsecond { floor( $_[0]-{rd_nanosecs} / 1000 ) }
 
 sub leap_seconds {
 my $self = shift;
@@ -1304,7 +1304,7 @@ sub _format_nanosecs {
 
 return sprintf(
 '%0' . $precision . 'u',
-round( $self-{rd_nanosecs} / $divide_by )
+floor( $self-{rd_nanosecs} / $divide_by )
 );
 }
 
@@ -2647,10 +2647,9 @@ Half a second is 500 milliseconds.
 =item * $dt-microsecond()
 
 Returns the fractional part of the second as microseconds (1E-6
-seconds). This value will be rounded to an integer.
+seconds).
 
-Half a second is 500_000 microseconds. This value will be rounded to
-an integer.
+Half a second is 500_000 microseconds.
 
 =item * $dt-nanosecond()
 
diff -rup DateTime-0.76.orig/t/03components.t DateTime-0.76/t/03components.t
--- DateTime-0.76.orig/t/03components.t	2012-07-01 23:55:52.0 +0200
+++ DateTime-0.76/t/03components.t	2012-09-25 09:36:49.0 +0200
@@ -260,14 +260,14 @@ is( $monday-day_of_week, 1, Monday is
 $dt-set( nanosecond = 500_000_500 );
 
 is( $dt-nanosecond,  500_000_500, 'nanosecond is 500,000,500' );
-is( $dt-microsecond, 500_001, 'microsecond is 500,001' );
+is( $dt-microsecond, 500_000, 'microsecond is 500,001' );
 is( $dt-millisecond, 500, 'millisecond is 500' );
 
 $dt-set( nanosecond = 499_999_999 );
 
 is( $dt-nanosecond,  499_999_999, 'nanosecond is 499,999,999' );
-is( $dt-microsecond, 500_000, 'microsecond is 500,000' );
-is( $dt-millisecond, 500, 'millisecond is 500' );
+is( $dt-microsecond, 499_999, 'microsecond is 499,999' );
+is( $dt-millisecond, 499, 'millisecond is 499' );
 
 $dt-set( nanosecond = 450_000_001 );
 
@@ -279,7 +279,7 @@ is( $monday-day_of_week, 1, Monday is
 
 is( $dt-nanosecond,  450_500_000, 'nanosecond is 450,500,000' );
 is( $dt-microsecond, 450_500, 'microsecond is 450,500' );
-is( $dt-millisecond, 451, 'millisecond is 451' );
+is( $dt-millisecond, 450, 'millisecond is 450' );
 }
 
 {
diff -rup DateTime-0.76.orig/t/13strftime.t DateTime-0.76/t/13strftime.t
--- DateTime-0.76.orig/t/13strftime.t	2012-07-01 23:55:52.0 +0200
+++ DateTime-0.76/t/13strftime.t	2012-09-25 09:41:22.0 +0200
@@ -209,7 +209,7 @@ year = 1999, month = 9, day = 7, hour
 %M = '02'
 %N = '123456789'
 %3N = '123'
-%6N = '123457'
+%6N = '123456'
 %10N = '1234567890'
 %p = 'PM'
 %r = '01:02:42 PM'
@@ -314,5 +314,5 @@ year = 2012, month = 1, day = 10
 year = 1999, month = 9, day = 7, hour = 13, minute = 2, second = 42, nanosecond = 00012345678
 %N = '012345678'
 %3N = '012'
-%6N = '012346'
+%6N = '012345'
 %10N = '0123456780'


[rt.cpan.org #79845] [PATCH] sub-second precision and rounding

2012-09-25 Thread Martin Becker via RT
URL: https://rt.cpan.org/Ticket/Display.html?id=79845 

Maintainers of DateTime,

DateTime goes to some lengths to be consistent when doing
mathematics.  This is very helpful.  At one point, however,
I see it breaking a pattern for no good reason.

The pattern I am referring to is that dates and durations are
split into units and sub-units (of various sizes) where units
are *truncated* to whole numbers, and sub-units represent the
finer-grained remainder if there is any.  For example, a time
shortly before noon is modeled with an hour value of 11,
not 12, as the fractional part of the hour will be stored in
(non-negative) minutes, seconds etc.

Starting with DateTime-0.67, this pattern is broken where
fractions of seconds are involved.  $dt-millisecond gives a
*rounded* value for the 1000th part of a second, which is not
documented but checked in the test suite, and $dt-microsecond
gives a *rounded* value for the millionth part of a second,
which is documented but not less strange.

It seems that this weirdness was introduced in an attempt to
fix a rounding error in from_epoch, pointed out by Michael
R. Davis in RT #66744.  The root cause of this error made a
second appearance in RT #67736 (reported by Zefram) and was
properly taken care of soon after in DateTime-0.68.

I would like to encourage you to revert the rounding behaviour
of milli- and microseconds back to truncating (patch enclosed).
It is funny that DateTime math should achieve consistency
dealing with the most messy details of our calendar and fail
to do so with its only metric component.  Rectifying this also
relieves DateTime from a non-core dependency.

To demonstrate that the current rounding behaviour is not
quite thought through, try:

  use DateTime 0.76;
  my $d = DateTime-new(
year   =  2012,
month  = 9,
day=25,
hour   =12,
minute =39,
second =59,
nanosecond = 999876000,
time_zone  = 'Europe/Berlin',
  );
  print $d-strftime('%H:%M:%S.%3N'), \n;

This will print 12:39:59.1000 rather than 12:39:59.999.
And no, please don't make it print 12:40:00.000 either.
Rounding is just not the right thing to do by default.

-Martin

[Cc to the mailing list as this might call for some discussion]