---
title: \pkg{anytime}&#58; Easier Date and Time Conversion

# Use letters for affiliations
author:
  - name: Dirk Eddelbuettel
    affiliation: 1
address:
  - code: 1
    address: Department of Statistics, University of Illinois, Urbana-Champaign, IL, USA

# For footer text  TODO(fold into template, allow free form two-authors)
lead_author_surname: Eddelbuettel

# Place DOI URL or CRAN Package URL here
doi: "https://cran.r-project.org/package=anytime"

# Abstract
abstract: |
  The \pkg{anytime} package provides functions which convert from both a
  number of different input variable types (integer, numeric, character,
  factor) and different input formats which are tried heuristically offering
  a powerful and versatile date and time converter that (generally) requires
  no user input and operates autonomously.

# Font size of the document, values of 9pt (default), 10pt, 11pt and 12pt
fontsize: 9pt

# Optional: Force one-column layout, default is two-column
two_column: true

# Optional: Enables lineno mode, but only if one_column mode is also true
#lineno: true

# Optional: Enable one-sided layout, default is two-sided
#one_sided: true

# Optional: Enable section numbering, default is unnumbered
#numbersections: true

# Optional: Specify the depth of section number, default is 5
#secnumdepth: 5

# Optional: Skip inserting final break between acknowledgements, default is false
skip_final_break: true

# Optional: Bibliography
bibliography: anytime

# Optional: Enable a 'Draft' watermark on the document
watermark: false

# Customize footer, eg by referencing the vignette
footer_contents: "anytime Vignette"

# Produce a pinp document
output:
  pinp::pinp:
    collapse: true
    keep_tex: false

