skip to content

In Ruby, how do Time.parse and Time.strptime from the time library differ, and which should read externally supplied timestamps?

level: middleimportance: should knowfreq 40%

answer

  1. guessing versus a declared format
  2. require "time" first
  3. missing parts filled from now
  4. 02/05/2026 is 2 May
  5. ArgumentError on failure

basics

~20 s

Time.parse guesses the format heuristically and fills missing parts from the current date; Time.strptime(string, format) accepts only the declared format and raises ArgumentError otherwise. For external input, prefer strptime or a strict parser such as Time.iso8601.

solid answer

~40 s

Both come from the `time` library, so they need `require "time"`. `Time.parse` hands the string to `Date._parse`, which recognises many shapes: it fills missing upper parts (year, date) from **now**, so `Time.parse("12:00")` is today at noon; reads slash dates day-first, so `"02/05/2026"` is 2 May; and silently ignores unknown zone abbreviations, treating the value as local. It raises `ArgumentError` only when it finds nothing, as in `"no time information"`. `Time.strptime(str, "%d/%m/%Y %H:%M")` matches the format you declare and raises `ArgumentError` ("invalid date or strptime format") on a mismatch. For input from other systems use `strptime`, `Time.iso8601`, or the strict core `Time.new(string)`, which parses ISO-like strings since Ruby 3.2 and rejects date-only strings since 3.3.

code

ruby · 13 lines
ruby
require "time"

Time.parse("12:00")                 # today at 12:00, local
Time.parse("02/05/2026")            # 2026-05-02 00:00, day-first
Time.parse("2026-09-30 10:00 XYZ")  # unknown zone ignored: local
Time.parse("hello")                 # ArgumentError: no time information

Time.strptime("30/09/2026 18:00 +0200", "%d/%m/%Y %H:%M %z")
# => 2026-09-30 18:00:00 +0200
Time.strptime("30/09/2026", "%Y-%m-%d")
# ArgumentError: invalid date or strptime format

Time.iso8601("2026-09-30T16:00:00Z") # strict, UTC

go deeper

for a junior

Recall that parse guesses, strptime takes an explicit format, both need require "time", and both raise ArgumentError when they cannot produce a time.

for a middle

Explain the gap-filling from now, the day-first slash order, the ignored zone abbreviations, and the strict alternatives iso8601, strptime and Time.new with a string.

for a senior

Show how silent mis-parses corrupt data between systems, and argue for strict parsing at every boundary with explicit offsets and a rescue that reports bad input.

for a principal

Set the contract: services exchange ISO 8601 with offsets only, heuristic parsing is confined to human input, and parse failures are counted rather than swallowed.

