lubridate dmy() in R: Parse Day-First Date Strings
The dmy() function in lubridate parses day-first date strings (like "15/01/2024" or "15 January 2024") into R Date objects. It is the right parser for UK, European, Indian, and Australian data sources where the day comes before the month.
dmy("15/01/2024") # European slash format
dmy("15 January 2024") # full month name
dmy("15-Jan-2024") # abbreviated month
dmy("15.01.2024") # German dot separator
dmy(c("15/01/2024", "20/03/2024")) # vector input
dmy_hms("15/01/2024 14:30:00") # date plus time
dmy("15/01/2024", tz = "Europe/London") # with timezoneNeed explanation? Read on for examples and pitfalls.
What dmy() does in one sentence
dmy("15/01/2024") reads a day-first string and returns a Date object. lubridate detects separators (/, -, ., space), accepts numeric or text month names, and is forgiving about padding (1 and 01 both work).
This parser exists because most data sources outside the United States write dates day-first. UK government feeds, European bank exports, Indian e-commerce records, and Australian healthcare data all default to this convention. Reach for dmy() whenever you trust the data follows that ordering.
Syntax
dmy(x, tz = NULL, locale = ..., quiet = FALSE). x is a character vector, factor, or numeric date.
Five common patterns
1. European slash dates
The default for UK forms, French invoices, and most European spreadsheets. Output is always ISO-ordered ("2024-01-15") regardless of how the input was written.
2. Dot-separated German format
German, Swiss, and Nordic data sources use dots as separators. dmy() handles them without any extra arguments. This is the case base R's as.Date() typically fails on without a custom format = string.
3. Date plus time with dmy_hms
dmy_hms() returns a POSIXct (date plus time) instead of a Date. Use dmy_hm() for hour and minute only, dmy_h() for hour only. All three follow the same day-month-year ordering rule.
4. Text month names in mixed languages
lubridate recognizes English full month names ("January") and three-letter abbreviations ("Jan"). For non-English month names, pass locale = "fr_FR.UTF-8" (or the relevant locale) so "janvier" or "März" parse correctly.
5. Specify a European timezone
Without tz, lubridate defaults to UTC. Pass an Olson zone name like "Europe/London", "Europe/Berlin", or "Asia/Kolkata" to fix the interpretation. List all available zones with OlsonNames().
dmy() and Jan 2 under mdy(). The string itself does not encode which ordering applies; only the source convention does. Always confirm the convention before choosing a parser, otherwise half your year is silently shifted.dmy() vs mdy() vs ymd() vs parse_date_time()
Pick the parser whose name matches the ordering in your source data.
| Parser | Ordering | Example input |
|---|---|---|
dmy() |
Day, month, year | "15/01/2024", "15-Jan-2024" |
mdy() |
Month, day, year | "01/15/2024", "Jan 15 2024" |
ymd() |
Year, month, day | "2024-01-15", "20240115" |
parse_date_time() |
Multiple orderings | Mixed: tries each orders= option |
When the source is mixed (a column with both UK and US rows, say), fall back to parse_date_time(x, orders = c("dmy","mdy","ymd")). It tries each format in turn and uses the first that succeeds for each element.
Common pitfalls
Pitfall 1: silent ambiguity with mdy(). dmy("01/02/2024") returns Feb 1; mdy("01/02/2024") returns Jan 2. Both parse without warning. If your source convention is wrong, every date in the first 12 days of each month is silently shifted to the wrong month. Always verify on a date you can recognize, like a known holiday.
Pitfall 2: out-of-range day rejection. dmy("15/13/2024") returns NA, because "13" cannot be a month. This is a useful canary: if you suspect a column might actually be month-first, dmy() will fail loudly on rows where the day exceeds 12.
Pitfall 3: two-digit years. dmy("15/01/24") returns 2024, not 1924. lubridate applies a 30/70 cutoff: years "00" to "68" map to the 2000s, "69" to "99" map to the 1900s. Verify after parsing if your data crosses that boundary.
sum(is.na(parsed)) after parsing and inspect the failing rows. Date bugs almost always trace back to a silent NA at the parse step.A practical dmy() workflow
Most date pipelines that reach for dmy() follow the same five steps.
- Read the raw column as character (CSV imports often do this by default).
- Inspect a sample of 5 to 10 rows. Look for any value where the first token exceeds 12, since that confirms day-first ordering.
- Parse with
dmy()(ordmy_hms()for datetimes). - Validate with
sum(is.na(parsed))and inspect the failed rows. - Extract components with
year(),month(),day()for grouping or filtering.
In a dplyr pipeline the same logic compresses to a single mutate block:
Parsing inside mutate() keeps the raw string column so you can spot-check any rows that failed to parse after the fact.
Try it yourself
Try it: Parse "1 May 2024", "15/06/2024", "04.07.2024" with dmy(). Save the result to ex_dmy.
Click to reveal solution
Explanation: dmy() handles each separator (space, slash, dot) and the text month in one call. Mixed formats inside a single vector parse correctly, which is common when concatenating CSVs from different European sources.
Related lubridate functions
After mastering dmy(), the most useful neighbors are:
mdy(),ymd(): other order parsersdmy_hms(),dmy_hm(),dmy_h(): day-first datetimesparse_date_time(): multi-order fallback for messy inputsyear(),month(),day(): extract date componentsfloor_date(),ceiling_date(): round to day, week, or monthtoday(),now(): current date or datetime
For date arithmetic on the parsed result, see days(), months(), and years() for friendlier offsets than base R's seq.Date(). The full reference is at lubridate.tidyverse.org.
FAQ
How do I convert "15/01/2024" to a Date in R?
Use dmy("15/01/2024") from the lubridate package. It returns a proper Date object that you can sort, filter, plot, and join on. No format = argument is needed; the parser handles slashes, dashes, dots, and spaces automatically.
What is the difference between dmy and mdy in R?
The two parsers differ only in the expected token ordering. dmy() reads day-first input ("15/01/2024", common in the UK and Europe); mdy() reads month-first input ("01/15/2024", common in the US). Both return the same Date output. Pick the one whose name matches the convention of your source data.
Why does dmy("01/02/2024") give February 1, not January 2?
dmy() treats the first token as the day, so it reads "01" as day 1 and "02" as month 2 (February). If your data is American month-first, use mdy() instead, which would return January 2 from the same input. When the convention is unclear, inspect 5 to 10 rows for any value where the first token exceeds 12.
Does dmy() handle dot-separated dates like "15.01.2024"?
Yes. dmy("15.01.2024") returns "2024-01-15" without any extra arguments. lubridate auto-detects dot, slash, dash, and space separators. This is the main reason to prefer dmy() over base R's as.Date(), which requires a custom format = "%d.%m.%Y" for the same input.
How do I handle parse failures from dmy()?
Bad inputs return NA with a warning. Run sum(is.na(dmy(x))) to count failures, then x[is.na(dmy(x))] to see the offending strings. Common causes: empty strings, month-first inputs misrouted to dmy(), or non-date placeholders like "N/A". Always validate before downstream steps depend on the parsed column.