Bug #9951

DateTime.strftime and Time.strftime differ in how they treat "%L"

Added by David Chelimsky 11 months ago. Updated 11 months ago.

[ruby-core:63211]
Status:Rejected
Priority:Normal
Assignee:Akira Tanaka
ruby -v:1.9.3 - 2.1.1 Backport:2.0.0: UNKNOWN, 2.1: UNKNOWN

Description

DateTime.new(2014,1,2,3,4,5.678).strftime("%L")
# => "678"
Time.new(2014,1,2,3,4,5.678).strftime("%L")
# => "677"

I think these should both produce "678", but at the very least they should produce the same number so users don't have to special case one or the other. I realize there is floating point math under the hood here, but that's an implementation detail I don't think users should care about in this case.

History

#1 Updated by Nobuyoshi Nakada 11 months ago

  • Description updated (diff)
  • Status changed from Open to Assigned
  • Assignee set to Akira Tanaka

DateTime seems to round the fraction of second in nanoseconds at initialization.

#2 Updated by Heesob Park 11 months ago

Time seems to truncate the fraction of second in strftime.

Here is a patch

diff --git a/strftime.c b/strftime.c
index 83550e9..a0a7de5 100644
--- a/strftime.c
+++ b/strftime.c
@@ -705,7 +705,7 @@ rb_strftime_with_timespec(char *s, size_t maxsize, const char *format, rb_encodi
                                 else {
                                         int i;
                                         for (i = 0; i < 9-precision; i++)
-                                                subsec /= 10;
+                                                subsec = (int)(subsec/10.0+0.5);
                                         snprintf(s, endp - s, "%0*ld", precision, subsec);
                                         s += precision;
                                 }
@@ -725,7 +725,7 @@ rb_strftime_with_timespec(char *s, size_t maxsize, const char *format, rb_encodi
                                         n *= 10;
                                 if (n != 1)
                                         subsec = mul(subsec, INT2FIX(n));
-                                subsec = div(subsec, INT2FIX(1));
+                                subsec = div(add(subsec,DBL2NUM(0.5)), INT2FIX(1));

                                 if (FIXNUM_P(subsec)) {
                                         (void)snprintf(s, endp - s, "%0*ld", precision, FIX2LONG(subsec));

#3 Updated by Akira Tanaka 11 months ago

  • Status changed from Assigned to Rejected

The problem of float is ruby can not know what user want to
specify 5.678 or 5.67799999999999993605115378159098327159881591796875.

DateTime's rounding to nanosecond means DateTime suppose
users don't want to specify under-nanosecond time.
I don't want to such assumption for Time.

%L uses floor because 0.9999 should be shown as 999, not 1000.

So, I don't have idea to solve this issue in Ruby side.

Please use rational, such as 5.678r.

% ruby -e 'p Time.new(2014,1,2,3,4,5.678r).strftime("%L")' 
"678"

#4 Updated by David Chelimsky 11 months ago

Akira, thanks for the "rational" suggestion. That appears to solve the problem for me.

Also available in: Atom PDF