lubridate dmy_hms() in R: Parse Day-First Datetime Strings
The dmy_hms() function in lubridate parses day-first datetime strings (like "15/01/2024 14:30:00" or "15 January 2024 14:30:00") into a POSIXct value. It auto-detects separators, defaults to UTC, and accepts an Olson tz argument for any time zone.
dmy_hms("15/01/2024 14:30:00") # UK/EU slash, 24h time
dmy_hms("15.01.2024 14:30:00") # German period separator
dmy_hms("15-01-2024 14:30:00") # dash separator
dmy_hms(c("15/01/2024 09:00:00", "20/02/2024 17:45:10")) # vector input
dmy_hms("15/01/2024 09:00:00", tz = "Europe/London") # explicit timezone
dmy_hm("15/01/2024 14:30") # no seconds
dmy_hms(x, truncated = 3) # allow missing H/M/S
parse_date_time(x, orders = c("dmYHMS","dmYHM")) # multi-format fallbackNeed explanation? Read on for examples and pitfalls.
What dmy_hms() does in one sentence
dmy_hms("15/01/2024 14:30:00") parses the string and returns a POSIXct date-time. The "d-m-y" prefix tells lubridate the ordering is day, month, 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.
Reach for dmy_hms() whenever the source data is written day-first. UK government feeds, EU statistical releases, Australian logistics exports, and most non-US enterprise systems format dates this way. Match the parser to the data convention, not the other way around.
Syntax
dmy_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(), wday(), floor_date()) and every base-R operation (difftime, comparison, sorting) works directly on the parsed result.
Five common patterns
1. Standard UK/EU slash datetime
The default case for UK, Irish, Australian, and most EU data. Output prints ISO-style ("2024-01-15") regardless of the input ordering; the underlying instant is what matters.
2. Period separators (German, Swiss, Austrian)
German-speaking countries write dates with dots. dmy_hms() handles both numeric-dot and text-month variants. Pass a locale = argument for non-English month names; the available locales depend on your operating system.
3. Vector input with mixed separators
dmy_hms() is vectorized. Mixed slashes, dashes, and text months parse correctly inside a single vector, so messy CSV columns rarely need pre-cleaning. The parser tries each candidate format and keeps the one that succeeds.
4. European time zone with DST
tz = "Europe/London" labels the input as local. lubridate honors British Summer Time automatically: a June 15 instant prints as BST (UTC+1), a January 15 instant prints as GMT (UTC+0). with_tz() then converts to UTC for storage. List valid zones with OlsonNames(); never use ambiguous abbreviations like "BST" or "CET" in the tz argument.
5. Inside a dplyr pipeline
After parsing, hour(), wday(), floor_date(), and the rest of lubridate's accessors chain naturally inside mutate(). Keep the raw string column in place so any failed row can still be inspected without re-running the import.
dmy_hms(); a UK analyst pulling US data still uses mdy_hms(). The parser is for the source format, not your reading habits. Choose by inspecting 5 to 10 rows of the actual source, never by where the code runs.dmy_hms() vs ymd_hms() vs mdy_hms() vs parse_date_time()
Pick the parser whose name matches the ordering in the source.
| Parser | Ordering | Example input | Use when |
|---|---|---|---|
dmy_hms() |
Day, month, year | "15/01/2024 14:30:00" |
UK, EU, AU, IN data |
ymd_hms() |
Year, month, day | "2024-01-15 14:30:00" |
ISO 8601, log files, databases |
mdy_hms() |
Month, day, year | "01/15/2024 14:30:00" |
US data, CRM exports |
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("dmYHMS","ymdHMS")) tries each ordering in turn and keeps the first that succeeds. For everyday day-first data the dedicated dmy_hms() is faster and clearer.
Common pitfalls
Pitfall 1: ambiguous dmy versus mdy strings. dmy_hms("01/02/2024 09:00:00") returns February 1; mdy_hms("01/02/2024 09:00:00") returns January 2. The same string has two valid readings. Confirm the source convention by sampling 5 to 10 rows (look for any day > 12 to disambiguate) 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 dispatch record from a Berlin warehouse becomes "09:00 UTC" (which is 10:00 or 11:00 Berlin depending on DST), one or two hours off. Pass tz = "Europe/Berlin" whenever the data is local time.
tz = "Europe/London" is invalid (the clock jumps from 01:00 to 03:00). lubridate returns the nearest valid instant and issues a warning. If your data covers transition weekends, run sum(is.na(parsed)) and inspect the warnings carefully.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 "01/06/2024 09:30:00", "14/07/2024 12:00:00", and "25/12/2024 23:59:00" with dmy_hms(). Save the result to ex_dt.
Click to reveal solution
Explanation: dmy_hms() reads the first number as the day. "01/06/2024" becomes June 1 (not January 6), and "14/07/2024" becomes July 14. The same strings parsed with mdy_hms() would return different (or invalid) dates.
Related lubridate functions
After mastering dmy_hms(), the most useful neighbors are:
dmy_hm(),dmy_h(): day-first datetimes without seconds or minutesymd_hms(),mdy_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 "15/01/2024 14:30:00" in R?
Use lubridate::dmy_hms("15/01/2024 14:30:00"). The function returns a POSIXct value defaulting to UTC. Pass tz = "Europe/London" (or another Olson name) when the data is local time. No format = argument is needed; the parser auto-detects separators and accepts slashes, dashes, periods, or text months.
What is the difference between dmy and dmy_hms?
dmy() returns a Date object storing year, month, and day only. dmy_hms() returns a POSIXct storing a full instant including hour, minute, second, and time zone. Use dmy() for calendar dates (birthdays, deadlines) and dmy_hms() for events with sub-day resolution like shipments, logs, or appointments.
How do I parse German dates with periods like 15.01.2024?
dmy_hms("15.01.2024 14:30:00") works directly. lubridate accepts periods, dashes, and slashes as date separators without any extra argument. For German month names ("15. Januar 2024 14:30:00"), pass locale = "de_DE.UTF-8" so the parser recognizes localized text.
Why does dmy_hms return NA for a valid-looking string?
The most common causes are a month-first input misrouted to dmy_hms() (mdy_hms("01/15/2024 14:30:00") works; dmy_hms() returns NA because day 15 is fine but month 15 is not), an unsupported separator, or a non-Olson timezone abbreviation in tz. Run dmy_hms(x, quiet = FALSE) to see the warning, then sanity-check against OlsonNames() or fall back to parse_date_time(x, orders = c("dmYHMS","ymdHMS")).
How do I convert dmy_hms output to a different time zone?
After parsing, use with_tz(parsed, "America/New_York") to convert the same instant to another zone (the moment in time does not change, only the displayed clock). Use force_tz(parsed, "America/New_York") to relabel without converting (the displayed clock does not change, the underlying instant shifts). The two functions answer different questions; pick deliberately.