Feature #20310 » feature-33988-v3.patch
| app/helpers/application_helper.rb | ||
|---|---|---|
| 493 | 493 | 
    h(page.pretty_title),  | 
| 494 | 494 | 
    href,  | 
| 495 | 495 | 
    :title => (if options[:timestamp] && page.updated_on  | 
| 496 | 
                             l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on))
   | 
|
| 496 | 
                             l(label_by_timestamp_format(:label_updated_time), format_timestamp(page.updated_on))
   | 
|
| 497 | 497 | 
    else  | 
| 498 | 498 | 
    nil  | 
| 499 | 499 | 
    end)  | 
| ... | ... | |
| 735 | 735 | 
    end  | 
| 736 | 736 | |
| 737 | 737 | 
      def authoring(created, author, options={})
   | 
| 738 | 
        l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe
   | 
|
| 738 | 
        l(label_by_timestamp_format(options[:label] || :label_added_time_by), :author => link_to_user(author), :age => time_tag(created)).html_safe
   | 
|
| 739 | 739 | 
    end  | 
| 740 | 740 | |
| 741 | 741 | 
    def time_tag(time)  | 
| 742 | 742 | 
    return if time.nil?  | 
| 743 | 743 | |
| 744 | 
        text = distance_of_time_in_words(Time.now, time)
   | 
|
| 744 | 
        text = format_timestamp(time)
   | 
|
| 745 | 745 | 
    if @project  | 
