lubridate icon indicating copy to clipboard operation
lubridate copied to clipboard

Argument roll=FALSE is not working as expected in update

Open luifrancgom opened this issue 4 years ago • 1 comments

I am using the function update from lubridate Version: 1.7.10 with the argument roll=FALSE but I am not getting a NA value for a non-existent civil time instant.

Here is a reproducible example with my session info:

library(lubridate)
#> 
#> Attaching package: 'lubridate'
#> The following objects are masked from 'package:base':
#> 
#>     date, intersect, setdiff, union

date <- ymd(x = "2021-04-05")

update(object = date,
       month = 2, 
       mday = 29, 
       roll = FALSE)
#> [1] "2021-03-01"

sessionInfo()
#> R version 4.0.4 (2021-02-15)
#> Platform: x86_64-pc-linux-gnu (64-bit)
#> Running under: Ubuntu 20.04.2 LTS
#> 
#> Matrix products: default
#> BLAS:   /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.9.0
#> LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.9.0
#> 
#> locale:
#>  [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
#>  [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
#>  [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
#>  [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                 
#>  [9] LC_ADDRESS=C               LC_TELEPHONE=C            
#> [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       
#> 
#> attached base packages:
#> [1] stats     graphics  grDevices utils     datasets  methods   base     
#> 
#> other attached packages:
#> [1] lubridate_1.7.10
#> 
#> loaded via a namespace (and not attached):
#>  [1] compiler_4.0.4    magrittr_2.0.1    generics_0.1.0    tools_4.0.4      
#>  [5] htmltools_0.5.1.1 yaml_2.2.1        Rcpp_1.0.5        stringi_1.5.3    
#>  [9] rmarkdown_2.7     highr_0.8         knitr_1.30        stringr_1.4.0    
#> [13] xfun_0.22         digest_0.6.27     rlang_0.4.10      evaluate_0.14

Created on 2021-04-05 by the reprex package (v0.3.0)

luifrancgom avatar Apr 05 '21 22:04 luifrancgom

With the clock approach, you generally only update one component at a time, which forces you to resolve the invalid date as soon as it arises.

library(clock)
library(magrittr)

date <- date_parse("2021-04-05")

date %>% set_month(2) %>% set_day(29)
#> Error: Invalid date found at location 1.
#> ℹ Resolve invalid date issues by specifying the `invalid` argument.

date %>% set_month(2) %>% set_day(29, invalid = "next")
#> [1] "2021-03-01"
date %>% set_month(2) %>% set_day(29, invalid = "previous")
#> [1] "2021-02-28"
date %>% set_month(2) %>% set_day(29, invalid = "NA")
#> [1] NA

To mimic the lubridate idea of updating multiple components at once, you'd switch to a calendar type like year-month-day, which can represent invalid dates directly. When you convert back to Date after applying all of the updates, you have to deal with any invalid ones you created in the process.

library(clock)
library(magrittr)

date <- date_parse("2021-04-05")

x <- date %>%
  as_year_month_day() %>%
  set_month(2) %>%
  set_day(29)

x
#> <year_month_day<day>[1]>
#> [1] "2021-02-29"

# Yes, this is invalid
invalid_detect(x)
#> [1] TRUE

as.Date(x)
#> Error: Conversion from a calendar requires that all dates are valid. Resolve invalid dates by calling `invalid_resolve()`.

as.Date(invalid_resolve(x, invalid = "next"))
#> [1] "2021-03-01"
as.Date(invalid_resolve(x, invalid = "previous"))
#> [1] "2021-02-28"
as.Date(invalid_resolve(x, invalid = "NA"))
#> [1] NA

Created on 2021-05-25 by the reprex package (v1.0.0)

DavisVaughan avatar May 25 '21 14:05 DavisVaughan

In devel all relevant update and tz operations gained two new arguments roll_month and roll_dst which allow for avery flexible control of the leap and DST behavior.

library(lubridate, warn.conflicts = F)
#> Loading required package: timechange
time <- ymd_hms(x = "2021-04-05 02:00:00")
update(time, months = 2, mdays = 29, roll_month = "postday")
#> [1] "2021-03-01 02:00:00 UTC"
update(time, months = 2, mdays = 29, roll_month = "preday")
#> [1] "2021-02-28 02:00:00 UTC"
update(time, months = 2, mdays = 29, roll_month = "boundary")
#> [1] "2021-03-01 UTC"

Created on 2022-11-02 with reprex v2.0.2

vspinu avatar Nov 02 '22 21:11 vspinu