Skip to content

Commit 478d357

Browse files
Merge branch 'master' into fix-#7354
2 parents 9af40c1 + 289a1ec commit 478d357

8 files changed

Lines changed: 249 additions & 109 deletions

File tree

.ci/atime/tests.R

Lines changed: 174 additions & 54 deletions
Large diffs are not rendered by default.

.github/workflows/R-CMD-check.yaml

Lines changed: 7 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,14 @@ jobs:
2727
# jobs (mainly test-coverage) to run on every commit in PRs so as to not slow down dev.
2828
# GHA does run these jobs concurrently but even so reducing the load seems like a good idea.
2929
- {os: windows-latest, r: 'devel'}
30-
- {os: macos-15-intel, r: 'release'}
31-
- {os: macos-15, r: 'release'}
30+
- {os: macos-15-intel, r: 'release'}
31+
- {os: macos-15, r: 'release'}
3232
# TODO(remotes>2.5.0): Use 24.04[noble?]
33-
- {os: ubuntu-22.04, r: 'release', rspm: "https://packagemanager.rstudio.com/cran/__linux__/jammy/latest"}
34-
# - {os: ubuntu-22.04, r: 'devel', rspm: "https://packagemanager.rstudio.com/cran/__linux__/jammy/latest", http-user-agent: "R/4.1.0 (ubuntu-22.04) R (4.1.0 x86_64-pc-linux-gnu x86_64 linux-gnu) on GitHub Actions" }
33+
- {os: ubuntu-22.04, r: 'release'}
34+
# - {os: ubuntu-22.04, r: 'devel'}
3535
# GLCI covers R-devel; no need to delay contributors in dev due to changes in R-devel in recent days
3636

3737
env:
38-
R_REMOTES_NO_ERRORS_FROM_WARNINGS: true
39-
RSPM: ${{ matrix.config.rspm }}
4038
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
4139
_R_CHECK_RD_CHECKRD_MINLEVEL_: -Inf
4240

@@ -47,29 +45,6 @@ jobs:
4745
with:
4846
r-version: ${{ matrix.config.r }}
4947

50-
51-
- name: Query dependencies
52-
run: |
53-
install.packages('remotes')
54-
saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2)
55-
writeLines(sprintf("R-%i.%i", getRversion()$major, getRversion()$minor), ".github/R-version")
56-
shell: Rscript {0}
57-
58-
- name: Restore R package cache
59-
uses: actions/cache@v5
60-
with:
61-
path: ${{ env.R_LIBS_USER }}
62-
key: ${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('.github/R-version') }}-1-${{ hashFiles('.github/depends.Rds') }}
63-
restore-keys: ${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('.github/R-version') }}-1-
64-
65-
- name: Install system dependencies
66-
if: runner.os == 'Linux'
67-
run: |
68-
while read -r cmd
69-
do
70-
eval sudo $cmd
71-
done < <(Rscript -e 'writeLines(remotes::system_requirements("ubuntu", "22.04"))')
72-
7348
- name: Install R Package Build Dependencies on MacOS, from https://github.com/stan-dev/cmdstanr/pull/1072/files
7449
if: runner.os == 'macOS'
7550
uses: r-hub/actions/setup-r-sysreqs@v1
@@ -90,23 +65,8 @@ jobs:
9065
fi
9166
fi # otherwise R-bundled runtime is fine
9267
93-
- name: Install dependencies
94-
run: |
95-
remotes::install_deps(dependencies = TRUE)
96-
remotes::install_cran("rcmdcheck")
97-
shell: Rscript {0}
98-
99-
- name: Check
100-
env:
101-
_R_CHECK_CRAN_INCOMING_REMOTE_: false
102-
run: |
103-
options(crayon.enabled = TRUE)
104-
rcmdcheck::rcmdcheck(args = c("--no-manual", "--as-cran"), error_on = "warning", check_dir = "check")
105-
shell: Rscript {0}
68+
- uses: yihui/actions/setup-r-dependencies@HEAD
10669

107-
- name: Upload check results
108-
if: failure()
109-
uses: actions/upload-artifact@main
70+
- uses: yihui/actions/check-r-package@HEAD
11071
with:
111-
name: ${{ runner.os }}-r${{ matrix.config.r }}-results
112-
path: check
72+
check-args: "--no-manual --as-cran"

.github/workflows/pkgup.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ jobs:
5858
Rscript -e 'tools::write_PACKAGES("public/src/contrib", fields="Revision")'
5959
- name: upload
6060
if: github.ref == 'refs/heads/master'
61-
uses: actions/upload-pages-artifact@v4
61+
uses: actions/upload-pages-artifact@v5
6262
with:
6363
path: "public"
6464
- name: deploy

NEWS.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030

