Project

General

Profile

Defect #40914 » 40914-v2.patch

Go MAEDA, 2024-07-23 10:31

View differences:

app/models/time_entry.rb
191 191
  def hours
192 192
    h = read_attribute(:hours)
193 193
    if h.is_a?(Float)
194
      h.round(2)
194
      # Convert the float value to a rational with a denominator of 60 to
195
      # avoid floating point errors.
196
      #
197
      # Examples:
198
      #  0.38333333333333336 => (23/60)   # 23m
199
      #  0.9913888888888889  => (59/60)   # 59m 29s is rounded to 59m
200
      #  0.9919444444444444  => (1/1)     # 59m 30s is rounded to 60m
201
      (h * 60).round / 60r
195 202
    else
196 203
      h
197 204
    end
lib/redmine/i18n.rb
50 50
    end
51 51

  
52 52
    def l_hours(hours)
53
      hours = hours.to_f
53
      hours = 0 unless hours.is_a?(Numeric)
54 54
      l((hours < 2.0 ? :label_f_hour : :label_f_hour_plural), :value => format_hours(hours))
55 55
    end
56 56

  
57 57
    def l_hours_short(hours)
58
      l(:label_f_hour_short, :value => format_hours(hours.to_f))
58
      l(:label_f_hour_short, :value => format_hours(hours.is_a?(Numeric) ? hours : 0))
59 59
    end
60 60

  
61 61
    def ll(lang, str, arg=nil)
test/unit/time_entry_test.rb
87 87
      "3 hours"  => 3.0,
88 88
      "12min"    => 0.2,
89 89
      "12 Min"    => 0.2,
90
      "0:23"   => Rational(23, 60), # 0.38333333333333336
91
      "0.9913888888888889" => Rational(59, 60), # 59m 29s is rounded to 59m
92
      "0.9919444444444444" => 1     # 59m 30s is rounded to 60m
90 93
    }
91 94
    assertions.each do |k, v|
92 95
      t = TimeEntry.new(:hours => k)
93
      assert_equal v, t.hours, "Converting #{k} failed:"
96
      assert v == t.hours && t.hours.is_a?(Rational), "Converting #{k} failed:"
94 97
    end
95 98
  end
96 99

  
100
  def test_hours_sum_precision
101
    # The sum of 10, 10, and 40 minutes should be 1 hour, but in older
102
    # versions of Redmine, the result was 1.01 hours. This was because
103
    # TimeEntry#hours was a float value rounded to 2 decimal places.
104
    #  [0.17, 0.17, 0.67].sum => 1.01
105

  
106
    hours = %w[10m 10m 40m].map {|m| TimeEntry.new(hours: m).hours}
107
    assert_equal 1, hours.sum
108
    hours.map {|h| assert h.is_a?(Rational)}
109
  end
110

  
97 111
  def test_hours_should_default_to_nil
98 112
    assert_nil TimeEntry.new.hours
99 113
  end
(3-3/4)