| 746 | 746 | 
    link_to(text,  | 
| 747 | 747 | 
    project_activity_path(@project, :from => User.current.time_to_date(time)),  | 
| app/helpers/settings_helper.rb | ||
|---|---|---|
| 234 | 234 | 
    end  | 
| 235 | 235 | 
    end  | 
| 236 | 236 | |
| 237 | 
    # Returns the options for the timestamp_format setting  | 
|
| 238 | 
    # Convert the date and time three days ago into each format and use it as an example  | 
|
| 239 | 
    def timestamp_format_setting_options  | 
|
| 240 | 
    %w[relative_time relative_time_with_absolute_time absolute_time].map do |f|  | 
|
| 241 | 
          ["#{format_timestamp(Time.now.ago(3.days), f)} (#{l('label_' + f)})", f]
   | 
|
| 242 | 
    end  | 
|
| 243 | 
    end  | 
|
| 244 | ||
| 237 | 245 | 
    def gravatar_default_setting_options  | 
| 238 | 246 | 
    [['Identicons', 'identicon'],  | 
| 239 | 247 | 
    ['Monster ids', 'monsterid'],  | 
| app/helpers/wiki_helper.rb | ||
|---|---|---|
| 73 | 73 | 
    end  | 
| 74 | 74 | |
| 75 | 75 | 
    def wiki_content_update_info(content)  | 
| 76 | 
        l(:label_updated_time_by, :author => link_to_user(content.author), :age => time_tag(content.updated_on)).html_safe
   | 
|
| 76 | 
        authoring(content.updated_on, content.author, label: :label_updated_time_by)
   | 
|
| 77 | 77 | 
    end  | 
| 78 | 78 | 
    end  | 
| app/views/issues/show.html.erb | ||
|---|---|---|
| 43 | 43 | 
    <p class="author">  | 
| 44 | 44 | 
    <%= authoring @issue.created_on, @issue.author %>.  | 
| 45 | 45 | 
    <% if @issue.created_on != @issue.updated_on %>  | 
| 46 | 
            <%= l(:label_updated_time, time_tag(@issue.updated_on)).html_safe %>.
   | 
|
| 46 | 
            <%= l(label_by_timestamp_format(:label_updated_time), time_tag(@issue.updated_on)).html_safe %>.
   | 
|
| 47 | 47 | 
    <% end %>  | 
| 48 | 48 | 
    </p>  | 
| 49 | 49 | |
| app/views/settings/_display.html.erb | ||
|---|---|---|
| 17 | 17 | |
| 18 | 18 | 
    <p><%= setting_select :timespan_format, [["%.2f" % 0.75, 'decimal'], ['0:45 h', 'minutes']], :blank => false %></p>  | 
| 19 | 19 | |
| 20 | 
    <p><%= setting_select :timestamp_format, timestamp_format_setting_options %></p>  | 
|
| 21 | ||
| 20 | 22 | 
    <p><%= setting_select :user_format, @options[:user_format] %></p>  | 
| 21 | 23 | |
| 22 | 24 | 
    <p><%= setting_check_box :gravatar_enabled, :data => {:enables => '#settings_gravatar_default'} %>
   | 
| config/locales/en.yml | ||
|---|---|---|
| 447 | 447 | 
    setting_date_format: Date format  | 
| 448 | 448 | 
    setting_time_format: Time format  | 
| 449 | 449 | 
    setting_timespan_format: Time span format  | 
| 450 | 
    setting_timestamp_format: Timestamp format  | 
|
| 450 | 451 | 
    setting_cross_project_issue_relations: Allow cross-project issue relations  | 
| 451 | 452 | 
    setting_cross_project_subtasks: Allow cross-project subtasks  | 
| 452 | 453 | 
    setting_issue_list_default_columns: Issues list defaults  | 
| ... | ... | |
| 879 | 880 | 
      label_f_hour: "%{value} hour"
   | 
| 880 | 881 | 
      label_f_hour_plural: "%{value} hours"
   | 
| 881 | 882 | 
      label_f_hour_short: "%{value} h"
   | 
| 883 | 
    label_relative_time: Relative time  | 
|
| 884 | 
    label_relative_time_with_absolute_time: Relative time with absolute time  | 
|
| 885 | 
    label_absolute_time: Absolute time  | 
|
| 882 | 886 | 
    label_time_tracking: Time tracking  | 
| 883 | 887 | 
    label_change_plural: Changes  | 
| 884 | 888 | 
    label_statistics: Statistics  | 
| ... | ... | |
| 940 | 944 | 
      label_added_time_by: "Added by %{author} %{age} ago"
   | 
| 941 | 945 | 
      label_updated_time_by: "Updated by %{author} %{age} ago"
   | 
| 942 | 946 | 
      label_updated_time: "Updated %{value} ago"
   | 
| 947 | 
      label_added_absolute_time_by: "Added by %{author} %{age}"
   | 
|
| 948 | 
      label_updated_absolute_time_by: "Updated by %{author} %{age}"
   | 
|
| 949 | 
      label_updated_absolute_time: "Updated %{value}"
   | 
|
| 943 | 950 | 
    label_jump_to_a_project: Jump to a project...  | 
| 944 | 951 | 
    label_file_plural: Files  | 
| 945 | 952 | 
    label_changeset_plural: Changesets  | 
| config/locales/ja.yml | ||
|---|---|---|
| 744 | 744 | 
      label_added_time_by: "%{author} さんが%{age}前に追加"
   | 
| 745 | 745 | 
      label_updated_time_by: "%{author} さんが%{age}前に更新"
   | 
| 746 | 746 | 
      label_updated_time: "%{value}前に更新"
   | 
| 747 | 
      label_added_absolute_time_by: "%{author} さんが%{age}に追加"
   | 
|
| 748 | 
      label_updated_absolute_time_by: "%{author} さんが%{age}に更新"
   | 
|
| 749 | 
      label_updated_absolute_time: "%{value}に更新"
   | 
|
| 747 | 750 | 
    label_jump_to_a_project: プロジェクトへ移動...  | 
| 748 | 751 | 
    label_file_plural: ファイル  | 
| 749 | 752 | 
    label_changeset_plural: 更新履歴  | 
| config/settings.yml | ||
|---|---|---|
| 174 | 174 | 
    default: ''  | 
| 175 | 175 | 
    timespan_format:  | 
| 176 | 176 | 
    default: 'minutes'  | 
| 177 | 
    timestamp_format:  | 
|
| 178 | 
    default: 'relative_time'  | 
|
| 177 | 179 | 
    user_format:  | 
| 178 | 180 | 
    default: :firstname_lastname  | 
| 179 | 181 | 
    format: symbol  | 
| lib/redmine/i18n.rb | ||
|---|---|---|
| 89 | 89 | 
          (include_date ? "#{format_date(local)} " : "") + ::I18n.l(local, **options)
   | 
| 90 | 90 | 
    end  | 
| 91 | 91 | |
| 92 | 
    def format_timestamp(time, timestamp_format=nil)  | 
|
| 93 | 
    case (timestamp_format || Setting.timestamp_format)  | 
|
| 94 | 
    when 'relative_time'  | 
|
| 95 | 
    distance_of_time_in_words(Time.now, time)  | 
|
| 96 | 
    when 'relative_time_with_absolute_time'  | 
|
| 97 | 
            "#{distance_of_time_in_words(Time.now, time)} (#{format_time(time)})"
   | 
|
| 98 | 
    when 'absolute_time'  | 
|
| 99 | 
    format_time(time)  | 
|
| 100 | 
    end  | 
|
| 101 | 
    end  | 
|
| 102 | ||
| 103 | 
    def label_by_timestamp_format(label_name)  | 
|
| 104 | 
    return label_name unless Setting.timestamp_format == 'absolute_time'  | 
|
| 105 | ||
| 106 | 
          label_name.to_s.gsub('_time', '_absolute_time').to_sym
   | 
|
| 107 | 
    end  | 
|
| 108 | ||
| 92 | 109 | 
    def format_hours(hours)  | 
| 93 | 110 | 
    return "" if hours.blank?  | 
| 94 | 111 | |
| test/helpers/application_helper_test.rb | ||
|---|---|---|
| 1798 | 1798 | 
    result = render_page_hierarchy(pages_by_parent_id, nil, :timestamp => true)  | 
| 1799 | 1799 | 
    assert_select_in(  | 
| 1800 | 1800 | 
    result, 'ul.pages-hierarchy li a[title=?]',  | 
| 1801 | 
    l(:label_updated_time,  | 
|
| 1802 | 
    distance_of_time_in_words(Time.now, parent_page.updated_on)))  | 
|
| 1801 | 
    l(:label_updated_time, format_timestamp(parent_page.updated_on)))  | 
|
| 1803 | 1802 | 
    assert_select_in(  | 
| 1804 | 1803 | 
    result, 'ul.pages-hierarchy li ul.pages-hierarchy a[title=?]',  | 
| 1805 | 
    l(:label_updated_time,  | 
|
| 1806 | 
    distance_of_time_in_words(Time.now, child_page.updated_on)))  | 
|
| 1804 | 
    l(:label_updated_time, format_timestamp(child_page.updated_on)))  | 
