Dates and Times

Packages for this section

library(tidyverse)
# library(lubridate)

lubridate is the package that handles dates and times, but is now part of the tidyverse, so no need to load separately.

Dates

  • Dates represented on computers as “days since an origin”, typically Jan 1, 1970, with a negative date being before the origin:
mydates <- c("1931-08-05", "1970-01-01", "2007-09-04")
tibble(text = mydates) %>%
  mutate(
    d = as.Date(text),
    numbers = as.numeric(d)
  ) -> somedates
somedates

Doing arithmetic with dates

  • Dates are “actually” numbers, so can add and subtract:
edit_date <- as.Date("2026-06-15")
edit_date
[1] "2026-06-15"
edit_date + 50 # add 50 days
[1] "2026-08-04"
birth_date <- as.Date("2007-09-04")
edit_date - birth_date
Time difference of 6859 days

Reading in dates from a file

  • read_csv and the others can guess that you have dates, if you format them as year-month-day, like column 1 of this .csv:
date,status,dunno
2011-08-03,hello,August 3 2011
2011-11-15,still here,November 15 2011
2012-02-01,goodbye,February 1 2012

… continued

  • Then read them in:
my_url <- "http://datafiles.ritsokiguess.site/mydates.csv"
dates1 <- read_csv(my_url)
Rows: 3 Columns: 3
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr  (2): status, dunno
date (1): date

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
  • read_csv guessed that the 1st column is dates, but not 3rd.

The data as read in

dates1

Dates in other formats

  • dates1 shows that dates are best stored as text in format yyyy-mm-dd (ISO 8601 standard).
  • To deal with dates in other formats, use lubridate functions to convert. For example, dates in US format with month first:
tibble(us_dates = c("05/27/2012", "01/03/2016", 
                    "12/31/2015")) %>%
  mutate(proper_dates = mdy(us_dates))

If you have UK dates

tibble(ukdates = c("27/5/2012", "1/3/2016", 
                   "12/31/2015")) %>%
  mutate(uk = dmy(ukdates))
  • The second one is ambiguous: is it March 1 (UK), Jan 3 (US)?
  • Note that day and month are gotten correct in uk, except for the last one, which makes no sense as a date written this way.

Our data frame’s last column:

  • Back to this:
dates1
# A tibble: 3 × 3
  date       status     dunno           
  <date>     <chr>      <chr>           
1 2011-08-03 hello      August 3 2011   
2 2011-11-15 still here November 15 2011
3 2012-02-01 goodbye    February 1 2012 
  • Column dunno has month (name), day, year in that order.

so interpret as such

(dates1 %>% mutate(date_from_text = mdy(dunno)) -> dates2)

Are they really the same?

  • Compare dates originally read in (in date) with those converted from dunno:
dates2 %>% mutate(equal = identical(date, date_from_text))
# A tibble: 3 × 5
  date       status     dunno            date_from_text equal
  <date>     <chr>      <chr>            <date>         <lgl>
1 2011-08-03 hello      August 3 2011    2011-08-03     TRUE 
2 2011-11-15 still here November 15 2011 2011-11-15     TRUE 
3 2012-02-01 goodbye    February 1 2012  2012-02-01     TRUE 
  • The two columns of dates are all the same.

The opposite problem

  • We know how to convert text to dates, but what if we want to convert dates to text, for example to display them in a certain format?

  • Can use strftime (challenging) or stamp (easier).

Using stamp

  • To use stamp:
    • first, create a “stamp” with the kind of format you want (but not necessarily the right date)
    • then, apply the stamp to your date(s):
my_stamp <- stamp("21 Jan, year 1999")
dates1 %>% 
  mutate(date_stamp = my_stamp(date))

When you run stamp

you get a message:

my_stamp <- stamp("21 Jan, year 1999")
Multiple formats matched: "%d %Om, year %Y"(1), "%d %b, year %Y"(1)
Using: "%d %b, year %Y"

These codes are what strftime uses:

  • %d is the day of the month (with leading zero)
  • %b is the abbreviated month name
  • %Y is the four-digit year

See here for all the codes.

strftime(dates1$date, format = "%d %b, year %Y")
[1] "03 Aug, year 2011" "15 Nov, year 2011" "01 Feb, year 2012"

Making dates from pieces

Starting from this file:

my_url <- "http://datafiles.ritsokiguess.site/pieces.txt"
dates3 <- read_delim(my_url, " ")
dates3

Literally make-ing a date

dates3 %>% 
  mutate(the_date = make_date(year, month, day)) -> newdates
newdates

Extracting information from dates

newdates %>%
  select(the_date) %>% 
  mutate(
    yr = year(the_date),
    mon = month(the_date, label = TRUE),
    day = day(the_date),
    weekday = wday(the_date, label = TRUE)
  )

