rubyguides

Date Time in Ruby: Instants, Calendar Days, and Timestamps

Ruby gives you everything you need to work with date and time values out of the box. Whether you are timestamping logs, building a calendar feature, or handling date time operations for user-facing features, the standard library has you covered.

This guide walks through the core classes, common operations, and practical patterns you’ll reach for every day.

TL;DR

Use Time for instants, Date for calendar days, and DateTime only when you need a date-oriented API that still carries a clock value. Parse known formats with strptime, format output with strftime, and keep timezone conversion close to the edge of your app. Ruby’s date and time classes stay easy to reason about when you decide early whether you are working with a moment or with a day.

The time class

Time is Ruby’s primary class for working with dates and times. It’s built into the language and ready to use without any require statement.

Time is usually the default because it handles instants, arithmetic, sorting, and timezone-aware output without extra ceremony. If you need the current moment for logging or scheduling, start here.

Getting the current time

Time.now  # => 2026-03-31 16:00:00 +0000

Time.now returns a Time object representing the current moment in your system’s local timezone.

That makes Time.now a good boundary point for application code. Fetch the time once, pass it through your methods, and avoid calling it repeatedly when you want a stable timestamp.

Creating a specific time

Pass year, month, day, and optionally hour, minute, second to create a specific point in time:

Time.new(2025, 6, 15)           # => 2025-06-15 00:00:00 +0000
Time.new(2025, 6, 15, 10, 30)   # => 2025-06-15 10:30:00 +0000
Time.new(2025, 6, 15, 10, 30, 45) # => 2025-06-15 10:30:45 +0000

The positional arguments above use the local system timezone by default. When you want the result in UTC instead, which is common in logs, databases, and API responses, call Time.utc with the same year, month, day, hour, and minute values. The method name makes the intent clear: anyone reading the code later knows the value is UTC without having to check the surrounding context.

Time.utc(2025, 6, 15, 10, 30)  # => 2025-06-15 10:30:00 UTC

Using utc makes the timezone explicit, which helps when the value will be stored, compared, or sent to another service. It also keeps examples easy to read because the clock value no longer depends on the machine running the code.

The date class

For pure date work without a time component, Ruby’s Date class lives in the standard library and needs a require:

require "date"

Date.today  # => #<Date: 2026-03-31>
Date.new(2025, 12, 25)  # => #<Date: 2025-12-25>

Date is useful when you’re tracking calendar days and don’t care about hours or minutes.

Use Date for birthdays, billing cycles, deadlines, and any rule that belongs to a day rather than a moment. That distinction keeps the code from smuggling in a time component you do not actually need.

The datetime class

DateTime extends Date with time-of-day support. Use it when you need the precision of Time but want Date-style parsing and arithmetic:

require "date"

DateTime.now          # => #<DateTime: 2026-03-31T16:00:00+00:00>
DateTime.new(2025, 6, 15, 10, 30, 45)  # => #<DateTime: 2025-06-15T10:30:45+00:00>

In practice, Time handles most use cases. Date and DateTime shine when you need date arithmetic or parsing dates without times.

When you are unsure which one to choose, prefer Time and move to Date only when the business rule is clearly day-based. That habit avoids a lot of accidental complexity later.

Parsing strings into time objects

Turning a string into a Time object is a daily task — parsing logs, user input, API responses.

Time.parse

require "time"

Time.parse("2025-06-15")              # => 2025-06-15 00:00:00 +0000
Time.parse("2025-06-15 10:30:45")     # => 2025-06-15 10:30:45 +0000

Time.parse uses the current system timezone unless the string includes timezone info.

That default is convenient for quick scripts, but it can hide ambiguity in production code. If the source data matters, make the timezone part of the input or convert it immediately after parsing.

Date.parse

require "date"

Date.parse("2025-06-15")   # => #<Date: 2025-06-15>
DateTime.parse("2025-06-15 10:30")  # => #<DateTime: 2025-06-15T10:30:00+00:00>

Date.parse is handy when the text is already date-shaped and you do not want the time component involved at all. DateTime.parse is the better choice when the same input also carries a clock value.

Parsing without raising errors

If the string format is unpredictable, Time.strptime gives you control:

require "time"

Time.strptime("15/06/2025", "%d/%m/%Y")   # => 2025-06-15 00:00:00 +0000
Time.strptime("10:30 AM", "%I:%M %p")     # => current date with 10:30 AM

strptime parses according to the format string you provide rather than guessing.

