Skip to content

Commit e0f6a85

Browse files
authored
Merge branch 'r-devel:main' into r-dev-day/issue-76
2 parents 23cd06a + 4222b9a commit e0f6a85

File tree

13 files changed

+232
-114
lines changed

13 files changed

+232
-114
lines changed

doc/NEWS.Rd

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,12 @@
368368
\item \code{debugonce(<S4-simple-body>, signature=*)} now works
369369
correctly when \dQuote{called twice}, fixing \PR{18824} thanks to
370370
\I{Michael Jagan}.
371+
372+
\item \code{format(dtime, digits=* / format=*)} is more consistent
373+
when the \code{POSIXt} date-time object \code{dtime} has fractional
374+
(non integer) seconds. Fixes \PR{17350}, thanks to new contributions
375+
by \I{LatinR}'s \sQuote{\I{R Dev Day}} participants, \I{Heather
376+
Turner} and \I{Dirk Eddelbuettel}.
371377
}
372378
}
373379
}

doc/manual/Makefile.in

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,9 @@ version.texi: Makefile $(top_srcdir)/VERSION $(SVN_REV)
211211
v="$${vv} (`sed -e 1d -e 's/^Last Changed Date: //' $(SVN_REV)`)"; \
212212
$(ECHO) "@set VERSION $${v}" >> $@; \
213213
rwv=`$(R_EXE) -f $(top_srcdir)/src/gnuwin32/fixed/rwver.R`; \
214-
$(ECHO) "@set RWVERSION $${rwv}" >> $@ )
214+
$(ECHO) "@set RWVERSION $${rwv}" >> $@; \
215+
rwtv=`$(ECHO) $${rwv} | sed -e 's/\.[^.]\+$$//'` >> $@; \
216+
$(ECHO) "@set RWTVERSION $${rwtv}" >> $@ )
215217
@if test "$(R_PAPERSIZE)" = "a4"; then \
216218
$(ECHO) "@afourpaper" >> $@ ; \
217219
fi

doc/manual/Makefile.win

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,9 @@ version.texi: Makefile.win $(top_srcdir)/VERSION $(SVN_REV)
171171
v="$${vv} (`sed -e 1d -e 's/^Last Changed Date: //' $(SVN_REV)`)"; \
172172
$(ECHO) "@set VERSION $${v}" >> $@; \
173173
rwv=$(shell ../../bin$(R_ARCH)/Rscript ../../src/gnuwin32/fixed/rwver.R); \
174-
$(ECHO) "@set RWVERSION $${rwv}" >> $@ )
174+
$(ECHO) "@set RWVERSION $${rwv}" >> $@; \
175+
rwtv=`$(ECHO) $${rwv} | sed -e 's/\.[^.]\+$$//'`; \
176+
$(ECHO) "@set RWTVERSION $${rwtv}" >> $@ )
175177
@if test "$(R_PAPERSIZE)" = "a4"; then \
176178
$(ECHO) "@afourpaper" >> $@ ; \
177179
fi

doc/manual/R-admin.texi

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1388,8 +1388,8 @@ The binary distribution of @R{} is currently built with tools
13881388
from
13891389
@uref{https://CRAN.R-project.org/bin/windows/Rtools/rtools44/rtools.html,Rtools44
13901390
for Windows}. See
1391-
@uref{https://CRAN.R-project.org/bin/windows/base/howto-R-devel.html, Building
1392-
R and packages} for more details on how to use it.
1391+
@uref{https://CRAN.R-project.org/bin/windows/base/howto-@value{RWTVERSION}.html,
1392+
Building @value{RWTVERSION} and packages on Windows} for more details on how to use it.
13931393

13941394
The toolset includes compilers (currently GCC version 13.2.0 with
13951395
selected additional patches) and runtime libraries from
@@ -2074,8 +2074,8 @@ code, and @code{install.packages(type="source")} will work for such
20742074
packages. Those with compiled code need the tools (see @ref{The Windows
20752075
toolset}). The tools are found automatically by @R{} when installed by
20762076
the toolset installer. See
2077-
@uref{https://cran.r-project.org/bin/windows/base/howto-R-devel.html,Building
2078-
R and packages} for more details.
2077+
@uref{https://cran.r-project.org/bin/windows/base/howto-@value{RWTVERSION}.html,
2078+
Building @value{RWTVERSION} and packages on Windows} for more details.
20792079

