Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Date ranges can go backward #2043

Open
bborgesr opened this issue Apr 26, 2018 · 6 comments

Comments

@bborgesr
Copy link
Contributor

commented Apr 26, 2018

Date ranges can go backward with no indication of error... Maybe even better would be to make it impossible to do so? (though that could break weird existing code?)

Repro

library(shiny)

ui <- fluidPage(
  dateRangeInput("daterange", "Date range:",
    start = "2010-01-01",
    end   = "2001-12-31"),
  verbatimTextOutput("res")
)
server <- function(input, output, session) {
  output$res <- renderPrint({
    paste("Start at", input$daterange[1], "and end at", input$daterange[2])
  })
}
shinyApp(ui, server)
@ColinFay

This comment has been minimized.

Copy link

commented Mar 28, 2019

Just stumbled upon this behavior causing bugs in an app I've been building.
You'd expect the user to be disciplined with this, but errors happen, notably when users think they are in a specific year but in fact they are not.

So I can see two things here:

  • prevent developers from launching a dateRange with end < start
newDateRange <- function (inputId, label, start = NULL, end = NULL, min = NULL, 
                            max = NULL, format = "yyyy-mm-dd", startview = "month", weekstart = 0, 
                            language = "en", separator = " to ", width = NULL, autoclose = TRUE) 
{
  #browser()
  if (inherits(start, "Date")) 
    start <- format(start, "%Y-%m-%d")
  if (inherits(end, "Date")) 
    end <- format(end, "%Y-%m-%d")
  if (inherits(min, "Date")) 
    min <- format(min, "%Y-%m-%d")
  if (inherits(max, "Date")) 
    max <- format(max, "%Y-%m-%d")
  restored <- restoreInput(id = inputId, default = list(start, 
                                                        end))
  start <- restored[[1]]
  end <- restored[[2]]
  if (start > end){
    stop(paste("Error at input", inputId, ":`start` can't be posterior to `end`."), call. = FALSE)
  }
  attachDependencies(div(id = inputId, class = "shiny-date-range-input form-group shiny-input-container", 
                         style = if (!is.null(width)) 
                           paste0("width: ", validateCssUnit(width), ";"), controlLabel(inputId, label), div(class = "input-daterange input-group", tags$input(class = "input-sm form-control", type = "text", `data-date-language` = language, `data-date-week-start` = weekstart, `data-date-format` = format, `data-date-start-view` = startview, `data-min-date` = min, `data-max-date` = max, `data-initial-date` = start, `data-date-autoclose` = if (autoclose) "true"else "false"), span(class = "input-group-addon", separator), tags$input(class = "input-sm form-control",  type = "text", `data-date-language` = language, `data-date-week-start` = weekstart, `data-date-format` = format, `data-date-start-view` = startview, `data-min-date` = min, `data-max-date` = max, `data-initial-date` = end, `data-date-autoclose` = if (autoclose) "true"else "false"))), datePickerDependency)
}

ui <- fluidPage(
  newDateRange("daterange", "Date range:",
                 start = "2010-01-01",
                 end   = "2001-12-31"),
  verbatimTextOutput("res")
)
Error: Error at input daterange :`start` can't be posterior to `end`.
  • Prevent the user of the app from selecting end < start. I wonder what would be the expected behavior here. Should start automatically update end and vice versa (which seems to me hard to implement / use)? Should an error be thrown?
@ColinFay

This comment has been minimized.

Copy link

commented Aug 26, 2019

Any news on that one?

@ColinFay

This comment has been minimized.

Copy link

commented Aug 28, 2019

So, I've been thinking around this yesterday, as it's problematic in an app I need to send to production.

The issue being that users can select start > end.
In my app, the user need to be able to select a start which can be any date before the end. And they need to be able to select any end but after start.

The current implementation of dateRangeInput() does not allow to easily implement that. A simple way to do that would be to wrap something that says: if the user changes the start, update the min of end, and if the user changes end, update the max of start. But currently, this approach is not possible as there is just one input sent to server, so you can't either observe one of the two, nor update only one min or max.

Idea around that:

  • Implement this if dance in JS straight in dateRangeInput().
  • Implement a second version of dateRangeInput() that has two set of min/max and that sends two inputs to the server so it's easier to handle.

For anyone coming there looking for a solution, here is a piece of code to handle that:

library(shiny)
ui <- fluidPage(
  shinyalert::useShinyalert(),
  dateRangeInput("daterange1", "Date range:",
                 start = "2010-12-01",
                 end   = "2010-12-31")
)

server <-  function(input, output, session) {
  
  r <- reactiveValues(
    start = lubridate::ymd("2010-12-01"),
    end = lubridate::ymd("2010-12-31")
  )
  
  observeEvent( input$daterange1 , {
    start <- lubridate::ymd(input$daterange1[[1]])
    end <- lubridate::ymd(input$daterange1[[2]])
    if (start >= end){
      shinyalert::shinyalert("start > end", type = "error")
      updateDateRangeInput(
        session, 
        "daterange1", 
        start = r$start,
        end = r$end
      )
    } else {
      r$start <- input$daterange1[[1]]
      r$end <- input$daterange1[[2]]
    }
  }, ignoreInit = TRUE)
  
}

shinyApp(ui, server)
@naveen-kumar-123

This comment has been minimized.

Copy link

commented Aug 30, 2019

Just stumbled upon this behavior causing bugs in an app I've been building.
You'd expect the user to be disciplined with this, but errors happen, notably when users think they are in a specific year but in fact they are not.

So I can see two things here:

  • prevent developers from launching a dateRange with end < start
