Jump to main navigation


Tutorial 3.4 - Time formatting and conversions

27 Mar 2017

It is possible to store and treat dates and times as either as either character strings or numbers. For example, the date of a particular observation could be stored as "29/02/2000" or even as 951746400 (the number of seconds elapsed between midnight January 1st 1970 and midnight February 29th 2000). Alternatively however, dates and times can be represented by one of a number of more specialized date or date/time objects. Such objects have additional properties that make formatting and manipulating dates and times more convenient.

Occasionally, this tutorial will make use of the Sys.time function. This function retrieves the current date and time (according to your system clock) and stores the outcome in a specific date/time format. Obviously, any outputs that result from the Sys.time function will differ each time they are run.

Creating Date and Time objects

From strings (character representations)

There are numerous ways of defining a date or date/time object from a string. The major distinctions between the three methods are highlighted in the following table.

as.Dateas.POSIXctas.POSIXltstrptime
DateYesYesYesYes
Date & timeNoYesYesYes
TimezonesNoYesYesYes
Storagelistlist (POSIXlt)

In each case, the default format of the string is in accordance with the rules of the ISO 8601 international standard and thus expressed as YEAR-MONTH-DAY (all as numerics). Alternative date string formats can be accommodated by specifically defining the format. The most common input (convert to date/time) and output (convert to character string) format specifications/definitions are given in the following table.

CodeValueExample
%dDay of the month (decimal)04
%mMonth (decimal)02
%bMonth (abbreviated)Feb
%BMonth (full name)February
%yYear (2 digits)12
%YYear (4 digit)2012
%aWeekday (abbreviated)Mon
%AWeekday (full name)Monday
%wWeekday (decimal, 0=Sunday)1
%UDay of year (decimal)226
%UWeek of year (decimal, starting Sunday)8
%WWeek of year (decimal, starting Monday)8
%HHour (24 hour)22
%IHour (12 hour)10
%MMinute (decimal)35
%SSecond (decimal)45
%pAM/PM (decimal)PM
%zOffset from GMT-0200
%ZTime zone (character, output only)EST
%xLocal specific Date (%y/%m/%d)2000/02/29
%xLocal specific Time (%H:%M:%S)22:35:45

as.Date

The simplest way to convert a character string to a date object is to use the as.Date function. Internally, this stores the date as the number of days since Jan 1 1970.

> as.Date("1970-01-10")
[1] "1970-01-10"
> as.numeric(as.Date("1970-01-10"))
[1] 9
> as.Date("2000-02-29")
[1] "2000-02-29"
> as.numeric(as.Date("2000-02-29"))
[1] 11016
Alternative date formats are accommodated via the format parameter. R broadly follows C notation for the generation of character strings from combinations of literal characters and variable contents. For those unfamiliar with this scheme, the format string notation consists of a character string that is reproduced in the output after substituting special formating codes (letters proceeded by a '%', see table above) with variable contents.
> as.Date("2000-02-29")
[1] "2000-02-29"
> as.Date("29/02/2000", format="%d/%m/%Y")
[1] "2000-02-29"
> as.Date("Feb 29, 2000", format="%b %d, %Y")
[1] "2000-02-29"
The as.Date function can also convert numeric values into date objects (see below).

as.POSIXct

POSIX (Portable Operating System Interface for Unix) stores dates (including times) as the number of seconds since Jan 1, 1970. The default date format is the same as for as.Date and the default format for the time component is hour:minute:seconds.
> as.POSIXct("2000-01-02 09:22:05")
[1] "2000-01-02 09:22:05 AEST"
> as.numeric(as.POSIXct("2000-01-02 09:22:05"))
[1] 946768925
And use the format to accommodates alternative input formats.
> as.POSIXct("2000-02-29 09:22:05")
[1] "2000-02-29 09:22:05 AEST"
> as.POSIXct("29/02/2000 13:22:05", format="%d/%m/%Y %H:%M:%s")
[1] "1970-01-01 10:00:05 AEST"
> #AM/PM
> as.POSIXct("Feb 29, 2000 10:35PM", format="%b %d, %Y %I:%M%p")
[1] "2000-02-29 22:35:00 AEST"
> #Tuesday in the 9th Week of the year 2000
> as.POSIXct("9Tue, 2000 10:35PM", format="%W%a, %Y %I:%M%p")
[1] "2000-02-29 22:35:00 AEST"
> #60th day of the year 2000
> as.POSIXct("60, 2000 10:35PM", format="%j, %Y %I:%M%p")
[1] "2000-02-29 22:35:00 AEST"
The lubridate way