20802080
Occasional permission problems after unpacking source packages have been
20812081
seen on some systems: these have been circumvented by setting the

src/library/base/R/dates.R

Lines changed: 80 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,14 @@ as.Date.character <- function(x, format,
5151
optional = FALSE, ...)
5252
{
5353
charToDate <- function(x) {
54-
is.na(x) <- !nzchar(x) # PR#17909
55-
xx <- x[1L]
54+
is.na(x) <- !nzchar(x) # PR#17909
55+
xx <- x[1L]
5656
if(is.na(xx)) {
5757
j <- 1L
5858
while(is.na(xx) && (j <- j+1L) <= length(x)) xx <- x[j]
5959
if(is.na(xx)) f <- "%Y-%m-%d" # all NAs
6060
}
61-
if(is.na(xx))
61+
if(is.na(xx))
6262
strptime(x, f)
6363
else {
6464
for(ff in tryFormats)
@@ -80,16 +80,16 @@ as.Date.numeric <- function(x, origin, ...)
8080
as.Date.default <- function(x, ...)
8181
{
8282
if(inherits(x, "Date"))
83-
x
83+
x
8484
else if(is.null(x))
8585
.Date(numeric())
8686
else if(is.logical(x) && all(is.na(x)))
87-
.Date(as.numeric(x))
87+
.Date(as.numeric(x))
8888
else
89-
stop(gettextf("do not know how to convert '%s' to class %s",
90-
deparse1(substitute(x)),
91-
dQuote("Date")),
92-
domain = NA)
89+
stop(gettextf("do not know how to convert '%s' to class %s",
90+
deparse1(substitute(x)),
91+
dQuote("Date")),
92+
domain = NA)
9393
}
9494

9595
## ## Moved to package date
@@ -123,13 +123,13 @@ print.Date <- function(x, max = NULL, ...)
123123
{
124124
if(is.null(max)) max <- getOption("max.print", 9999L)
125125
if(max < length(x)) {
126-
print(format(x[seq_len(max)]), max=max+1, ...)
127-
cat(" [ reached 'max' / getOption(\"max.print\") -- omitted",
128-
length(x) - max, 'entries ]\n')
126+
print(format(x[seq_len(max)]), max=max+1, ...)
127+
cat(" [ reached 'max' / getOption(\"max.print\") -- omitted",
128+
length(x) - max, 'entries ]\n')
129129
} else if(length(x))
130-
print(format(x), max = max, ...)
131-
else
132-
cat(class(x)[1L], "of length 0\n")
130+
print(format(x), max = max, ...)
131+
else
132+
cat(class(x)[1L], "of length 0\n")
133133
invisible(x)
134134
}
135135

@@ -203,7 +203,7 @@ Summary.Date <- function (..., na.rm)
203203
ok <- switch(.Generic, max = , min = , range = TRUE, FALSE)
204204
if (!ok) stop(gettextf("%s not defined for \"Date\" objects", .Generic),
205205
domain = NA)
206-
.Date(NextMethod(.Generic), oldClass(list(...)[[1L]]))
206+
.Date(NextMethod(.Generic), oldClass(...elt(1L)))
207207
}
208208

209209
`[.Date` <- function(x, ..., drop = TRUE)
@@ -329,84 +329,84 @@ cut.Date <-
329329
x <- as.Date(x)
330330

331331
if (inherits(breaks, "Date")) {
332-
breaks <- sort(as.Date(breaks))
332+
breaks <- sort(as.Date(breaks))
333333
} else if(is.numeric(breaks) && length(breaks) == 1L) {
334-
## specified number of breaks
334+
## specified number of breaks
335335
} else if(is.character(breaks) && length(breaks) == 1L) {
336-
by2 <- strsplit(breaks, " ", fixed = TRUE)[[1L]]
337-
if(length(by2) > 2L || length(by2) < 1L)
338-
stop("invalid specification of 'breaks'")
339-
valid <-
340-
pmatch(by2[length(by2)],
341-
c("days", "weeks", "months", "years", "quarters"))
342-
if(is.na(valid)) stop("invalid specification of 'breaks'")
343-
start <- as.POSIXlt(min(x, na.rm=TRUE))
344-
if(valid == 1L) incr <- 1L
345-
if(valid == 2L) { # weeks
346-
start$mday <- start$mday - start$wday
347-
if(start.on.monday)
348-
start$mday <- start$mday + ifelse(start$wday > 0L, 1L, -6L)
336+
by2 <- strsplit(breaks, " ", fixed = TRUE)[[1L]]
337+
if(length(by2) > 2L || length(by2) < 1L)
338+
stop("invalid specification of 'breaks'")
339+
valid <-
340+
pmatch(by2[length(by2)],
341+
c("days", "weeks", "months", "years", "quarters"))
342+
if(is.na(valid)) stop("invalid specification of 'breaks'")
343+
start <- as.POSIXlt(min(x, na.rm=TRUE))
344+
if(valid == 1L) incr <- 1L
345+
if(valid == 2L) { # weeks
346+
start$mday <- start$mday - start$wday
347+
if(start.on.monday)
348+
start$mday <- start$mday + ifelse(start$wday > 0L, 1L, -6L)
349349
start$isdst <- -1L
350-
incr <- 7L
351-
}
352-
if(valid == 3L) { # months
353-
start$mday <- 1L
350+
incr <- 7L
351+
}
352+
if(valid == 3L) { # months
353+
start$mday <- 1L
354354
start$isdst <- -1L
355355
maxx <- max(x, na.rm = TRUE)
356-
end <- as.POSIXlt(maxx)
357-
step <- if(length(by2) == 2L) as.integer(by2[1L]) else 1L
358-
end <- as.POSIXlt(end + (31 * step * 86400))
359-
end$mday <- 1L
356+
end <- as.POSIXlt(maxx)
357+
step <- if(length(by2) == 2L) as.integer(by2[1L]) else 1L
358+
end <- as.POSIXlt(end + (31 * step * 86400))
359+
end$mday <- 1L
360360
end$isdst <- -1L
361-
breaks <- as.Date(seq(start, end, breaks))
361+
breaks <- as.Date(seq(start, end, breaks))
362362
## 31 days ahead could give an empty level, so
363-
lb <- length(breaks)
364-
if(maxx < breaks[lb-1]) breaks <- breaks[-lb]
365-
} else if(valid == 4L) { # years
366-
start$mon <- 0L
367-
start$mday <- 1L
363+
lb <- length(breaks)
364+
if(maxx < breaks[lb-1]) breaks <- breaks[-lb]
365+
} else if(valid == 4L) { # years
366+
start$mon <- 0L
367+
start$mday <- 1L
368368
start$isdst <- -1L
369369
maxx <- max(x, na.rm = TRUE)
370-
end <- as.POSIXlt(maxx)
371-
step <- if(length(by2) == 2L) as.integer(by2[1L]) else 1L
372-
end <- as.POSIXlt(end + (366 * step * 86400))
373-
end$mon <- 0L
374-
end$mday <- 1L
370+
end <- as.POSIXlt(maxx)
371+
step <- if(length(by2) == 2L) as.integer(by2[1L]) else 1L
372+
end <- as.POSIXlt(end + (366 * step * 86400))
373+
end$mon <- 0L
374+
end$mday <- 1L
375375
end$isdst <- -1L
376-
breaks <- as.Date(seq(start, end, breaks))
376+
breaks <- as.Date(seq(start, end, breaks))
377377
## 366 days ahead could give an empty level, so
378-
lb <- length(breaks)
379-
if(maxx < breaks[lb-1]) breaks <- breaks[-lb]
380-
} else if(valid == 5L) { # quarters
381-
qtr <- rep(c(0L, 3L, 6L, 9L), each = 3L)
382-
start$mon <- qtr[start$mon + 1L]
383-
start$mday <- 1L
378+
lb <- length(breaks)
379+
if(maxx < breaks[lb-1]) breaks <- breaks[-lb]
380+
} else if(valid == 5L) { # quarters
381+
qtr <- rep(c(0L, 3L, 6L, 9L), each = 3L)
382+
start$mon <- qtr[start$mon + 1L]
383+
start$mday <- 1L
384384
start$isdst <- -1L
385-
maxx <- max(x, na.rm = TRUE)
386-
end <- as.POSIXlt(maxx)
387-
step <- if(length(by2) == 2L) as.integer(by2[1L]) else 1L
388-
end <- as.POSIXlt(end + (93 * step * 86400))
389-
end$mon <- qtr[end$mon + 1L]
390-
end$mday <- 1L
385+
maxx <- max(x, na.rm = TRUE)
386+
end <- as.POSIXlt(maxx)
387+
step <- if(length(by2) == 2L) as.integer(by2[1L]) else 1L
388+
end <- as.POSIXlt(end + (93 * step * 86400))
389+
end$mon <- qtr[end$mon + 1L]
390+
end$mday <- 1L
391391
end$isdst <- -1L
392-
breaks <- as.Date(seq(start, end, paste(step * 3L, "months")))
393-
## 93 days ahead could give an empty level, so
394-
lb <- length(breaks)
395-
if(maxx < breaks[lb-1]) breaks <- breaks[-lb]
396-
} else {
397-
start <- as.Date(start)
398-
if (length(by2) == 2L) incr <- incr * as.integer(by2[1L])
399-
maxx <- max(x, na.rm = TRUE)
400-
breaks <- seq(start, maxx + incr, breaks)
401-
breaks <- breaks[seq_len(1L+max(which(breaks <= maxx)))]
402-
}
392+
breaks <- as.Date(seq(start, end, paste(step * 3L, "months")))
393+
## 93 days ahead could give an empty level, so
394+
lb <- length(breaks)
395+
if(maxx < breaks[lb-1]) breaks <- breaks[-lb]
396+
} else {
397+
start <- as.Date(start)
398+
if (length(by2) == 2L) incr <- incr * as.integer(by2[1L])
399+
maxx <- max(x, na.rm = TRUE)
400+
breaks <- seq(start, maxx + incr, breaks)
401+
breaks <- breaks[seq_len(1L+max(which(breaks <= maxx)))]
402+
}
403403
} else stop("invalid specification of 'breaks'")
404404
res <- cut(unclass(x), unclass(breaks), labels = labels,
405-
right = right, ...)
405+
right = right, ...)
406406
if(is.null(labels)) {
407-
levels(res) <-
408-
as.character(if (is.numeric(breaks)) x[!duplicated(res)]
409-
else breaks[-length(breaks)])
407+
levels(res) <-
408+
as.character(if (is.numeric(breaks)) x[!duplicated(res)]
409+
else breaks[-length(breaks)])
410410
}
411411
res
412412
}
@@ -465,9 +465,8 @@ diff.Date <- function (x, lag = 1L, differences = 1L, ...)
465465
r[-nrow(r):-(nrow(r) - lag + 1L), , drop = FALSE]
466466
else for (i in seq_len(differences))
467467
r <- r[i1] - r[-length(r):-(length(r) - lag + 1L)]
468-
dots <- list(...)
469-
if("units" %in% names(dots) && dots$units != "auto")
470-
units(r) <- match.arg(dots$units, choices = setdiff(eval(formals(difftime)$units), "auto"))
468+
if("units" %in% ...names() && (dunits <- list(...)$units) != "auto")
469+
units(r) <- match.arg(dunits, choices = setdiff(eval(formals(difftime)$units), "auto"))
471470
r
472471
}
473472

