5.7 Golf: side effects

A new concern with the timestamp: The timestamps depend on which part of the world you’re in.

Problem: The month names vary, as does the time, and even the date.

Proposed Solution: Create timestamps that are all in a fixed time zone.

  • We can force a certain locale with Sys.setlocale() and force a particular time zone by adjusting the TZ environment variable.

Our attempt at implementing this:

timestamp <- function(time = Sys.time()) {
  Sys.setlocale("LC_TIME", "C")
  Sys.setenv(TZ = "UTC")
  format(time, "%Y-%B-%d_%H-%M-%S")
}

However, a user in Brazil would see this after using outfile_path() from our package:

outfile_path("INFILE.csv")
#> [1] "2022-April-17_07-14-18_INFILE_clean.csv"

format(Sys.time(), "%Y-%B-%d_%H-%M-%S")
#> [1] "2022-April-17_07-14-18"

Our calls to Sys.setlocale() and Sys.setenv() inside timestamp() have made persistent changes to their R session. This sort of side effect is very undesirable and is extremely difficult to track down and debug, especially in more complicated settings.

Solution:

# use withr::local_*() functions to keep the changes local to timestamp()
timestamp <- function(time = Sys.time()) {
  withr::local_locale(c("LC_TIME" = "C"))
  withr::local_timezone("UTC")
  format(time, "%Y-%B-%d_%H-%M-%S")
}

# use the tz argument to format.POSIXct()
timestamp <- function(time = Sys.time()) {
  withr::local_locale(c("LC_TIME" = "C"))
  format(time, "%Y-%B-%d_%H-%M-%S", tz = "UTC")
}

# put the format() call inside withr::with_*()
timestamp <- function(time = Sys.time()) {
  withr::with_locale(
    c("LC_TIME" = "C"),
    format(time, "%Y-%B-%d_%H-%M-%S", tz = "UTC")
  )
}

The locale in our timestamp is only temporarily modified with the withr::with_locale function.

  • In this example, the mistake we made was changing the user’s overall state.
    • If you have to do this, make sure this is documented explicitly or try to make them reversible

You need to adopt a different mindset when defining functions inside a package. Try to avoid making any changes to the user’s overall state. If such changes are unavoidable, make sure to reverse them (if possible) or to document them explicitly (if related to the function’s primary purpose).