|
| 1807 | 1805 | 
    end  | 
| 1808 | 1806 | |
| 1809 | 1807 | 
    def test_render_page_hierarchy_when_action_is_export  | 
| test/helpers/settings_helper_test.rb | ||
|---|---|---|
| 29 | 29 | 
        options = date_format_setting_options('en')
   | 
| 30 | 30 | 
    assert_include ["2015-07-14 (yyyy-mm-dd)", "%Y-%m-%d"], options  | 
| 31 | 31 | 
    end  | 
| 32 | ||
| 33 | 
    def test_timestamp_format_setting_options_should_include_human_readable_format  | 
|
| 34 | 
    sample_time = Time.now.ago(3.days)  | 
|
| 35 | ||
| 36 | 
    options = timestamp_format_setting_options  | 
|
| 37 | 
    assert_include ["3 days (Relative time)", 'relative_time'], options  | 
|
| 38 | 
        assert_include ["3 days (#{format_time(sample_time)}) (Relative time with absolute time)", 'relative_time_with_absolute_time'], options
   | 
|
| 39 | 
        assert_include ["#{format_time(sample_time)} (Absolute time)", 'absolute_time'], options
   | 
|
| 40 | 
    end  | 
|
| 32 | 41 | 
    end  | 
| test/unit/lib/redmine/i18n_test.rb | ||
|---|---|---|
| 22 | 22 | 
    class Redmine::I18nTest < ActiveSupport::TestCase  | 
