blob: 3738a363521437b005787b9490a95ec93b0e6430 [file] [log] [blame]
# File src/library/base/R/datetime.R
# Part of the R package, https://www.R-project.org
#
# Copyright (C) 1995-2018 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/
Sys.time <- function() .POSIXct(.Internal(Sys.time()))
### Extensively rewritten for R 3.4.4
### There is no portable way to find the system timezone by location.
### For some ideas (not all accurate) see
### https://stackoverflow.com/questions/3118582/how-do-i-find-the-current-system-timezone
### See http://mm.icann.org/pipermail/tz/2017-December/025617.html for
### why you cannot deduce the timezone name from current abbreviations
### and offset from UTC -- cf Europe/Dublin and Europe/London which
### (despite the GB-Eire alias) have a different history including of
### DST in 1971.
### Will be called from C startup code for internal tzcode as Sys.timezone()
### For bootstrapping, it must be simple if TZ is set.
Sys.timezone <- function(location = TRUE)
{
if(!location)
.Deprecated(msg = "Sys.timezone(location = FALSE) is defunct and ignored")
## caching added in 3.5.0
if(!is.na(tz <- get0(".sys.timezone", baseenv(), mode = "character",
inherits = FALSE, ifnotfound = NA_character_)))
return(tz)
cacheIt <- function(tz) assign(".sys.timezone", tz, baseenv())
## Many Unix set TZ, e.g. Solaris and AIX.
## For Solaris the system setting is a line in /etc/TIMEZONE
tz <- Sys.getenv("TZ")
if(nzchar(tz)) return(tz)
if(.Platform$OS.type == "windows") return(.Internal(tzone_name()))
## At least tzcode and glibc respect TZDIR.
## glibc uses $(datadir)/zoneinfo
## musl does not mention it, just reads /etc/localtime (as from 1.1.13)
## (A search of /usr/share/zoneinfo, /share/zoneinfo, /etc/zoneinfo
## is hardcoded in musl.)
## Systems using --with-internal-tzcode will use the database at
## file.path(R.home("share"), "zoneinfo"), but it is a reasonable
## assumption that /etc/localtime is based on the system database.
tzdir <- Sys.getenv("TZDIR")
if(nzchar(tzdir) && !dir.exists(tzdir)) tzdir <- ""
if(!nzchar(tzdir)) { ## See comments in OlsonNames
if(dir.exists(tzdir <- "/usr/share/zoneinfo") ||
dir.exists(tzdir <- "/share/zoneinfo") ||
dir.exists(tzdir <- "/usr/share/lib/zoneinfo") ||
dir.exists(tzdir <- "/usrlib/zoneinfo") ||
dir.exists(tzdir <- "/usr/local/etc/zoneinfo") ||
dir.exists(tzdir <- "/etc/zoneinfo") ||
dir.exists(tzdir <- "/usr/etc/zoneinfo")) {
} else tzdir <- ""
}
## First try timedatectl: should work on any modern Linux
## as part of systemd (and probably nowhere else)
if (nzchar(Sys.which("timedatectl"))) {
inf <- system("timedatectl", intern = TRUE)
## typical format:
## " Time zone: Europe/London (GMT, +0000)"
## " Time zone: Europe/Vienna (CET, +0100)"
lines <- grep("Time zone: ", inf)
if (length(lines)) {
tz <- sub(" .*", "", sub(" *Time zone: ", "", inf[lines[1L]]))
## quick sanity check
if(nzchar(tzdir)) {
if(file.exists(file.path(tzdir, tz))) {
cacheIt(tz)
return(tz)
} else
warning(sprintf("%s indicates the non-existent timezone name %s",
sQuote("timedatectl"), sQuote(tz)),
call. = FALSE, immediate. = TRUE, domain = NA)
} else {
cacheIt(tz)
return(tz)
}
}
}
## Debian/Ubuntu Linux do things differently, so try that next.
## Derived loosely from PR#17186
## As the Java sources say
##
## 'There's no spec of the file format available. This parsing
## assumes that there's one line of an Olson tzid followed by a
## '\n', no leading or trailing spaces, no comments.'
##
## but we do trim whitespace and do a sanity check (Java does not)
if (grepl("linux", R.Version()$platform, ignore.case = TRUE) &&
file.exists("/etc/timezone")) {
tz0 <- try(readLines("/etc/timezone"))
if(!inherits(tz0, "try-error") && length(tz0) == 1L) {
tz <- trimws(tz0)
## quick sanity check
if(nzchar(tzdir)) {
if(file.exists(file.path(tzdir, tz))) {
cacheIt(tz)
return(tz)
} else
warning(sprintf("%s indicates the non-existent timezone name %s",
sQuote("/etc/timezone"), sQuote(tz)),
call. = FALSE, immediate. = TRUE, domain = NA)
} else {
cacheIt(tz)
return(tz)
}
}
}
## non-Debian Linux (if not covered above), macOS, *BSD, ...
## According to the glibc's (at least 2.26)
## manual/time.texi, it can be configured to use
## /etc/localtime or /usr/local/etc/localtime
## (and in fact can be overridden when glibc is installed)
## This should be a symlink,
## but people including Debian have copied files instead.
## 'man 5 localtime' says (even on Debian)
## 'Because the timezone identifier is extracted from the symlink
## target name of /etc/localtime, this file may not be a normal
## file or hardlink.'
## tzcode mentions /usr/local/etc/zoneinfo/localtime
## as the 'local time zone file' (not seen in the wild)
## man tzset on macOS (from BSD) mentions /var/db/timezone/localtime
if ((file.exists(lt0 <- "/etc/localtime") ||
file.exists(lt0 <- "/usr/local/etc/localtime") ||
file.exists(lt0 <- "/usr/local/etc/zoneinfo/localtime") ||
file.exists(lt0 <- "/var/db/timezone/localtime")) &&
!is.na(lt <- Sys.readlink(lt0)) && nzchar(lt)) { # so it is a symlink
tz <- NA_character_
## glibc and macOS < 10.13 this is a link into /usr/share/zoneinfo
## (Debian Etch and later replaced it with a copy,
## as have RHEL/Centos 6.x.)
## macOS 10.13.0 is a link into /usr/share/zoneinfo.default
## macOS 10.13.[12] is a link into /var/db/timezone/zoneinfo,
## itself a link (with target different on different machines)
if ((nzchar(tzdir) && grepl(pat <- paste0("^", tzdir, "/"), lt)) ||
grepl(pat <- "^/usr/share/zoneinfo.default/", lt))
tz <- sub(pat, "", lt)
## all the locations listed for OlsonNames end in zoneinfo
else if(grepl(pat <- ".*/zoneinfo/(.*)", lt))
tz <- sub(pat, "\\1", lt)
if(!is.na(tz)) {
cacheIt(tz)
return(tz)
} else
message("unable to deduce timezone name from ", sQuote(lt))
}
## Last-gasp (slow, several seconds) fallback: compare a
## non-link lt0 to all the files under tzdir (as Java does).
## This may match more than one tz file: we don't care which.
if (nzchar(tzdir) && # we already found lt0
(is.na(lt <- Sys.readlink(lt0)) || !nzchar(lt))) {
warning(sprintf("Your system is mis-configured: %s is not a symlink",
sQuote(lt0)),
call. = FALSE, immediate. = TRUE, domain = NA)
if(nzchar(Sys.which("cmp"))) {
known <- dir(tzdir, recursive = TRUE)
for(tz in known) {
status <- system2("cmp", c("-s", lt0, file.path(tzdir, tz)))
if (status == 0L) {
cacheIt(tz)
warning(sprintf("It is strongly recommended to set envionment variable TZ to %s (or equivalent)",
sQuote(tz)),
call. = FALSE, immediate. = TRUE, domain = NA)
return(tz)
}
}
warning(sprintf("%s is not identical to any known timezone file",
sQuote(lt0)),
call. = FALSE, immediate. = TRUE, domain = NA)
}
}
## all heuristics have failed, so give up
NA_character_
}
as.POSIXlt <- function(x, tz = "", ...) UseMethod("as.POSIXlt")
as.POSIXlt.Date <- function(x, ...) .Internal(Date2POSIXlt(x))
## ## Moved to packages date and chron.
## as.POSIXlt.date <- as.POSIXlt.dates <- function(x, ...)
## as.POSIXlt(as.POSIXct(x), ...)
as.POSIXlt.POSIXct <- function(x, tz = "", ...)
{
if((missing(tz) || is.null(tz)) &&
!is.null(tzone <- attr(x, "tzone"))) tz <- tzone[1L]
.Internal(as.POSIXlt(x, tz))
}
as.POSIXlt.factor <- function(x, ...)
{
y <- as.POSIXlt(as.character(x), ...)
names(y$year) <- names(x)
y
}
as.POSIXlt.character <-
function(x, tz = "", format,
tryFormats = c("%Y-%m-%d %H:%M:%OS",
"%Y/%m/%d %H:%M:%OS",
"%Y-%m-%d %H:%M",
"%Y/%m/%d %H:%M",
"%Y-%m-%d",
"%Y/%m/%d"), optional = FALSE, ...)
{
x <- unclass(x) # precaution PR#7826
if(!missing(format)) {
res <- strptime(x, format, tz = tz)
if(nzchar(tz)) attr(res, "tzone") <- tz
return(res)
}
xx <- x[!is.na(x)]
if (!length(xx)) { # all NA
res <- strptime(x, "%Y/%m/%d")
if(nzchar(tz)) attr(res, "tzone") <- tz
return(res)
} else
for(f in tryFormats)
if(all(!is.na(strptime(xx, f, tz = tz)))) {
res <- strptime(x, f, tz = tz)
if(nzchar(tz)) attr(res, "tzone") <- tz
return(res)
}
## no success :
if(optional)
as.POSIXlt.character(rep.int(NA_character_, length(x)), tz=tz)
else stop("character string is not in a standard unambiguous format")
}
as.POSIXlt.numeric <- function(x, tz = "", origin, ...)
{
if(missing(origin)) stop("'origin' must be supplied")
as.POSIXlt(as.POSIXct(origin, tz = "UTC", ...) + x, tz = tz)
}
as.POSIXlt.default <- function(x, tz = "", optional = FALSE, ...)
{
if(inherits(x, "POSIXlt")) return(x)
if(is.logical(x) && all(is.na(x)))
return(as.POSIXlt(as.POSIXct.default(x), tz = tz))
if(optional)
as.POSIXlt.character(rep.int(NA_character_, length(x)), tz=tz)
else stop(gettextf("do not know how to convert '%s' to class %s",
deparse(substitute(x)),
dQuote("POSIXlt")),
domain = NA)
}
as.POSIXct <- function(x, tz = "", ...) UseMethod("as.POSIXct")
as.POSIXct.Date <- function(x, ...) .POSIXct(unclass(x)*86400)
## ## Moved to package date
## as.POSIXct.date <- function(x, ...)
## {
## if(inherits(x, "date")) {
## x <- (x - 3653) * 86400 # origin 1960-01-01
## return(.POSIXct(x))
## } else stop(gettextf("'%s' is not a \"date\" object",
## deparse(substitute(x)) ))
## }
## ## Moved to package chron
## as.POSIXct.dates <- function(x, ...)
## {
## if(inherits(x, "dates")) {
## z <- attr(x, "origin")
## x <- as.numeric(x) * 86400
## if(length(z) == 3L && is.numeric(z))
## x <- x + as.numeric(ISOdate(z[3L], z[1L], z[2L], 0))
## return(.POSIXct(x))
## } else stop(gettextf("'%s' is not a \"dates\" object",
## deparse(substitute(x)) ))
## }
as.POSIXct.POSIXlt <- function(x, tz = "", ...)
{
tzone <- attr(x, "tzone")
if(missing(tz) && !is.null(tzone)) tz <- tzone[1L]
## <FIXME>
## Move names handling to C code eventually ...
y <- .Internal(as.POSIXct(x, tz))
names(y) <- names(x$year)
.POSIXct(y, tz)
## </FIXME>
}
as.POSIXct.numeric <- function(x, tz = "", origin, ...)
{
if(missing(origin)) stop("'origin' must be supplied")
.POSIXct(as.POSIXct(origin, tz = "GMT", ...) + x, tz)
}
as.POSIXct.default <- function(x, tz = "", ...)
{
if(inherits(x, "POSIXct")) return(x)
if(is.character(x) || is.factor(x))
return(as.POSIXct(as.POSIXlt(x, tz, ...), tz, ...))
if(is.logical(x) && all(is.na(x)))
return(.POSIXct(as.numeric(x)))
stop(gettextf("do not know how to convert '%s' to class %s",
deparse(substitute(x)),
dQuote("POSIXct")),
domain = NA)
}
`length<-.POSIXct` <- function(x, value)
.POSIXct(NextMethod(), attr(x, "tzone"), oldClass(x))
as.double.POSIXlt <- function(x, ...) as.double(as.POSIXct(x))
## POSIXlt is not primarily a list, but primarily an abstract vector of
## time stamps:
length.POSIXlt <- function(x) length(unclass(x)[[1L]])
`length<-.POSIXlt` <- function(x, value)
.POSIXlt(lapply(unclass(x), `length<-`, value),
attr(x, "tzone"), oldClass(x))
format.POSIXlt <- function(x, format = "", usetz = FALSE, ...)
{
if(!inherits(x, "POSIXlt")) stop("wrong class")
if(any(f0 <- format == "")) {
## need list [ method here.
times <- unlist(unclass(x)[1L:3L])[f0]
secs <- x$sec[f0]; secs <- secs[!is.na(secs)]
np <- getOption("digits.secs")
np <- if(is.null(np)) 0L else min(6L, np)
if(np >= 1L)
for (i in seq_len(np)- 1L)
if(all( abs(secs - round(secs, i)) < 1e-6 )) {
np <- i
break
}
format[f0] <-
if(all(times[!is.na(times)] == 0)) "%Y-%m-%d"
else if(np == 0L) "%Y-%m-%d %H:%M:%S"
else paste0("%Y-%m-%d %H:%M:%OS", np)
}
## <FIXME>
## Move names handling to C code eventually ...
y <- .Internal(format.POSIXlt(x, format, usetz))
names(y) <- names(x$year)
y
## </FIXME>
}
## prior to 2.9.0 the same as format.POSIXlt.
## now more or less the same as format.POSIXct but also works for Dates.
strftime <- function(x, format = "", tz = "", usetz = FALSE, ...)
format(as.POSIXlt(x, tz = tz), format = format, usetz = usetz, ...)
strptime <- function(x, format, tz = "")
{
## <FIXME>
## Move names handling to C code eventually ...
y <- .Internal(strptime(as.character(x), format, tz))
## Assuming we can rely on the names of x ...
names(y$year) <- names(x)
y
## </FIXME>
}
format.POSIXct <- function(x, format = "", tz = "", usetz = FALSE, ...)
{
if(!inherits(x, "POSIXct")) stop("wrong class")
## NB identical(tz, "") is *NOT* the same as missing(tz)
if(missing(tz) && !is.null(tzone <- attr(x, "tzone"))) tz <- tzone
structure(format.POSIXlt(as.POSIXlt(x, tz), format, usetz, ...),
names = names(x))
}
## keep in sync with print.Date() in ./dates.R
print.POSIXct <-
print.POSIXlt <- function(x, tz = "", usetz = TRUE, max = NULL, ...)
{
if(is.null(max)) max <- getOption("max.print", 9999L)
FORM <- if(missing(tz))
function(z) format(z, usetz = usetz)
else function(z) format(z, tz = tz, usetz = usetz)
if(max < length(x)) {
print(FORM(x[seq_len(max)]), max=max+1, ...)
cat(" [ reached 'max' / getOption(\"max.print\") -- omitted",
length(x) - max, 'entries ]\n')
} else if(length(x))
print(FORM(x), max = max, ...)
else
cat(class(x)[1L], "of length 0\n")
invisible(x)
}
summary.POSIXct <- function(object, digits = 15L, ...)
{
x <- summary.default(unclass(object), digits = digits, ...)
if(m <- match("NA's", names(x), 0L)) {
NAs <- as.integer(x[m])
x <- x[-m]
attr(x, "NAs") <- NAs
}
.POSIXct(x,
tz = attr(object, "tzone"),
cl = c("summaryDefault", "table", oldClass(object)))
}
summary.POSIXlt <- function(object, digits = 15, ...)
summary(as.POSIXct(object), digits = digits, ...)
`+.POSIXt` <- function(e1, e2)
{
## need to drop "units" attribute here
coerceTimeUnit <- function(x)
as.vector(switch(attr(x,"units"),
secs = x, mins = 60*x, hours = 60*60*x,
days = 60*60*24*x, weeks = 60*60*24*7*x))
if (nargs() == 1L) return(e1)
# only valid if one of e1 and e2 is a scalar/difftime
if(inherits(e1, "POSIXt") && inherits(e2, "POSIXt"))
stop("binary '+' is not defined for \"POSIXt\" objects")
if(inherits(e1, "POSIXlt")) e1 <- as.POSIXct(e1)
if(inherits(e2, "POSIXlt")) e2 <- as.POSIXct(e2)
if (inherits(e1, "difftime")) e1 <- coerceTimeUnit(e1)
if (inherits(e2, "difftime")) e2 <- coerceTimeUnit(e2)
.POSIXct(unclass(e1) + unclass(e2), check_tzones(e1, e2))
}
`-.POSIXt` <- function(e1, e2)
{
## need to drop "units" attribute here
coerceTimeUnit <- function(x)
as.vector(switch(attr(x,"units"),
secs = x, mins = 60*x, hours = 60*60*x,
days = 60*60*24*x, weeks = 60*60*24*7*x))
if(!inherits(e1, "POSIXt"))
stop("can only subtract from \"POSIXt\" objects")
if (nargs() == 1L) stop("unary '-' is not defined for \"POSIXt\" objects")
if(inherits(e2, "POSIXt")) return(difftime(e1, e2))
if (inherits(e2, "difftime")) e2 <- coerceTimeUnit(e2)
if(!is.null(attr(e2, "class")))
stop("can only subtract numbers from \"POSIXt\" objects")
e1 <- as.POSIXct(e1)
.POSIXct(unclass(e1) - e2, attr(e1, "tzone"))
}
Ops.POSIXt <- function(e1, e2)
{
if (nargs() == 1L)
stop(gettextf("unary '%s' not defined for \"POSIXt\" objects",
.Generic), domain = NA)
boolean <- switch(.Generic, "<" = , ">" = , "==" = ,
"!=" = , "<=" = , ">=" = TRUE, FALSE)
if (!boolean)
stop(gettextf("'%s' not defined for \"POSIXt\" objects", .Generic),
domain = NA)
if(inherits(e1, "POSIXlt") || is.character(e1)) e1 <- as.POSIXct(e1)
if(inherits(e2, "POSIXlt") || is.character(e2)) e2 <- as.POSIXct(e2)
check_tzones(e1, e2)
NextMethod(.Generic)
}
Math.POSIXt <- function (x, ...)
{
stop(gettextf("'%s' not defined for \"POSIXt\" objects", .Generic),
domain = NA)
}
check_tzones <- function(...)
{
tzs <- unique(sapply(list(...), function(x) {
y <- attr(x, "tzone")
if(is.null(y)) "" else y[1L]
}))
tzs <- tzs[nzchar(tzs)]
if(length(tzs) > 1L)
warning("'tzone' attributes are inconsistent")
if(length(tzs)) tzs[1L] else NULL
}
Summary.POSIXct <- function (..., na.rm)
{
ok <- switch(.Generic, max = , min = , range = TRUE, FALSE)
if (!ok)
stop(gettextf("'%s' not defined for \"POSIXt\" objects", .Generic),
domain = NA)
args <- list(...)
tz <- do.call("check_tzones", args)
.POSIXct(NextMethod(.Generic), tz = tz, cl = oldClass(args[[1L]]))
}
Summary.POSIXlt <- function (..., na.rm)
{
ok <- switch(.Generic, max = , min = , range = TRUE, FALSE)
if (!ok)
stop(gettextf("'%s' not defined for \"POSIXt\" objects", .Generic),
domain = NA)
args <- list(...)
tz <- do.call("check_tzones", args)
args <- lapply(args, as.POSIXct)
val <- do.call(.Generic, c(args, na.rm = na.rm))
as.POSIXlt(.POSIXct(val, tz))
}
`[.POSIXct` <-
function(x, ..., drop = TRUE)
.POSIXct(NextMethod("["), attr(x, "tzone"), oldClass(x))
`[[.POSIXct` <-
function(x, ..., drop = TRUE)
.POSIXct(NextMethod("[["), attr(x, "tzone"), oldClass(x))
`[<-.POSIXct` <-
function(x, ..., value) {
if(!length(value)) return(x)
value <- unclass(as.POSIXct(value))
.POSIXct(NextMethod(.Generic), attr(x, "tzone"), oldClass(x))
}
as.character.POSIXt <- function(x, ...) format(x, ...)
as.data.frame.POSIXct <- as.data.frame.vector
as.list.POSIXct <- function(x, ...)
{
nms <- names(x)
names(x) <- NULL
y <- lapply(unclass(x), .POSIXct, attr(x, "tzone"), oldClass(x))
names(y) <- nms
y
}
is.na.POSIXlt <- function(x)
is.na(as.POSIXct(x))
anyNA.POSIXlt <- function(x, recursive = FALSE)
anyNA(as.POSIXct(x))
## <FIXME> check the argument validity
## This is documented to remove the timezone
c.POSIXct <- function(..., recursive = FALSE)
.POSIXct(c(unlist(lapply(list(...), unclass))))
## we need conversion to POSIXct as POSIXlt objects can be in different tz.
c.POSIXlt <- function(..., recursive = FALSE)
as.POSIXlt(do.call("c", lapply(list(...), as.POSIXct)))
ISOdatetime <- function(year, month, day, hour, min, sec, tz = "")
{
if(min(vapply(list(year, month, day, hour, min, sec), length, 1, USE.NAMES=FALSE)) == 0L)
.POSIXct(numeric(), tz = tz)
else {
x <- paste(year, month, day, hour, min, sec, sep = "-")
as.POSIXct(strptime(x, "%Y-%m-%d-%H-%M-%OS", tz = tz), tz = tz)
}
}
ISOdate <- function(year, month, day, hour = 12, min = 0, sec = 0, tz = "GMT")
ISOdatetime(year, month, day, hour, min, sec, tz)
as.matrix.POSIXlt <- function(x, ...)
{
as.matrix(as.data.frame(unclass(x)), ...)
}
mean.POSIXct <- function (x, ...)
.POSIXct(mean(unclass(x), ...), attr(x, "tzone"))
mean.POSIXlt <- function (x, ...)
as.POSIXlt(mean(as.POSIXct(x), ...))
## ----- difftime -----
difftime <-
function(time1, time2, tz,
units = c("auto", "secs", "mins", "hours", "days", "weeks"))
{
if (missing(tz)) {
time1 <- as.POSIXct(time1)
time2 <- as.POSIXct(time2)
} else {
## Wishlist PR#14182
time1 <- as.POSIXct(time1, tz = tz)
time2 <- as.POSIXct(time2, tz = tz)
}
z <- unclass(time1) - unclass(time2)
attr(z, "tzone") <- NULL # it may get copied from args of `-`
units <- match.arg(units)
if(units == "auto")
units <-
if(all(is.na(z))) "secs"
else {
zz <- min(abs(z), na.rm = TRUE)
if(!is.finite(zz) || zz < 60) "secs"
else if(zz < 3600) "mins"
else if(zz < 86400) "hours"
else "days"
}
switch(units,
"secs" = .difftime(z, units = "secs"),
"mins" = .difftime(z/60, units = "mins"),
"hours" = .difftime(z/3600, units = "hours"),
"days" = .difftime(z/86400, units = "days"),
"weeks" = .difftime(z/(7*86400), units = "weeks")
)
}
## "difftime" constructor
## Martin Maechler, Date: 16 Sep 2002
## Numeric input version Peter Dalgaard, December 2006
as.difftime <- function(tim, format = "%X", units = "auto")
{
if (inherits(tim, "difftime")) return(tim)
if (is.character(tim)) {
difftime(strptime(tim, format = format),
strptime("0:0:0", format = "%X"), units = units)
} else {
if (!is.numeric(tim)) stop("'tim' is not character or numeric")
if (units == "auto") stop("need explicit units for numeric conversion")
if (!(units %in% c("secs", "mins", "hours", "days", "weeks")))
stop("invalid units specified")
.difftime(tim, units = units)
}
}
### For now, these have only difftime methods, but you never know...
units <- function(x) UseMethod("units")
`units<-` <- function(x, value) UseMethod("units<-")
units.difftime <- function(x) attr(x, "units")
`units<-.difftime` <- function(x, value)
{
from <- units(x)
if (from == value) return(x)
if (!(value %in% c("secs", "mins", "hours", "days", "weeks")))
stop("invalid units specified")
sc <- cumprod(c(secs = 1, mins = 60, hours = 60, days = 24, weeks = 7))
newx <- unclass(x) * as.vector(sc[from]/sc[value])
.difftime(newx, value)
}
as.double.difftime <- function(x, units = "auto", ...)
{
if (units != "auto") units(x) <- units
as.vector(x, "double")
}
as.data.frame.difftime <- as.data.frame.vector
format.difftime <- function(x,...)
paste(format(unclass(x),...), units(x))
print.difftime <- function(x, digits = getOption("digits"), ...)
{
if(is.array(x) || length(x) > 1L) {
cat("Time differences in ", attr(x, "units"), "\n", sep = "")
y <- unclass(x); attr(y, "units") <- NULL
print(y, digits=digits, ...)
}
else
cat("Time difference of ", format(unclass(x), digits = digits), " ",
attr(x, "units"), "\n", sep = "")
invisible(x)
}
`[.difftime` <- function(x, ..., drop = TRUE)
.difftime(NextMethod("["), attr(x, "units"), oldClass(x))
diff.difftime <- function(x, ...)
.difftime(NextMethod("diff"), attr(x, "units"), oldClass(x))
Ops.difftime <- function(e1, e2)
{
coerceTimeUnit <- function(x)
{
switch(attr(x, "units"),
secs = x, mins = 60*x, hours = 60*60*x,
days = 60*60*24*x, weeks = 60*60*24*7*x)
}
if (nargs() == 1L) {
switch(.Generic, "+" = {}, "-" = {e1[] <- -unclass(e1)},
stop(gettextf("unary '%s' not defined for \"difftime\" objects",
.Generic), domain = NA, call. = FALSE)
)
return(e1)
}
boolean <- switch(.Generic, "<" = , ">" = , "==" = ,
"!=" = , "<=" = , ">=" = TRUE, FALSE)
if (boolean) {
## assume user knows what he/she is doing if not both difftime
if(inherits(e1, "difftime") && inherits(e2, "difftime")) {
e1 <- coerceTimeUnit(e1)
e2 <- coerceTimeUnit(e2)
}
NextMethod(.Generic)
} else if(.Generic == "+" || .Generic == "-") {
if(inherits(e1, "difftime") && !inherits(e2, "difftime"))
return(.difftime(NextMethod(.Generic),
units = attr(e1, "units")))
if(!inherits(e1, "difftime") && inherits(e2, "difftime"))
return(.difftime(NextMethod(.Generic),
units = attr(e2, "units")))
u1 <- attr(e1, "units")
if(attr(e2, "units") == u1) {
.difftime(NextMethod(.Generic), units = u1)
} else {
e1 <- coerceTimeUnit(e1)
e2 <- coerceTimeUnit(e2)
.difftime(NextMethod(.Generic), units = "secs")
}
} else {
## '*' is covered by a specific method
stop(gettextf("'%s' not defined for \"difftime\" objects", .Generic),
domain = NA)
}
}
`*.difftime` <- function (e1, e2)
{
## need one scalar, one difftime.
if(inherits(e1, "difftime") && inherits(e2, "difftime"))
stop("both arguments of * cannot be \"difftime\" objects")
if(inherits(e2, "difftime")) {tmp <- e1; e1 <- e2; e2 <- tmp}
.difftime(e2 * unclass(e1), attr(e1, "units"))
}
`/.difftime` <- function (e1, e2)
{
## need one scalar, one difftime.
if(inherits(e2, "difftime"))
stop("second argument of / cannot be a \"difftime\" object")
.difftime(unclass(e1) / e2, attr(e1, "units"))
}
## "Math": some methods should work; the other ones are meaningless :
Math.difftime <- function (x, ...)
{
switch(.Generic,
"abs" =, "sign" =, "floor" =, "ceiling" =, "trunc" =,
"round" =, "signif" = {
units <- attr(x, "units")
.difftime(NextMethod(), units)
},
### otherwise :
stop(gettextf("'%s' not defined for \"difftime\" objects", .Generic),
domain = NA))
}
mean.difftime <- function (x, ...)
.difftime(mean(unclass(x), ...), attr(x, "units"))
Summary.difftime <- function (..., na.rm)
{
## FIXME: this could return in the smallest of the units of the inputs.
coerceTimeUnit <- function(x)
{
as.vector(switch(attr(x,"units"),
secs = x, mins = 60*x, hours = 60*60*x,
days = 60*60*24*x, weeks = 60*60*24*7*x))
}
ok <- switch(.Generic, max = , min = , sum=, range = TRUE, FALSE)
if (!ok)
stop(gettextf("'%s' not defined for \"difftime\" objects", .Generic),
domain = NA)
x <- list(...)
Nargs <- length(x)
if(Nargs == 0) {
.difftime(do.call(.Generic), "secs")
} else {
units <- sapply(x, attr, "units")
if(all(units == units[1L])) {
args <- c(lapply(x, as.vector), na.rm = na.rm)
} else {
args <- c(lapply(x, coerceTimeUnit), na.rm = na.rm)
units <- "secs"
}
.difftime(do.call(.Generic, args), units[[1L]])
}
}
c.difftime <-
function(..., recursive = FALSE)
{
coerceTimeUnit <- function(x) {
switch(attr(x, "units"),
secs = x, mins = 60*x, hours = 60*60*x,
days = 60*60*24*x, weeks = 60*60*24*7*x)
}
args <- list(...)
if(!length(args)) return(.difftime(double(), "secs"))
ind <- sapply(args, inherits, "difftime")
pos <- which(!ind)
units <- sapply(args[ind], attr, "units")
if(all(units == (un1 <- units[1L]))) {
if(length(pos))
args[pos] <-
lapply(args[pos], as.difftime, units = un1)
.difftime(unlist(args), un1)
} else {
if(length(pos))
args[pos] <-
lapply(args[pos], as.difftime, units = "secs")
args[ind] <- lapply(args[ind], coerceTimeUnit)
.difftime(unlist(args), "secs")
}
}
`length<-.difftime` <-
function(x, value)
.difftime(NextMethod(), attr(x, "units"), oldClass(x))
## ----- convenience functions -----
seq.POSIXt <-
function(from, to, by, length.out = NULL, along.with = NULL, ...)
{
if (missing(from)) stop("'from' must be specified")
if (!inherits(from, "POSIXt")) stop("'from' must be a \"POSIXt\" object")
cfrom <- as.POSIXct(from)
if(length(cfrom) != 1L) stop("'from' must be of length 1")
tz <- attr(cfrom , "tzone")
if (!missing(to)) {
if (!inherits(to, "POSIXt")) stop("'to' must be a \"POSIXt\" object")
if (length(as.POSIXct(to)) != 1) stop("'to' must be of length 1")
}
if (!missing(along.with)) {
length.out <- length(along.with)
} else if (!is.null(length.out)) {
if (length(length.out) != 1L) stop("'length.out' must be of length 1")
length.out <- ceiling(length.out)
}
status <- c(!missing(to), !missing(by), !is.null(length.out))
if(sum(status) != 2L)
stop("exactly two of 'to', 'by' and 'length.out' / 'along.with' must be specified")
if (missing(by)) {
from <- unclass(cfrom)
to <- unclass(as.POSIXct(to))
## Till (and incl.) 1.6.0 :
##- incr <- (to - from)/length.out
##- res <- seq.default(from, to, incr)
res <- seq.int(from, to, length.out = length.out)
return(.POSIXct(res, tz))
}
if (length(by) != 1L) stop("'by' must be of length 1")
valid <- 0L
if (inherits(by, "difftime")) {
by <- switch(attr(by,"units"), secs = 1, mins = 60, hours = 3600,
days = 86400, weeks = 7*86400) * unclass(by)
} else if(is.character(by)) {
by2 <- strsplit(by, " ", fixed = TRUE)[[1L]]
if(length(by2) > 2L || length(by2) < 1L)
stop("invalid 'by' string")
valid <- pmatch(by2[length(by2)],
c("secs", "mins", "hours", "days", "weeks",
"months", "years", "DSTdays", "quarters"))
if(is.na(valid)) stop("invalid string for 'by'")
if(valid <= 5L) {
by <- c(1, 60, 3600, 86400, 7*86400)[valid]
if (length(by2) == 2L) by <- by * as.integer(by2[1L])
} else
by <- if(length(by2) == 2L) as.integer(by2[1L]) else 1
} else if(!is.numeric(by)) stop("invalid mode for 'by'")
if(is.na(by)) stop("'by' is NA")
if(valid <= 5L) { # secs, mins, hours, days, weeks
from <- unclass(as.POSIXct(from))
if(!is.null(length.out))
res <- seq.int(from, by = by, length.out = length.out)
else {
to0 <- unclass(as.POSIXct(to))
## defeat test in seq.default
res <- seq.int(0, to0 - from, by) + from
}
return(.POSIXct(res, tz))
} else { # months or years or DSTdays or quarters
r1 <- as.POSIXlt(from)
if(valid == 7L) { # years
if(missing(to)) { # years
yr <- seq.int(r1$year, by = by, length.out = length.out)
} else {
to <- as.POSIXlt(to)
yr <- seq.int(r1$year, to$year, by)
}
r1$year <- yr
} else if(valid %in% c(6L, 9L)) { # months or quarters
if (valid == 9L) by <- by * 3
if(missing(to)) {
mon <- seq.int(r1$mon, by = by, length.out = length.out)
} else {
to0 <- as.POSIXlt(to)
mon <- seq.int(r1$mon, 12*(to0$year - r1$year) + to0$mon, by)
}
r1$mon <- mon
} else if(valid == 8L) { # DSTdays
if(!missing(to)) {
## We might have a short day, so need to over-estimate.
length.out <- 2L + floor((unclass(as.POSIXct(to)) -
unclass(as.POSIXct(from)))/(by * 86400))
}
r1$mday <- seq.int(r1$mday, by = by, length.out = length.out)
}
r1$isdst <- -1L
res <- as.POSIXct(r1)
## now shorten if necessary.
if(!missing(to)) {
to <- as.POSIXct(to)
res <- if(by > 0) res[res <= to] else res[res >= to]
}
res
}
}
## *very* similar to cut.Date [ ./dates.R ] -- keep in sync!
cut.POSIXt <-
function (x, breaks, labels = NULL, start.on.monday = TRUE,
right = FALSE, ...)
{
if(!inherits(x, "POSIXt")) stop("'x' must be a date-time object")
x <- as.POSIXct(x)
if (inherits(breaks, "POSIXt")) {
breaks <- sort(as.POSIXct(breaks))
} else if(is.numeric(breaks) && length(breaks) == 1L) {
## specified number of breaks
} else if(is.character(breaks) && length(breaks) == 1L) {
by2 <- strsplit(breaks, " ", fixed = TRUE)[[1L]]
if(length(by2) > 2L || length(by2) < 1L)
stop("invalid specification of 'breaks'")
valid <-
pmatch(by2[length(by2)],
c("secs", "mins", "hours", "days", "weeks",
"months", "years", "DSTdays", "quarters"))
if(is.na(valid)) stop("invalid specification of 'breaks'")
start <- as.POSIXlt(min(x, na.rm = TRUE))
incr <- 1
if(valid > 1L) { start$sec <- 0L; incr <- 60 }
if(valid > 2L) { start$min <- 0L; incr <- 3600 }
## start of day need not be on the same DST, PR#14208
if(valid > 3L) { start$hour <- 0L; start$isdst <- -1L; incr <- 86400 }
if(valid == 5L) { # weeks
start$mday <- start$mday - start$wday
if(start.on.monday)
start$mday <- start$mday + ifelse(start$wday > 0L, 1L, -6L)
incr <- 7*86400
}
if(valid == 8L) incr <- 25*3600 # DSTdays
if(valid == 6L) { # months
start$mday <- 1L
end <- as.POSIXlt(max(x, na.rm = TRUE))
step <- if(length(by2) == 2L) as.integer(by2[1L]) else 1L
end <- as.POSIXlt(end + (31 * step * 86400))
end$mday <- 1L
end$isdst <- -1L
breaks <- seq(start, end, breaks)
} else if(valid == 7L) { # years
start$mon <- 0L
start$mday <- 1L
end <- as.POSIXlt(max(x, na.rm = TRUE))
step <- if(length(by2) == 2L) as.integer(by2[1L]) else 1L
end <- as.POSIXlt(end + (366 * step* 86400))
end$mon <- 0L
end$mday <- 1L
end$isdst <- -1L
breaks <- seq(start, end, breaks)
} else if(valid == 9L) { # quarters
qtr <- rep(c(0L, 3L, 6L, 9L), each = 3L)
start$mon <- qtr[start$mon + 1L]
start$mday <- 1L
maxx <- max(x, na.rm = TRUE)
end <- as.POSIXlt(maxx)
step <- if(length(by2) == 2L) as.integer(by2[1L]) else 1L
end <- as.POSIXlt(end + (93 * step * 86400))
end$mon <- qtr[end$mon + 1L]
end$mday <- 1L
end$isdst <- -1L
breaks <- seq(start, end, paste(step * 3, "months"))
## 93 days ahead could give an empty level, so
lb <- length(breaks)
if(maxx < breaks[lb-1]) breaks <- breaks[-lb]
} else { # weeks or shorter
if (length(by2) == 2L) incr <- incr * as.integer(by2[1L])
maxx <- max(x, na.rm = TRUE)
breaks <- seq(start, maxx + incr, breaks)
breaks <- breaks[seq_len(1+max(which(breaks <= maxx)))]
}
} else stop("invalid specification of 'breaks'")
res <- cut(unclass(x), unclass(breaks), labels = labels,
right = right, ...)
if(is.null(labels)) {
levels(res) <-
as.character(if (is.numeric(breaks)) x[!duplicated(res)]
else breaks[-length(breaks)])
}
res
}
julian <- function(x, ...) UseMethod("julian")
julian.POSIXt <- function(x, origin = as.POSIXct("1970-01-01", tz = "GMT"), ...)
{
origin <- as.POSIXct(origin)
if(length(origin) != 1L) stop("'origin' must be of length one")
res <- difftime(as.POSIXct(x), origin, units = "days")
structure(res, "origin" = origin)
}
## Note that 'abbreviate' works *vectorized* here :
weekdays <- function(x, abbreviate) UseMethod("weekdays")
weekdays.POSIXt <- function(x, abbreviate = FALSE)
{
format(x, ifelse(abbreviate, "%a", "%A"))
}
months <- function(x, abbreviate) UseMethod("months")
months.POSIXt <- function(x, abbreviate = FALSE)
{
format(x, ifelse(abbreviate, "%b", "%B"))
}
quarters <- function(x, abbreviate) UseMethod("quarters")
quarters.POSIXt <- function(x, ...)
{
x <- (as.POSIXlt(x)$mon)%/%3
paste0("Q", x+1)
}
trunc.POSIXt <-
function(x, units = c("secs", "mins", "hours", "days", "months", "years"), ...)
{
units <- match.arg(units)
x <- as.POSIXlt(x)
if(length(x$sec))
switch(units,
"secs" = {x$sec <- trunc(x$sec)},
"mins" = {x$sec[] <- 0},
"hours" = {x$sec[] <- 0; x$min[] <- 0L},
## start of day need not be on the same DST.
"days" = {
x$sec[] <- 0; x$min[] <- 0L; x$hour[] <- 0L;
x$isdst[] <- -1L
},
"months" = {
x$sec[] <- 0; x$min[] <- 0L; x$hour[] <- 0L;
x$mday[] <- 1L
x$isdst[] <- -1L
## To get wday and yday correctly:
x <- as.POSIXlt(as.POSIXct(x))
},
"years" = {
x$sec[] <- 0; x$min[] <- 0L; x$hour[] <- 0L;
x$mday[] <- 1L; x$mon[] <- 0L
x$isdst[] <- -1L
## To get wday and yday correctly:
x <- as.POSIXlt(as.POSIXct(x))
}
)
x
}
round.POSIXt <-
function(x, units = c("secs", "mins", "hours", "days", "months", "years"))
{
.round_x_to_l_or_u <- function(lx, ll, lu) {
## lx ll lu all POSIXlt, lu not necessarily valid yet.
cu <- as.POSIXct(lu)
lu <- as.POSIXlt(cu)
tu <- unclass(cu)
tx <- unclass(as.POSIXct(lx))
tl <- unclass(as.POSIXct(ll))
up <- ((tu - tx) <= (tx - tl))
up <- !is.na(up) & up
y <- ll
y[up] <- lu[up]
y
}
## this gets the default from the generic's 2nd arg 'digits = 0' :
units <- if(is.numeric(units) && units == 0.) "secs" else match.arg(units)
if(units == "months") {
x <- as.POSIXlt(x)
## Start of this month:
ll <- trunc.POSIXt(x, "months")
## Start of next month:
lu <- ll
lu$mon <- lu$mon + 1L
## Now make lu valid and round ...
.round_x_to_l_or_u(x, ll, lu)
}
else if(units == "years") {
x <- as.POSIXlt(x)
## Start of this year:
ll <- trunc.POSIXt(x, "years")
## Start of next year:
lu <- ll
lu$year <- lu$year + 1L
## Now make lu valid and round ...
.round_x_to_l_or_u(x, ll, lu)
}
else
trunc.POSIXt(as.POSIXct(x) +
switch(units,
"secs" = 0.5,
"mins" = 30,
"hours" = 1800,
"days" = 43200),
units = units)
}
## ---- additions in 1.5.0 -----
## R 3.5.0 added a j index to the [ POSIXlt extract and replace methods,
## in order to avoid having users unclass (and reclass) for extracting
## or replacing single components (as the [[ method was changed to work
## on datetimes rather than components, and the $ methods are convenient
## for component literals only).
##
## Dealing with the j index is not quite straightforward though: we now
## insist on it being a character string, and if it does not exactly
## match a component name we extract or replace nothing. If it matches,
## the replace method currently does not ensure the "validity" (correct
## length or type etc) of the replacement (as the internal POSIXlt codes
## seem rather generous when dealing with invalid components).
##
## Dealing with a character i index is not quite straightforward either,
## as the names of POSIXlt objects are kept in the 'year' component. It
## seems that for extracting we can get by (and get results consistent
## with the POSIXct case) via matching i against the names of x, whereas
## this does not deal with all boundary (out-of-bounds etc) cases for
## replacing: hence for the latter, we simply add the names to all
## components (if i is character).
`[.POSIXlt` <- function(x, i, j, drop = TRUE)
{
if(!(mj <- missing(j)))
if(!is.character(j) || (length(j) != 1L))
stop("component subscript must be a character string")
if(missing(i)) {
if(mj)
x
else
unclass(x)[[j]]
} else {
if(is.character(i))
i <- match(i, names(x),
incomparables = c("", NA_character_))
if(mj)
.POSIXlt(lapply(X = unclass(x), FUN = "[", i, drop = drop),
attr(x, "tzone"), oldClass(x))
else
unclass(x)[[j]][i]
}
}
`[<-.POSIXlt` <- function(x, i, j, value)
{
if(!(mj <- missing(j)))
if(!is.character(j) || (length(j) != 1L))
stop("component subscript must be a character string")
if(!length(value))
return(x)
cl <- oldClass(x)
class(x) <- NULL
if(missing(i)) {
if(mj)
x <- as.POSIXlt(value)
else
x[[j]] <- value
} else {
ici <- is.character(i)
nms <- names(x$year)
if(mj) {
value <- unclass(as.POSIXlt(value))
if(ici) {
for(n in names(x))
names(x[[n]]) <- nms
}
for(n in names(x))
x[[n]][i] <- value[[n]]
} else {
if(ici) {
names(x[[j]]) <- nms
}
x[[j]][i] <- value
}
}
class(x) <- cl
x
}
as.data.frame.POSIXlt <- function(x, row.names = NULL, optional = FALSE, ...)
{
value <- as.data.frame.POSIXct(as.POSIXct(x), row.names, optional, ...)
if (!optional)
names(value) <- deparse(substitute(x))[[1L]]
value
}
## ---- additions in 1.8.0 -----
rep.POSIXct <- function(x, ...)
.POSIXct(NextMethod(), attr(x, "tzone"), oldClass(x))
rep.POSIXlt <- function(x, ...)
.POSIXlt(lapply(X = unclass(x), FUN = rep, ...),
attr(x, "tzone"), oldClass(x))
diff.POSIXt <- function (x, lag = 1L, differences = 1L, ...)
{
ismat <- is.matrix(x)
r <- if(inherits(x, "POSIXlt")) as.POSIXct(x) else x
xlen <- if (ismat) dim(x)[1L] else length(r)
if (length(lag) != 1L || length(differences) > 1L || lag < 1L || differences < 1L)
stop("'lag' and 'differences' must be integers >= 1")
if (lag * differences >= xlen) return(.difftime(numeric(), "secs"))
i1 <- -seq_len(lag)
if (ismat) for (i in seq_len(differences)) r <- r[i1, , drop = FALSE] -
r[-nrow(r):-(nrow(r) - lag + 1), , drop = FALSE]
else for (i in seq_len(differences))
r <- r[i1] - r[-length(r):-(length(r) - lag + 1L)]
r
}
## ---- additions in 2.2.0 -----
duplicated.POSIXlt <- function(x, incomparables = FALSE, ...)
{
x <- as.POSIXct(x)
NextMethod("duplicated", x)
}
unique.POSIXlt <- function(x, incomparables = FALSE, ...)
x[!duplicated(x, incomparables, ...)]
## ---- additions in 2.4.0 -----
sort.POSIXlt <- function(x, decreasing = FALSE, na.last = NA, ...)
x[order(as.POSIXct(x), na.last = na.last, decreasing = decreasing)]
## ---- additions in 2.6.0 -----
is.numeric.POSIXt <- function(x) FALSE
## ---- additions in 2.8.0 -----
split.POSIXct <-
function(x, f, drop = FALSE, ...)
lapply(split.default(as.double(x), f, drop = drop, ...),
.POSIXct, attr(x, "tzone"), oldClass(x))
xtfrm.POSIXct <- function(x) as.numeric(x)
xtfrm.POSIXlt <- function(x) as.double(x) # has POSIXlt method
xtfrm.difftime <- function(x) as.numeric(x)
is.numeric.difftime <- function(x) FALSE
## Class generators added in 2.11.0, class order changed in 2.12.0.
## FIXME:
## At least temporarily avoide structure() for performance reasons.
## .POSIXct <- function(xx, tz = NULL)
## structure(xx, class = c("POSIXct", "POSIXt"), tzone = tz)
.POSIXct <- function(xx, tz = NULL, cl = c("POSIXct", "POSIXt")) {
class(xx) <- cl
attr(xx, "tzone") <- tz
xx
}
## FIXME:
## At least temporarily avoide structure() for performance reasons.
## .POSIXlt <- function(xx, tz = NULL)
## structure(xx, class = c("POSIXlt", "POSIXt"), tzone = tz)
.POSIXlt <- function(xx, tz = NULL, cl = c("POSIXlt", "POSIXt")) {
class(xx) <- cl
attr(xx, "tzone") <- tz
xx
}
## FIXME:
## At least temporarily avoide structure() for performance reasons.
## .difftime <- function(xx, units)
## structure(xx, units = units, class = "difftime")
.difftime <- function(xx, units, cl = "difftime") {
class(xx) <- cl
attr(xx, "units") <- units
xx
}
## ---- additions in 2.13.0 -----
names.POSIXlt <-
function(x)
names(x$year)
`names<-.POSIXlt` <-
function(x, value)
{
names(x$year) <- value
x
}
## Added in 3.1.0.
OlsonNames <- function(tzdir = NULL)
{
if (is.null(tzdir)) {
if(.Platform$OS.type == "windows")
tzdir <- Sys.getenv("TZDIR", file.path(R.home("share"), "zoneinfo"))
else {
## Try known locations in turn.
## The list is not exhaustive (mac OS 10.13's
## /usr/share/zoneinfo is a symlink) and there is a risk that
## the wrong one is found.
## We assume that if the second exists that the system was
## configured with --with-internal-tzcode
tzdirs <- c(Sys.getenv("TZDIR"), # defaults to ""
file.path(R.home("share"), "zoneinfo"),
"/usr/share/zoneinfo", # Linux, macOS, FreeBSD
"/share/zoneinfo", # in musl's search
"/usr/share/lib/zoneinfo", # Solaris, AIX
"/usr/lib/zoneinfo", # early glibc
"/usr/local/etc/zoneinfo", # tzcode default
"/etc/zoneinfo", "/usr/etc/zoneinfo")
tzdirs <- tzdirs[file.exists(tzdirs)]
if (!length(tzdirs)) {
warning("no Olson database found")
return(character())
} else tzdir <- tzdirs[1L]
}
} else if(!dir.exists(tzdir))
stop(sprintf("%s is not a directory", sQuote(tzdir)), domain = NA)
x <- list.files(tzdir, recursive = TRUE)
## Some databases have VERSION (tzdata hence --with-internal-tzcode),
## some +VERSION (Apple), some neither (including glibc)
ver <- if(file.exists(vf <- file.path(tzdir, "VERSION")))
readLines(vf, warn = FALSE)
else if(file.exists(vf <- file.path(tzdir, "+VERSION")))
readLines(vf, warn = FALSE)
## else NULL
x <- setdiff(x, "VERSION")
## all other auxiliary files are l/case.
ans <- grep("^[ABCDEFGHIJKLMNOPQRSTUVWXYZ]", x, value = TRUE)
if(!is.null(ver)) attr(ans, "Version") <- ver
ans
}
## Added in 3.5.0.
`[[.POSIXlt` <- function(x, i, drop = TRUE)
{
if(!missing(i) && is.character(i)) {
i <- match(i, names(x), incomparables = c("", NA_character_))
}
.POSIXlt(lapply(X = unclass(x), FUN = "[[", i, drop = drop),
attr(x, "tzone"), oldClass(x))
}
as.list.POSIXlt <- function(x, ...)
{
nms <- names(x)
names(x) <- NULL
y <- lapply(X = do.call(Map, c(list, unclass(x))),
FUN = .POSIXlt, attr(x, "tzone"), oldClass(x))
names(y) <- nms
y
}
## Added in 3.6.0.
`[[<-.POSIXlt` <- function(x, i, value)
{
cl <- oldClass(x)
class(x) <- NULL
if(!missing(i) && is.character(i)) {
nms <- names(x$year)
for(n in names(x))
names(x[[n]]) <- nms
}
value <- unclass(as.POSIXlt(value))
for(n in names(x))
x[[n]][[i]] <- value[[n]]
class(x) <- cl
x
}