3

I recently noticed an odd behavior in R I can't explain. I had this in some old code:

The following should produce the list of months from 01/1980 -> 01/2029 and it works as expected:

length(chron::seq.dates("01/31/80", "01/03/29", by="months"))
[1] 588

This is where things become strange. The following is the same as above but should produce dates until the year 2030:

length(chron::seq.dates("01/31/80", "01/03/30", by="months"))
Error during wrapup: "from" must be a date before "to"

So what is happening here?

Denis
  • 11,796
  • 16
  • 88
  • 150
  • Is this `seq.dates` from a package, also, shouldn't the date be converted to `Date` class – akrun Jan 02 '20 at 19:05
  • It is from the `chron` package – Denis Jan 02 '20 at 19:05
  • With `seq` it is not showing an error `seq(as.Date("01/31/80", format = "%m/%d/%y"), as.Date("01/03/30", format = "%m/%d/%y"), by = "month")` – akrun Jan 02 '20 at 19:07
  • Also, I tried after converting to 'Date' class -> numeric `seq.dates(as.numeric(as.Date("01/31/80", format = "%m/%d/%y")), as.numeric(as.Date("01/03/30", format = "%m/%d/%y")), by = "months") %>% length [1] 600`, so it could be a bug in parsing the year – akrun Jan 02 '20 at 19:11
  • 1
    My hunch just now was that it is treating it as 1980 - 2029 and 1980 - 1930 (instead of 2030) which is why it complains... – Denis Jan 02 '20 at 19:12

3 Answers3

3

When expanding a two digit year to a 4 digit year the chron cutoff is 30 by default. That is, if the two digit year is less than 30 it is assumed to be 20yy and otherwise 19yy. This is controlled by the chron.year.expand option which is by default set to the chron year.expand function which in turn has a default cutoff of 30 but this can be changed as follows:

library(chron)

# change cutoff to 50
options(chron.year.expand = 
     function (y, cut.off = 50, century = c(1900, 2000), ...) {
        chron:::year.expand(y, cut.off = cut.off, century = century, ...)
     }
)

length(seq.dates("01/31/80", "01/03/30", by="months"))
## [1] 600

Each of these also work and do not require that chron.year.expand be set:

length(seq(as.chron("1980-01-31"), as.chron("2030-01-03"), by="months"))

length(seq.dates("01/31/80", as.chron("2030-01-03"), by="months"))

length(seq.dates("01/31/80", chron(julian(1, 3, 2030)), by="months"))

length(seq.dates("01/31/80", julian(1, 3, 2030), by="months"))

length(as.chron(seq(as.Date("1980-01-31"), as.Date("2030-01-03"), by = "month")))

length(seq.dates("01/31/80", length = 600, by="months"))
G. Grothendieck
  • 254,981
  • 17
  • 203
  • 341
  • Kind of a dangerous GOTCHA. Great explanation though. I wonder how much code uses the chron package but doesn't realize what will happen in 2030 :-) – Denis Jan 02 '20 at 22:26
  • 1
    All date classes in R support 2 digit years so this is not specific to chron although the specific cutoff value is different. The cutoff for `Date` and `POSIXct` classes is 69. That is 00 to 68 are prefixed with 20 and the rest with 19. See `%y` in `?strptime` – G. Grothendieck Jan 02 '20 at 22:33
2

It would be better to convert to Date class as the 2-digit year can be an issue when we extend the years.

library(chron)
date1 <- as.Date("01/31/80", format = "%m/%d/%y")
date2 <- as.Date("01/03/30", format = "%m/%d/%y")

here, the conversion is correct

date1
#[1] "1980-01-31"
date2
#[1] "2030-01-03"

Based on the ?seq.dates, either we can pass a character string or numeric value (convert the 'Date' class to 'numeric'

length(seq.dates(as.numeric(date1), as.numeric(date2), by = "months"))
#[1] 600

Or a julian date

j1 <- julian(date1, origin = as.Date('1970-01-01'))
j2 <- julian(date2, origin = as.Date('1970-01-01')) 
length(seq.dates(j1, j2, by = 'months'))
#[1] 600

Or use a 4-digit year in character format

length(chron::seq.dates("01/31/1980", "01/03/2030", by="months"))
#[1] 600

If the dates are already available in 2 digits, can insert the specific digits with sub

sub("(\\d+)$", "20\\1", "01/03/30")
#[1] "01/03/2030"

and pass that value in the seq.dates

length(seq.dates("01/31/80", sub("(\\d+)$", "20\\1", "01/03/30"), by = "months"))
#[1] 600
akrun
  • 874,273
  • 37
  • 540
  • 662
0

Working with R4.1 running on Linux Mint, the code below shows what I see. The default cutoff appears to be 68, not 30. Is there a way to specify the true default cutoff to be 30 in a given install of R? In other words, I don't want to have to run the option function given above to set it to 30 every time I run R.

library(chron)

chron("2/29/68")-chron("11/23/69") Time in days: [1] 35892

chron("2/29/1968")-chron("11/23/1969") [1] -633

options(chron.year.expand =

  • function (y, cut.off = 30, century = c(1900, 2000), ...) {
    
  • chron:::year.expand(y, cut.off = cut.off, century = century, ...)
    
  • }
    
  • )

chron("2/29/68")-chron("11/23/69") [1] -633

chron("2/29/1968")-chron("11/23/1969") [1] -633

user1930565
  • 238
  • 1
  • 6