Dates and times

  • Standard format for times is to put the time after the date, hours, minutes, seconds:
(dd <- tibble(text = c(
  "1970-01-01 07:50:01", "2007-09-04 15:30:00",
  "1940-04-15 06:45:10", "2016-02-10 12:26:40"
)))
# A tibble: 4 × 1
  text               
  <chr>              
1 1970-01-01 07:50:01
2 2007-09-04 15:30:00
3 1940-04-15 06:45:10
4 2016-02-10 12:26:40

Converting text to date-times:

  • Then get from this text using ymd_hms:
dd %>% mutate(dt = ymd_hms(text)) %>% pull(dt)
[1] "1970-01-01 07:50:01 UTC" "2007-09-04 15:30:00 UTC"
[3] "1940-04-15 06:45:10 UTC" "2016-02-10 12:26:40 UTC"

Timezones

  • Default timezone is UTC, “Universal Coordinated Time”. Change it via tz= and the name of a timezone:
dd %>% 
  mutate(dt = ymd_hms(text, tz = "America/Toronto")) -> dd
dd %>% mutate(zone = tz(dt))

Finding a timezone name

  • Use OlsonNames(). Some of them:
sample(OlsonNames(), 10)
 [1] "America/Edmonton"     "America/Grenada"      "America/Coyhaique"   
 [4] "Europe/Gibraltar"     "America/Rankin_Inlet" "Australia/Sydney"    
 [7] "Europe/Belgrade"      "Pacific/Midway"       "Etc/GMT-3"           
[10] "Atlantic/Reykjavik"  
  • Timezones are a mess. If you are arranging a meeting with people in different time zones, you should give times in UTC, and then everybody can convert for their time zone and daylight time.

Extracting time parts

  • As you would expect:
dd %>%
  select(-text) %>%
  mutate(
    h = hour(dt), sec = second(dt),
    min = minute(dt), zone = tz(dt)
  )
# A tibble: 4 × 5
  dt                      h   sec   min zone           
  <dttm>              <int> <dbl> <int> <chr>          
1 1970-01-01 07:50:01     7     1    50 America/Toronto
2 2007-09-04 15:30:00    15     0    30 America/Toronto
3 1940-04-15 06:45:10     6    10    45 America/Toronto
4 2016-02-10 12:26:40    12    40    26 America/Toronto

Same times, but different time zone:

dd %>%
  select(dt) %>%
  mutate(oz = with_tz(dt, "Australia/Sydney"))
  • that is, if you are in Toronto and call someone in Sydney, Australia at the time shown in dt, the time it will be for them is as shown in oz.

In more detail

dd %>%
  mutate(oz = with_tz(dt, "Australia/Sydney")) %>%
  pull(oz)
[1] "1970-01-01 22:50:01 AEST" "2007-09-05 05:30:00 AEST"
[3] "1940-04-15 21:45:10 AEST" "2016-02-11 04:26:40 AEDT"

“Australian Eastern Time”, Standard or Daylight. Note when the Australian summer is.

How long between date-times?

  • We may need to calculate the time between two events. For example, these are the dates and times that some patients were admitted to and discharged from a hospital:
admit,discharge
1981-12-10 22:00:00,1982-01-03 14:00:00
2014-03-07 14:00:00,2014-03-08 09:30:00
2016-08-31 21:00:00,2016-09-02 17:00:00

Do they get read in as date-times?

  • These ought to get read in and converted to date-times:
my_url <- "http://ritsokiguess.site/datafiles/hospital.csv"
stays <- read_csv(my_url)
stays
  • and so it proves.

Subtracting the date-times

  • In the obvious way, this gets us an answer:
stays %>% mutate(stay = discharge - admit)
  • Number of hours; hard to interpret.
  • The drtn at the top of the column stands for “duration”.

Days

  • Fractional number of days would be better:
stays %>% 
  mutate(
    stay_days = as.period(admit %--% discharge) / days(1))
  • The distinction between “duration” and “period” is explained later.

Completed days

  • Pull out with day() etc, as for a date-time:
stays %>% 
  mutate(
    stay = as.period(admit %--% discharge),
    stay_days = day(stay),
    stay_hours = hour(stay)
    ) %>%
  select(starts_with("stay"))

Comments

  • Date-times are stored internally as seconds-since-something, so that subtracting two of them will give, internally, a number of seconds.
  • Just subtracting the date-times is displayed as a time (in units that R chooses for us).
  • Convert to fractional times via a “period”, then divide by days(1), months(1) etc.
  • These ideas useful for calculating time from a start point until an event happens (in this case, a patient being discharged from hospital).