The lubridate package contains a series of convenience functions for converting strings into POSIX dates. These functions are named in accordance to the broad format of the string date and are clever enough to parse a large variety of formatting strings.

> library(lubridate)
> ymd("2000-02-29")
[1] "2000-02-29"
> dmy("20/2/00")
[1] "2000-02-20"
> ymd_hms("2000-02-29 09:22:05")
[1] "2000-02-29 09:22:05 UTC"
> ymd_hms("2000-02-29 09:22:05PM")
[1] "2000-02-29 21:22:05 UTC"
> dmy_hm("20/2/00 09:22")
[1] "2000-02-20 09:22:00 UTC"

as.POSIXlt (list)

Similar to as.POSIXct, as.POSIXlt converts character strings to date/time in accordance to the POSIX conventions. However, rather than store the date/time as a numeric, it is broken down into each of its components (day, month, year, hour etc) and stored as a list. Nevertheless, all of the methods that can be applied to a as.POSIXct are overloaded and thus outwardly behave (yield) the same.
> as.POSIXlt("2000-02-29 09:22:05")
[1] "2000-02-29 09:22:05 AEST"
> unlist(as.POSIXlt("2000-02-29 09:22:05"))
   sec    min   hour   mday    mon   year   wday   yday  isdst   zone gmtoff 
   "5"   "22"    "9"   "29"    "1"  "100"    "2"   "59"    "0" "AEST"     NA 
> as.POSIXlt("29/02/2000 13:22:05", format="%d/%m/%Y %H:%M:%s")
[1] "1970-01-01 10:00:05 AEST"
> #AM/PM
> as.POSIXlt("Feb 29, 2000 10:35PM", format="%b %d, %Y %I:%M%p")
[1] "2000-02-29 22:35:00 AEST"
> #Tuesday in the 9th Week of the year 2000
> as.POSIXlt("9Tue, 2000 10:35PM", format="%W%a, %Y %I:%M%p")
[1] "2000-02-29 22:35:00 AEST"
> #60th day of the year 2000
> as.POSIXlt("60, 2000 10:35PM", format="%j, %Y %I:%M%p")
[1] "2000-02-29 22:35:00 AEST"
> #different time zones
> as.POSIXlt(Sys.time(), "Australia/Brisbane")
[1] "2017-03-27 15:44:52 AEST"
> as.POSIXlt(Sys.time(), "Australia/Darwin")
[1] "2017-03-27 15:14:52 ACST"
> as.POSIXlt(Sys.time(), "Australia/Perth")
[1] "2017-03-27 13:44:52 AWST"

strptime

strptime converts objects (usually character strings) into POSIXlt objects. The main difference between as.POSIXlt and strptime is that the latter first pre-processes (converts) the input into a character string.
> strptime("29/02/2000 13:22:05", format="%d/%m/%Y %H:%M:%s")
[1] "1970-01-01 10:00:05 AEST"
> #AM/PM
> strptime("Feb 29, 2000 10:35PM", format="%b %d, %Y %I:%M%p")
[1] "2000-02-29 22:35:00 AEST"
> #Tuesday in the 9th Week of the year 2000
> strptime("9Tue, 2000 10:35PM", format="%W%a, %Y %I:%M%p")
[1] "2000-02-29 22:35:00 AEST"
> #60th day of the year 2000
> strptime("60, 2000 10:35PM", format="%j, %Y %I:%M%p")
[1] "2000-02-29 22:35:00 AEST"
> # different timezones
> strptime(Sys.time(), format="%Y-%m-%d %H:%M:%S",tz="Australia/Brisbane")
[1] "2017-03-27 15:44:52 AEST"
> strptime(Sys.time(), format="%Y-%m-%d %H:%M:%S",tz="Australia/Darwin")
[1] "2017-03-27 15:44:52 ACST"
> strptime(Sys.time(), format="%Y-%m-%d %H:%M:%S",tz="Australia/Perth")
[1] "2017-03-27 15:44:52 AWST"

From a numeric time

Each of the above (overloaded) functions can also be used to convert numerics (that represent units of elapsed time) into date/time objects. In each case, the origin must be provided either as a date/time object (or can be automatically converted to such object).

as.Date()

> #ten days passed Jan 1 1970
> as.Date(10, origin="1970-01-01")
[1] "1970-01-11"
> as.Date(10, origin="2000-02-29")
[1] "2000-03-10"
> #create a reference date from a character string
> ref.dt <- as.Date("29/02/2000",format="%d/%m/%Y")
> as.Date(10,origin=ref.dt)
[1] "2000-03-10"
> #same as above, except in a single step the format parameter is passed internally to a further call to as.Date
> as.Date(10, origin="29/02/2000",format="%d/%m/%Y")
[1] "2000-03-10"

as.POSIXct()

Given that date/times stored in the POSIXct class are essentially the number of seconds that have elapsed since a specific time (typically 1st Jan 1970) with an additional set of properties, numerics representing elapsed times can be formally defined as date/times by altering their class (and manually indicating the origin property if not 1970-01-01).
> aa <- 951827700
> class(aa) = c('POSIXt','POSIXct')
> #OR
> structure(aa,class=c('POSIXt','POSIXct'))
[1] "2000-02-29 22:35:00 AEST"
> #Equivalently 
> as.POSIXct(951827700,origin="1970-01-01")
[1] "2000-02-29 22:35:00 AEST"
From decimal dates - the lubridate way

> library(lubridate)
> date_decimal(2000.162)
[1] "2000-02-29 07:00:28 UTC"

as.POSIXlt()

> as.POSIXlt(951827700,origin="1970-01-01")
[1] "2000-02-29 22:35:00 AEST"
> #defining non standard input format
> as.POSIXlt(951827700,origin="01/01/1970", format="%d/%m/%Y")
[1] "2000-02-29 22:35:00 AEST"
> #defining non standard origin date
> as.POSIXlt(951827700,origin="01/01/2000", format="%d/%m/%Y")
[1] "2030-02-28 22:35:00 AEST"

Once an object has been converted to a date/time (POSIXlt or POSIXct class), it is possible to perform a range of time conversions and calculations.

Formating dates/times

Once an object has been converted to a date/time (POSIXlt or POSIXct class), it can be converted into a character string. In doing so, the format of the output follows much the same conversion rules as input specifications described above. format and strftime
> format(Sys.time(),format="%b %d, %Y, %I:%M%p")
[1] "Mar 27, 2017, 03:44PM"
> times <-seq(as.POSIXct("2000-02-29 09:45:00"),as.POSIXct("2000-02-29 12:45:00"),by="hour")
> format(times,format="%b %d, %Y, %I:%M%p")
[1] "Feb 29, 2000, 09:45AM" "Feb 29, 2000, 10:45AM" "Feb 29, 2000, 11:45AM"
[4] "Feb 29, 2000, 12:45PM"
> cut(times, breaks="weeks")
[1] 2000-02-28 2000-02-28 2000-02-28 2000-02-28
Levels: 2000-02-28

Another convenient way to round, truncate or cut the date/time output is to round it to the nearest seconds, minutes, hours or days. The round and trunc functions requires a character string (either "secs", "mins", "hours", "days") denoting the time unit to round the output to. The cut function requires a character string ("sec", "min", "hour", "day", "DSTday", "week", "month", "quarter" or "year").
> round(times,units="hours")
[1] "2000-02-29 10:00:00 AEST" "2000-02-29 11:00:00 AEST" "2000-02-29 12:00:00 AEST"
[4] "2000-02-29 13:00:00 AEST"
> trunc(times,units="hours")
[1] "2000-02-29 09:00:00 AEST" "2000-02-29 10:00:00 AEST" "2000-02-29 11:00:00 AEST"
[4] "2000-02-29 12:00:00 AEST"
> cut(times,breaks="hours")
[1] 2000-02-29 09:00:00 2000-02-29 10:00:00 2000-02-29 11:00:00 2000-02-29 12:00:00
4 Levels: 2000-02-29 09:00:00 2000-02-29 10:00:00 ... 2000-02-29 12:00:00
> times <- seq(as.POSIXct("2000-02-29 09:45:00"),as.POSIXct("2000-05-29 12:45:00"),by="weeks")

Extracting components

As demonstrated earlier, a POSIXlt object stores each of its components as list items. Hence it is thereafter possible to extract any of these components by reference to their index. Additionally, there are a series of convenient functions that can be used to extract derivatives of some of these components.
  • weekdays: returns the name of the day of the week
  • months: returns the name of the month
  • quarters: returns one of 'Q1' through to 'Q4'
  • julian: returns the number of days that have elapsed between an origin and an input date or date/time
> #Generate a set of haphazard dates
> othertimes <- c(as.POSIXct("2012-01-13"),as.POSIXct("2012-04-15"),as.POSIXct("2012-07-21"))
> #Extract the weekday names
> weekdays(othertimes)
[1] "Friday"   "Sunday"   "Saturday"
> #Extract the month names
> months(othertimes)
[1] "January" "April"   "July"   
> #Extract the quarters
> quarters(othertimes)
[1] "Q1" "Q2" "Q3"
> #Calculate the number of days since the start of the year 2012
> julian(othertimes, origin=as.POSIXct("2012-01-01"))
Time differences in days
[1]  12 105 202
attr(,"origin")
[1] "2012-01-01 AEST"

Milliseconds

Sub-second (millisecond) accuracy is handled by the "%OSn" format code (where n is the number of decimal places for the milliseconds).
> format(as.POSIXct(951827700.225, origin="1970-01-01"),format="%d/%m/%Y %H:%M:%OS3")
[1] "29/02/2000 22:35:00.225"

Sequences of times/dates

A convenient way to generate a regular series of date/times is with the overloaded seq function. When applied to either Date or POSIXct or POSIXlt objects, the seq function must receive two of the following;
  • from: a Date or POSIXt object specifying the starting date/time of the sequence
  • to: a Date or POSIXt object specifying the final date/time of the sequence
  • by: one of the following
    • the number of seconds between successive date/times
    • a character string representing discrete time increments
      • "sec", "min", "hour", "day", "DSTday", "week", "month" or "year"
      • any of the above can be proceeded by a number (positive or negative integer) and a space and then followed by an 's' to indicate steps other than single units (see examples)
  • length.out: the length (number of date/times) of the sequence
  • along.with: get the length of the sequence from the length of this other object
> #Every second Friday between Jan 13, 2012 and March 13 2012
> seq(as.Date("2012-01-13"),as.Date("2012-03-13"),by="2 weeks")
[1] "2012-01-13" "2012-01-27" "2012-02-10" "2012-02-24" "2012-03-09"
> #A set of four every second Fridays starting with Jan 13, 2012
> seq(as.Date("2012-01-13"),by="2 weeks", length.out=4)
[1] "2012-01-13" "2012-01-27" "2012-02-10" "2012-02-24"
> #A set of equally spaced dates between Jan 13, 2012 and March 13, 2012
> seq(as.Date("2012-01-13"),as.Date("2012-03-13"),length=5)
[1] "2012-01-13" "2012-01-28" "2012-02-12" "2012-02-27" "2012-03-13"
> #Create a sequence of dates/times to use in subsequent examples
> times <-seq(as.POSIXct("2000-02-29 09:05:00"),as.POSIXct("2000-02-29 12:05:00"),by="hour")
> times
[1] "2000-02-29 09:05:00 AEST" "2000-02-29 10:05:00 AEST" "2000-02-29 11:05:00 AEST"
[4] "2000-02-29 12:05:00 AEST"

Cut a series up into classes (bins)

The cut function can also be used to create a regular series of dates or date/times. This function begins by dividing the range of dates up into a series of intervals or bins. Each bin is labelled according to the centroid (middle) date or date/time and each date or date/time value in the original input is placed into one of the bins. This function is also useful for preparing a series of dates or date/times for frequency analysis or histogram construction.
> cut(times, breaks="hours")
[1] 2000-02-29 09:00:00 2000-02-29 10:00:00 2000-02-29 11:00:00 2000-02-29 12:00:00
4 Levels: 2000-02-29 09:00:00 2000-02-29 10:00:00 ... 2000-02-29 12:00:00

Calculations with dates/times