newDateRange <- function (inputId, label, start = NULL, end = NULL, min = NULL, 
                            max = NULL, format = "yyyy-mm-dd", startview = "month", weekstart = 0, 
                            language = "en", separator = " to ", width = NULL, autoclose = TRUE) 
{
  #browser()
  if (inherits(start, "Date")) 
    start <- format(start, "%Y-%m-%d")
  if (inherits(end, "Date")) 
    end <- format(end, "%Y-%m-%d")
  if (inherits(min, "Date")) 
    min <- format(min, "%Y-%m-%d")
  if (inherits(max, "Date")) 
    max <- format(max, "%Y-%m-%d")
  restored <- restoreInput(id = inputId, default = list(start, 
                                                        end))
  start <- restored[[1]]
  end <- restored[[2]]
  if (start > end){
    stop(paste("Error at input", inputId, ":`start` can't be posterior to `end`."), call. = FALSE)
  }
  attachDependencies(div(id = inputId, class = "shiny-date-range-input form-group shiny-input-container", 
                         style = if (!is.null(width)) 
                           paste0("width: ", validateCssUnit(width), ";"), controlLabel(inputId, label), div(class = "input-daterange input-group", tags$input(class = "input-sm form-control", type = "text", `data-date-language` = language, `data-date-week-start` = weekstart, `data-date-format` = format, `data-date-start-view` = startview, `data-min-date` = min, `data-max-date` = max, `data-initial-date` = start, `data-date-autoclose` = if (autoclose) "true"else "false"), span(class = "input-group-addon", separator), tags$input(class = "input-sm form-control",  type = "text", `data-date-language` = language, `data-date-week-start` = weekstart, `data-date-format` = format, `data-date-start-view` = startview, `data-min-date` = min, `data-max-date` = max, `data-initial-date` = end, `data-date-autoclose` = if (autoclose) "true"else "false"))), datePickerDependency)
}

ui <- fluidPage(
  newDateRange("daterange", "Date range:",
                 start = "2010-01-01",
                 end   = "2001-12-31"),
  verbatimTextOutput("res")
)
Error: Error at input daterange :`start` can't be posterior to `end`.
  • Prevent the user of the app from selecting end < start. I wonder what would be the expected behavior here. Should start automatically update end and vice versa (which seems to me hard to implement / use)? Should an error be thrown?

This is my error:
Error: Error at input daterange :startcan't be posterior toend.
Here's what i did:

library(shiny)

newDateRange <- function (inputId, label, start = NULL, end = NULL, min = NULL, 
                          max = NULL, format = "yyyy-mm-dd", startview = "month", weekstart = 0, 
                          language = "en", separator = " to ", width = NULL, autoclose = TRUE) 
{
  #browser()
  if (inherits(start, "Date")) 
    start <- format(start, "%Y-%m-%d")
  if (inherits(end, "Date")) 
    end <- format(end, "%Y-%m-%d")
  if (inherits(min, "Date")) 
    min <- format(min, "%Y-%m-%d")
  if (inherits(max, "Date")) 
    max <- format(max, "%Y-%m-%d")
  restored <- restoreInput(id = inputId, default = list(start, 
                                                        end))
  start <- restored[[1]]
  end <- restored[[2]]
  if (start > end){
    stop(paste("Error at input", inputId, ":`start` can't be posterior to `end`."), call. = FALSE)
  }
  attachDependencies(div(id = inputId, class = "shiny-date-range-input form-group shiny-input-container", 
                         style = if (!is.null(width)) 
                           paste0("width: ", validateCssUnit(width), ";"), controlLabel(inputId, label), div(class = "input-daterange input-group", tags$input(class = "input-sm form-control", type = "text", `data-date-language` = language, `data-date-week-start` = weekstart, `data-date-format` = format, `data-date-start-view` = startview, `data-min-date` = min, `data-max-date` = max, `data-initial-date` = start, `data-date-autoclose` = if (autoclose) "true"else "false"), span(class = "input-group-addon", separator), tags$input(class = "input-sm form-control",  type = "text", `data-date-language` = language, `data-date-week-start` = weekstart, `data-date-format` = format, `data-date-start-view` = startview, `data-min-date` = min, `data-max-date` = max, `data-initial-date` = end, `data-date-autoclose` = if (autoclose) "true"else "false"))), datePickerDependency)
}


ui_function <- fluidPage(
  newDateRange("daterange", "Date range:",
               start = "2010-01-01",
               end   = "2001-12-31"),
  verbatimTextOutput("res")
)

server_function <- function(input, output){
  output$res <- renderPrint({
    paste("Start at", input$daterange[1], "and end at", input$daterange[2])
  })
}


shinyApp(ui_function, server_function)
@eaurele

This comment has been minimized.

Copy link

commented Aug 30, 2019

An alternative to prevent user from selecting start > end can be found in shinyWidgets:

library(shiny)

ui <- fluidPage(
  shinyWidgets::airDatepickerInput("daterange", "Date range:",
                                   range = TRUE,
                                   value = c("2010-01-01", "2001-12-31")),
  verbatimTextOutput("res")
)
server <- function(input, output, session) {
  output$res <- renderPrint({
    paste("Start at", input$daterange[1], "and end at", input$daterange[2])
  })
}
shinyApp(ui, server)
@ColinFay

This comment has been minimized.

Copy link

commented Sep 6, 2019

Hey @eaurele,

Thanks for pointing.

I can't use airDatepickerInput in some apps because it's causing some namespace conflicts with the rest of the code.

Cheers,
C.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
7 participants
You can’t perform that action at this time.