Skip to content

Commit 2bdd816

Browse files
author
maechler
committed
more thorough fixing format.POSIXlt() for fractional secs
git-svn-id: https://svn.r-project.org/R/trunk@87419 00db46b3-68df-0310-9c12-caf00c1e9a41
1 parent 76b51d9 commit 2bdd816

File tree

4 files changed

+181
-20
lines changed

4 files changed

+181
-20
lines changed

doc/NEWS.Rd

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -398,17 +398,15 @@
398398
when the \code{POSIXt} date-time object \code{dtime} has fractional
399399
(non integer) seconds. Fixes \PR{17350}, thanks to new contributions
400400
by \I{LatinR}'s \sQuote{\I{R Dev Day}} participants, \I{Heather
401-
Turner} and \I{Dirk Eddelbuettel}.
401+
Turner} and \I{Dirk Eddelbuettel}; also fixes more cases, notably
402+
when \code{format} contains "%OS<nodigit>".
402403
403404
\item \code{options(scipen = NULL)} and other invalid values now
404405
signal an error instead of invalidating ops relying on a finite
405406
integer value. Newly values outside the range -9 .. 9999 are warned
406407
about and set to a respective boundary or to the default \code{0},
407408
e.g., in case of an \code{NA}.
408409
409-
\item \code{isGeneric(fdef = print)} now works, fixing \PR{18369}
410-
thanks to \I{Mikael Jagan}.
411-
412410
\item \code{cbind()} could segfault with \code{NULL} inputs.
413411
(Seen when \R was built with \command{gcc14}, \abbr{LTO} and C99 inlining
414412
semantics.)
@@ -464,6 +462,9 @@
464462
\item \code{isGeneric(<primitive>, fdef=*, getName=TRUE)} now also
465463
returns the name instead of just \code{TRUE}, fixing \PR{18829}
466464
reported by \I{Mikael Jagan}.
465+
466+
\item \code{isGeneric(fdef = print)} now works, fixing \PR{18369}
467+
thanks to \I{Mikael Jagan}.
467468
}
468469
}
469470
}

src/library/base/R/datetime.R

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -382,24 +382,35 @@ 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 == "" | 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)
385+
nf <- length(format)
386+
useDig <- function(secs, digits) {
387+
secs <- secs[is.finite(secs)]
388+
np <- min(6L, digits)
389+
if(np >= 1L) # no unnecessary trailing '0'; use trunc() as .Internal() code:
390+
for (i in seq_len(np)- 1L)
391391
if(all( abs(secs - trunc(secs*(ti <- 10^i))/ti) < 1e-6 )) {
392392
np <- i
393393
break
394394
}
395-
} else np <- 0L
396-
## need list `[` method here to get 1:3 ~ {sec, min, hour}:
397-
times <- unlist(unclass(x)[1L:3L], use.names=FALSE)[f0]
398-
format[f0] <-
399-
if(all(times[is.finite(times)] == 0)) "%Y-%m-%d"
400-
else if(np == 0L) "%Y-%m-%d %H:%M:%S"
401-
else paste0("%Y-%m-%d %H:%M:%OS", np)
395+
np
402396
}
397+
if(any(f0 <- format == "")) {
398+
x_ <- if(nf == 1L) x else x[f0] # any(f0) & nf = 1 ==> x[f0] = x
399+
np <- if(!is.null(digits)) useDig(x_$sec, digits) else 0L
400+
## need list `[` method here to get 1:3 ~ {sec, min, hour} :
401+
times <- unlist(unclass(x_)[1L:3L], use.names = FALSE)
402+
format[f0] <-
403+
if(all(times[is.finite(times)] == 0)) "%Y-%m-%d"
404+
else if(np == 0L) "%Y-%m-%d %H:%M:%S"
405+
else paste0("%Y-%m-%d %H:%M:%OS", np)
406+
}
407+
if(!missing(digits) && !is.null(digits) && digits != getOption("digits.secs", 0L) &&
408+
any(OS. <- grepl("%OS[^0-9]", format))) { ## use digits to find n to use for "%OS<n>"
409+
x_ <- if(nf == 1L) x else x[OS.]
410+
np <- useDig(x_$sec, digits)
411+
format[OS.] <- gsub("%OS([^0-9])", paste0("%OS", np, "\\1"), format[OS.])
412+
}
413+
## C code in do_formatPOSIXlt() *does* recycle {x, format} as needed:
403414
.Internal(format.POSIXlt(x, format, usetz))
404415
}
405416