That predictability is why strptime is so useful in application code. When the input format is fixed, you want a parser that either succeeds in the exact way you expect or fails loudly.

Formatting time with strftime

strftime (string format time) formats a Time object into a human-readable string using format directives.

time = Time.new(2025, 6, 15, 10, 30, 45)

time.strftime("%Y-%m-%d")        # => "2025-06-15"
time.strftime("%H:%M:%S")        # => "10:30:45"
time.strftime("%B %d, %Y")       # => "June 15, 2025"
time.strftime("%d/%m/%Y %H:%M")  # => "15/06/2025 10:30"

Common format directives:

DirectiveMeaningExample
%YFour-digit year2025
%mZero-padded month06
%dZero-padded day15
%HHour (24-hour, 00-23)10
%IHour (12-hour, 01-12)10
%MMinute (00-59)30
%SSecond (00-59)45
%pAM or PMAM
%BFull month nameJune
%bAbbreviated monthJun

The directives are easy to compose once you get used to them. You can build formats for logs, reports, API payloads, and user-facing strings without leaving the standard library.

Timezone handling

Working with utc

Time.now.utc  # => 2026-03-31 16:00:00 UTC

Calling utc returns a new Time object in UTC without modifying the original.

That copy-on-convert behavior is helpful because it keeps the original value intact. If you later need the local version again, you still have it.

Converting between timezones

For timezone-aware applications, Ruby’s TZInfo library handles conversions:

require "tzinfo"

tz = TZInfo::Timezone.get("America/New_York")
time = tz.local_time(2025, 6, 15, 10, 30, 45)
time.strftime("%H:%M %Z")  # => "10:30 EDT"

Rails: in_time_zone

If you’re working in a Rails application, in_time_zone converts a Time to a specific zone. Rails wraps this in Active Support, so the method is available on any Time object without an extra require. The zone name uses the same IANA identifiers as TZInfo, so you can pass values like "Tokyo", "Eastern Time (US & Canada)", or any valid timezone string. Here is what it looks like in practice:

time = Time.now.in_time_zone("Tokyo")
time.strftime("%H:%M %Z")  # => "01:00 JST"

Time arithmetic

Adding and subtracting time is straightforward — Ruby handles the math for you.

The math is based on seconds, so a one-hour shift is just 3600 and a one-day shift is 86400. That keeps the API simple, but it also means you should be careful around daylight saving changes when the calendar day matters more than the exact number of seconds.

Adding time

future = Time.now + 3600          # 3600 seconds = 1 hour from now
Time.new(2025, 6, 15) + 86400     # 86400 seconds = 1 day later

Adding a fixed offset is the most direct form of time arithmetic. The value is always in seconds, so think in terms of 3600 for an hour and 86400 for a day. This low-level approach keeps the API predictable: there is no hidden rounding or calendar-aware adjustment, which means you always know exactly what the operation does. Once you are comfortable with adding offsets, subtracting works the same way in reverse.

Subtracting time

past = Time.now - 86400           # 1 day ago
difference = Time.now - past      # => 86400.0 (seconds between them)

The result of subtracting one Time from another is always a Float representing the number of seconds between them. This is useful for measuring elapsed time, computing durations, and comparing intervals. Because the return type is always a float, you get sub-second precision for free, which matters when you are benchmarking code or measuring network latency.

Subtracting two times gives a float

start = Time.now
sleep(0.5)
finish = Time.now
duration = finish - start  # => 0.5 (approximately)

The result is always in seconds as a Float.

That return value is useful for timing operations because it gives you fractional precision without extra work. You can compare durations, average them, or turn them into milliseconds when needed.

When your problem is calendar-based rather than stopwatch-based, switch to Date arithmetic. Date objects treat days as the unit, so adding or subtracting an integer moves forward or backward by whole days. This keeps billing cycles, vacation windows, and reminder dates readable without converting everything into timestamps first.

Date arithmetic

require "date"

tomorrow = Date.today + 1    # => one day from now
last_week = Date.today - 7   # => seven days ago
days_between = Date.new(2025, 6, 15) - Date.new(2025, 6, 10)  # => 5

Date arithmetic is usually the cleanest way to express calendar rules. A billing cycle, vacation window, or reminder date tends to read better when you keep it as a day count instead of turning it into timestamps too early.

Comparing times

All time classes support standard comparison operators.

That means they can be sorted, ranged, and compared with the same operators you already use for numbers. The key is to keep the objects in the same family, because a Date and a Time do not mean exactly the same thing even when they look similar in a printout.

