lubridate ymd_hms() in R: Parse Date-Times From Strings
The ymd_hms() function in lubridate parses year-month-day hour-minute-second strings into a POSIXct date-time. It auto-detects separators, defaults to UTC, and accepts an explicit tz argument for any Olson time zone.
ymd_hms("2024-01-15 14:30:00") # basic parse, returns POSIXct (UTC)
ymd_hms("2024-01-15T14:30:00Z") # ISO 8601 with Z suffix
ymd_hms("20240115143000") # no separators
ymd_hms("2024/01/15 14:30:00", tz = "Asia/Kolkata") # custom timezone
ymd_hm("2024-01-15 14:30") # no seconds
ymd_h("2024-01-15 14") # hour only
ymd_hms(x, truncated = 3) # allow missing H/M/S
parse_date_time(x, orders = c("ymdHMS","mdYHM")) # mixed ordersNeed explanation? Read on for examples and pitfalls.
What ymd_hms() does in one sentence
ymd_hms("2024-01-15 14:30:00") parses the string and returns a POSIXct date-time. Unlike ymd(), which returns a Date (no time), ymd_hms() returns POSIXct (a date plus a time-of-day plus a time zone). The default zone is UTC.
The function is forgiving about separators and padding. "2024-01-15 14:30:00", "2024/01/15T14:30:00", and "20240115 143000" all parse to the same instant.
Syntax
ymd_hms(x, tz = "UTC", locale = ..., truncated = 0, quiet = FALSE). x is a character vector or factor.
The return type is the key contrast with ymd(). POSIXct stores an instant as seconds since 1970-01-01 UTC; Date stores a count of days. You need POSIXct whenever the time component matters: log analysis, event scheduling, duration arithmetic at sub-day resolution.
tz argument is parsed as a label, not converted. ymd_hms("2024-01-15 14:30:00", tz = "America/New_York") interprets "14:30" as Eastern time. It does NOT shift a UTC time into Eastern. To convert an existing POSIXct, use with_tz() instead.Five common patterns
1. Basic datetime parse
The T separator and trailing Z (Zulu = UTC) are both recognized. ISO 8601 is the most portable datetime format and ymd_hms() handles it without configuration.
2. Explicit time zone
tz = "America/New_York" labels the input as Eastern. with_tz() then converts the same instant to UTC for storage. List valid zones with OlsonNames().
3. Partial times with ymd_hm() and ymd_h()
Each variant fills missing components with zero. Pick the one that matches your data; do not mix variants in a single vector (use parse_date_time for that).
4. Truncated input via truncated=
truncated = 3 tells ymd_hms() it is OK if up to 3 trailing components (S, then M, then H) are missing. Missing pieces become zero. This is the single-call alternative to dispatching between ymd_hms, ymd_hm, and ymd.
5. Vectorized over a column
ymd_hms() is fully vectorized. After parsing, hour(), minute(), second(), floor_date() all chain naturally inside mutate().
tzone attribute or the session's Sys.timezone()). Two POSIXct values can print differently but represent the same moment. Always reason about the stored instant first, the displayed string second.ymd_hms() vs as.POSIXct() vs strptime() vs anytime
Four datetime parsers with different trade-offs.
| Function | Package | Format detection | Default tz | Best for |
|---|---|---|---|---|
ymd_hms() |
lubridate | Auto, year-first | UTC | Most cases, ISO-like input |
as.POSIXct() |
base R | Manual format= |
Sys.timezone() |
Strict format, no dependency |
strptime() |
base R | Manual format | Sys.timezone() |
Returns POSIXlt (component access) |
anytime::anytime() |
anytime | Auto, very loose | Sys.timezone() |
Truly chaotic mixed inputs |
For everyday work, ymd_hms() is the default. Switch to base R when reproducibility across environments matters and you want a strict format. The session-default tz in base R is a frequent source of surprises; lubridate's UTC default is more predictable.
Common pitfalls
Pitfall 1: silent UTC default. ymd_hms("2024-01-15 14:30:00") returns "14:30 UTC" even if your data was recorded locally. If the source is local, pass tz = "..." explicitly or you will be off by hours.
Pitfall 2: DST gap and overlap. During spring-forward, an hour does not exist. ymd_hms("2024-03-10 02:30:00", tz = "America/New_York") returns NA because 02:30 was skipped. During fall-back, 01:30 occurs twice and lubridate picks the first (pre-shift) instance.
tz = "EST" is not portable, since "EST" can mean US Eastern Standard or Australian Eastern Standard depending on the system. Always use Olson zones like "America/New_York" or "Australia/Sydney".Pitfall 3: factor inputs. ymd_hms() accepts factors but converts to character first. If your factor levels are out of order, the underlying integer codes are irrelevant; only the level strings matter.
Try it yourself
Try it: Parse the strings "2024-06-15 09:00:00", "2024-06-15 09:00", and "2024-06-15" as one vector. Save the result to ex_dt.
Click to reveal solution
Explanation: truncated = 3 tells ymd_hms() to accept inputs missing up to three trailing components (S, M, H). Missing pieces default to zero, so a date-only string parses as midnight.
Related lubridate functions
After mastering ymd_hms, look at:
ymd_hm(),ymd_h(): shorter time variantsmdy_hms(),dmy_hms(): other date orderings with full timeparse_date_time(): try multiple orders in one callwith_tz(),force_tz(): convert versus relabel a time zonehour(),minute(),second(): extract components from POSIXctfloor_date(),ceiling_date(),round_date(): bucket datetimesnow(): current POSIXct in the session zone
For an introduction to the lubridate family overall, see the lubridate package guide. The official reference is at lubridate.tidyverse.org.
FAQ
How do I parse a datetime string in R?
Use lubridate::ymd_hms("2024-01-15 14:30:00") for year-first input. The function returns a POSIXct value defaulting to UTC. For other orderings use mdy_hms() (month first) or dmy_hms() (day first). All variants accept a tz argument when the input is local time.
What is the difference between ymd and ymd_hms?
ymd() returns a Date object that stores only year, month, and day. ymd_hms() returns a POSIXct object that stores a full instant with hour, minute, second, and time zone. Use ymd() for calendar dates and ymd_hms() for events with sub-day resolution.
How do I handle timezones with ymd_hms?
Pass tz = "Olson/Zone" to label the input as that zone. To convert an already-parsed POSIXct to another zone, use with_tz(). To relabel without converting (fix a wrong zone), use force_tz(). Always prefer Olson names like "Europe/London" over abbreviations like "GMT".
Why does ymd_hms return NA for a valid-looking string?
The most common causes are a daylight-saving gap (the time was skipped), a non-Olson zone abbreviation, or a separator the parser does not recognize. Run lubridate::ymd_hms(x, quiet = FALSE) to see the warning, then check the input against OlsonNames() or use parse_date_time with explicit orders.
Is ymd_hms faster than as.POSIXct?
For large vectors, lubridate::fast_strptime() is the fastest parser when you know the exact format. ymd_hms() is slightly slower because it auto-detects. as.POSIXct() with a format argument is comparable. For one-off scripts the difference is negligible; for millions of rows use fast_strptime().