tests/datetime5.R

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
### tests of strftime (formatting POSIXlt objects).
1+
### tests of strftime (formatting POSIXlt objects via format.POSIXlt)
22

33
Sys.setenv(TZ = "Europe/Rome")
44

@@ -27,3 +27,15 @@ for (f in c("P", "k", "l", "s")) {
2727
## week numbers
2828
dt2 <- as.POSIXlt(sprintf("%d-01-01 09:03;04", 2015:2018))
2929
cat(format(dt2, "%Y: %U %V %W"), sep = "\n")
30+
31+
## recycling *both* {x, format} "heavily"; digits = <n> must influence %OS<empty>; PR#17350
32+
(fmt <- c("", paste0("%H:%M:%OS", c("", 2), " in %Y"), # || nasty (but "correct")
33+
paste0("%Y-%m-%d", c("", paste0(" %H:%M:%OS", c("", 0, 1, 6, 9, 11))))))
34+
weekD <- seq(as.Date("2020-04-01"), by = "weeks", length.out = 5 * length(fmt)) ; joff <- (0:4)*length(fmt)
35+
weekPlt <- as.POSIXlt(weekD, tz = "UTC")
36+
(Lf1 <- split(f1 <- format(weekPlt, format = fmt), fmt))
37+
(Lf. <- split(f. <- format(weekPlt + 0.25, format = fmt), fmt))
38+
(Lf3 <- split(f3 <- format(weekPlt + 0.25, format = fmt, digits = 3), fmt))
39+
stopifnot(f3[2L+joff] == f3[3L+joff],
40+
grepl("^00:00:00.25 in 202[01]", f3[2L+joff]))
41+
## digits = 3 had no effect on "%OS "

tests/datetime5.Rout.save

Lines changed: 139 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11

2-
R Under development (unstable) (2024-03-19 r86151) -- "Unsuffered Consequences"
2+
R Under development (unstable) (2024-12-03 r87418) -- "Unsuffered Consequences"
33
Copyright (C) 2024 The R Foundation for Statistical Computing
44
Platform: x86_64-pc-linux-gnu
55

@@ -15,7 +15,7 @@ Type 'demo()' for some demos, 'help()' for on-line help, or
1515
'help.start()' for an HTML browser interface to help.
1616
Type 'q()' to quit R.
1717

18-
> ### tests of strftime (formatting POSIXlt objects).
18+
> ### tests of strftime (formatting POSIXlt objects via format.POSIXlt)
1919
>
2020
> Sys.setenv(TZ = "Europe/Rome")
2121
>
@@ -89,4 +89,141 @@ Type 'q()' to quit R.
8989
2017: 01 52 00
9090
2018: 00 01 01
9191
>
92+
> ## recycling *both* {x, format} "heavily"; digits = <n> must influence %OS<empty>; PR#17350
93+
> (fmt <- c("", paste0("%H:%M:%OS", c("", 2), " in %Y"), # || nasty (but "correct")
94+
+ paste0("%Y-%m-%d", c("", paste0(" %H:%M:%OS", c("", 0, 1, 6, 9, 11))))))
95+
[1] "" "%H:%M:%OS in %Y" "%H:%M:%OS2 in %Y"
96+
[4] "%Y-%m-%d" "%Y-%m-%d %H:%M:%OS" "%Y-%m-%d %H:%M:%OS0"
97+
[7] "%Y-%m-%d %H:%M:%OS1" "%Y-%m-%d %H:%M:%OS6" "%Y-%m-%d %H:%M:%OS9"
98+
[10] "%Y-%m-%d %H:%M:%OS11"
99+
> weekD <- seq(as.Date("2020-04-01"), by = "weeks", length.out = 5 * length(fmt)) ; joff <- (0:4)*length(fmt)
100+
> weekPlt <- as.POSIXlt(weekD, tz = "UTC")
101+
> (Lf1 <- split(f1 <- format(weekPlt, format = fmt), fmt))
102+
[[1]]
103+
[1] "2020-04-01" "2020-06-10" "2020-08-19" "2020-10-28" "2021-01-06"
104+
105+
$`%H:%M:%OS in %Y`
106+
[1] "00:00:00 in 2020" "00:00:00 in 2020" "00:00:00 in 2020" "00:00:00 in 2020"
107+
[5] "00:00:00 in 2021"
108+
109+
$`%H:%M:%OS2 in %Y`
110+
[1] "00:00:00.00 in 2020" "00:00:00.00 in 2020" "00:00:00.00 in 2020"
111+
[4] "00:00:00.00 in 2020" "00:00:00.00 in 2021"
112+
113+
$`%Y-%m-%d`
114+
[1] "2020-04-22" "2020-07-01" "2020-09-09" "2020-11-18" "2021-01-27"
115+
116+
$`%Y-%m-%d %H:%M:%OS`
117+
[1] "2020-04-29 00:00:00" "2020-07-08 00:00:00" "2020-09-16 00:00:00"
118+
[4] "2020-11-25 00:00:00" "2021-02-03 00:00:00"
119+
120+
$`%Y-%m-%d %H:%M:%OS0`
121+
[1] "2020-05-06 00:00:00" "2020-07-15 00:00:00" "2020-09-23 00:00:00"
122+
[4] "2020-12-02 00:00:00" "2021-02-10 00:00:00"
123+
124+
$`%Y-%m-%d %H:%M:%OS1`
125+
[1] "2020-05-13 00:00:00.0" "2020-07-22 00:00:00.0" "2020-09-30 00:00:00.0"
126+
[4] "2020-12-09 00:00:00.0" "2021-02-17 00:00:00.0"
127+
128+
$`%Y-%m-%d %H:%M:%OS11`
129+
[1] "2020-06-03 00:00:00.01" "2020-08-12 00:00:00.01" "2020-10-21 00:00:00.01"
130+
[4] "2020-12-30 00:00:00.01" "2021-03-10 00:00:00.01"
131+
132+
$`%Y-%m-%d %H:%M:%OS6`
133+
[1] "2020-05-20 00:00:00.000000" "2020-07-29 00:00:00.000000"
134+
[3] "2020-10-07 00:00:00.000000" "2020-12-16 00:00:00.000000"
135+
[5] "2021-02-24 00:00:00.000000"
136+
137+
$`%Y-%m-%d %H:%M:%OS9`
138+
[1] "2020-05-27 00:00:00.000000" "2020-08-05 00:00:00.000000"
139+
[3] "2020-10-14 00:00:00.000000" "2020-12-23 00:00:00.000000"
140+
[5] "2021-03-03 00:00:00.000000"
141+
142+
> (Lf. <- split(f. <- format(weekPlt + 0.25, format = fmt), fmt))
143+
[[1]]
144+
[1] "2020-04-01 00:00:00" "2020-06-10 00:00:00" "2020-08-19 00:00:00"
145+
[4] "2020-10-28 00:00:00" "2021-01-06 00:00:00"
146+
147+
$`%H:%M:%OS in %Y`
148+
[1] "00:00:00 in 2020" "00:00:00 in 2020" "00:00:00 in 2020" "00:00:00 in 2020"
149+
[5] "00:00:00 in 2021"
150+
151+
$`%H:%M:%OS2 in %Y`
152+
[1] "00:00:00.25 in 2020" "00:00:00.25 in 2020" "00:00:00.25 in 2020"
153+
[4] "00:00:00.25 in 2020" "00:00:00.25 in 2021"
154+
155+
$`%Y-%m-%d`
156+
[1] "2020-04-22" "2020-07-01" "2020-09-09" "2020-11-18" "2021-01-27"
157+
158+
$`%Y-%m-%d %H:%M:%OS`
159+
[1] "2020-04-29 00:00:00" "2020-07-08 00:00:00" "2020-09-16 00:00:00"
160+
[4] "2020-11-25 00:00:00" "2021-02-03 00:00:00"
161+
162+
$`%Y-%m-%d %H:%M:%OS0`
163+
[1] "2020-05-06 00:00:00" "2020-07-15 00:00:00" "2020-09-23 00:00:00"
164+
[4] "2020-12-02 00:00:00" "2021-02-10 00:00:00"
165+
166+
$`%Y-%m-%d %H:%M:%OS1`
167+
[1] "2020-05-13 00:00:00.2" "2020-07-22 00:00:00.2" "2020-09-30 00:00:00.2"
168+
[4] "2020-12-09 00:00:00.2" "2021-02-17 00:00:00.2"
169+
170+
$`%Y-%m-%d %H:%M:%OS11`
171+
[1] "2020-06-03 00:00:00.21" "2020-08-12 00:00:00.21" "2020-10-21 00:00:00.21"
172+
[4] "2020-12-30 00:00:00.21" "2021-03-10 00:00:00.21"
173+
174+
$`%Y-%m-%d %H:%M:%OS6`
175+
[1] "2020-05-20 00:00:00.250000" "2020-07-29 00:00:00.250000"
176+
[3] "2020-10-07 00:00:00.250000" "2020-12-16 00:00:00.250000"
177+
[5] "2021-02-24 00:00:00.250000"
178+
179+
$`%Y-%m-%d %H:%M:%OS9`
180+
[1] "2020-05-27 00:00:00.250000" "2020-08-05 00:00:00.250000"
181+
[3] "2020-10-14 00:00:00.250000" "2020-12-23 00:00:00.250000"
182+
[5] "2021-03-03 00:00:00.250000"
183+
184+
> (Lf3 <- split(f3 <- format(weekPlt + 0.25, format = fmt, digits = 3), fmt))
185+
[[1]]
186+
[1] "2020-04-01 00:00:00.25" "2020-06-10 00:00:00.25" "2020-08-19 00:00:00.25"
187+
[4] "2020-10-28 00:00:00.25" "2021-01-06 00:00:00.25"
188+
189+
$`%H:%M:%OS in %Y`
190+
[1] "00:00:00.25 in 2020" "00:00:00.25 in 2020" "00:00:00.25 in 2020"
191+
[4] "00:00:00.25 in 2020" "00:00:00.25 in 2021"
192+
193+
$`%H:%M:%OS2 in %Y`
194+
[1] "00:00:00.25 in 2020" "00:00:00.25 in 2020" "00:00:00.25 in 2020"
195+
[4] "00:00:00.25 in 2020" "00:00:00.25 in 2021"
196+
197+
$`%Y-%m-%d`
198+
[1] "2020-04-22" "2020-07-01" "2020-09-09" "2020-11-18" "2021-01-27"
199+
200+
$`%Y-%m-%d %H:%M:%OS`
201+
[1] "2020-04-29 00:00:00" "2020-07-08 00:00:00" "2020-09-16 00:00:00"
202+
[4] "2020-11-25 00:00:00" "2021-02-03 00:00:00"
203+
204+
$`%Y-%m-%d %H:%M:%OS0`
205+
[1] "2020-05-06 00:00:00" "2020-07-15 00:00:00" "2020-09-23 00:00:00"
206+
[4] "2020-12-02 00:00:00" "2021-02-10 00:00:00"
207+
208+
$`%Y-%m-%d %H:%M:%OS1`
209+
[1] "2020-05-13 00:00:00.2" "2020-07-22 00:00:00.2" "2020-09-30 00:00:00.2"
210+
[4] "2020-12-09 00:00:00.2" "2021-02-17 00:00:00.2"
211+
212+
$`%Y-%m-%d %H:%M:%OS11`
213+
[1] "2020-06-03 00:00:00.21" "2020-08-12 00:00:00.21" "2020-10-21 00:00:00.21"
214+
[4] "2020-12-30 00:00:00.21" "2021-03-10 00:00:00.21"
215+
216+
$`%Y-%m-%d %H:%M:%OS6`
217+
[1] "2020-05-20 00:00:00.250000" "2020-07-29 00:00:00.250000"
218+
[3] "2020-10-07 00:00:00.250000" "2020-12-16 00:00:00.250000"
219+
[5] "2021-02-24 00:00:00.250000"
220+
221+
$`%Y-%m-%d %H:%M:%OS9`
222+
[1] "2020-05-27 00:00:00.250000" "2020-08-05 00:00:00.250000"
223+
[3] "2020-10-14 00:00:00.250000" "2020-12-23 00:00:00.250000"
224+
[5] "2021-03-03 00:00:00.250000"
225+
226+
> stopifnot(f3[2L+joff] == f3[3L+joff],
227+
+ grepl("^00:00:00.25 in 202[01]", f3[2L+joff]))
228+
> ## digits = 3 had no effect on "%OS "
92229
>

0 commit comments

Comments
 (0)