Skip to content

Commit faf9394

Browse files
author
maechler
committed
seq.Date() & seq.POSIXt(): from now optional; refactoring "in sync"
git-svn-id: https://svn.r-project.org/R/trunk@87502 00db46b3-68df-0310-9c12-caf00c1e9a41
1 parent 10e4a08 commit faf9394

File tree

6 files changed

+236
-163
lines changed

6 files changed

+236
-163
lines changed

doc/NEWS.Rd

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,14 @@
143143

144144
\item If \code{La_library()} is empty, \code{sessionInfo()} still
145145
reports \code{La_version()} when available.
146+
147+
\item \code{seq.Date(from, to, by, ....)} and \code{seq.POSIXt(..)}
148+
now also work when \code{from} is missing and sufficient further
149+
arguments are provided, thanks to \I{Michael Chirico}'s report, patch
150+
proposal in \PR{17672} and \sQuote{\I{R Dev Day}} contributions.
151+
152+
The \code{Date} method also works for \code{seq(from, to)}, when
153+
\code{by} is missing and now defaults to \code{"1 days"}.
146154
}
147155
}
148156
@@ -492,17 +500,17 @@
492500
empty string as its \code{path} argument.
493501
494502
\item Silent integer overflow could occur in the
495-
\sQuote{exact} computations for \code{fisher.test()} for
496-
unrealistic inputs: this is now an error.
503+
\sQuote{exact} computations for \code{fisher.test()} for
504+
unrealistic inputs: this is now an error.
497505
498-
\item Some invalid C-level memory accesses are avoided for
499-
\code{loglin(, margin = NULL)}.
506+
\item Some invalid C-level memory accesses are avoided for
507+
\code{loglin(, margin = NULL)}.
500508
501-
\code{loglin(, param = TRUE)} no longer gives an error in corner
502-
cases such as a one-dimensional input.
509+
\code{loglin(, param = TRUE)} no longer gives an error in corner
510+
cases such as a one-dimensional input.
503511
504-
\item \code{dev.capabilities() $ events} now reports \code{"Idle"} if
505-
the device provides it, fixing \PR{18836}, thanks to \I{Trevor Davis}.
512+
\item \code{dev.capabilities() $ events} now reports \code{"Idle"} if
513+
the device provides it, fixing \PR{18836}, thanks to \I{Trevor Davis}.
506514
}
507515
}
508516
}

src/library/base/R/dates.R

Lines changed: 41 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# File src/library/base/R/dates.R
22
# Part of the R package, https://www.R-project.org
33
#
4-
# Copyright (C) 1995-2024 The R Core Team
4+
# Copyright (C) 1995-2025 The R Core Team
55
#
66
# This program is free software; you can redistribute it and/or modify
77
# it under the terms of the GNU General Public License as published by
@@ -128,7 +128,7 @@ print.Date <- function(x, max = NULL, ...)
128128
length(x) - max, 'entries ]\n')
129129
} else if(length(x))
130130
print(format(x), max = max, ...)
131-
else
131+
else
132132
cat(class(x)[1L], "of length 0\n")
133133
invisible(x)
134134
}
@@ -242,82 +242,60 @@ mean.Date <- function (x, ...)
242242