early = Time.new(2025, 1, 1)
late  = Time.new(2025, 12, 31)

early < late   # => true
late > early   # => true
early == early # => true

You can sort an array of times the same way you sort numbers or strings. Ruby’s comparison operators are consistent across all time objects, so sort, min, max, and sort_by all work without any extra setup. Sorting is most predictable when all the values share the same timezone; if they come from different zones, convert them to UTC first so the order reflects the same real-world moment.

times = [Time.new(2025, 6, 15), Time.new(2025, 1, 1), Time.new(2025, 12, 31)]
times.sort  # => [2025-01-01, 2025-06-15, 2025-12-31]

Sorting works best when all the values are in the same timezone or all have already been normalized to UTC. If the values come from different zones, convert them first so the order reflects the same real-world moment.

Unix timestamps

A Unix timestamp is the number of seconds since January 1, 1970 UTC. It’s the lingua franca of time representation across systems.

It is compact, portable, and easy to store, which is why so many APIs still use it. The tradeoff is that it is not very human-friendly, so you usually convert it at the edges of your app.

Getting the current timestamp

Time.now.to_i  # => 1743446400 (example)

The integer representation drops sub-second precision, making it ideal for storage, comparison, and exchange between systems that only need second-level accuracy. When you want to go the other direction and turn an integer back into a Time object, Time.at does the conversion cleanly.

Creating time from a timestamp

Time.at(1743446400)  # => 2026-03-31 16:00:00 +0000

Millisecond timestamps

Many APIs, especially those written in JavaScript or Java, return timestamps in milliseconds rather than seconds. The pattern is always the same: divide the millisecond value by 1000.0 before passing it to Time.at. Using a float division ensures the sub-second part is preserved in the resulting Time object.

Time.at(1743446400000 / 1000.0)  # => 2026-03-31 16:00:00 +0000

When you need to go the other direction and produce a millisecond timestamp from a Time object, multiply the float representation by 1000. This is common when building API payloads that expect epoch milliseconds, such as certain payment gateways and analytics services. The result is a float, so call .to_i if the receiving system expects an integer.

Time.now.to_f * 1000  # => 1743446400000.0

Epoch timestamps are the most portable representation of time across languages and platforms. Once you have them in the right format, the next common task is pulling apart a Time object to get at individual fields. Ruby makes this straightforward with a family of accessor methods that return integers for each component.

Extracting components

Pull individual parts out of a Time object:

t = Time.new(2025, 6, 15, 10, 30, 45)

t.year    # => 2025
t.month   # => 6
t.day     # => 15
t.hour    # => 10
t.min     # => 30
t.sec     # => 45
t.wday    # => 0 (Sunday, 0-6)
t.yday    # => 166 (day of year)
t_zone    # => "UTC"

Extracting components is useful when you need to format a report, build a filename, or group records by day or hour. Try to keep that work close to the display layer so the rest of the app can stay on higher-level date and time values.

Choosing the right time type

The best date code usually starts with a small choice: are you modeling a moment, a calendar day, or a value that needs both a date and a time component? Time is the most common answer because it handles instants, arithmetic, and comparisons well. Date is better when the clock is irrelevant. DateTime sits in between, mostly useful when parsing needs to stay close to a calendar-oriented API.

That choice matters because each class communicates something different to the next reader. A Time value says “this happened at a specific instant.” A Date says “this belongs to this day.” A DateTime says “I need both pieces, but I still want calendar-style operations.” When the type matches the idea, the code becomes easier to read before you even look at the method calls.

Once you pick the class, keep the rest of the code honest about that choice. If a rule is day-based, stay in Date until you actually need clock math. If the data comes from a timestamp or needs timezone-aware display, use Time and convert as close to the boundary as possible. If a string format is fixed, use strptime; if you are formatting output for people, use strftime with a small set of directives.

It is also worth separating parsing, conversion, and presentation in your head. Parsing decides what the input means. Conversion changes the timezone or the representation. Presentation turns the value into text. When those three steps are kept separate, the code is much easier to change later because each step has only one job.

The last useful habit is to prefer explicitness over guessing. If the input format is known, say so with a format string. If the timezone matters, set or convert it in one place. If the code is about a day rather than a moment, do not smuggle a time into the value just because the API allows it.

Used that way, Ruby’s date and time classes stay pleasantly small. They do not try to hide the problem for you. They give you the right building blocks, and the rest of the clarity comes from choosing the right one at the right time.

See also