| 23 | 23 | 
    include Redmine::I18n  | 
| 24 | 24 | 
    include ActionView::Helpers::NumberHelper  | 
| 25 | 
    include ActionView::Helpers::DateHelper  | 
|
| 25 | 26 | |
| 26 | 27 | 
    def setup  | 
| 27 | 28 | 
    User.current = nil  | 
| ... | ... | |
| 125 | 126 | 
    end  | 
| 126 | 127 | 
    end  | 
| 127 | 128 | |
| 129 | 
    def test_format_timestamp  | 
|
| 130 | 
    travel_to Time.utc(2023, 5, 31, 23, 59, 59) # Time.now => 2023/5/31 23:59:59  | 
|
| 131 | 
    sample_time = Time.utc(2022, 1, 1, 0, 0, 0)  | 
|
| 132 | ||
| 133 | 
    with_settings :timestamp_format => 'relative_time' do  | 
|
| 134 | 
    assert_equal 'over 1 year', format_timestamp(sample_time)  | 
|
| 135 | 
    end  | 
|
| 136 | 
    with_settings :timestamp_format => 'relative_time_with_absolute_time', :date_format => '%d/%m/%Y', :time_format => '%H:%M' do  | 
|
| 137 | 
    assert_equal 'over 1 year (01/01/2022 00:00)', format_timestamp(sample_time)  | 
|
| 138 | 
    end  | 
|
| 139 | 
    with_settings :timestamp_format => 'absolute_time', :date_format => '%d/%m/%Y', :time_format => '%H:%M' do  | 
|
| 140 | 
    assert_equal '01/01/2022 00:00', format_timestamp(sample_time)  | 
|
| 141 | 
    end  | 
|
| 142 | ||
| 143 | 
    # If there is an argument, it takes precedence over the set value  | 
|
| 144 | 
    with_settings :timestamp_format => 'absolute_time', :date_format => '%d/%m/%Y', :time_format => '%H:%M' do  | 
|
| 145 | 
    assert_equal 'over 1 year', format_timestamp(sample_time, 'relative_time')  | 
|
| 146 | 
    end  | 
|
| 147 | 
    end  | 
|
| 148 | ||
| 149 | 
    def test_label_by_timestamp_format  | 
|
| 150 | 
    with_settings :timestamp_format => 'relative_time' do  | 
|
| 151 | 
    assert_equal :label_added_time_by, label_by_timestamp_format(:label_added_time_by)  | 
|
| 152 | 
    assert_equal :label_updated_time_by, label_by_timestamp_format(:label_updated_time_by)  | 
|
| 153 | 
    assert_equal :label_updated_time, label_by_timestamp_format(:label_updated_time)  | 
|
| 154 | 
    end  | 
|
| 155 | 
    with_settings :timestamp_format => 'relative_time_with_absolute_time' do  | 
|
| 156 | 
    assert_equal :label_added_time_by, label_by_timestamp_format(:label_added_time_by)  | 
|
| 157 | 
    assert_equal :label_updated_time_by, label_by_timestamp_format(:label_updated_time_by)  | 
|
| 158 | 
    assert_equal :label_updated_time, label_by_timestamp_format(:label_updated_time)  | 
|
| 159 | 
    end  | 
|
| 160 | 
    with_settings :timestamp_format => 'absolute_time' do  | 
|
| 161 | 
    assert_equal :label_added_absolute_time_by, label_by_timestamp_format(:label_added_time_by)  | 
|
| 162 | 
    assert_equal :label_updated_absolute_time_by, label_by_timestamp_format(:label_updated_time_by)  | 
|
| 163 | 
    assert_equal :label_updated_absolute_time, label_by_timestamp_format(:label_updated_time)  | 
|
| 164 | 
    end  | 
|
| 165 | 
    end  | 
|
| 166 | ||
| 128 | 167 | 
    def test_number_to_human_size_for_each_language  | 
| 129 | 168 | 
    valid_languages.each do |lang|  | 
| 130 | 169 | 
    set_language_if_valid lang  |