10.2 lubridate: Dates and Times

DATA 412/612 · Week 10 · PDF · Source (.qmd)

Parse with ymd(), pull pieces out, and know duration vs period vs interval.

1. Load lubridate and look at the class

tidyverse attaches lubridate, but load it yourself so the parsers are obvious.

library(tidyverse)
library(lubridate)

Two classes matter. A Date is a calendar day. A POSIXct is a datetime: a Date plus a time of day, stored as seconds since 1970-01-01 UTC. class() tells you which you have.

ymd("2017-01-31")
## [1] "2017-01-31"
ymd_hms("2017-01-31 20:11:59")
## [1] "2017-01-31 20:11:59 UTC"

2. Parsers and constructors

Name the function after the order of the pieces. The separators can vary.

ymd("2017-01-31")
## [1] "2017-01-31"
mdy("January 31st, 2017")
## [1] "2017-01-31"
dmy("31-Jan-2017")
## [1] "2017-01-31"
ymd_hms("2017-01-31 20:11:59")
## [1] "2017-01-31 20:11:59 UTC"
mdy_hm("01/31/2017 08:15")
## [1] "2017-01-31 08:15:00 UTC"

make_date() and make_datetime() build a date from numeric columns.

make_date(2017, 1, 31)
## [1] "2017-01-31"
make_datetime(2017, 1, 31, 20, 11, 59)
## [1] "2017-01-31 20:11:59 UTC"

Put the CSV files from the Data section of the course page in a data/ folder of your project. wmata_ridership.csv has a Date column (ISO text) and a Total column (daily Metro rail rides).

wmata <- read_csv("data/wmata_ridership.csv")
wmata <- wmata |>
  mutate(Date = ymd(Date))

3. Pull pieces out

dt <- ymd_hms("2016-07-08 12:34:56")
year(dt)
## [1] 2016
month(dt)
## [1] 7
month(dt, label = TRUE)
## [1] Jul
## 12 Levels: Jan < Feb < Mar < Apr < May < Jun < Jul < Aug < Sep < ... < Dec
day(dt)
## [1] 8
wday(dt)
## [1] 6
wday(dt, label = TRUE)
## [1] Fri
## Levels: Sun < Mon < Tue < Wed < Thu < Fri < Sat
hour(dt)
## [1] 12

label = TRUE on month() and wday() returns an ordered factor. abbr = FALSE spells the name out.

wmata |>
  mutate(
    year = year(Date),
    month = month(Date, label = TRUE),
    wday = wday(Date, label = TRUE)
  )
## Error:
## ! object 'wmata' not found

4. Durations, periods, and intervals

A duration is an exact number of seconds. ddays(1) is always 86,400 seconds.

A period is a human unit. days(1) is “one calendar day,” which can be 23 or 25 hours when clocks change.

An interval is the span between two instants.

ddays(1)
## [1] "86400s (~1 days)"
days(1)
## [1] "1d 0H 0M 0S"
years(1)
## [1] "1y 0m 0d 0H 0M 0S"
ymd("2016-01-01") + ddays(365)
## [1] "2016-12-31"
ymd("2016-01-01") + days(365)
## [1] "2016-12-31"
ymd("2016-01-01") + years(1)
## [1] "2017-01-01"
int <- interval(ymd("2004-01-01"), ymd("2018-12-21"))
int
## [1] 2004-01-01 UTC--2018-12-21 UTC
as.duration(int)
## [1] "472435200s (~14.97 years)"

2016 is a leap year, so ddays(365) and days(365) land on different days. Use a duration when you want elapsed time. Use a period when you want “same date next month.” Use an interval when you have two endpoints.


5. Time zones

Datetimes default to UTC. force_tz() keeps the clock face and changes the zone (the instant moves). with_tz() keeps the instant and redraws the clock in another zone.

x <- ymd_hms("2020-03-08 01:30:00")
x
## [1] "2020-03-08 01:30:00 UTC"
force_tz(x, "America/New_York")
## [1] "2020-03-08 01:30:00 EST"
with_tz(x, "America/New_York")
## [1] "2020-03-07 20:30:00 EST"

OlsonNames() lists the zone names. "America/New_York" is Eastern time, daylight saving included.

A clock that is missing a timezone is often stored as UTC by accident. On the spring-forward Sunday the computed duration can jump by an hour. force_tz() is the fix when the numbers on the clock were already Eastern.

toy <- tibble(
  start = mdy_hm(c("3/13/2016 1:30", "3/13/2016 1:30")),
  end   = mdy_hm(c("3/13/2016 3:30", "3/13/2016 3:30"))
)
toy |>
  mutate(
    start_ny = force_tz(start, "America/New_York"),
    end_ny = force_tz(end, "America/New_York"),
    hours_utc = as.numeric(end - start) / 3600,
    hours_ny = as.numeric(end_ny - start_ny) / 3600
  )
## # A tibble: 2 × 6
##   start               end                 start_ny           
##   <dttm>              <dttm>              <dttm>             
## 1 2016-03-13 01:30:00 2016-03-13 03:30:00 2016-03-13 01:30:00
## 2 2016-03-13 01:30:00 2016-03-13 03:30:00 2016-03-13 01:30:00
## # ℹ 3 more variables: end_ny <dttm>, hours_utc <dbl>, hours_ny <dbl>

6. Practice

  1. Parse "2010-02-01", "February 1, 2010", "01-02-2010", and "2010-02-01 08:15:00". "01-02-2010" is dmy or mdy depending on the country.
  2. Build dates from this tribble and keep the rows before 2010-02-01.
events <- tribble(
  ~year, ~month, ~day,
  2009,     12,   31,
  2010,      1,   15,
  2010,      2,    1,
  2010,      3,   10
)