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

Fix timezone issues in scale_*_datetime #1767

Merged
merged 8 commits into from
Sep 26, 2016

Conversation

thomasp85
Copy link
Member

Fixes #1718

@thomasp85 thomasp85 self-assigned this Sep 21, 2016
@thomasp85
Copy link
Member Author

@hadley I think I've been able to fix the datetime scale with minimal implication for surrounding code - can you stress-test it?

There are some points you may not like:

  • We reassign the transformation function if a timezone exists - this is needed as the scale constructor has no acces to the data. This should be safe as the user has no access to pass transformation functions to the scale so we know it is a time transformation object.
  • We assign a function with access to self as the labeller and postpone creation of the labeller function until it is actually called - this is needed so the labeller function gets the timezone for the same reasons as with the trans object

Example adapted from #1718

df <- data.frame(
  time = as.POSIXct("2010-01-01", tz = "America/Chicago") + 1:10 * 3600,
  y = 1:10  
)
ggplot(df, aes(time, y)) + geom_point() + scale_x_datetime(date_labels = "%H:%M %Z")

rplot

@@ -125,7 +125,10 @@ scale_datetime <- function(aesthetics, trans,
minor_breaks <- date_breaks(date_minor_breaks)
}
if (!is.waive(date_labels)) {
labels <- date_format(date_labels)
labels <- function(self, x) {
tz <- if (is.null(self$timezone)) "UTC" else self$timezone
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the default should be "", which means the default local timezone.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And can we add a tz parameter to the constructor to override the default?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How should this behave if there is a mismatch between the timezone in the data and the one provided in the constructor? Should the constructor value only kick in if the data does not have a timezone provided?

self$timezone <- tz
self$trans <- time_trans(self$timezone)
} else {
if (!identical(self$timezone, tz)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can drop this warning, as it's not very important.

@hadley
Copy link
Member

hadley commented Sep 21, 2016

I'll write up a few unit tests for you too

@hadley
Copy link
Member

hadley commented Sep 21, 2016

These tests all look good to me:

base_time <- function(tz = "") {
  as.POSIXct(strptime("2015-06-01", "%Y-%m-%d", tz = tz))
}

df <- data.frame(
  time1 = base_time("") + 0:6 * 3600,
  time2 = base_time("UTC") + 0:6 * 3600,
  time3 = base_time("Australia/Lord_Howe") + (0:6 + 13) * 3600, # has half hour offset
  y = seq_along(x)
)

test_that("inherits timezone from data", {
  # Local time
  p <- ggplot(df, aes(y = y)) + geom_point(aes(time1))
  sc <- layer_scales(p)$x
  expect_equal(sc$timezone, NULL)
  expect_equal(sc$get_labels()[1], "00:00") 

  # UTC
  p <- ggplot(df, aes(y = y)) + geom_point(aes(time2))
  sc <- layer_scales(p)$x
  expect_equal(sc$timezone, "UTC")
  expect_equal(sc$get_labels()[1], "00:00") 
})


test_that("first timezone wins", {
  p <- ggplot(df, aes(y = y)) +
    geom_point(aes(time2)) + 
    geom_point(aes(time3), colour = "red") + 
    scale_x_datetime(date_breaks = "hour", date_labels = "%H:%M")
  sc <- layer_scales(p)$x
  expect_equal(sc$timezone, "UTC")
})

test_that("not cached across calls", {
  scale_x <- scale_x_datetime(date_breaks = "hour", date_labels = "%H:%M")

  p1 <- ggplot(df, aes(y = y)) + geom_point(aes(time2)) + scale_x
  p2 <- ggplot(df, aes(y = y)) + geom_point(aes(time3)) + scale_x

  expect_equal(layer_scales(p1)$x$timezone, "UTC")
  expect_equal(layer_scales(p2)$x$timezone, "Australia/Lord_Howe")
})

@hadley
Copy link
Member

hadley commented Sep 22, 2016

LGTM

@thomasp85
Copy link
Member Author

And the timezone default stuff..?

@hadley
Copy link
Member

hadley commented Sep 22, 2016

Oh my tests seem to suggest that it all works currently. I certainly couldn't figure out how to elicit the bug

@thomasp85
Copy link
Member Author

I was more referring to your wish for an argument in the constructor to set the timezone

@hadley
Copy link
Member

hadley commented Sep 22, 2016

Oh, yeah, please do that!

@thomasp85
Copy link
Member Author

Should the argument be ignored in the presence of one encoded in the data?

@hadley
Copy link
Member

hadley commented Sep 22, 2016

No, it should override the data. (i.e. it should default to NULL)

@thomasp85
Copy link
Member Author

So if data has one timezone and the scale has another it should first convert the data to the other timezone, or should the original timezone simply be ignored?

@hadley
Copy link
Member

hadley commented Sep 22, 2016

There's no converting - the tzone attribute just controls how the instant in time is displayed.

@thomasp85
Copy link
Member Author

I see, that's easy then

@thomasp85
Copy link
Member Author

You can now do this:

df <- data.frame(
  time = as.POSIXct("2010-01-01", tz = "America/Chicago") + 1:10 * 3600,
  y = 1:10  
)
ggplot(df, aes(time, y)) + geom_point() + 
  scale_x_datetime(date_labels = "%H:%M %Z", timezone = "CET")

rplot01

@hadley
Copy link
Member

hadley commented Sep 23, 2016

Looks good!

@thomasp85 thomasp85 merged commit 04eb3b1 into tidyverse:master Sep 26, 2016
@lock
Copy link

lock bot commented Jan 18, 2019

This old issue has been automatically locked. If you believe you have found a related problem, please file a new issue (with reprex) and link to this issue. https://reprex.tidyverse.org/

@lock lock bot locked and limited conversation to collaborators Jan 18, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants