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.Date | as.POSIXct | as.POSIXlt | strptime | |
---|---|---|---|---|
Date | Yes | Yes | Yes | Yes |
Date & time | No | Yes | Yes | Yes |
Timezones | No | Yes | Yes | Yes |
Storage | list | list (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.
Code | Value | Example |
---|---|---|
%d | Day of the month (decimal) | 04 |
%m | Month (decimal) | 02 |
%b | Month (abbreviated) | Feb |
%B | Month (full name) | February |
%y | Year (2 digits) | 12 |
%Y | Year (4 digit) | 2012 |
%a | Weekday (abbreviated) | Mon |
%A | Weekday (full name) | Monday |
%w | Weekday (decimal, 0=Sunday) | 1 |
%U | Day of year (decimal) | 226 |
%U | Week of year (decimal, starting Sunday) | 8 |
%W | Week of year (decimal, starting Monday) | 8 |
%H | Hour (24 hour) | 22 |
%I | Hour (12 hour) | 10 |
%M | Minute (decimal) | 35 |
%S | Second (decimal) | 45 |
%p | AM/PM (decimal) | PM |
%z | Offset from GMT | -0200 |
%Z | Time zone (character, output only) | EST |
%x | Local specific Date (%y/%m/%d) | 2000/02/29 |
%x | Local 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
> 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"
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
> 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
> 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
> 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