Working With Dates in R

This is a “going down the rabbit hole” post. Handling dates in the general case is a much bigger topic than you’d expect. In the interest of full disclosure, most of the research I’ve done has used monthly, quarterly, or annual time series data, and in the other cases, I’ve not worried about irregularities, I’ve aggregated to a monthly or quarterly frequency, or I just haven’t done much with the time dimension. Those are all very easy to handle using only base R. Let me briefly cover that first.

Easy Cases I

First, let’s consider monthly, quarterly, or annual time series. Suppose you have a vector or matrix of data x.raw. You can add time series properties like this:

x.ts <- ts(x.raw, start=c(1990,1), frequency=12)
x.ts <- ts(x.raw, start=c(1990,1), frequency=4)
x.ts <- ts(x.raw, start=1990, frequency=1)

Easy Cases II

Now assume we have a series of daily observations. We want to estimate an AR(p) model, and while some of the observations are missing, we’ll just ignore that and treat consecutive observations as a time series even if they’re not equally-spaced. All you have to do is treat them as if they’re annual data:

x.ts <- ts(x.raw, start=1, frequency=1)

In the context of annual time series, a frequency of one means once per year. With daily data, or for that matter any other data where we are treating the data as if it’s equally-spaced, the “unit of time” is one observation.

Aggregation

Aggregation to a lower frequency is a little more difficult. You have to understand dates a little bit in order to do aggregation. Be forewarned that at this point, there is no longer a single correct way to do things. You can do a search and find alternative approaches that work just fine once you leave the realm of regular time series. In particular, to reduce the number of dependencies, I work with base R when possible.

Date, as.Date, and seq.Date

It’s assumed that you have a vector holding the series (x.raw) and another vector holding the dates of the observations (x.dates). In some cases, you can create x.dates using seq.Date:

# Daily dates starting Jan 1, 2025 for 10 observations
seq.Date(as.Date("2025-01-01"), length.out=10, by="days")

# Daily dates starting Jan 1, 2025 for all observations in x.raw
# Assumes no missing observations
seq.Date(as.Date("2025-01-01"), length.out=length(x.raw), by="days")

# Does the same thing
seq.Date(as.Date("2025-01-01"), along.with=x.raw, by="days")

# Daily dates starting Jan 1, 2025 through June 30, 2025
seq.Date(as.Date("2025-01-01"), as.Date("2025-06-30"), by="days")

There’s a lot more that can be done with that function. by can be a number and then the observations will be that number of days apart. by can “day”, “week”, “month”, “quarter”, or “year”. It can be of the form “2 weeks”. This calculates paydates if you get paid every other Friday starting on January 3:

seq.Date(as.Date("2025-01-03"), as.Date("2025-12-31"), by="2 weeks")

More commonly, you’d have the date stored as its own observation, and then you’d do the conversion manually:

x.dates <- as.Date(x.raw[,1])

For example, the first three trading days of 2025:

as.Date(c("2025-01-02", "2025-01-03", "2025-01-06"))

aggregate

Now let’s see how to use the aggregate function to achieve our goal of aggregating a daily time series to monthly.

The first step is to convert to the year and month format (202501 for Jan 2025, etc.):

# x.dates is a Date object
x.months <- format(x.dates, "%Y%m")

# Will not work if it's a character vector, so do this
x.months <- format(as.Date(x.dates), "%Y%m")

Now find the average value in each month:

aggregate(x.ts, list(x.months), mean)

The second argument has to be a list. Quarterly aggregation is a tad more complicated because base R does not to my knowledge have any way to extract the year and quarter from a date. You have to do this:

y <- format(x.dates, "%Y")
m <- as.numeric(format(x.dates, "%m"))
q <- ceiling(m/3)
x.quarters <- paste0(y, "Q", q)
aggregate(x.ts, list(x.quarters), mean)

A note, since I’ve been asked this before: aggregating daily data to weekly doesn’t fit in this framework, because a weekly time series is not regular. For instance, the first week of the year might be a Thursday.

xts

For more general handling of time series, for example if you want to deal with trade-by-trade data, you should work with xts objects rather than ts objects. Here’s an example using the daily S&P 500 closing price for the end of 2024 and beginning of 2025:

library(xts)
sp500 <- c(5907, 5882, 5869, 5942, 5975, 5909)
dates <- c("2024-12-30", "2024-12-31", "2025-01-02", "2025-01-03", "2025-01-06", "2025-01-07")
sp500.xts <- xts(sp500, order.by=as.Date(dates))

To make full use of the information about dates held inside an xts object, you need to work with functions and libraries that were written specifically for xts objects, not ts objects. Example are the tsgarch and tsmarch packages, for which it is important to know the distance between observations.

Going Further

This brief introduction to R dates has only scratched the surface. Other topics include timezones and POSIXct/POSIXlt. There are different ways to aggregate data, handle missing observations, make forecasts, and do transformations. The highfrequency package gives you an idea of the ways date information is used.

Last Updated June 11, 2025