Project

General

Profile

Defect #40914 » 40914.patch

Go MAEDA, 2024-07-01 01:41

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 to avoid rounding errors
195
      #
196
      # Examples:
197
      #  0.38333333333333336 => (23/60)   # 23 minutes
198
      #  1.1166666666666667 => (67/60)    # 1 hour 7 minutes
199
      (h * 60).round / 60r
195 200
    else
196 201
      h
197 202
    end
lib/redmine/i18n.rb
50 50
    end
51 51

  
52 52
    def l_hours(hours)
53
      hours = hours.to_f
53
      hours = hours.to_r
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.to_r))
59 59
    end
60 60

  
61 61
    def ll(lang, str, arg=nil)
......
93 93
      return "" if hours.blank?
94 94

  
95 95
      if Setting.timespan_format == 'minutes'
96
        h = hours.floor
97
        m = ((hours - h) * 60).round
96
        # Ensure the hours value is rational
97
        rational_hours = hours.is_a?(Rational) ? hours : (hours * 60).round / 60r
98
        h = rational_hours.truncate
99
        m = ((rational_hours - h) * 60).round
98 100
        "%d:%02d" % [h, m]
99 101
      else
100 102
        number_with_delimiter(sprintf('%.2f', hours.to_f), delimiter: nil)
test/functional/my_controller_test.rb
48 48
    preferences.save!
49 49
    with_issue =
50 50
      TimeEntry.create!(
51
        :user => User.find(2), :spent_on => Date.yesterday,
51
        :user => User.find(2), :spent_on => User.current.today.yesterday,
52 52
        :hours => 2.5, :activity_id => 10, :issue_id => 1
53 53
      )
54 54
    without_issue =
55 55
      TimeEntry.create!(
56
        :user => User.find(2), :spent_on => Date.yesterday,
56
        :user => User.find(2), :spent_on => User.current.today.yesterday,
57 57
        :hours => 3.5, :activity_id => 10, :project_id => 1
58 58
      )
59
    # Issues that causes a rounding error
60
    # The sum of 10m, 40m and 10m should be 1h, but it may displayed as 1h1m
61
    # if hours are rounded to 2 decimals
62
    #  [0.17, 0.67, 0.17].sum => 1.01
63
    %w[10m 40m 10m].each do |hours|
64
      TimeEntry.create!(
65
        :user => User.find(2), :spent_on => User.current.today,
66
        :hours => hours, :activity_id => 10, :issue_id => 1
67
      )
68
    end
59 69
    get :page
60 70
    assert_response :success
61 71
    assert_select "tr#time-entry-#{with_issue.id}" do
......
65 75
    assert_select "tr#time-entry-#{without_issue.id}" do
66 76
      assert_select 'td.hours', :text => '3:30'
67 77
    end
78
    assert_select 'tr:first' do # today's total
79
      assert_select 'td.hours span.hours-int', :text => '1'
80
      assert_select 'td.hours span.hours-dec', :text => ':00'
81
    end
68 82
  end
69 83

  
70 84
  def test_page_with_assigned_issues_block_and_no_custom_settings
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
      "1:07"   => Rational(67, 60)   # 1.1166666666666667
90 92
    }
91 93
    assertions.each do |k, v|
92 94
      t = TimeEntry.new(:hours => k)
93
      assert_equal v, t.hours, "Converting #{k} failed:"
95
      assert v == t.hours && t.hours.is_a?(Rational), "Converting #{k} failed:"
94 96
    end
95 97
  end
96 98

  
(2-2/4)