Dates and Times with lubridate

· 5 min read · Updated March 11, 2026 · beginner
r tidyverse lubridate dates datetime

Working with dates and times is one of the most frustrating parts of data analysis. Base R has the POSIXct and Date classes, but using them feels like fighting the language rather than working with it. Enter lubridate — a tidyverse package that makes dates and times genuinely enjoyable to work with.

This guide covers the essential lubridate functions you’ll use daily: parsing strings into date objects, extracting date components, and performing arithmetic with dates.

Why lubridate?

Base R stores dates as the number of days since 1970-01-01 and datetimes as seconds since that epoch. This underlying numeric representation is elegant but painful to work with directly. You end up writing cryptic strptime formats and wrestling with time zones.

lubridate solves this with intuitive function names that match the date formats you encounter. The function name itself tells R how to parse the string. No more memorizing format codes.

Installing and Loading lubridate

install.packages("lubridate")
library(lubridate)

Or install the full tidyverse:

install.packages("tidyverse")
library(tidyverse)

Parsing Dates

The parsing functions are lubridate’s signature feature. The function name encodes the order of year (y), month (m), and day (d) in your string.

# Year-Month-Day
ymd("2024-03-15")
ymd(20240315)  # Also works with numeric input

# Day-Month-Year
dmy("15-03-2024")

# Month-Day-Year
mdy("03/15/2024")

# Year-Month
ym("2024-03")

# Various separators work automatically
ymd("2024/03/15")
ymd("2024.03.15")
dmy("15.03.2024")

What if your data has mixed formats? lubridate handles most separators automatically:

# Multiple formats in one vector
dates <- c("2024-01-15", "15/01/2024", "20240115")
ymd(dates)  # Works because formats are consistent

The key insight: choose the function whose letters match the order in your string. That’s it.

Parsing Date-Times

For timestamps with hours, minutes, and seconds, add the time components to the function name:

# Year-Month-Day Hour-Minute-Second
ymd_hms("2024-03-15 14:30:00")

# With different time formats
mdy_hm("03/15/2024 2:30 PM")
dmy_hms("15-03-2024 14:30:45")

By default, lubridate assigns UTC as the time zone. You can specify a different zone:

ymd_hms("2024-03-15 14:30:00", tz = "America/New_York")
ymd_hms("2024-03-15 14:30:00", tz = "Europe/London")

Getting Current Date and Time

Two convenience functions return the system’s current date and time:

# Current date
today()
# [1] "2026-03-11"

# Current date-time (in your local timezone)
now()
# [1] "2026-03-11 07:15:00 GMT"

These are useful for calculating ages, durations, or any time-sensitive comparison.

Extracting Date Components

Once you have a date or datetime, you need to pull out individual components. lubridate provides accessor functions for this:

date <- ymd("2024-03-15")

year(date)    # 2024
month(date)   # 3
day(date)     # 15

The functions return numeric values you can use for grouping, filtering, or creating new features:

# Extract and label months
month(date, label = TRUE)
# [1] Mar
# 12 Levels: Jan < Feb < Mar < ...

# Day of week
wday(date)        # 6 (Saturday)
wday(date, label = TRUE)
# [1] Sat
# Levels: Sun < Mon < Tue < Wed < Thu < Fri < Sat

# Day of year
yday(date)  # 75

# Week of year
week(date)  # 11

For datetimes, you can extract time components too:

datetime <- ymd_hms("2024-03-15 14:30:45")

hour(datetime)      # 14
minute(datetime)   # 30
second(datetime)   # 45
tz(datetime)       # "UTC"

You can also set components using the same functions:

# Change the month
month(date) <- 6
date
# [1] "2024-06-15"

# Change the hour
hour(datetime) <- 18
datetime
# [1] "2024-03-15 18:30:45"

Date Arithmetic

This is where lubridate really shines. You can add and subtract timespans from dates using simple arithmetic.

Durations

Durations represent an exact number of seconds. They’re the most straightforward way to do arithmetic:

# Add 10 days
today() + days(10)

# Subtract 3 hours
now() - hours(3)

# Add multiple units
ymd("2024-03-15") + weeks(2) + days(3)

Available duration functions: seconds(), minutes(), hours(), days(), weeks(), months()

# Duration between two dates
start <- ymd("2024-01-01")
end <- ymd("2024-03-15")

end - start
# Time difference of 74 days

The result is a difftime object. To get the numeric value:

as.numeric(end - start, units = "days")
# 74

Periods

Periods are different. They represent human-friendly units like “1 month” or “2 hours” — units whose length in seconds can vary. This matters for months and years:

# Adding a month to January 31st
jan31 <- ymd("2024-01-31")
jan31 + months(1)
# 2024-03-02 (R correctly rolls over to March 2nd)

# Adding a month to March 31st
mar31 <- ymd("2024-03-31")
mar31 + months(1)
# 2024-05-01 (April has only 30 days)

This behavior is usually what you want. If you need exact seconds regardless of calendar quirks, use durations instead.

Intervals

An interval is a span of time anchored to specific start and end points:

# Create an interval
start <- ymd("2024-01-01")
end <- ymd("2024-03-15")

interval <- interval(start, end)

# Check if a date falls within an interval
ymd("2024-02-15") %within% interval
# TRUE

# Or if two intervals overlap
another_interval <- interval(ymd("2024-03-01"), ymd("2024-04-01"))
int_overlaps(interval, another_interval)
# TRUE

Intervals are particularly useful for detecting overlaps in event data or calculating precise spans across DST boundaries.

Time Zones

Two functions handle time zones without changing the underlying moment in time:

# with_tz: display the same moment in a different timezone
now_ny <- now(tz = "America/New_York")
with_tz(now_ny, tz = "Europe/London")

# force_tz: change the timezone label (changes the moment!)
force_tz(now_ny, tz = "Europe/London")

The difference is subtle but important. with_tz converts the clock time. force_tz pretends the same numeric time belongs to a different zone.

Common Patterns

A few patterns you’ll use constantly:

# Calculate age from birthdate
birthdate <- ymd("1990-05-15")
age <- interval(birthdate, today()) / years(1)
as.numeric(age)

# Find the last day of a month
floor_date(ymd("2024-03-15"), "month") + months(1) - days(1)
# Or use the built-in
stamp("2024-03-31")(1)

# Round to nearest unit
round_date(ymd_hms("2024-03-15 14:30:30"), "hour")
# 2024-03-15 15:00:00

# Create sequences of dates
seq(from = today(), by = "1 week", length.out = 10)

See Also

Summary

lubridate transforms date handling from a chore into something you don’t dread. The key functions to remember:

TaskFunctions
Parsingymd(), dmy(), mdy(), ymd_hms()
Currenttoday(), now()
Extractyear(), month(), day(), hour(), wday()
Arithmeticdays(), weeks(), months(), interval()
Time zoneswith_tz(), force_tz()

Start with parsing and component extraction. Add date arithmetic when you need it. You’ll wonder how you ever worked with dates in R without lubridate.