lubridate ymd_hm() in R: Parse Date-Times Without Seconds
The ymd_hm() function in lubridate parses year-month-day hour-minute strings (no seconds) into a POSIXct date-time. It defaults to UTC, auto-detects separators, and treats the seconds field as zero.
ymd_hm("2024-01-15 14:30") # basic parse, returns POSIXct (UTC)
ymd_hm("2024-01-15T14:30") # ISO 8601 separator
ymd_hm("202401151430") # no separators
ymd_hm("2024/01/15 14:30", tz = "Asia/Kolkata") # custom timezone
ymd_hm(x, truncated = 1) # allow missing minutes
ymd_hm(c("2024-01-15 14:30", "2024-01-15 15:00"))# vectorized
ymd_hm("2024-01-15 14:30", quiet = TRUE) # suppress warnings
parse_date_time(x, orders = c("ymdHM", "ymdHMS"))# mixed precisionNeed explanation? Read on for examples and pitfalls.
What ymd_hm() does in one sentence
ymd_hm("2024-01-15 14:30") parses the string and returns a POSIXct at second 00. The function is one of lubridate's nine fixed-order parsers. The first three letters (ymd) name the date order; the trailing hm says the time has hours and minutes but no seconds.
Use ymd_hm() when your data records minute-level precision: meeting times, shift schedules, log lines that report HH:MM, survey timestamps. For anything finer, use ymd_hms(); for anything coarser, use ymd_h() or ymd().
Syntax
ymd_hm(x, tz = "UTC", locale = ..., truncated = 0, quiet = FALSE). x is a character vector or factor of date-time strings.
The return type is POSIXct, identical to what ymd_hms() returns. Internally lubridate stores the moment as seconds since 1970-01-01 UTC, then prints it according to the active time zone. The seconds component is always 00 after a ymd_hm() parse.
tz argument when the source is local time. ymd_hm("2024-01-15 14:30", tz = "America/Chicago") labels the input as Central. Without tz, lubridate assumes UTC and your downstream conversions will be off by the UTC offset.Five common patterns
1. Standard minute-precision parse
The space, the ISO 8601 T, and a slash-separated date all parse to the same instant. ymd_hm() looks for any non-digit run as a separator and ignores it.
2. Explicit time zone
The tz argument names the input. with_tz() then converts the stored instant for display in another zone. Run OlsonNames() to see the full list of valid zones on your system.
3. Truncated input with truncated=
truncated = 2 allows up to two trailing components (M, then H) to be missing. Empty pieces become zero. This avoids dispatching between ymd_hm, ymd_h, and ymd when your data is inconsistent.
4. Vectorized over a data frame column
ymd_hm() is fully vectorized, so it slots into mutate() without purrr::map(). After parsing, hour(), minute(), floor_date(), and arithmetic with minutes(15) all chain naturally.
5. Suppressing parse warnings
Set quiet = TRUE when bad rows are expected and you handle NA downstream. Without it, lubridate emits a warning per unparseable string, which can flood logs in batch jobs.
ymd_hms() only when the seconds field is meaningful (log analysis, instrumentation, financial ticks). Picking the right parser keeps your data type honest about its resolution.ymd_hm() vs ymd_hms() vs ymd_h()
Three sibling parsers for the same date order, different time precision.
| Function | Time precision | Trailing fields filled as | Best for |
|---|---|---|---|
ymd_hms() |
hour, minute, second | none | logs, instrumentation, ISO 8601 timestamps |
ymd_hm() |
hour, minute | second = 00 | meetings, schedules, surveys |
ymd_h() |
hour only | minute = 00, second = 00 | hourly batches, weather data |
All three return POSIXct, so downstream code (hour(), floor_date(), arithmetic) is identical. The only difference is what the parser expects to find. Passing a string with too many components fails, which is sometimes useful as a sanity check.
parse_date_time() covers mixed precision. When one column holds 2024-01-15 14:30 and 2024-01-15 14:30:45 side by side, parse_date_time(x, orders = c("ymdHM", "ymdHMS")) parses each row with the right specification. Faster to write than dispatching by string length.Common pitfalls
Pitfall 1: seconds in the input string. ymd_hm("2024-01-15 14:30:45") parses as 2024-01-15 14:30:00 UTC and emits a warning. The :45 is discarded. If your data sometimes has seconds, use ymd_hms() or parse_date_time(); do not rely on ymd_hm() to truncate cleanly.
Pitfall 2: silent UTC default. Like all lubridate parsers, ymd_hm() returns UTC unless you pass tz. Downstream filters like between(ts, ymd_hm("2024-01-15 09:00"), ymd_hm("2024-01-15 17:00")) then run on UTC bounds, which is rarely what you want for "business hours". Pass tz once, at parse time.
NA. ymd_hm("2024-03-10 02:30", tz = "America/New_York") is NA because 02:30 was skipped during spring-forward. The function does not push the time forward or roll back; you get NA and a warning. Handle these by shifting to UTC before parsing, or filtering known DST dates.Pitfall 3: factors with stale levels. ymd_hm() accepts factors but converts via as.character(). Only the level strings matter, so a reordered factor still parses correctly; the integer codes are ignored.
Try it yourself
Try it: Parse the strings "2024-06-15 09:00", "2024-06-15 09", and "2024-06-15" as one vector. Save the result to ex_dt.
Click to reveal solution
Explanation: truncated = 2 lets ymd_hm() accept inputs missing up to two trailing components (M, then H). Missing pieces default to zero, so the date-only string parses as midnight UTC.
Related lubridate functions
After mastering ymd_hm(), look at:
ymd_hms(),ymd_h(): sibling parsers with different time precisionmdy_hm(),dmy_hm(): other date orderings, minute precisionparse_date_time(): try multiple orders in one callwith_tz(),force_tz(): convert versus relabel a time zonehour(),minute(): extract components from POSIXctfloor_date(),ceiling_date(): bucket datetimes to a unitnow(): current POSIXct in the session zone
For a tour of the lubridate family overall, see the lubridate package guide. The official function reference is at lubridate.tidyverse.org.
FAQ
What does ymd_hm do in R?
ymd_hm() from the lubridate package parses a character string in year-month-day hour-minute order into a POSIXct date-time. The seconds field is set to zero. The default time zone is UTC; pass tz = "Olson/Zone" to label the input as local time. Use it for minute-precision data like meeting times, shift schedules, and survey timestamps.
What is the difference between ymd_hm and ymd_hms?
ymd_hm() expects hours and minutes only; ymd_hms() expects hours, minutes, and seconds. Both return POSIXct, but feeding a seconds-containing string to ymd_hm() discards the seconds and emits a warning. If your data has mixed precision, use parse_date_time(x, orders = c("ymdHM", "ymdHMS")) so each row parses with the correct specification.
How do I parse a datetime without seconds in R?
Call lubridate::ymd_hm("2024-01-15 14:30"). For non-year-first inputs use mdy_hm() (US month-first) or dmy_hm() (European day-first). All three accept a tz argument, are vectorized over character vectors, and return POSIXct. The truncated argument lets you accept partial inputs in the same call.
How do I handle timezones with ymd_hm?
Pass tz = "Olson/Zone" at parse time, for example ymd_hm("2024-01-15 14:30", tz = "Europe/London"). To convert an already-parsed POSIXct to a different zone, use with_tz(). To relabel without converting (when the original zone was wrong), use force_tz(). Prefer Olson names like "America/Los_Angeles" over abbreviations like "PST", which are ambiguous.
Why does ymd_hm return NA for a string I think is valid?
The three most common causes are a daylight-saving gap (the time was skipped that day), seconds present in the input (try ymd_hms()), or a non-standard separator. Run the call with quiet = FALSE to see the warning text, and check the time zone against OlsonNames(). For chaotic inputs use parse_date_time with explicit orders.