There are three classes in Ruby that handle date and time. Date and DateTime are both from date library. And there’s another class Time from its own time library.

Both DateTime and Time can be used to handle year, month, day, hour, min, sec attributes. But on the backend side, Time class stores integer numbers, which presents the seconds intervals since the Epoch. We also call it unix time.

Time class has some limit.  Firstly,  it can only represent dates between 1970 and 2038 ( since ruby v1.9.2, it can represent 1823-11-12 to 2116-02-20 ).  Secondly,  the time zone is limited to UTC and the system’s local time zone in ENV['TZ'].

What’s more, Rails provide a really good time class called ActiveSupport::TimeWithZone. This class is similar as ruby’s Time class, with the support for time zones.

One thing worth to notice is that, Rails will always convert time zone to UTC when ever it writes to or reads from the database, no matter what time zone you set in the configuration file. You can use `<attribute_name>_before_type_cast` to get the original time that store in database. For example ( e.g. created_at):

object.created_at_before_type_cast

Below are some useful snippet code that I use most to deal with date and time.

1. Time

# Get current time using the time zone of current local system
Time.now
 
# Get current time using the time zone of UTC
Time.now.utc
 
# Get the unix timestamp of current time => 1364046539
Time.now.to_i
 
# Convert from unix timestamp back to time form
Time.at(1364046539)
 
# Use some string format, this one returns => "March 23, 2013 at 09:48 AM"
Time.at(1364046539).strftime("%B %e, %Y at %I:%M %p")

For the time class, I prefer to convert it to unix timestamp, because the integer form presentation can be easily stored, indexed or ordered. Also it can be used in the situation where the distance between two time is more important than the actually time, like in tweets, where it’s better to show ’1 minute ago’ instead of the actually time.

More time string format can be found at ruby’s Time document.

2. Time with Zone (ActiveSupport::TimeWithZone)

TimeWithZone instances implement the same API as Ruby Time instances.

# Set the time zone of the TimeWithZone instance
Time.zone = 'Central Time (US & Canada)'
 
# Get current time using the time zone you set
Time.zone.now
 
# Convert from unix timestamp back to time format using the time zone you set
Time.zone.at(1364046539)
 
# Convert from unix timestamp back to time format using the time zone you set,
#  and the required string format => "03/23/13 09:48 AM"
Time.at(1364046539).in_time_zone("Eastern Time (US & Canada)").strftime("%m/%d/%y %I:%M %p")

Rails also provides a lot of very useful helper methods, they are using pretty straightforward english format.

# Get the date time of n day, week, month, year ago
1.day.ago
2.days.ago
1.week.ago
3.months.ago
1.year.ago

# beginning of or end of the day, week, month ...
Time.now.beginning_of_day
30.days.ago.end_of_day
1.week.ago.end_of_month

# feel free to use those methods from Time class
1.week.ago.beginning_of_day.to_i

You can find more methods by checking the doc.

Time distance

Rails also provides time distance methods to get the twitter styled time format inside of ActionView::Helpers

# inside of your .erb view files

diff = Time.now.to_i - 1.hour.ago.to_i
distance_of_time_in_words(diff)

distance_of_time_in_words_to_now(1.hour.ago)

Use customized time zone by user

For Rails application, you can set the default time zone under /config/application.rb

# /config/application.rb
config.time_zone = 'Central Time (US & Canada)'

To get a list of time zone names supported by Rails, you can use

ActiveSupport::TimeZone.zones_map(&:name)

Normally, we would like to provide a form for user to choose their desired time zone. You can create a string field (e.g. :time_zone), and the form can be implemented as

<%= f.time_zone_select :time_zone %>
 
# use US time zone only, with default
<%= f.time_zone_select :time_zone, ActiveSupport::TimeZone.us_zones, :default => "Pacific Time (US & Canada)" %>

To make user’s time zone setting work, we can use the method called use_zone, which override the Time.zone locally inside the supplied block.

To use this method. We can add around_filter inside of  ApplicationController as suggested by railscast, like this

# /app/controllers/application_controller.rb
 
around_filter :user_time_zone, if: :current_user
 
private
 
  def current_user
    @current_user ||= User.find(session[:user_id]) if session[:user_id]
  end
  helper_method :current_user
 
  def user_time_zone(&block)
    Time.use_zone(current_user.time_zone, &block)
  end

3. Date and DateTime

For most cases, the Time class with the time zone of Rails’ ActiveSupport is sufficient. But sometimes, when you just need a string format of year,  month and day, Date class still worth a try.

Remember to add require ‘date’ when you encounter undefined method error.

Both Date and DateTime come with parse method, which allows you to convert a date string into an Ruby object. There is also a decent method called strptime, which allows you to explicitly define the template format of the date and time you are going to parse. Examples are shown below:

# use parse to create a date object 
Date.parse("2014-1-1")
DateTime.parse("2014-1-1")

# explicitly define the date format using strptime
Date.strptime("12/13/2013", "%m/%d/%Y")

You can also generate a list of date string easily. For example, in one of my applications,  we use date string as keys to store count information in Redis.

# Generate date string in 30 days
days_str = (30.days.ago.to_date...Date.today).map{ |date| date.strftime("%Y:%m:%d") }

Date also comes with a very handy array of day names, that you can easily use it in your drop-down select field.

Date.today.wday # the day of week
Date::DAYNAMES[Date.today.wday] # => "Saturday"
Date::DAYNAMES.each_with_index.to_a #  => [["Sunday", 0], ["Monday", 1], ["Tuesday", 2], ["Wednesday", 3], ["Thursday", 4], ["Friday", 5], ["Saturday", 6]]
 
# Use it in select field like this
# ...
# <%= select(:report, :day, Date::DAYNAMES.each_with_index.to_a, {:selected => 1}, :class => "form-control") %>

Time, Date, DateTime are all interchangeable by using to_time, to_date, to_datetime methods

# Convert DateTime to Time
DateTime.parse('March 3rd 2013 04:05:06 AM').to_time.class # => Time
 
# Convert Time to Date
1.day.ago.to_date.class # => Date

After this, you can use to_i method to change a Date or DateTime object into an Unix timestamp which represents an integer number of seconds since the Epoch.

# Parse date string and convert to timestamp in seconds
Date.parse("2014-1-1").to_time.to_i
DateTime.parse("2014-1-1").to_time.to_i

One Thought on “Date, Time, DateTime in Ruby and Rails

  1. Pingback: Ruby on Rails lietuviškai » Geras paaiškinimas kaip naudoti Date, Time, DateTime objektus su Ruby on Rails

Leave a Reply

Post Navigation