In addition to being able to alter the format of dates/times, there are also numereous functions for performing calculations on date or date/time objects. Simple date/time arithmetic is supported by overloading the usual mathematical operators.
> times[3]
[1] "2000-02-29 11:05:00 AEST"
> #Add 10 seconds
> times[3] + 10
[1] "2000-02-29 11:05:10 AEST"
> #Add 10 minutes
> times[3] + (10*60)
[1] "2000-02-29 11:15:00 AEST"
> #Subtract 10 minutes
> times[3] - (10*60)
[1] "2000-02-29 10:55:00 AEST"

Differences between dates/times

Similarly, the difference between two dates/times can be calculated by subtracting one date or date/time object from another.
> times[3]-times[1]
Time difference of 2 hours
To express the difference between two date/times in other units, the difftime function is useful. In addition to the two date or date/time objects, the difftime takes a units parameter (with values of "auto","secs", "mins", "hours", "days", "weeks"). The object returned by difftime can be further manipulated by a limited set of arithmetic operators ('+', '-', '*', '/'). The print method for this function is a formatted character string. In order to use the result of a difftime in more complex calculations or indeed to be treated as a pure number, the object should first be converted into a numeric.
> difftime(times[3],times[1],units="mins")
Time difference of 120 mins
> difftime(as.Date("2012-01-13"),as.Date("2012-02-29"),units="weeks")
Time difference of -6.714286 weeks
> as.numeric(difftime(times[3],times[1],units="mins"))
[1] 120

Date/time summaries

> #Calculate the centroid (mean) of a series of date/times
> mean(times)
[1] "2000-02-29 10:35:00 AEST"
> #Calculate the spread (range) of a series of date/times 
> range(times)
[1] "2000-02-29 09:05:00 AEST" "2000-02-29 12:05:00 AEST"
> #Calculate a range of location and spread characteristics of a series of date/times
> summary(times)
                 Min.               1st Qu.                Median                  Mean 
"2000-02-29 09:05:00" "2000-02-29 09:50:00" "2000-02-29 10:35:00" "2000-02-29 10:35:00" 
              3rd Qu.                  Max. 
"2000-02-29 11:20:00" "2000-02-29 12:05:00" 
<!-- html table generated in R 3.3.2 by xtable 1.8-2 package -->
<!-- Mon Mar 27 15:44:52 2017 -->
<table border='3' cellpadding='2' cellspacing='0'>
<tr> <th>  </th> <th> Between </th> <th> Plot </th> <th> Time.0 </th> <th> Time.1 </th> <th> Time.2 </th>  </tr>
  <tr> <td align="middle"> <b> R1 </b> </td> <td align="middle"> A1 </td> <td align="middle"> P1 </td> <td align="middle">   8 </td> <td align="middle">  14 </td> <td align="middle">  14 </td> </tr>
  <tr> <td align="middle"> <b> R2 </b> </td> <td align="middle"> A1 </td> <td align="middle"> P2 </td> <td align="middle">  10 </td> <td align="middle">  12 </td> <td align="middle">  11 </td> </tr>
  <tr> <td align="middle"> <b> R3 </b> </td> <td align="middle"> A2 </td> <td align="middle"> P3 </td> <td align="middle">   7 </td> <td align="middle">  11 </td> <td align="middle">   8 </td> </tr>
  <tr> <td align="middle"> <b> R4 </b> </td> <td align="middle"> A2 </td> <td align="middle"> P4 </td> <td align="middle">  11 </td> <td align="middle">   9 </td> <td align="middle">   2 </td> </tr>
   </table>

Examples

Creating a series (n=5) of random dates (for sampling purposes) during a 10 week period starting on March 1st, 2012.
> cut(as.Date("2012-03-01") + 7 #days per week
+                                 *10 #number of weeks
+                                 *stats::runif(5), #length of the series
+                                 "weeks")
[1] 2012-03-19 2012-04-02 2012-04-09 2012-04-02 2012-03-12
Levels: 2012-03-12 2012-03-19 2012-03-26 2012-04-02 2012-04-09
> #Or truncated code
> cut(as.Date("2012-03-01") + 70*stats::runif(5), "weeks")
[1] 2012-04-23 2012-04-16 2012-04-23 2012-03-05 2012-04-16
8 Levels: 2012-03-05 2012-03-12 2012-03-19 2012-03-26 2012-04-02 ... 2012-04-23

Welcome to the end of this tutorial