Last Updated: September 09, 2019
·
5.892K
· mbillard

Display date ranges in text in Rails

Edit: This helper now comes in the form of the time_will_tell gem!

Ever wanted to display dates ranges that follow this format:

Oct 3 - 8, 2012

Jan 30 - Feb 5, 2013

Dec 26, 2012 - Jan 3, 2013

Note that the months and years only appear when appropriate. You want it? Simply add this time_helper.rb helper in your helpers:

# encoding: utf-8
module TimeHelper

  def date_range(from_date, until_date, options = {})
    options.symbolize_keys!
    format = options[:format] || :short
    separator = options[:separator] || "—"

    if format.to_sym == :short
      month_names = I18n.t("date.abbr_month_names")
    else
      month_names = I18n.t("date.month_names")
    end

    from_day = from_date.day
    from_month = month_names[from_date.month]
    from_year = from_date.year
    until_day = until_date.day

    dates = { from_day: from_day }

    if from_date.month == until_date.month && from_date.year == until_date.year
      date_format = "same_month"
      dates.merge!(until_day: until_day, month: from_month, year: from_year)
    else
      until_month = month_names[until_date.month]

      dates.merge!(from_month: from_month, until_month: until_month, until_day: until_day)

      if from_date.year == until_date.year
        date_format = "different_months_same_year"
        dates.merge!(year: from_year)
      else
        until_year = until_date.year

        date_format = "different_years"
        dates.merge!(from_year: from_year, until_year: until_year)
      end
    end

    I18n.t("date_range.#{format}.#{date_format}", dates.merge(sep: separator))
  end

end

And the following anywhere in your locales (I use active_support.en.yml:

en:
  date_range:
    long:
      different_months_same_year: "%{from_month} %{from_day} %{sep} %{until_month} %{until_day}, %{year}"
      different_years: "%{from_month} %{from_day}, %{from_year} %{sep} %{until_month} %{until_day}, %{until_year}"
      same_month: "%{month} %{from_day} %{sep} %{until_day}, %{year}"
    short:
      different_months_same_year: "%{from_month} %{from_day} %{sep} %{until_month} %{until_day}, %{year}"
      different_years: "%{from_month} %{from_day}, %{from_year} %{sep} %{until_month} %{until_day}, %{until_year}"
      same_month: "%{month} %{from_day} %{sep} %{until_day}, %{year}"

Then whenever you want to display a date range, just use the following:

<%= date_range(from_date, until_date) %>

Note that there are 2 options in the code above:

format which can be either :short or :long (for short month names or full names)

separator to specify the separator to use between dates

6 Responses
Add your response

You could probably make this a bit more readable and shorter if you called I18n.t("date_range.#{format}.#{date_format}", dates) at the end, once, and created a dates hash with the dates before hand.

Also, you use things like from_date.day after having already defined a variable from_day.

over 1 year ago ·

Thanks @niuage, I've updated my answer with your recommendations and it does make it more readable.

over 1 year ago ·

Thanks for this! There's a small bug though: If your dates are Feb 10, 2014 - Feb 10, 2015, the result will be incorrect. You need to change

if from_date.month == until_date.month

to

if from_date.month == until_date.month && from_date.year == until_date.year
over 1 year ago ·

thanks @dopa, I've fixed the code.

over 1 year ago ·

Hello! Me and my colleague have wroted the gem based on your article. Thanks for your work and please look at githib: https://github.com/darkleaf/date_range_formatter

over 1 year ago ·

@darkleaf nice work, I've actually implemented a gem myself, just forgot to mention it here: https://github.com/mbillard/time_will_tell

It also has a helper for another thing I do with datetimes regularly which is to display distances of time but precisely (ex: "3 hours 42 minutes" instead of "3 hours ago").

over 1 year ago ·