I found a solution for something but wondering if there's a better one. Scenario: We have timestamps for events and we want to know if the event occurred on Thanksgiving Day. The times are all stored in UTC BUT the people are in different time zones and we know their time zones. How do you know if the event happened on Thanksgiving Day? Here's my solution with simulated data.
options(width=200)
library(tibble)
library(dplyr)
#>
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:stats':
#>
#> filter, lag
#> The following objects are masked from 'package:base':
#>
#> intersect, setdiff, setequal, union
library(purrr)
library(lubridate)
#>
#> Attaching package: 'lubridate'
#> The following objects are masked from 'package:base':
#>
#> date, intersect, setdiff, union
library(tidyr)
set.seed(12345)
N <- 40
alltimes <- ymd_hms('2025-11-26 12:00:00', tz = "UTC") +
seconds(0:(86400 * 3 - 1))
head(alltimes)
#> [1] "2025-11-26 12:00:00 UTC" "2025-11-26 12:00:01 UTC" "2025-11-26 12:00:02 UTC" "2025-11-26 12:00:03 UTC" "2025-11-26 12:00:04 UTC" "2025-11-26 12:00:05 UTC"
tail(alltimes)
#> [1] "2025-11-29 11:59:54 UTC" "2025-11-29 11:59:55 UTC" "2025-11-29 11:59:56 UTC" "2025-11-29 11:59:57 UTC" "2025-11-29 11:59:58 UTC" "2025-11-29 11:59:59 UTC"
sampdat <-
tibble(
EventDateTimeUTC = sample(alltimes, N),
TimeZone = sample(
c(
"US/Eastern",
"US/Central",
"US/Mountain",
"US/Pacific",
"US/Hawaii",
"US/Arizona",
"US/Alaska"
),
N,
replace = TRUE
)
)
sampdat |> count(TimeZone)
#> # A tibble: 7 × 2
#> TimeZone n
#> <chr> <int>
#> 1 US/Alaska 11
#> 2 US/Arizona 2
#> 3 US/Central 8
#> 4 US/Eastern 3
#> 5 US/Hawaii 4
#> 6 US/Mountain 5
#> 7 US/Pacific 7
sampdat_adj <-
sampdat |>
nest(.by = TimeZone) |>
mutate(
data = map2(data, TimeZone, function(x, y) {
x |>
mutate(
DateTimeLocal = with_tz(EventDateTimeUTC, y),
DateLocal = as.Date(DateTimeLocal, tz = y),
DateUTC = as.Date(EventDateTimeUTC, tz = "UTC")
)
})
) |>
unnest(data) |>
mutate(
OnThanksgiving = DateLocal == ymd("2025-11-27") # we care if it was Thanksgiving for them!
)
sampdat_adj |> filter(DateLocal != DateUTC)
#> # A tibble: 14 × 6
#> TimeZone EventDateTimeUTC DateTimeLocal DateLocal DateUTC OnThanksgiving
#> <chr> <dttm> <dttm> <date> <date> <lgl>
#> 1 US/Alaska 2025-11-27 06:13:30 2025-11-26 23:13:30 2025-11-26 2025-11-27 FALSE
#> 2 US/Alaska 2025-11-27 05:19:27 2025-11-26 22:19:27 2025-11-26 2025-11-27 FALSE
#> 3 US/Alaska 2025-11-29 00:33:42 2025-11-28 17:33:42 2025-11-28 2025-11-29 FALSE
#> 4 US/Alaska 2025-11-29 04:31:03 2025-11-28 21:31:03 2025-11-28 2025-11-29 FALSE
#> 5 US/Alaska 2025-11-28 01:52:16 2025-11-27 18:52:16 2025-11-27 2025-11-28 TRUE
#> 6 US/Alaska 2025-11-28 04:42:56 2025-11-27 21:42:56 2025-11-27 2025-11-28 TRUE
#> 7 US/Alaska 2025-11-28 04:23:40 2025-11-27 21:23:40 2025-11-27 2025-11-28 TRUE
#> 8 US/Central 2025-11-29 03:48:58 2025-11-28 20:48:58 2025-11-28 2025-11-29 FALSE
#> 9 US/Central 2025-11-28 03:08:28 2025-11-27 20:08:28 2025-11-27 2025-11-28 TRUE
#> 10 US/Hawaii 2025-11-29 01:11:47 2025-11-28 18:11:47 2025-11-28 2025-11-29 FALSE
#> 11 US/Pacific 2025-11-28 00:31:04 2025-11-27 17:31:04 2025-11-27 2025-11-28 TRUE
#> 12 US/Pacific 2025-11-28 06:15:23 2025-11-27 23:15:23 2025-11-27 2025-11-28 TRUE
#> 13 US/Pacific 2025-11-28 03:07:46 2025-11-27 20:07:46 2025-11-27 2025-11-28 TRUE
#> 14 US/Pacific 2025-11-27 05:13:45 2025-11-26 22:13:45 2025-11-26 2025-11-27 FALSE
# What timezone does each date end up being
sampdat_adj |> select(contains("date")) |> map_chr(tz)
#> EventDateTimeUTC DateTimeLocal DateLocal DateUTC
#> "UTC" "US/Arizona" "UTC" "UTC"
sampdat_adj |> arrange(DateTimeLocal) |> print(n=N)
#> # A tibble: 40 × 6
#> TimeZone EventDateTimeUTC DateTimeLocal DateLocal DateUTC OnThanksgiving
#> <chr> <dttm> <dttm> <date> <date> <lgl>
#> 1 US/Eastern 2025-11-26 12:47:27 2025-11-26 05:47:27 2025-11-26 2025-11-26 FALSE
#> 2 US/Central 2025-11-26 16:42:33 2025-11-26 09:42:33 2025-11-26 2025-11-26 FALSE
#> 3 US/Pacific 2025-11-26 18:06:47 2025-11-26 11:06:47 2025-11-26 2025-11-26 FALSE
#> 4 US/Arizona 2025-11-26 19:05:47 2025-11-26 12:05:47 2025-11-26 2025-11-26 FALSE
#> 5 US/Central 2025-11-26 21:05:18 2025-11-26 14:05:18 2025-11-26 2025-11-26 FALSE
#> 6 US/Mountain 2025-11-26 23:23:15 2025-11-26 16:23:15 2025-11-26 2025-11-26 FALSE
#> 7 US/Pacific 2025-11-27 05:13:45 2025-11-26 22:13:45 2025-11-26 2025-11-27 FALSE
#> 8 US/Alaska 2025-11-27 05:19:27 2025-11-26 22:19:27 2025-11-26 2025-11-27 FALSE
#> 9 US/Alaska 2025-11-27 06:13:30 2025-11-26 23:13:30 2025-11-26 2025-11-27 FALSE
#> 10 US/Mountain 2025-11-27 10:04:56 2025-11-27 03:04:56 2025-11-27 2025-11-27 TRUE
#> 11 US/Alaska 2025-11-27 13:31:53 2025-11-27 06:31:53 2025-11-27 2025-11-27 TRUE
#> 12 US/Hawaii 2025-11-27 14:01:03 2025-11-27 07:01:03 2025-11-27 2025-11-27 TRUE
#> 13 US/Central 2025-11-27 14:37:26 2025-11-27 07:37:26 2025-11-27 2025-11-27 TRUE
#> 14 US/Pacific 2025-11-27 15:01:31 2025-11-27 08:01:31 2025-11-27 2025-11-27 TRUE
#> 15 US/Central 2025-11-27 15:28:28 2025-11-27 08:28:28 2025-11-27 2025-11-27 TRUE
#> 16 US/Eastern 2025-11-27 18:07:39 2025-11-27 11:07:39 2025-11-27 2025-11-27 TRUE
#> 17 US/Central 2025-11-27 18:37:00 2025-11-27 11:37:00 2025-11-27 2025-11-27 TRUE
#> 18 US/Hawaii 2025-11-27 19:05:01 2025-11-27 12:05:01 2025-11-27 2025-11-27 TRUE
#> 19 US/Central 2025-11-27 19:58:58 2025-11-27 12:58:58 2025-11-27 2025-11-27 TRUE
#> 20 US/Mountain 2025-11-27 20:16:51 2025-11-27 13:16:51 2025-11-27 2025-11-27 TRUE
#> 21 US/Arizona 2025-11-27 22:08:50 2025-11-27 15:08:50 2025-11-27 2025-11-27 TRUE
#> 22 US/Pacific 2025-11-27 22:39:50 2025-11-27 15:39:50 2025-11-27 2025-11-27 TRUE
#> 23 US/Pacific 2025-11-28 00:31:04 2025-11-27 17:31:04 2025-11-27 2025-11-28 TRUE
#> 24 US/Alaska 2025-11-28 01:52:16 2025-11-27 18:52:16 2025-11-27 2025-11-28 TRUE
#> 25 US/Pacific 2025-11-28 03:07:46 2025-11-27 20:07:46 2025-11-27 2025-11-28 TRUE
#> 26 US/Central 2025-11-28 03:08:28 2025-11-27 20:08:28 2025-11-27 2025-11-28 TRUE
#> 27 US/Alaska 2025-11-28 04:23:40 2025-11-27 21:23:40 2025-11-27 2025-11-28 TRUE
#> 28 US/Alaska 2025-11-28 04:42:56 2025-11-27 21:42:56 2025-11-27 2025-11-28 TRUE
#> 29 US/Pacific 2025-11-28 06:15:23 2025-11-27 23:15:23 2025-11-27 2025-11-28 TRUE
#> 30 US/Hawaii 2025-11-28 11:40:35 2025-11-28 04:40:35 2025-11-28 2025-11-28 FALSE
#> 31 US/Alaska 2025-11-28 18:25:35 2025-11-28 11:25:35 2025-11-28 2025-11-28 FALSE
#> 32 US/Mountain 2025-11-28 21:03:11 2025-11-28 14:03:11 2025-11-28 2025-11-28 FALSE
#> 33 US/Alaska 2025-11-28 21:23:13 2025-11-28 14:23:13 2025-11-28 2025-11-28 FALSE
#> 34 US/Alaska 2025-11-28 21:38:31 2025-11-28 14:38:31 2025-11-28 2025-11-28 FALSE
#> 35 US/Alaska 2025-11-29 00:33:42 2025-11-28 17:33:42 2025-11-28 2025-11-29 FALSE
#> 36 US/Hawaii 2025-11-29 01:11:47 2025-11-28 18:11:47 2025-11-28 2025-11-29 FALSE
#> 37 US/Central 2025-11-29 03:48:58 2025-11-28 20:48:58 2025-11-28 2025-11-29 FALSE
#> 38 US/Alaska 2025-11-29 04:31:03 2025-11-28 21:31:03 2025-11-28 2025-11-29 FALSE
#> 39 US/Eastern 2025-11-29 09:40:26 2025-11-29 02:40:26 2025-11-29 2025-11-29 FALSE
#> 40 US/Mountain 2025-11-29 10:44:41 2025-11-29 03:44:41 2025-11-29 2025-11-29 FALSE
Created on 2026-09-18 with reprex v2.1.1