blob: b8c09e3767e1b663ff2419e3009b572fcbc82f7d [file] [log] [blame]
# File src/library/graphics/R/datetime.R
# Part of the R package, https://www.R-project.org
#
# Copyright (C) 1995-2016 The R Core Team
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# A copy of the GNU General Public License is available at
# https://www.R-project.org/Licenses/
axis.POSIXct <- function(side, x, at, format, labels = TRUE, ...)
{
has.at <- !missing(at) && !is.null(at)
x <- as.POSIXct(if(has.at) at else x)
range <- sort(par("usr")[if(side %% 2) 1L:2L else 3L:4L])
## find out the scale involved
d <- range[2L] - range[1L]
z <- c(range, x[is.finite(x)])
attr(z, "tzone") <- attr(x, "tzone")
if(d < 1.1*60) { # seconds
sc <- 1
if(missing(format)) format <- "%S"
} else if (d < 1.1*60*60) { # minutes
sc <- 60
if(missing(format)) format <- "%M:%S"
} else if (d < 1.1*60*60*24) {# hours
sc <- 60*60
if(missing(format)) format <- "%H:%M"
} else if (d < 2*60*60*24) {
sc <- 60*60
if(missing(format)) format <- "%a %H:%M"
} else if (d < 7*60*60*24) {# days of a week
sc <- 60*60*24
if(missing(format)) format <- "%a"
} else { # days, up to a couple of months
sc <- 60*60*24
}
if(d < 60*60*24*50) {
zz <- pretty(z/sc)
z <- zz*sc
z <- .POSIXct(z, attr(x, "tzone"))
if(sc == 60*60*24) z <- as.POSIXct(round(z, "days"))
if(missing(format)) format <- "%b %d"
} else if(d < 1.1*60*60*24*365) { # months
z <- .POSIXct(z, attr(x, "tzone"))
zz <- unclass(as.POSIXlt(z))
zz$mday <- zz$wday <- zz$yday <- 1
zz$isdst <- -1; zz$hour <- zz$min <- zz$sec <- 0
zz$mon <- pretty(zz$mon)
m <- length(zz$mon); M <- 2*m
m <- rep.int(zz$year[1L], m)
zz$year <- c(m, m+1)
zz <- lapply(zz, function(x) rep(x, length.out = M))
zz <- .POSIXlt(zz, attr(x, "tzone"))
z <- as.POSIXct(zz)
if(missing(format)) format <- "%b"
} else { # years
z <- .POSIXct(z, attr(x, "tzone"))
zz <- unclass(as.POSIXlt(z))
zz$mday <- zz$wday <- zz$yday <- 1
zz$isdst <- -1; zz$mon <- zz$hour <- zz$min <- zz$sec <- 0
zz$year <- pretty(zz$year); M <- length(zz$year)
zz <- lapply(zz, function(x) rep(x, length.out = M))
z <- as.POSIXct(.POSIXlt(zz))
if(missing(format)) format <- "%Y"
}
if(has.at) z <- x[is.finite(x)] # override changes
keep <- z >= range[1L] & z <= range[2L]
z <- z[keep]
if (!is.logical(labels)) labels <- labels[keep]
else if (isTRUE(labels))
labels <- format(z, format = format)
else if (isFALSE(labels))
labels <- rep("", length(z)) # suppress labelling of ticks
axis(side, at = z, labels = labels, ...)
}
hist.POSIXt <- function(x, breaks, ..., xlab = deparse(substitute(x)),
plot = TRUE, freq = FALSE,
start.on.monday = TRUE, format, right = TRUE)
{
if(!inherits(x, "POSIXt")) stop("wrong method")
xlab
x <- as.POSIXct(x)
incr <- 1
## handle breaks ourselves
if(missing(breaks))
stop("Must specify 'breaks' in hist(<POSIXt>)")
if (inherits(breaks, "POSIXt")) {
breaks <- as.POSIXct(breaks)
d <- min(abs(diff(unclass(breaks))))
if(d > 60) incr <- 60
if(d > 3600) incr <- 3600
if(d > 86400) incr <- 86400
if(d > 86400*7) incr <- 86400*7
if(d > 86400*28) incr <- 86400*28
if(d > 86400*366) incr <- 86400*366
num.br <- FALSE
} else {
num.br <- is.numeric(breaks) && length(breaks) == 1
if(num.br) {
## specified number of breaks
} else if(is.character(breaks) && length(breaks) == 1) {
valid <-
pmatch(breaks,
c("secs", "mins", "hours", "days", "weeks",
"months", "years", "quarters"))
if(is.na(valid)) stop("invalid specification of 'breaks'")
start <- as.POSIXlt(min(x, na.rm = TRUE))
## may alter later
## we need to invalidate isdst whenever we play with components
incr <- 1
if(valid > 1L) { start$sec <- 0; incr <- 59.99 }
if(valid > 2L) { start$min <- 0L; incr <- 3600 - 1 }
if(valid > 3L) { start$hour <- 0L; incr <- 86400 - 1 }
if(valid > 4L) { start$isdst <- -1L}
if(valid == 5L) { # "weeks"
start$mday <- start$mday - start$wday
if(start.on.monday)
start$mday <- start$mday + ifelse(start$wday > 0, 1, -6)
incr <- 7*86400
}
if(valid == 6L) { # "months"
start$mday <- 1L
end <- as.POSIXlt(max(x, na.rm = TRUE))
end <- as.POSIXlt(end + (31 * 86400))
end$mday <- 1L
end$isdst <- -1L
breaks <- seq(start, end, "months")
ind <- seq_along(breaks[-1L])
if (right)
breaks[ind] <- breaks[ind] - 86400
if (missing(format)) format <- "%Y-%m-%d"
} else if(valid == 7L) { # "years"
start$mon <- 0L
start$mday <- 1L
end <- as.POSIXlt(max(x, na.rm = TRUE))
end <- as.POSIXlt(end + (366 * 86400))
end$mon <- 0L
end$mday <- 1L
end$isdst <- -1L
breaks <- seq(start, end, "years")
ind <- seq_along(breaks[-1L])
if (right)
breaks[ind] <- breaks[ind] - 86400
if (missing(format)) format <- "%Y-%m-%d"
} else if(valid == 8L) { # "quarters"
qtr <- rep(c(0L, 3L, 6L, 9L), each = 3L)
start$mon <- qtr[start$mon + 1L]
start$mday <- 1L
end <- as.POSIXlt(max(x, na.rm = TRUE))
end <- as.POSIXlt(end + (93 * 86400))
end$mon <- qtr[end$mon + 1L]
end$mday <- 1L
end$isdst <- -1L
breaks <- seq(start, end, "3 months")
ind <- seq_along(breaks[-1L])
if (right)
breaks[ind] <- breaks[ind] - 86400
if (missing(format)) format <- "%Y-%m-%d"
} else { # "days" or "weeks"
maxx <- max(x, na.rm = TRUE)
breaks <- seq(start, maxx + incr, breaks)
breaks <- breaks[seq_len(1L + max(which(breaks < maxx)))]
}
}
else stop("invalid specification of 'breaks'")
}
res <- hist.default(unclass(x), unclass(breaks), plot = FALSE,
warn.unused = FALSE, right = right, ...)
res$equidist <- TRUE # years are of uneven lengths
res$intensities <- res$intensities*incr
res$xname <- xlab
if(plot) {
## trick to swallow arguments for hist.default, separate out 'axes'
myplot <- function(res, xlab, freq, format, breaks,
right, include.lowest, labels = FALSE,
axes = TRUE, xaxt = par("xaxt"), ...)
{
plot(res, xlab = xlab, axes = FALSE, freq = freq,
labels = labels, ...)
if(axes) {
axis(2, ...)
if(xaxt != "n") {
if(num.br) breaks <- c.POSIXct(res$breaks)
axis.POSIXct(1, at = breaks, format = format, ...)
# '...' : e.g. cex.axis
}
}
}
myplot(res, xlab, freq, format, breaks, ...)
}
invisible(res)
}
## methods for class "Date"
axis.Date <- function(side, x, at, format, labels = TRUE, ...)
{
has.at <- !missing(at) && !is.null(at)
x <- as.Date(if(has.at) at else x)
range <- sort(par("usr")[if(side %% 2) 1L:2L else 3:4L])
range[1L] <- ceiling(range[1L])
range[2L] <- floor(range[2L])
## find out the scale involved
d <- range[2L] - range[1L]
z <- c(range, x[is.finite(x)])
class(z) <- "Date"
if (d < 7) # days of a week
if(missing(format)) format <- "%a"
if(d < 100) { # month and day
z <- structure(pretty(z), class="Date")
if(missing(format)) format <- "%b %d"
} else if(d < 1.1*365) { # months
zz <- as.POSIXlt(z)
zz$mday <- 1
zz$mon <- pretty(zz$mon)
m <- length(zz$mon)
m <- rep.int(zz$year[1L], m)
zz$year <- c(m, m+1)
z <- as.Date(zz)
if(missing(format)) format <- "%b"
} else { # years
zz <- as.POSIXlt(z)
zz$mday <- 1; zz$mon <- 0
zz$year <- pretty(zz$year)
z <- as.Date(zz)
if(missing(format)) format <- "%Y"
}
if(has.at) z <- x[is.finite(x)] # override changes
keep <- z >= range[1L] & z <= range[2L]
z <- z[keep]
z <- sort(unique(z)); class(z) <- "Date"
if (!is.logical(labels)) labels <- labels[keep]
else if (isTRUE(labels))
labels <- format.Date(z, format = format)
else if (isFALSE(labels))
labels <- rep("", length(z)) # suppress labelling of ticks
axis(side, at = z, labels = labels, ...)
}
hist.Date <- function(x, breaks, ..., xlab = deparse(substitute(x)),
plot = TRUE, freq = FALSE,
start.on.monday = TRUE, format, right = TRUE)
{
if(!inherits(x, "Date")) stop("wrong method")
force(xlab)
incr <- 1
## handle breaks ourselves
if(missing(breaks))
stop("Must specify 'breaks' in hist(<Date>)")
if (inherits(breaks, "Date")) {
breaks <- as.Date(breaks)
d <- min(abs(diff(unclass(breaks))))
if(d > 1) incr <- 1
if(d > 7) incr <- 7
if(d > 28) incr <- 28
if(d > 366) incr <- 366
num.br <- FALSE
} else {
num.br <- is.numeric(breaks) && length(breaks) == 1L
if(num.br) {
## specified number of breaks
} else if(is.character(breaks) && length(breaks) == 1L) {
valid <- pmatch(breaks, c("days", "weeks", "months", "years",
"quarters"))
if(is.na(valid)) stop("invalid specification of 'breaks'")
start <- as.POSIXlt(min(x, na.rm = TRUE))
incr <- 1
if(valid > 1L) { start$isdst <- -1L}
if(valid == 2L) { ## "weeks"
start$mday <- start$mday - start$wday
if(start.on.monday)
start$mday <- start$mday + ifelse(start$wday > 0L, 1L, -6L)
incr <- 7
## drops through to "days".
}
if(valid == 3L) { ## "months"
start$mday <- 1
end <- as.POSIXlt(max(x, na.rm = TRUE))
end <- as.POSIXlt(end + (31 * 86400))
end$mday <- 1
end$isdst <- -1
breaks <- as.Date(seq(start, end, "months"))
if (right)
breaks <- breaks - 1
if (missing(format)) format <- "%Y-%m-%d"
} else if(valid == 4L) { ## "years"
start$mon <- 0L
start$mday <- 1L
end <- as.POSIXlt(max(x, na.rm = TRUE))
end <- as.POSIXlt(end + (366 * 86400))
end$mon <- 0L
end$mday <- 1L
end$isdst <- -1
breaks <- as.Date(seq(start, end, "years"))
if (right)
breaks <- breaks - 1
if (missing(format)) format <- "%Y-%m-%d"
} else if(valid == 5L) { ## "quarters"
qtr <- rep(c(0L, 3L, 6L, 9L), each = 3L)
start$mon <- qtr[start$mon + 1L]
start$mday <- 1L
end <- as.POSIXlt(max(x, na.rm = TRUE))
end <- as.POSIXlt(end + (93 * 86400))
end$mon <- qtr[end$mon + 1L]
end$mday <- 1L
end$isdst <- -1
breaks <- as.Date(seq(start, end, "3 months"))
if (right)
breaks <- breaks - 1
if (missing(format)) format <- "%Y-%m-%d"
} else { ## "days" (or "weeks")
start <- as.Date(start)
maxx <- max(x, na.rm = TRUE)
breaks <- seq(start, maxx + incr, breaks)
breaks <- breaks[seq_len(1L + max(which(breaks < maxx)))]
}
} else stop("invalid specification of 'breaks'")
}
res <- hist.default(unclass(x), unclass(breaks), plot = FALSE, warn.unused = FALSE, right = right, ...)
res$equidist <- TRUE # years are of uneven lengths
res$intensities <- res$intensities*incr
res$xname <- xlab
if(plot) {
## trick to swallow arguments for hist.default, separate out 'axes'
myplot <- function(res, xlab, freq, format, breaks,
right, include.lowest, labels = FALSE,
axes = TRUE, xaxt = par("xaxt"), ...)
{
plot(res, xlab = xlab, axes = FALSE, freq = freq,
labels = labels, ...)
if(axes && xaxt != "n") {
axis(2, ...)
if(num.br) breaks <- c.Date(res$breaks)
axis.Date(1, at = breaks, format = format, ...)
}
}
myplot(res, xlab, freq, format, breaks, ...)
}
invisible(res)
}
Axis.Date <- function(x=NULL, at=NULL, ..., side, labels=TRUE)
axis.Date(side=side, x=x, at=at, labels=labels, ...)
Axis.POSIXt <- function(x=NULL, at=NULL, ..., side, labels=TRUE)
axis.POSIXct(side=side, x=x, at=at, labels=labels, ...)