## Where the parsers live Core `Time` can build and format times, but most parsing methods come from the **`time`** library, a default gem that ships with Ruby. After `require "time"`, `Time` gains class methods such as `Time.parse`, `Time.strptime`, `Time.iso8601` (alias `xmlschema`), `Time.rfc2822` and `Time.httpdate`. In a script that has not loaded it, `Time.parse` raises `NoMethodError`. The library itself loads `date`, because all of these delegate the actual text scanning to `Date._parse` or `Date._strptime`. ## Time.parse: the heuristic parser `Time.parse(string, now = Time.now)` does not know the format in advance. It scans the string for anything that looks like a date, a time or a zone and builds a `Time` from what it finds: - **Missing upper parts come from `now`.** `Time.parse("12:00")` is today at 12:00; `Time.parse("Aug 31")` is 31 August of the current year. - **Missing lower parts become their minimum.** A date alone is midnight. - **Slash dates are read day-first.** `"02/05/2026"` is 2 May 2026, which surprises anyone sending month-first dates. - **Zone abbreviations are limited.** It understands the RFC 822 abbreviations (such as `EST` or `PST`) and the system zone; any other abbreviation is ignored and the time is taken as local. - **It fails only on nothing.** `Time.parse("hello")` raises `ArgumentError` (`no time information in "hello"`); a partly understood string does not fail. - **Length is capped.** The underlying `Date._parse` rejects strings longer than 128 characters with `ArgumentError`. The library's own documentation calls it a fail-safe fallback, such as `Time.rfc2822(date) rescue Time.parse(date)`, and warns that a failure should still be checked. That is the right mental model: good for forgiving input typed by a person, risky for data exchanged between systems. ## Time.strptime: the declared-format parser `Time.strptime(string, format)` takes the same directives as `strftime` (`%Y`, `%m`, `%d`, `%H`, `%M`, `%S`, `%z` and others) and matches the string against them: 1. The string must fit the format; otherwise it raises `ArgumentError` with `invalid date or strptime format`. 2. Nothing is guessed about field order: `"%d/%m/%Y"` and `"%m/%d/%Y"` mean what they say. 3. Include `%z` when the input carries an offset; without one the result is local time. A **block** on either method converts two-digit years, for example `{ |y| y + 2000 }`. ## Strict parsers for known standards | Method | Accepts | On bad input | |---|---|---| | `Time.iso8601(s)` | XML Schema dateTime, such as `2026-09-30T18:00:00+02:00` | `ArgumentError` | | `Time.rfc2822(s)` | email-style dates | `ArgumentError` | | `Time.httpdate(s)` | HTTP-date headers | `ArgumentError` | | `Time.new(s)` (core) | `YYYY-MM-DD HH:MM:SS` with optional fraction and offset, `T` allowed | `ArgumentError` | `Time.new` with a string is core behaviour, added in **Ruby 3.2** and made stricter in **3.3**: `Time.new("2023-12-20")` now raises `ArgumentError` (`no time information`) instead of returning a surprising value. Its `in:` keyword only supplies a default: an offset inside the string wins. ## The zone of the result Every parser above returns a **local** time when the input carries no offset, so the result depends on the server's zone. For inputs known to be UTC but written without an offset, core `Time.new(string, in: "UTC")` applies `in:` as a default, while an offset inside the string still wins. With `strptime`, keep `%z` in the format and require senders to include the offset: a missing offset then fails loudly instead of being guessed. ## Testing parsers - Pass a fixed `now` as the last argument of `Time.parse` or `Time.strptime`, so partial inputs resolve the same way in every run. - Include inputs from both sides of a daylight-saving change, and one longer than 128 characters. - Assert on `utc_offset` as well as on the wall-clock fields. ## Choosing - Data from another system: `Time.iso8601` or `Time.strptime` with an explicit format, including the offset. - Timestamps you produced yourself: whatever strict method mirrors the formatter you used. - A human typing something vague into a search box: `Time.parse`, with an `ArgumentError` rescue and a sanity check on the result. - Dates without times: `Date.strptime` or `Date.iso8601` from the `date` library.

  • In Ruby, why can Time.parse return a wrong value instead of raising on bad input?
    Because it scans for anything date-like and fills gaps: upper parts from `now`, lower parts with minimums, day-first slash order, and unknown zone abbreviations dropped. It raises only when nothing usable is found. A malformed but partly readable string therefore produces a plausible, wrong `Time`, which is worse than an exception for data exchanged between systems.
  • In Ruby 4.0, what does Time.new("2026-09-30 18:00:00 +0200", in: "UTC") return?
    `2026-09-30 18:00:00 +0200`. When `Time.new` parses a string that already carries an offset, the `in:` keyword is only a default and is ignored. Without an offset in the string, `in:` decides the zone.

saying these in an interview costs you the question

  • Time.parse raises ArgumentError whenever the input is not a valid date.
  • Time.parse reads 02/05/2026 as February 5th.
  • Time.parse is available without requiring anything.
  • Time.strptime guesses the field order like parse does.
  • An unknown zone abbreviation makes Time.parse raise.