lubridate makes R date-times parse, inspect and shift across time zones
Make working with dates in R just that little bit easier
At a glance
- What is it?
- lubridate is an R package that adds format aware parsing, component accessors, time zone helpers and three time span classes to base R, aimed at analysts who keep losing an afternoon to POSIXct quirks and ambiguous date strings.
- Who is it for?
- Adopt lubridate when your R code parses dates from strings, reads or writes year, month and weekday components, or moves timestamps between time zones, since each of those tasks maps onto a documented function. Leave it out when you only pass base R Date and POSIXct values around untouched, or when your team standardises on a different date-time package already.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 130 days ago.
- What is it written in?
- Mainly R, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Why base R date-times are hard to reason about
The project's own framing is blunt about the problem. R commands for date-times are generally unintuitive and change depending on the type of date-time object being used, and the methods we use with date-times must cope with time zones, leap days, daylight savings times and other time related quirks, and R lacks these capabilities in some situations. That is the whole pitch: lubridate makes it easier to do the things R does with date-times and possible to do the things R does not. The audience is not the R runtime author but the analyst or data engineer who receives a column of strings such as 4/1/17 and needs to know what it means before it reaches a group_by or a plot. The package ships as part of the tidyverse family, its homepage is https://lubridate.tidyverse.org, and a cheatsheet is published through the rstudio cheatsheets repository for quick reference. For newcomers the project points to the date and times chapter in R for data science as the recommended starting point rather than to the function reference.
Format aware parsers replace ambiguous string handling
The first family of functions is parsing, and the point is that the format is stated in the function name instead of buried in a format argument. The project lists easy and fast parsing of date-times with ymd(), ymd_hms, dmy(), dmy_hms and mdy(), and the examples show the same idea with a compact numeric literal and a slash separated string. A parser is chosen for the order the data actually uses, and the result comes back as a plain date rather than a timezone stamped instant, which is the behaviour you want before you decide what zone the value belongs to. The difference from the base R route matters: strptime relies on a format string, and a mismatch between that string and the data produces a silently wrong or missing value rather than an error. The lubridate parsers remove the format argument entirely, so there is no string to get wrong, at the cost of needing to know which order the source data uses:
ymd(20101215)
#> [1] "2010-12-15"
mdy("4/1/17")
#> [1] "2017-04-01"Note what these two calls have in common: no format string, and a result printed without a zone or a time.
Component accessors that also work as setters
Once a value is parsed, the second family of functions reads and writes its parts: year(), month(), mday(), hour(), minute() and second(), plus weekday helpers such as wday() and wday() with a label argument. The assignment form is the interesting part, because these are not plain getters. You can write a new year onto an existing date with the standard replacement syntax, and the weekday changes accordingly, which the project demonstrates by taking a day of the month parsed from a British format and then assigning a different year, after which the weekday label moves from Sunday to Friday. That behaviour falls out of the underlying calendar arithmetic rather than from string manipulation, which is exactly why the package exists. Note the convention visible in the examples: dates are printed without a time and without a zone, and the month accessor returns a plain number such as 10, which is what makes it usable as a grouping key:
bday <- dmy("14/10/1979")
month(bday)
#> [1] 10
wday(bday, label = TRUE)
#> [1] Sun
#> Levels: Sun < Mon < Tue < Wed < Thu < Fri < Sat
year(bday) <- 2016
wday(bday, label = TRUE)
#> [1] Fri
#> Levels: Sun < Mon < Tue < Wed < Thu < Fri < Satwith_tz changes what you see, force_tz changes what you mean
Time zone handling gets two helpers, and the documentation is explicit that they are not interchangeable. with_tz changes printing, so the same instant is shown in the requested zone; force_tz changes time, so the wall clock reading is kept while the zone label changes. The contrast is easiest to see in the example: a value parsed as 2010-12-13 15:30:30 is displayed in UTC, then shown as 09:30:30 CST under with_tz because the instant moved, while force_tz prints 15:30:30 CST because the clock face was held still and only the zone was relabelled. Getting this distinction right is what separates a correct daily aggregation from one that shifts by an hour twice a year. The two functions are documented as helper functions for handling time zones, and the daylight savings behaviour of periods, discussed next, depends on this distinction holding cleanly:
time <- ymd_hms("2010-12-13 15:30:30")
time
#> [1] "2010-12-13 15:30:30 UTC"
# Changes printing
with_tz(time, "America/Chicago")
#> [1] "2010-12-13 09:30:30 CST"
# Changes time
force_tz(time, "America/Chicago")
#> [1] "2010-12-13 15:30:30 CST"Durations, periods and intervals: three different answers to how long
Lubridate also expands the type of mathematical operations that can be performed with date-time objects by introducing three new time span classes borrowed from the Joda time library. Durations measure the exact amount of time between two points. Periods accurately track clock times despite leap years, leap seconds and daylight savings time. Intervals are described as a protean summary of the time information between two points. The reason to care is that a duration and a period answer different questions. Adding a duration of one day always moves 24 hours of elapsed time, so a day that loses an hour to a daylight savings change still shifts by the same elapsed amount, while adding a period of one day means the same clock time tomorrow, which the documentation contrasts with the fact that R lacks some of these capabilities on its own. For anyone computing billing periods, subscription renewals or shift schedules, choosing between the two is the difference between a calendar answer and an elapsed-time answer.
Installing from CRAN or GitHub and building the docs locally
Installation is a single call, and three routes are offered: the whole tidyverse, the package on its own, or the development version from GitHub through devtools. The first two lines are commented in the source file, the third is the one you would actually run to track the newest commits. The development route needs devtools, which appears as its own commented install line:
# The easiest way to get lubridate is to install the whole tidyverse:
install.packages("tidyverse")
# Alternatively, install just lubridate:
install.packages("lubridate")
# Or the development version from GitHub:
# install.packages("devtools")
devtools::install_github("tidyverse/lubridate")A session normally starts by loading the package:
library(lubridate, warn.conflicts = FALSE)The repository also ships a Makefile for contributors, with targets for version information, install, site and test, and a site target that regenerates the NAMESPACE with devtools::document() and builds the pkgdown site with a preview. The project is published on CRAN, which means ordinary users should not need the repository at all, and the presence of revdep/ and vignettes/ directories shows that reverse dependency checks and vignettes are part of the release routine rather than an afterthought.
Limits: custom licensing, a version gap, and no hidden timezone handling
Three caveats sit under the tidy surface. First, licensing: the repository carries a custom licence that GitHub cannot classify, with LICENSE and LICENSE.md files at the top level, so the terms are not the usual MIT or GPL text and should be read before the package is vendored into a commercial product. Second, the release history shows v1.9.5 on 2026-02-09 and v1.9.4 on 2026-01-29 following v1.9.2 from 2023-02-14, and the last push to the repository was on 2026-06-15, so the versioning you will install from CRAN may not include everything that has landed since. Third, lubridate does not remove the underlying R date-time types, it adds a friendlier layer over them, and the documentation does not explain how the package resolves historical time zone rules for dates far in the past, where the operating system time zone database is the deciding factor. If your data contains dates from decades ago or spans a daylight savings boundary with a subscription billing cycle, test those cases directly rather than assuming the helpers cover them.
Editorial conclusion
Adopt lubridate when your R code parses dates from strings, reads or writes year, month and weekday components, or moves timestamps between time zones, since each of those tasks maps onto a documented function. Leave it out when you only pass base R Date and POSIXct values around untouched, or when your team standardises on a different date-time package already. Verify first that install.packages("lubridate") pulls a version whose parsing behaviour matches your data, that force_tz is used only where a fixed offset is genuinely wanted, and that the custom licence in the LICENSE file clears your distribution policy.
Frequently asked questions
How do I install lubridate in R?
The README gives install.packages("tidyverse") for the whole suite or install.packages("lubridate") for this package alone. A development version can be installed from GitHub with devtools::install_github("tidyverse/lubridate"), which requires devtools.
What is the difference between with_tz and force_tz in lubridate?
with_tz changes printing, so the instant is displayed in the requested zone, while force_tz changes time, so the same clock reading is kept and only the zone label changes. The README shows a value printed as 09:30:30 CST under with_tz and as 15:30:30 CST under force_tz.
How do durations and periods differ in lubridate?
Durations measure the exact amount of time between two points, while periods accurately track clock times despite leap years, leap seconds and daylight savings time. The package introduces both classes, along with intervals, borrowing the model from the Joda time library.
How do I parse a date string with lubridate?
Pick the parser that matches the order in the data: ymd() for year, month and day, dmy() for day, month and year, mdy() for month, day and year, with ymd_hms and dmy_hms for values that carry a time as well. The README also shows month() and wday() for reading components back out.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/tidyverse-lubridate)Was this useful?