243243
seq.Date <- function(from, to, by, length.out = NULL, along.with = NULL, ...)
244244
{
245-
if (missing(from)) stop("'from' must be specified")
246-
if (!inherits(from, "Date")) stop("'from' must be a \"Date\" object")
247-
if(length(as.Date(from)) != 1L) stop("'from' must be of length 1")
248-
if (!missing(to)) {
249-
if (!inherits(to, "Date")) stop("'to' must be a \"Date\" object")
250-
if (length(as.Date(to)) != 1L) stop("'to' must be of length 1")
251-
}
252245
if (!missing(along.with)) {
253246
length.out <- length(along.with)
254-
} else if (!is.null(length.out)) {
255-
if (length(length.out) != 1L) stop("'length.out' must be of length 1")
247+
} else if(!is.null(length.out)) {
248+
if (length(length.out) != 1L) stop(gettextf("'%s' must be of length 1", "length.out"), domain=NA)
256249
length.out <- ceiling(length.out)
257250
}
258-
if (!missing(to) && missing(by)) {
259-
from <- as.integer(as.Date(from))
260-
to <- as.integer(as.Date(to))
261-
res <- seq.int(from, to, length.out = length.out)
251+
if(missing(by)) {
252+
if(((mTo <- missing(to)) & (mFr <- missing(from))))
253+
stop("without 'by', at least one of 'to' and 'from' must be specified")
254+
if((mTo || mFr) && is.null(length.out))
255+
stop("without 'by', when one of 'to', 'from' is missing, 'length.out' / 'along.with' must be specified")
256+
if(!mFr) from <- as.integer(as.Date(from))
257+
if(!mTo) to <- as.integer(as.Date(to))
258+
res <- if(mFr) seq.int(to = to, length.out = length.out)
259+
else if(mTo) seq.int(from, length.out = length.out)
260+
else seq.int(from, to, length.out = length.out)
262261
return(.Date(res))
263262
}
264-
## else
265-
status <- c(!missing(to), !missing(by), !is.null(length.out))
266-
if(sum(status) != 2L)
267-
stop("exactly two of 'to', 'by' and 'length.out' / 'along.with' must be specified")
268-
if (length(by) != 1L) stop("'by' must be of length 1")
269-
valid <- 0L
263+
## else 'by' is not missing
264+
if (length(by) != 1L) stop(gettextf("'%s' must be of length 1", "by"), domain=NA)
265+
missing_arg <- names(which(c(from = missing(from), to = missing(to),
266+
length.out = is.null(length.out))))
267+
if(length(missing_arg) != 1L)
268+
stop("given 'by', exactly two of 'to', 'from' and 'length.out' / 'along.with' must be specified")
270269
if (inherits(by, "difftime")) {
271-
by <- switch(attr(by,"units"), secs = 1/86400, mins = 1/1440,
272-
hours = 1/24, days = 1, weeks = 7) * as.integer(by)
270+
units(by) <- "days"
271+
by <- as.vector(by)
273272
} else if(is.character(by)) {
274273
by2 <- strsplit(by, " ", fixed = TRUE)[[1L]]
275274
if(length(by2) > 2L || length(by2) < 1L)
276275
stop("invalid 'by' string")
277276
valid <- pmatch(by2[length(by2)],
278277
c("days", "weeks", "months", "quarters", "years"))
279278
if(is.na(valid)) stop("invalid string for 'by'")
280-
if(valid <= 2L) {
281-
by <- c(1L, 7L)[valid]
282-
if (length(by2) == 2L) by <- by * as.integer(by2[1L])
283-
} else
284-
by <- if(length(by2) == 2L) as.integer(by2[1L]) else 1L
285-
} else if(!is.numeric(by)) stop("invalid mode for 'by'")
286-
if(is.na(by)) stop("'by' is NA")
287-
288-
if(valid <= 2L) { # days or weeks
289-
from <- as.integer(as.Date(from))
290-
res <- .Date(if(!is.null(length.out))
291-
seq.int(from, by = by, length.out = length.out)
292-
else # defeat test in seq.default
293-
seq.int(0L, as.integer(as.Date(to)) - from, by) + from)
294-
} else { # months or quarters or years
295-
r1 <- as.POSIXlt(from)
296-
if(valid == 5L) { # years
297-
r1$year <-
298-
if(missing(to))
299-
seq.int(r1$year, by = by, length.out = length.out)
300-
else
301-
seq.int(r1$year, as.POSIXlt(to)$year, by)
302-
res <- as.Date(r1)
303-
} else { # months or quarters
304-
if (valid == 4L) by <- by * 3L
305-
r1$mon <-
306-
if(missing(to))
307-
seq.int(r1$mon, by = by, length.out = length.out)
308-
else {
309-
to0 <- as.POSIXlt(to)
310-
seq.int(r1$mon, 12L*(to0$year - r1$year) + to0$mon, by)
311-
}
312-
res <- as.Date(r1)
279+
if(valid > 2L) { # seq.POSIXt handles the logic for non-arithmetic cases
280+
res <- switch(missing_arg,
281+
from = seq(to = as.POSIXlt(to), by = by, length.out = length.out),
282+
to = seq(from = as.POSIXlt(from), by = by, length.out = length.out),
283+
length.out = seq(from = as.POSIXlt(from), to = as.POSIXlt(to), by = by)
284+
)
285+
return(as.Date(res))
313286
}
287+
by <- c(1L, 7L)[valid]
288+
if (length(by2) == 2L) by <- by * as.integer(by2[1L])
314289
}
315-
## can overshoot
316-
if (!missing(to)) {
317-
to <- as.Date(to)
318-
res <- if (by > 0) res[res <= to] else res[res >= to]
319-
}
320-
res
290+
else if(!is.numeric(by)) stop("invalid mode for 'by'")
291+
if(is.na(by)) stop("'by' is NA")
292+
293+
res <- switch(missing_arg,
294+
from = seq.int(to = unclass(to), by = by, length.out = length.out),
295+
to = seq.int(from = unclass(from), by = by, length.out = length.out),
296+
length.out = seq.int(from = unclass(from), to = unclass(to), by = by)
297+
)
298+
.Date(res)
321299
}
322300

323301
## *very* similar to cut.POSIXt [ ./datetime.R ] -- keep in sync!
@@ -413,7 +391,7 @@ cut.Date <-
413391

414392
julian.Date <- function(x, origin = as.Date("1970-01-01"), ...)
415393
{
416-
if(length(origin) != 1L) stop("'origin' must be of length one")
394+
if(length(origin) != 1L) stop(gettextf("'%s' must be of length 1", "origin"), domain=NA)
417395
structure(unclass(x) - unclass(origin), "origin" = origin)
418396
}
419397

src/library/base/R/datetime.R

Lines changed: 71 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# File src/library/base/R/datetime.R
22
# Part of the R package, https://www.R-project.org
33
#
4-
# Copyright (C) 1995-2024 The R Core Team
4+
# Copyright (C) 1995-2025 The R Core Team
55
#
66
# This program is free software; you can redistribute it and/or modify
77
# it under the terms of the GNU General Public License as published by
@@ -340,7 +340,7 @@ as.POSIXct.default <- function(x, tz = "", ...)
340340
{
341341
if(inherits(x, "POSIXct"))
342342
return(if(missing(tz)) x else .POSIXct(x, tz))
343-
if(is.null(x)) return(.POSIXct(numeric(), tz))
343+
if(is.null(x)) return(.POSIXct(integer(), tz))
344344
if(is.character(x) || is.factor(x))
345345
return(as.POSIXct(as.POSIXlt(x, tz, ...), tz, ...))
346346
if(is.logical(x) && all(is.na(x)))
@@ -665,7 +665,7 @@ c.POSIXlt <- function(..., recursive = FALSE) {
665665
ISOdatetime <- function(year, month, day, hour, min, sec, tz = "")
666666
{
667667
if(min(lengths(list(year, month, day, hour, min, sec), use.names=FALSE)) == 0L)
668-
.POSIXct(numeric(), tz = tz)
668+
.POSIXct(integer(), tz = tz)
669669
else {
670670
x <- paste(year, month, day, hour, min, sec, sep = "-")
671671
as.POSIXct(strptime(x, "%Y-%m-%d-%H-%M-%OS", tz = tz), tz = tz)
@@ -736,9 +736,9 @@ as.difftime <- function(tim, format = "%X", units = "auto", tz = "UTC")
736736
nms <- names(tim)
737737
tim <- as.double(tim)
738738
names(tim) <- nms
739-
if (units == "auto") stop("need explicit units for numeric conversion")
739+
if (units == "auto") stop("need explicit units for numeric conversion")
740740
if (!(units %in% c("secs", "mins", "hours", "days", "weeks")))
741-
stop("invalid units specified")
741+
stop("invalid units specified")
742742
.difftime(tim, units = units)
743743
}
744744
}
@@ -945,39 +945,41 @@ function(x, value)
945945
seq.POSIXt <-
946946
function(from, to, by, length.out = NULL, along.with = NULL, ...)
947947
{
948-
if (missing(from)) stop("'from' must be specified")
949-
if (!inherits(from, "POSIXt")) stop("'from' must be a \"POSIXt\" object")
950-
cfrom <- as.POSIXct(from)
951-
if(length(cfrom) != 1L) stop("'from' must be of length 1")
952-
tz <- attr(cfrom , "tzone")
953-
if (!missing(to)) {
954-
if (!inherits(to, "POSIXt")) stop("'to' must be a \"POSIXt\" object")
955-
if (length(as.POSIXct(to)) != 1) stop("'to' must be of length 1")
956-
}
957948
if (!missing(along.with)) {
958949
length.out <- length(along.with)
959950
} else if (!is.null(length.out)) {
960-
if (length(length.out) != 1L) stop("'length.out' must be of length 1")
951+
if (length(length.out) != 1L) stop(gettextf("'%s' must be of length 1", "length.out"), domain=NA)
961952
length.out <- ceiling(length.out)
962953
}
963-
status <- c(!missing(to), !missing(by), !is.null(length.out))
964-
if(sum(status) != 2L)
965-
stop("exactly two of 'to', 'by' and 'length.out' / 'along.with' must be specified")
966-
if (missing(by)) {
967-
from <- unclass(cfrom)
968-
to <- unclass(as.POSIXct(to))
969-
## Till (and incl.) 1.6.0 :
970-
##- incr <- (to - from)/length.out
971-
##- res <- seq.default(from, to, incr)
954+
missing_arg <- names(which(c(from = missing(from), to = missing(to),
955+
length.out = is.null(length.out), by = missing(by))))
956+
if(length(missing_arg) != 1L)
957+
stop("exactly three of 'to', 'from', 'by' and 'length.out' / 'along.with' must be specified")
958+
# NB: process 'to' first so that 'tz' is overwritten to that from 'from' unless missing(from)
959+
if (missing_arg != "to") {
960+
if (!inherits(to, "POSIXt")) stop(gettextf("'%s' must be a \"%s\" object", "to", "POSIXt"), domain=NA)
961+
if (length(to) != 1L) stop(gettextf("'%s' must be of length 1", "to"), domain=NA)
962+
cto <- as.POSIXct(to)
963+
tz <- attr(cto, "tzone")
964+
}
965+
if (missing_arg != "from") {
966+
if (!inherits(from, "POSIXt")) stop(gettextf("'%s' must be a \"%s\" object", "from", "POSIXt"), domain=NA)
967+
if (length(from) != 1L) stop(gettextf("'%s' must be of length 1", "from"), domain=NA)
968+
cfrom <- as.POSIXct(from)
969+
tz <- attr(cfrom, "tzone")
970+
}
971+
if (missing_arg == "by") {
972+
from <- unclass(as.POSIXct(from))
973+
to <- unclass(as.POSIXct(to))
972974
res <- seq.int(from, to, length.out = length.out)
973-
return(.POSIXct(res, tz))
975+
return(.POSIXct(res, tz = attr(from, "tzone")))
974976
}
975-
976-
if (length(by) != 1L) stop("'by' must be of length 1")
977+
## else 'by' is not missing
978+
if (length(by) != 1L) stop(gettextf("'%s' must be of length 1", "by"), domain=NA)
977979
valid <- 0L
978980
if (inherits(by, "difftime")) {
979-
by <- switch(attr(by,"units"), secs = 1, mins = 60, hours = 3600,
980-
days = 86400, weeks = 7*86400) * unclass(by)
981+
units(by) <- "secs"
982+
by <- as.vector(by)
981983
} else if(is.character(by)) {
982984
by2 <- strsplit(by, " ", fixed = TRUE)[[1L]]
983985
if(length(by2) > 2L || length(by2) < 1L)
@@ -989,57 +991,50 @@ function(from, to, by, length.out = NULL, along.with = NULL, ...)
989991
if(valid <= 5L) {
990992
by <- c(1, 60, 3600, 86400, 7*86400)[valid]
991993
if (length(by2) == 2L) by <- by * as.integer(by2[1L])
992-
} else
993-
by <- if(length(by2) == 2L) as.integer(by2[1L]) else 1
994+
} else # months or longer
995+
by <- if(length(by2) == 2L) as.integer(by2[1L]) else 1L
994996
} else if(!is.numeric(by)) stop("invalid mode for 'by'")
995997
if(is.na(by)) stop("'by' is NA")
996998

997-
if(valid <= 5L) { # secs, mins, hours, days, weeks
998-
from <- unclass(as.POSIXct(from))
999-
if(!is.null(length.out))
1000-
res <- seq.int(from, by = by, length.out = length.out)
1001-
else {
1002-
to0 <- unclass(as.POSIXct(to))
1003-
## defeat test in seq.default
1004-
res <- seq.int(0, to0 - from, by) + from
1005-
}
1006-
return(.POSIXct(res, tz))
1007-
} else { # months or years or DSTdays or quarters
1008-
r1 <- as.POSIXlt(from)
1009-
if(valid == 7L) { # years
1010-
if(missing(to)) { # years
1011-
yr <- seq.int(r1$year, by = by, length.out = length.out)
1012-
} else {
1013-
to <- as.POSIXlt(to)
1014-
yr <- seq.int(r1$year, to$year, by)
1015-
}
1016-
r1$year <- yr
1017-
} else if(valid %in% c(6L, 9L)) { # months or quarters
1018-
if (valid == 9L) by <- by * 3
1019-
if(missing(to)) {
1020-
mon <- seq.int(r1$mon, by = by, length.out = length.out)
1021-
} else {
1022-
to0 <- as.POSIXlt(to)
1023-
mon <- seq.int(r1$mon, 12*(to0$year - r1$year) + to0$mon, by)
1024-
}
1025-
r1$mon <- mon
1026-
} else if(valid == 8L) { # DSTdays
1027-
if(!missing(to)) {
1028-
## We might have a short day, so need to over-estimate.
1029-
length.out <- 2L + floor((unclass(as.POSIXct(to)) -
1030-
unclass(as.POSIXct(from)))/(by * 86400))
1031-
}
1032-
r1$mday <- seq.int(r1$mday, by = by, length.out = length.out)
1033-
}
1034-
r1$isdst <- -1L
1035-
res <- as.POSIXct(r1)
1036-
## now shorten if necessary.
1037-
if(!missing(to)) {
1038-
to <- as.POSIXct(to)
1039-
res <- if(by > 0) res[res <= to] else res[res >= to]
999+
# if one of secs, mins, hours, days, or weeks
1000+
if(valid <= 5L) { # days or weeks
1001+
res <- switch(missing_arg,
1002+
from = seq.int(to = unclass(cto), by = by, length.out = length.out),
1003+
to = seq.int(from = unclass(cfrom), by = by, length.out = length.out),
1004+
length.out = seq.int(from = unclass(cfrom), to = unclass(cto), by = by)
1005+
)
1006+
return(.POSIXct(as.numeric(res), tz))
10401007
}
1041-
res
1008+
lres <- as.POSIXlt(if (missing_arg != "from") from else to)
1009+
if (missing_arg == "length.out") lto <- as.POSIXlt(to)
1010+
if(valid == 7L) { # years
1011+
lres$year <- switch(missing_arg,
1012+
from = seq.int(to = lres$year, by = by, length.out = length.out),
1013+
to = seq.int(from = lres$year, by = by, length.out = length.out),
1014+
length.out = seq.int(from = lres$year, to = lto$year, by = by)
1015+
)
1016+
} else if(valid %in% c(6L, 9L)) { # months or quarters
1017+
if (valid == 9L) by <- by * 3
1018+
lres$mon <- switch(missing_arg,
1019+
from = seq.int(to = lres$mon, by = by, length.out = length.out),
1020+
to = seq.int(from = lres$mon, by = by, length.out = length.out),
1021+
length.out = seq.int(lres$mon, 12*(lto$year - lres$year) + lto$mon, by)
1022+
)
1023+
} else if(valid == 8L) { # DSTdays
1024+
lres$mday <- switch(missing_arg,
1025+
from = seq.int(to = lres$mday, by = by, length.out = length.out),
1026+
to = seq.int(from = lres$mday, by = by, length.out = length.out),
1027+
## We might have a short day, so need to over-estimate.
1028+
length.out = seq.int(lres$mday, by = by,
1029+
length.out = 2L + floor((unclass(cto) - unclass(cfrom))/(by * 86400)))
1030+
)
10421031
}
1032+
lres$isdst <- -1L
1033+
res <- as.POSIXct(lres)
1034+
if(missing_arg == "length.out") # shorten if necessary.
1035+
res[if(by > 0) res <= cto else res >= cto]
1036+
else
1037+
res
10431038
}
10441039

10451040
## *very* similar to cut.Date [ ./dates.R ] -- keep in sync!
@@ -1139,7 +1134,7 @@ julian <- function(x, ...) UseMethod("julian")
11391134
julian.POSIXt <- function(x, origin = as.POSIXct("1970-01-01", tz = "GMT"), ...)
11401135
{
11411136
origin <- as.POSIXct(origin)
1142-
if(length(origin) != 1L) stop("'origin' must be of length one")
1137+
if(length(origin) != 1L) stop(gettextf("'%s' must be of length 1", "origin"), domain=NA)
11431138
res <- difftime(as.POSIXct(x), origin, units = "days")
11441139
structure(res, "origin" = origin)
11451140
}

0 commit comments

Comments
 (0)