3131
5. `tables()` can now optionally report `data.table` objects stored one level deep inside list objects when `depth=1L`, [#2606](https://github.com/Rdatatable/data.table/issues/2606). Thanks @MichaelChirico for the report and @manmita for the PR
3232

33+
6. `yearqtr()` and `yearmon()` now gain an optional format specifier [#7694](https://github.com/Rdatatable/data.table/issues/7694). 'numeric' is the default, which preserves the original behavior, but 'character' formats `yearqtr()` as YYYYQ# (e.g. 2025Q2) and `yearmon()` as YYYYM## (e.g. 2025M02, 2025M10). Thanks to @jan-swissre for the report and @LunaticSage218 for the implementation.
34+
3335
### BUG FIXES
3436

3537
1. `fread()` with `skip=0` and `(header=TRUE|FALSE)` no longer skips the first row when it has fewer fields than subsequent rows, [#7463](https://github.com/Rdatatable/data.table/issues/7463). Thanks @emayerhofer for the report and @ben-schwen for the fix.
@@ -50,6 +52,8 @@
5052

5153
9. `fread()` no longer replaces a literal header column name `"NA"` with an auto-generated `Vn` name when `na.strings` includes `"NA"`, [#5124](https://github.com/Rdatatable/data.table/issues/5124). Data rows still continue to parse `"NA"` as missing. Thanks @Mashin6 for the report and @shrektan for the fix.
5254

55+
10. `fread()` no longer misreads dates with negative years, [#7704](https://github.com/Rdatatable/data.table/issues/7704). Thanks to @kevinushey for the report and @aitap for the fix.
56+
5357
### Notes
5458

5559
1. {data.table} now depends on R 3.5.0 (2018).

R/IDateTime.R

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -365,8 +365,30 @@ isoyear = function(x) as.integer(format(as.IDate(x), "%G"))
365365
month = function(x) convertDate(as.IDate(x), "month")
366366
quarter = function(x) convertDate(as.IDate(x), "quarter")
367367
year = function(x) convertDate(as.IDate(x), "year")
368-
yearmon = function(x) convertDate(as.IDate(x), "yearmon")
369-
yearqtr = function(x) convertDate(as.IDate(x), "yearqtr")
368+
yearmon = function(x, format = c("numeric", "character")) {
369+
format = match.arg(format)
370+
x_as_idate = as.IDate(x)
371+
ymon = convertDate(x_as_idate, "yearmon")
372+
if (format == "numeric") return(ymon)
373+
ans = rep(NA_character_, length(x_as_idate))
374+
ok = !is.na(x_as_idate)
375+
yr = floor(ymon[ok])
376+
mon = round((ymon[ok] - yr) * 12) + 1L
377+
ans[ok] = sprintf("%dM%02d", as.integer(yr), as.integer(mon))
378+
ans
379+
}
380+
yearqtr = function(x, format = c("numeric", "character")) {
381+
format = match.arg(format)
382+
x_as_idate = as.IDate(x)
383+
yqtr = convertDate(x_as_idate, "yearqtr")
384+
if (format == "numeric") return(yqtr)
385+
ans = rep(NA_character_, length(x_as_idate))
386+
ok = !is.na(x_as_idate)
387+
yr = floor(yqtr[ok])
388+
qtr = round((yqtr[ok] - yr) * 4) + 1L
389+
ans[ok] = sprintf("%dQ%d", as.integer(yr), as.integer(qtr))
390+
ans
391+
}
370392

371393
convertDate = function(x, type) {
372394
type = match.arg(type, c("yday", "wday", "mday", "week", "month", "quarter", "year", "yearmon", "yearqtr"))

inst/tests/tests.Rraw

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21639,3 +21639,23 @@ close(con)
2163921639
file.create(f <- tempfile())
2164021640
test(2367.6, fread(file(f)), data.table(), warning="Connection has size 0.")
2164121641
unlink(f)
21642+
21643+
# negative years caused UB in leap year calculation, #7704
21644+
x = fread("x\n-1-01-01")$x
21645+
test(2368.1, year(x), -1L)
21646+
test(2368.2, month(x), 1L)
21647+
test(2368.3, mday(x), 1L)
21648+
21649+
# yearqtr() and yearmon() could optionally output 2025Q4 format #7694
21650+
x = c("1111-11-11", "2019-01-01", "2019-02-28", "2019-03-01", "2019-12-31", "2020-02-29", "2020-03-01", "2020-12-31", "2040-01-01", "2040-12-31", "2100-03-01", NA)
21651+
test(2369.1, yearqtr(x, format="numeric"), c(1111.75, 2019, 2019, 2019, 2019.75, 2020, 2020, 2020.75, 2040, 2040.75, 2100, NA))
21652+
test(2369.2, yearqtr(x, format="numeric"), yearqtr(x)) # numeric is the default, preserves backwards compatibility
21653+
test(2369.3, yearqtr(x, format="character"), c("1111Q4", "2019Q1", "2019Q1", "2019Q1", "2019Q4", "2020Q1", "2020Q1", "2020Q4", "2040Q1", "2040Q4", "2100Q1", NA_character_))
21654+
test(2369.4, yearqtr("2016-08-03 01:02:03.45", format="character"), "2016Q3")
21655+
test(2369.5, yearqtr(NA, format="character"), NA_character_)
21656+
21657+
test(2370.1, yearmon(x, format="numeric"), c(1111+10/12, 2019, 2019+1/12, 2019+2/12, 2019+11/12, 2020+1/12, 2020+2/12, 2020+11/12, 2040, 2040+11/12, 2100+2/12, NA))
21658+
test(2370.2, yearmon(x, format="numeric"), yearmon(x)) # numeric is the default, preserves backwards compatibility
21659+
test(2370.3, yearmon(x, format="character"), c("1111M11", "2019M01", "2019M02", "2019M03", "2019M12", "2020M02", "2020M03", "2020M12", "2040M01", "2040M12", "2100M03", NA_character_))
21660+
test(2370.4, yearmon("2016-08-03 01:02:03.45", format="character"), "2016M08")
21661+
test(2370.5, yearmon(NA, format="character"), NA_character_)

man/IDateTime.Rd

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,8 @@ isoyear(x)
9797
month(x)
9898
quarter(x)
9999
year(x)
100-
yearmon(x)
101-
yearqtr(x)
100+
yearmon(x, format = c("numeric", "character"))
101+
yearqtr(x, format = c("numeric", "character"))
102102

103103
}
104104

@@ -115,6 +115,7 @@ yearqtr(x)
115115
the S3 generic.}
116116
\item{units}{one of the units listed for truncating. May be abbreviated.}
117117
\item{ms}{ For \code{as.ITime} methods, what should be done with sub-second fractions of input? Valid values are \code{'truncate'} (floor), \code{'nearest'} (round), and \code{'ceil'} (ceiling). See Details. }
118+
\item{format}{For \code{yearmon} and \code{yearqtr}, either \code{"numeric"} (default) or \code{"character"}. \code{"character"} formats the result as \code{"2025M04"} for \code{yearmon} and \code{"2025Q2"} for \code{yearqtr}.}
118119
}
119120
\details{
120121
\code{IDate} is a date class derived from \code{Date}. It has the same
@@ -209,7 +210,11 @@ Similarly, \code{isoyear()} returns the ISO 8601 year corresponding to the ISO w
209210
for second, minute, hour, day of year, day of week,
210211
day of month, week, month, quarter, and year, respectively.
211212
\code{yearmon} and \code{yearqtr} return double values representing
212-
respectively \code{year + (month-1) / 12} and \code{year + (quarter-1) / 4}.
213+
respectively \code{year + (month-1) / 12} and \code{year + (quarter-1) / 4}
214+
when \code{format = "numeric"} (the default). When \code{format = "character"},
215+
they return character vectors of the form \code{"YYYYM##"} (e.g. \code{"2025M04"},
216+
zero-padded for sortability) and \code{"YYYYQN"} (e.g. \code{"2025Q2"}) respectively,
217+
with \code{NA} input returned as \code{NA_character_}.
213218
214219
\code{second}, \code{minute}, \code{hour} are taken directly from
215220
the \code{POSIXlt} representation.
@@ -296,6 +301,11 @@ year(d2)
296301
isoweek(d2)
297302
isoyear(d2)
298303

304+
# Character format for yearmon() and yearqtr()
305+
d3 = as.IDate(c("2019-01-01", "2019-12-31"))
306+
yearmon(d3, format = "character") # "2019M01" "2019M12"
307+
yearqtr(d3, format = "character") # "2019Q1" "2019Q4"
308+
299309
}
300310
\keyword{utilities}
301311

src/fread.c

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1087,9 +1087,13 @@ static void parse_iso8601_date_core(const char **pch, int32_t *target)
10871087
if (day == NA_INT32 || day < 1 || (day > (isLeapYear ? leapYearDays[month - 1] : normYearDays[month - 1])))
10881088
return;
10891089

1090+
int32_t cycle_year = year % 400;
1091+
if (cycle_year < 0) cycle_year += 400;
1092+
int32_t cycle = (year - cycle_year) / 400;
1093+
10901094
*target =
1091-
(year / 400 - 4) * cumDaysCycleYears[400] + // days to beginning of 400-year cycle
1092-
cumDaysCycleYears[year % 400] + // days to beginning of year within 400-year cycle
1095+
(cycle - 4) * cumDaysCycleYears[400] + // days to beginning of 400-year cycle
1096+
cumDaysCycleYears[cycle_year] + // days to beginning of year within 400-year cycle
10931097
(isLeapYear ? cumDaysCycleMonthsLeap[month - 1] : cumDaysCycleMonthsNorm[month - 1]) + // days to beginning of month within year
10941098
day - 1; // day within month (subtract 1: 1970-01-01 -> 0)
10951099

0 commit comments

Comments
 (0)