src/library/base/R/datetime.R

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -382,18 +382,20 @@ format.POSIXlt <- function(x, format = "", usetz = FALSE,
382382
digits = getOption("digits.secs"), ...)
383383
{
384384
if(!inherits(x, "POSIXlt")) stop("wrong class")
385-
if(any(f0 <- format == "")) {
386-
## need list [ method here.
387-
times <- unlist(unclass(x)[1L:3L])[f0]
388-
secs <- x$sec[f0]; secs <- secs[is.finite(secs)]
389-
np <- if(is.null(digits)) 0L else min(6L, digits)
390-
if(np >= 1L) # no unnecessary trailing '0' :
391-
for (i in seq_len(np)- 1L)
392-
if(all( abs(secs - round(secs, i)) < 1e-6 )) {
385+
if(any(f0 <- format == "" | grepl("%OS$", format))) {
386+
if(!is.null(digits)) {
387+
secs <- x$sec[f0]; secs <- secs[is.finite(secs)]
388+
np <- min(6L, digits)
389+
## no unnecessary trailing '0' ; use trunc() as .Internal() code:
390+
for(i in seq_len(np)- 1L)
391+
if(all( abs(secs - trunc(secs*(ti <- 10^i))/ti) < 1e-6 )) {
393392
np <- i
394393
break
395394
}
396-
format[f0] <-
395+
} else np <- 0L
396+
## need list `[` method here to get 1:3 ~ {sec, min, hour}:
397+
times <- unlist(`names<-`(unclass(x)[1L:3L], NULL))[f0]
398+
format[f0] <-
397399
if(all(times[is.finite(times)] == 0)) "%Y-%m-%d"
398400
else if(np == 0L) "%Y-%m-%d %H:%M:%S"
399401
else paste0("%Y-%m-%d %H:%M:%OS", np)

src/library/base/man/strptime.Rd

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,11 @@ strptime(x, format, tz = "")
3939
methods is
4040
\code{"\%Y-\%m-\%d \%H:\%M:\%S"} if any element has a time
4141
component which is not midnight, and \code{"\%Y-\%m-\%d"}
42-
otherwise. If \code{\link{options}("digits.secs")} is set, up to
43-
the specified number of digits will be printed for seconds.}
42+
otherwise. In the first case and if \code{digits} is not \code{NULL},
43+
i.e., by default when \code{\link{options}("digits.secs")} is set, up to
44+
the specified number of digits will be printed for seconds, using
45+
\code{"\%OS<n>"} instead of \code{"\%S"} in the format, see also
46+
\sQuote{Details}.}
4447
\item{\dots}{further arguments to be passed from or to other methods.}
4548
\item{usetz}{logical. Should the time zone abbreviation be appended
4649
to the output? This is used in printing times, and more reliable
@@ -235,11 +238,12 @@ strptime(x, format, tz = "")
235238
236239
Specific to \R is \code{\%OSn}, which for output gives the seconds
237240
truncated to \code{0 <= n <= 6} decimal places (and if \code{\%OS} is
238-
not followed by a digit, it uses the setting of
239-
\code{\link{getOption}("digits.secs")}, or if that is unset, \code{n =
240-
0}). Further, for \code{strptime} \code{\%OS} will input seconds
241-
including fractional seconds. Note that \code{\%S} does not read
242-
fractional parts on output.
241+
not followed by a digit, it uses \code{digits} unless that is
242+
\code{NULL}, when \code{n = 0}). Note that the precedence is
243+
\code{format="...\%OSn"} \eqn{\ll}{>>} \code{digits = n} \eqn{\ll}{>>}
244+
\code{getOption("digits.prec")}. Further, for \code{strptime}
245+
\code{\%OS} will input seconds including fractional seconds. Note that
246+
\code{\%S} does not read fractional parts on output.
243247
244248
The behaviour of other conversion specifications (and even if other
245249
character sequences commencing with \code{\%} \emph{are} conversion
@@ -307,7 +311,7 @@ strptime(x, format, tz = "")
307311
year. (On some platforms this works better after conversion to
308312
\code{"POSIXct"}. Some platforms only recognize hour or half-hour
309313
offsets for output.)%% strftime in macOS 13.
310-
314+
311315
Using \code{\%z} for input makes most sense with \code{tz = "UTC"}.
312316
}
313317
@@ -424,13 +428,17 @@ z2 <- strptime(x, "\%m/\%d/\%y \%H:\%M:\%S")
424428
## *here* (but not in general), the same as format():
425429
stopifnot(identical(format(z2), as.character(z2)))
426430
427-
## time with fractional seconds
428-
z3 <- strptime("20/2/06 11:16:16.683", "\%d/\%m/\%y \%H:\%M:\%OS") \donttest{
429-
z3 # prints without fractional seconds by default, digits.sec = NULL ("= 0")}
430-
op <- options(digits.secs = 3)
431-
\donttest{z3 # shows the 3 extra digits}
432-
as.character(z3) # ditto
431+
## time with fractional seconds (setting `tz = ..` for reproducible output)
432+
z3 <- strptime("20/2/06 11:16:16.683", "\%d/\%m/\%y \%H:\%M:\%OS", tz = "UTC")
433+
z3 # prints without fractional seconds by default, digits.sec = NULL ("= 0")
434+
format(z3, digits = 3) # shows extra digits
435+
format(z3, digits = 6) # still 3 digits: *not* showing trailing zeros
436+
format(z3, format = "\%Y-\%m-\%d \%H:\%M:\%OS6") # *does* keep trailing zeros
437+
op <- options(digits.secs = 3) # global option, the default for `digits`
438+
z3 # shows the 3 extra digits
433439
options(op)
440+
as.character(z3) # ditto
441+
434442
435443
## time zone names are not portable, but 'EST5EDT' comes pretty close.
436444
## (but its interpretation may not be universal: see ?timezones)

0 commit comments

Comments
 (0)