header-includes: >
  \newcommand{\proglang}[1]{\textsf{#1}}
  \newcommand{\pkg}[1]{\textbf{#1}}

# Required: Vignette metadata for inclusion in a package.
vignette: >
  %\VignetteIndexEntry{Introduction to anytime}
  %\VignetteKeywords{anytime, date, datetime, conversion}
  %\VignettePackage{anytime}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
  %\usepackage[utf8]{inputenc}
---

```{r initialsetup, include=FALSE}
knitr::opts_chunk$set(cache=FALSE)
```


# Motivation

R excels at computing with dates, and times.  Using a _typed_ representation for your data
is highly recommended not only because of the functionality offered but also because of
the added safety stemming from proper representation.

But there is a small nuisance cost in interactive work as well as in programming. Users
must have told `as.POSIXct()` about a million times that the origin is (of course) the
[epoch](https://en.wikipedia.org/wiki/Unix_time). Do we really have to say it a million
more times?  Similarly, when parsing dates that are _some variant_ of the common YYYYMMDD
format, do we really have to manually convert from `integer` or `numeric` or `factor` or
`ordered` to character?  Having one of several common separators and/or date formats
(YYYY-MM-DD, YYYY/MM/DD, YYYYMMDD, YYYY-mon-DD and so on, with or without times), do we
really need a format string? Or could a smart converter function do this for us?

The `anytime()` function aims to provide such a _general purpose_ converter returning a
proper `POSIXct` (or `Date`) object no matter the input (provided it was parseable),
relying on [Boost Date_Time](http://www.boost.org/libs/date_time) for the (efficient,
performant) conversion. `anydate()` is an additional wrapper returning a `Date` object
instead. `utctime()` and `utcdate()` are two variants which interpret input as
_coordinated universal time_ (UTC), _i.e._ free of any timezone.


# Examples

We set up the R environment and display for the examples below. Note that the package
caches the (local) timezone information (and `anytime:::setTZ()` can be used to reset this
value later).

```{r, setup}
Sys.setenv(TZ=anytime:::getTZ()) # TZ helper
library(anytime)                 # caches TZ info
options(width=50,                # column width
        digits.secs=6)           # fractional secs
```


## From Integer, Numeric, Factor or Ordered

For numeric dates in the range of the (numeric) _yyyymmdd_ format, we use `anydate()`.

```{r, int_num_date}
## integer
anydate(20160101L + 0:2)

## numeric
anydate(20160101 + 0:2)
```

Numeric input also works for datetimes if its range corresponds to the range of `as.numeric()`
values of `POSIXct` variables:

```{r, int_num_dt}
## integer
anytime(1451628000L + 0:2)

## numeric
anytime(1451628000 + 0:2)
```

This is a change from version 0.3.0; the old behaviour (which was not fully consistent in
how it treated numeric input values, but convenient for input in the ranges shown here)
can be enabled via either an argument to the function or a global options, see
`help(anytime)` for details:

```{r, int_num_alt}
## integer
anytime(20160101L + 0:2, oldHeuristic=TRUE)

## numeric
anytime(20160101 + 0:2, oldHeuristic=TRUE)
```

In general, it is now preferred to use `anydate()` on values in this range (or resort to
using `oldHeuristics=TRUE` as shown).

## Factor or Ordered

Factor variables and their order variant are also supported directly.

```{r, fac_ord}
## factor
anytime(as.factor(20160101 + 0:2))

## ordered
anytime(as.ordered(20160101 + 0:2))
```

Note that `factor` and `ordered` variables may _appear_ to be like numeric variables, they are in fact
converted to character first and treated just like character input (described in the next
section).


## Character: Simple

Character input is supported in a variety of formats.  We first show simple formats.

```{r, char_dates}
## Dates: Character
anytime(as.character(20160101 + 0:2))

## Dates: alternate formats
anytime(c("20160101", "2016/01/02", "2016-01-03"))
```


## Character: ISO

ISO8661 date(time) formats are supported with both 'T' and a space as separator of date
and time.

```{r, char_iso}
## Datetime: ISO with/without fractional seconds
anytime(c("2016-01-01 10:11:12",
          "2016-01-01T10:11:12.345678"))
```

## Character: Textual month formats

Date formats with month abbreviations are supported in a number of common orderings.

```{r, char_date_text}
## ISO style
anytime(c("2016-Sep-01 10:11:12",
          "Sep/01/2016 10:11:12",
          "Sep-01-2016 10:11:12"))

## Datetime: Mixed format
## (cf http://stackoverflow.com/questions/39259184)
anytime(c("Thu Sep 01 10:11:12 2016",
          "Thu Sep 01 10:11:12.345678 2016"))
```

## Character: Dealing with DST

This shows an important aspect. When not working in localtime (by overriding to `UTC`) the
_change in difference_ to UTC is correctly covered (which the underlying [Boost
Date_Time](https://www.boost.org/libs/date_time) library does not do by itself).


```{r, date_iso}
## Datetime: pre/post DST
anytime(c("2016-01-31 12:13:14",
          "2016-08-31 12:13:14"))
## important: catches change
anytime(c("2016-01-31 12:13:14",
          "2016-08-31 12:13:14"), tz="UTC")
```

# Technical Details

The actual parsing and conversion is done by two different [Boost](http://www.boost.org)
libraries.  First, the top-level R function checks the input argument type and branches on
date or datetime types. All other types get handed to a function using [Boost
lexical_cast](https://www.boost.org/libs/lexical_cast) to convert from _anything numeric_ to a
string representation.  This textual representation is then parsed by [Boost
Date_Time](https://www.boost.org/libs/date_time) to create the corresponding date, or
datetime, type.  (There are also a number of special cases where numeric values are
directly converted; see below for a discussion.) We use the
[\pkg{BH}](http://dirk.eddelbuettel.com/code/bh.html) package \citep{CRAN:BH} to access
these [Boost](https://www.boost.org) libraries, and rely on
[\pkg{Rcpp}](http://dirk.eddelbuettel.com/code/rcpp.html)
\citep{JSS:Rcpp,Eddelbuettel:2013,CRAN:Rcpp} for a seamless C++ interface to and from
[R](https://www.r-project.org).

The [Boost Date_Time](https://www.boost.org/libs/date_time)
library is addressing the need for parsing date and datetimes from text. It permits us
to loop over a suitably large number of _candidate formats_ with considerable ease.  The
formats are generally variants of the ISO 8601 date format, _i.e._, of the YYYY-MM-DD
ordering.  We also allow for textual representation of months, _e.g._, 'Jan'
for January. This feature is not internationalised.

The list of current formats can be retrieved by the `getFormats()` function. Users can
also add to this list at run-time by calling `addFormats()`, as well as removing
formats. User-provided formats are tried before the formats supplied by the package.

```{r}
fmts <- getFormats()
length(fmts)
head(fmts,10)
tail(fmts,10)
```

As a fallback for, _e.g._, different behavior on Windows where Boost does not consult the
\code{TZ} environment variable, and to be generally as close as possible to parsing by the
R language and system, we also support the parser from R itself.  As R does not expose
this part of its API at the C level, we use the \pkg{Rcpp} package \citep{JSS:Rcpp,Eddelbuettel:2013,
CRAN:Rcpp}.  This code path is enabled when `useR=TRUE` is used.

# Output Formats

A related topic is faithful and easy to read _representation_ of
datetime objects in output, _i.e.,_ formatting and printing such objects.

In the spirit of _no configuration_ used on the parsing side,
formating support is provided via several functions. These all follow
different known standards and are accessible by the name of the
standard, or, in one case, the non-standard convention. All return a 
a character representation.

```{r output_ex}
pt <- anytime("2016-01-31 12:13:14.123456")
iso8601(pt)
rfc2822(pt)
rfc3339(pt)
yyyymmdd(pt)
```

# Ambiguities

The \pkg{anytime} package is designed to operate heuristically on a number of _plausible_
and _sane_ formats.  This cannot possibly cover all conceivable cases.

## North America versus the world

In general, \pkg{anytime} tries to gently nudge users towards ISO 8601 order of _year_
followed by _month_ and _day_. But for example in the United States, another prevalent
form insists on month-day-year ordering. As many users are likely to encounter such input
format, \pkg{anytime} accomodates this use provided a separator is used: input with either
a slash (`/`) or a hyphen (`-`) is accepted and parsed.

# Asserts

The \pkg{anytime} package also contains two helper functions that can assist in defensive
programming by validating input arguments. The `assertTime()` and `assertDate()` functions
validate if the given input can be parsed, respectively, as `Datetime` or `Date`
objects. In case one of the inputs cannot be parsed, an error is triggered.  Otherwise the
parsed input is returned invisibly.

# Comparison

The \pkg{anytime} aims to satisfy two goal: be performant, and the same time flexible in
terms of not requiring an explicit input format.  We can gauge the relative performance via several pairwise compariosns.


## Speed

The `as.POSIXct()` function in R provides a useful baseline as it is also implemented in
compiled code.  The `fastPOSIct()` function from the \pkg{fasttime} package
\citep{CRAN:fasttime} excels at converting one (and only one) input format _fast_ to a
(UTC-only) datetime object.  A simple benchmark converting 100 input strings 100,000 times
finds both `as.POSIXct()` and `anytime()` at very comparable and similar performance, but
well over one order of magnitude slower that the highly-focussed `fastPOSIXct()`. Table
\ref{tab:speed} shows the detailed results; the underlying code can be seen in the
[appendix](#appendix).  This result is reasonable: a highly specialised function can (yand
should) outperform two (relatively fast) universal converters.  `anytime()` is still
compelling as it easier to use than `as.POSIXct()` by not requiring a format string (for
formats other than ISO 8601).

<!-- ` ` `{r benchmark_speed, cache=TRUE} -->
<!-- library(rbenchmark) -->
<!-- library(fasttime) -->
<!-- inp <- rep("2019-01-02 03:04:05", 100) -->
<!-- benchmark(fasttime=fastPOSIXct(inp), -->
<!--           baseR=as.POSIXct(inp), -->
<!--           anytime=anytime(inp), -->
<!--           replications=1e5)[,1:4] -->
<!-- ` ` ` -->

```{r knitr_speed, echo=FALSE}
df <- read.table(stringsAsFactors=FALSE, text="
      test replications elapsed relative
3  anytime       100000  16.556   20.515
2    baseR       100000  15.692   19.445
1 fasttime       100000   0.807    1.000
")
knitr::kable(df, "latex", booktabs=TRUE, row.names=FALSE,
             caption="\\label{tab:speed}Comparison of anytime and base R to fasttime")
```

## Generality 

The \pkg{parsedate} package \citep{CRAN:parsedate} brings the very general date parsing
utility from the \textsf{git} version control software to \proglang{R}. In a similar
comparison of 100 input strings parsed 10,000 times, we find its `parse_date()` function
to be more than an order of magnitude slower than `anytime()` or `as.POSIXct()`---see
table \ref{tab:generality} for the results based on the code in the [appendix](#appendix).
Again, this result is reasonable as the greater flexibility of \pkg{parsedate} comes at a
cost in performance relative to the more restricted alternatives.


<!-- ` ` `{r benchmark_generality, cache=TRUE} -->
<!-- library(rbenchmark) -->
<!-- library(parsedate) -->
<!-- inp <- rep("2019-01-02 03:04:05", 100) -->
<!-- benchmark(parsedate=parse_date(inp), -->
<!--           baseR=as.POSIXct(inp), -->
<!--           anytime=anytime(inp), -->
<!--           replications=5e3)[,1:4] -->
<!-- ` ` ` -->

```{r knitr_generality, echo=FALSE}
df <- read.table(stringsAsFactors=FALSE, text="
       test replications elapsed relative
3   anytime        10000   1.653    1.069
2     baseR        10000   1.546    1.000
1 parsedate        10000  21.827   14.118
")
knitr::kable(df, "latex", booktabs=TRUE, row.names=FALSE,
             caption="\\label{tab:generality}Comparison of anytime and base R to parsedate")
```


## All-in

The \pkg{lubridate} package \citep{CRAN:lubridate} is a widely-used package for working
with dates and times. It offers a very anywide variety of functions for working with dates
and times: we count a full 168 exported functions in the current version.  Its parser for
dates and times requires at least a _hint_: the user has to specify whether input is
ordered as, say, year-month-day, or day-month-year, or another form. \pkg{lubridate} has
changed its internals considerably over the years. Early versions did not contain compiled
code; a C-based parser was added first, and current versions embed the CCTZ C++ library
\citep{GitHub:CCTZ} which was first made available to R by the \pkg{RcppCCTZ} package
\citep{CRAN:RcppCCTZ}.

While \pkg{lubridate} is less general than \pkg{anytime} (in that it generally requires
user input on the ordering of date elements), it is also slower as can be seen from the
results in table \ref{tab:lubridate} based on the code in the [appendix](#appendix). The
more-widely used form (here `ymd_hms()`) is over an order of magnitude slower; the less
well-known function `parse_data_times()` (which still requires _hints_) is still several
times slower as shown below.


<!-- ` ` `{r benchmark_lubridate, cache=TRUE} -->
<!-- library(rbenchmark) -->
<!-- suppressMessages(library(lubridate)) -->
<!-- inp <- rep("2019-01-02 03:04:05", 100) -->
<!-- benchmark(ymd_hms=ymd_hms(inp), -->
<!--           parse_dt=parse_date_time(inp, "ymd_HMS"), -->
<!--           anytime=anytime(inp), -->
<!--           replications=1e4)[, 1:4] -->
<!-- ` ` ` -->


```{r knitr_lubridate, echo=FALSE}
df <- read.table(stringsAsFactors=FALSE, text="
             test replications elapsed relative
3         anytime        10000   1.652    1.000
2 parse_date_time        10000  12.770    7.730
1         ymd_hms        10000  25.162   15.231
")
knitr::kable(df, "latex", booktabs=TRUE, row.names=FALSE,
             caption="\\label{tab:lubridate}Comparison of anytime to two lubridate functions")
```


# Summary

We describe the \pkg{anytime} package which offers fast, convenient and reliable date and
datetime conversion for R users along with helper functions for formatting and assertions.
Different types of input are illustrated and described in detail, and performance is
analyzed via several benchmark comparisons.

We show that the \pkg{anytime} package is no slower than the base R parser, and much faster
than either the most flexible parsing alternative, or a commonly-used package in this
space---all the while freeing users from having to supply explicit formats specified in
advance. The combination of features, performance and ease-of-use may make \pkg{anytime} a
compelling alternative for R users parsing and analysing dates and times.

# Appendix

The benchmark results shown in tables \ref{tab:speed}, \ref{tab:generality} and
\ref{tab:lubridate} are based on the code included below, and obtained via execution under
R version 3.6.1 running under Ubuntu 19.04 with Linux kernel 5.0.0-25 on an Intel i7-8700k
processor.

```r
library(anytime)
library(rbenchmark)
library(fasttime)
inp <- rep("2019-01-02 03:04:05", 100)
res1 <- benchmark(fasttime=fastPOSIXct(inp),
                  baseR=as.POSIXct(inp),
                  anytime=anytime(inp),
                  replications=1e5)[, 1:4]
res1

library(parsedate)
inp <- rep("2019-01-02 03:04:05", 100)
res2 <- benchmark(parsedate=parse_date(inp),
                  baseR=as.POSIXct(inp),
                  anytime=anytime(inp),
                  replications=1e4)[, 1:4]
res2

suppressMessages(library(lubridate))
inp <- rep("2019-01-02 03:04:05", 100)
res3 <- benchmark(ymd_hms=ymd_hms(inp),
                  parse_date_time=
                      parse_date_time(inp,
                                      "ymd_HMS"),
                  anytime=anytime(inp),
                  replications=1e4)[, 1:4]
res3
```
