lubridate mdy_hms() in R: Parse US Datetime Strings
The mdy_hms() function in lubridate parses month-first datetime strings (like "01/15/2024 14:30:00" or "January 15, 2024 2:30:00 PM") into a POSIXct value. It auto-detects separators, defaults to UTC, and accepts an Olson tz argument for any time zone.
mdy_hms("01/15/2024 14:30:00") # US slash, 24h time
mdy_hms("January 15, 2024 2:30:00 PM") # 12h with AM/PM
mdy_hms("01-15-2024 14:30:00") # dash separator
mdy_hms(c("01/15/2024 09:00:00", "02/20/2024 17:45:10")) # vector input
mdy_hms("01/15/2024 09:00:00", tz = "America/Chicago") # explicit timezone
mdy_hm("01/15/2024 14:30") # no seconds
mdy_hms(x, truncated = 3) # allow missing H/M/S
parse_date_time(x, orders = c("mdYHMS","mdYHM")) # multi-format fallbackNeed explanation? Read on for examples and pitfalls.
What mdy_hms() does in one sentence
mdy_hms("01/15/2024 14:30:00") parses the string and returns a POSIXct date-time. The "m-d-y" prefix tells lubridate the ordering is month, day, year; the "h-m-s" suffix says the input also carries hour, minute, and second. Output is POSIXct (a single instant plus a time zone), defaulting to UTC.
Use mdy_hms() when the source data writes dates US-style. Spreadsheets, CRM exports, US healthcare records, and many government feeds default to month-first. Pair the parser with the data convention; do not reformat the strings first.
Syntax
mdy_hms(x, tz = "UTC", locale = ..., truncated = 0, quiet = FALSE). x is a character vector or factor.
The return value is POSIXct, the same class returned by Sys.time(). Every downstream lubridate accessor (hour(), minute(), floor_date()) and every base-R operation (difftime, comparison, sorting) works directly on the parsed result.
Five common patterns
1. Standard US slash datetime
The most common case for US data. Output prints ISO-style ("2024-01-15") regardless of the input ordering; the underlying instant is what matters.
2. 12-hour clock with AM/PM
lubridate recognizes "AM" and "PM" tokens and shifts the hour accordingly. Noon parses as 12:00, midnight as 00:00. US-sourced logs and emails frequently use this format; do not hand-convert to 24-hour first.
3. Vector input with mixed separators
mdy_hms() is vectorized. Mixed slashes, dashes, and text months parse correctly inside a single vector, so messy CSV columns rarely need pre-cleaning.
4. Explicit US time zone
tz = "America/Chicago" labels the input as Central time. with_tz() then converts the same instant to UTC for storage or comparison. List valid zones with OlsonNames(); never use ambiguous abbreviations like "CST".
5. Inside a dplyr pipeline
After parsing, hour(), wday(), floor_date(), and the rest of lubridate's accessors chain naturally inside mutate(). The raw string column stays in place so any failed row can still be inspected.
mdy() returns a Date (no time). mdy_hms() returns a POSIXct (full instant). The two are not interchangeable: a Date cannot represent 14:30, and a POSIXct carries a time zone that a Date ignores. Pick the resolution your downstream code expects.mdy_hms() vs ymd_hms() vs dmy_hms() vs parse_date_time()
Pick the parser whose name matches the ordering in the source.
| Parser | Ordering | Example input | Use when |
|---|---|---|---|
mdy_hms() |
Month, day, year | "01/15/2024 14:30:00" |
US data, CRM exports, spreadsheets |
ymd_hms() |
Year, month, day | "2024-01-15 14:30:00" |
ISO 8601, log files, databases |
dmy_hms() |
Day, month, year | "15/01/2024 14:30:00" |
UK, EU, Australian data |
parse_date_time() |
Multiple | Mixed: tries each orders= |
Truly mixed or unknown sources |
When two formats co-exist in one column, parse_date_time(x, orders = c("mdYHMS","ymdHMS")) tries each ordering in turn and keeps the first that succeeds. For everyday US data the dedicated mdy_hms() is faster and clearer.
Common pitfalls
Pitfall 1: ambiguous mdy versus dmy strings. mdy_hms("01/02/2024 09:00:00") returns January 2; dmy_hms("01/02/2024 09:00:00") returns February 1. The same string has two valid readings. Confirm the source convention by sampling 5 to 10 rows before choosing a parser.
Pitfall 2: silent UTC default. Without tz, the result is labeled UTC even if the data was captured locally. A 09:00 sales record from a Chicago store becomes "09:00 UTC" (which is 03:00 Central), six hours off. Pass tz = "America/Chicago" whenever the data is local.
mdy_hms("01/15/24 14:30:00") returns 2024. lubridate applies a 30/70 cutoff: "00" to "68" maps to the 2000s, "69" to "99" maps to the 1900s. If your data spans that boundary (rare but possible in legacy archives), prepend the century manually before parsing.Pitfall 3: silent NA on parse failure. A typo, an empty string, or a "N/A" placeholder returns NA with a single warning. Always run sum(is.na(parsed)) after parsing, then inspect x[is.na(parsed)] to see the offending rows. Date bugs almost always trace back to a silent NA at the parse step.
Try it yourself
Try it: Parse the strings "06/15/2024 9:30 AM", "07/04/2024 12:00 PM", and "12/25/2024 11:59 PM" with mdy_hms(). Save the result to ex_dt.
Click to reveal solution
Explanation: mdy_hms() recognizes the AM/PM tokens and shifts the hour accordingly. "12:00 PM" stays at 12:00 (noon), and "11:59 PM" becomes 23:59. No format = string is needed.
Related lubridate functions
After mastering mdy_hms(), the most useful neighbors are:
mdy_hm(),mdy_h(): month-first datetimes without seconds or minutesymd_hms(),dmy_hms(): other date orderings with full timeparse_date_time(): multi-order fallback for genuinely mixed inputswith_tz(),force_tz(): convert versus relabel a time zonehour(),minute(),second(),wday(): extract components from POSIXctfloor_date(),ceiling_date(),round_date(): bucket datetimes to day, hour, minutenow(),today(): current POSIXct or Date in the session zone
The official function reference lives at lubridate.tidyverse.org.
FAQ
How do I parse "01/15/2024 14:30:00" in R?
Use lubridate::mdy_hms("01/15/2024 14:30:00"). The function returns a POSIXct value defaulting to UTC. Pass tz = "America/Chicago" (or another Olson name) when the data is local time. No format = argument is needed; the parser auto-detects separators.
What is the difference between mdy and mdy_hms?
mdy() returns a Date object that stores year, month, and day only. mdy_hms() returns a POSIXct that stores a full instant including hour, minute, second, and time zone. Use mdy() for calendar dates and mdy_hms() for events with sub-day resolution like orders, logs, or appointments.
How do I handle AM/PM with mdy_hms?
mdy_hms() recognizes "AM" and "PM" tokens automatically. mdy_hms("01/15/2024 2:30:00 PM") parses to 14:30 in the result. Noon ("12:00 PM") stays at 12:00 and midnight ("12:00 AM") becomes 00:00. Do not hand-convert to 24-hour time before parsing.
Why does mdy_hms return NA for a valid-looking string?
The most common causes are an unsupported separator, a non-Olson timezone abbreviation in tz, or a day-first input misrouted to mdy_hms(). Run mdy_hms(x, quiet = FALSE) to see the warning, then sanity-check against OlsonNames() or fall back to parse_date_time(x, orders = c("mdYHMS","dmYHMS","ymdHMS")).
How do I convert mdy_hms output to a different time zone?
After parsing, use with_tz(parsed, "Europe/London") to convert the same instant to another zone (the moment in time does not change, only the displayed clock). Use force_tz(parsed, "Europe/London") to relabel without converting (the displayed clock does not change, the underlying instant shifts). The two functions answer different questions; pick deliberately.