【问题标题】:Convert vector with local times to UTC将带有当地时间的矢量转换为 UTC
【发布时间】:2016-05-16 16:09:05
【问题描述】:

我有一个 POSIXct 向量,它稍微滥用了该格式:

> head(df$datetime)
[1] "2016-03-03 12:30:00 UTC" "2016-03-03 12:00:00 UTC" "2016-02-27 09:00:00 UTC" "2016-03-03 17:30:00 UTC"
[5] "2016-03-03 10:30:00 UTC" "2016-03-03 14:30:00 UTC"

这些日期时间被标记为 UTC 时间,但实际上属于各种本地时区:

> df %>% select(datetime, timezone) %>% head
         datetime            timezone
1 2016-03-03 12:30:00 Australia/Melbourne
2 2016-03-03 12:00:00 Europe/Berlin
3 2016-02-27 09:00:00 Europe/Amsterdam
4 2016-03-03 17:30:00 Australia/Brisbane
5 2016-03-03 10:30:00 Europe/Amsterdam
6 2016-03-03 14:30:00 Europe/Berlin

我想将这些日期时间正确地转换为 UTC——在某种意义上,herehere 面临的逆问题——但我遇到了困难。第二个链接的解决方案的变体有效:

get_utc_time <- function(timestamp_local, local_tz) {
  l <- lapply(seq(length(timestamp_local)), 
              function(x) {with_tz(force_tz(timestamp_local[x], tzone=local_tz[x]), tzone='UTC')})
  as.POSIXct(combine(l), origin = '1970-01-01 00:00.00', tz = 'UTC')
}

df$datetime_utc <- get_utc_time(df$datetime, df$timezone)

dplyr::mutate(df, datetime_utc = get_utc_time(datetime, timezone)),我认为是等效的,抛出一个错误。)

但是由于这不是矢量化的,因此在具有 50 万行的 data.frame 上速度非常慢。有没有更优雅、更快捷的方法来做到这一点?

【问题讨论】:

    标签: r time


    【解决方案1】:

    我所知道的最“官方”的方式涉及格式化和重新解析; David Smith had a post on this a while ago 在 REvolutions 博客上。

    时间序列库,尤其是那些能够感知时区的库,也可以做到这一点。这是一种使用 RcppCCTZ 的方法,它是我对 CCTZ 的封装(由一些 Google 员工编写,但不是 Google 官方库)——它计算两个时区之间的差异(默认以小时为单位)。

    library(RcppCCTZ)  # you need the GitHub version though
    
    # your data
    df <- read.csv(text="datetime,timezone
    2016-03-03 12:30:00,Australia/Melbourne
    2016-03-03 12:00:00,Europe/Berlin
    2016-02-27 09:00:00,Europe/Amsterdam
    2016-03-03 17:30:00,Australia/Brisbane
    2016-03-03 10:30:00,Europe/Amsterdam
    2016-03-03 14:30:00,Europe/Berlin", stringsAsFactor=FALSE)
    
    # parse to POSIXct
    df[,"pt"] <- as.POSIXct(df[,"datetime"])
    
    # compute difference
    for (i in 1:6) 
        df[i,"diff"] <- tzDiff("UTC", df[i,"timezone"], df[i,"pt"])
    

    这让我们得到了这个data.frame:

    R> df
                 datetime            timezone                  pt diff
    1 2016-03-03 12:30:00 Australia/Melbourne 2016-03-03 12:30:00   11
    2 2016-03-03 12:00:00       Europe/Berlin 2016-03-03 12:00:00    1
    3 2016-02-27 09:00:00    Europe/Amsterdam 2016-02-27 09:00:00    1
    4 2016-03-03 17:30:00  Australia/Brisbane 2016-03-03 17:30:00   10
    5 2016-03-03 10:30:00    Europe/Amsterdam 2016-03-03 10:30:00    1
    6 2016-03-03 14:30:00       Europe/Berlin 2016-03-03 14:30:00    1
    R> 
    

    返回解析的日期时间偏移量也很简单, 但是小助手函数tzDiff 目前没有这样做。如果您想走这条路,我可以将其添加为第二个辅助功能...

    编辑: 这是一个有趣的问题。我现在已经向 RcppCCTZ 添加了一些代码来做到这一点,但它还没有(至少)矢量化。也就是说,恕我直言,我们可以使用 data.table 获得更简单更快的解决方案。

    让我们首先对您的解决方案及其所需的三个包进行编码:

    library(lubridate)
    library(magrittr)
    library(dplyr)
    useLubridate <- function(df) {
        df %>%
            group_by(timezone) %>%
            mutate(datetime_local = ymd_hms(datetime, tz=unique(timezone))) %>%
            mutate(datetime_utc = with_tz(datetime_local, tzone = 'UTC')) %>% 
            ungroup %>%
            select(datetime_local) -> df
        df
    }
    

    让我们对 data.table 做同样的事情:

    library(data.table)
    useDataTable <- function(df) {
        dt <- as.data.table(df)
        dt[, pt := as.POSIXct(datetime, tz=timezone[1]), by=timezone] 
        dt[]
    }
    

    请注意,这将返回三列,而不仅仅是一列。

    趁着我们的时间,让我们来一场赛马吧:

    R> library(microbenchmark)
    R> microbenchmark( useDataTable(df), useLubridate(df) )
    Unit: milliseconds
                 expr     min      lq    mean  median      uq      max neval cld
     useDataTable(df) 1.23148 1.53900 1.61174 1.57635 1.64734  3.85423   100  a 
     useLubridate(df) 7.51158 8.88734 9.10439 9.19390 9.38032 15.27572   100   b
    R> 
    

    因此 data.table 更快,同时还返回更多有用的信息。将第三列整理回 data.frame(或类似的)会花费更多时间。

    【讨论】:

    • 这是否考虑到了夏令时,还是 UTC 偏移量在一年中是固定的?
    • 确实如此,如果您查看函数签名,则在计算差异时将“从”和“到”时区作为日期(时间)对象。即对我来说,芝加哥正确地确定了一年中柏林的“你的”夏令时与我的不同的三周。事实上,example(tzDiff) 显示正是(尽管在纽约和伦敦之间)。
    • 终于有机会试试这个了。在具有约 500k 行的数据帧上,它仍然非常慢。不像我原来的那么慢,但仍然比一月份的糖蜜慢。
    • 感谢您不厌其烦地提出另一个解决方案!我怀疑使用lubridate 会导致我的解决方案中的一些开销。可能没有,但我的速度已经足够快了,我不太关心额外的速度增益。
    【解决方案2】:

    这个dplyr + lubridate 解决方案似乎有效并且速度非常快:

    df %>%
        group_by(timezone) %>%
        mutate(datetime_local = ymd_hms(datetime, tz=unique(timezone))) %>%
        mutate(datetime_utc = with_tz(datetime_local, tzone = 'UTC')) %>% 
        ungroup %>%
        select(datetime_local) -> df
    

    请注意,生成的 df 中的 datetime_local 位于 AEST 中,并且可能与您期望的完全不同。我怀疑这是由于 R 中的限制,POSIXct 向量中的所有元素必须具有相同的时区。所以datetime_localungroup 上被强制转换为AEST

    【讨论】:

    • 查看我刚刚添加到答案中的编辑。仅使用 data.table 似乎使其速度提高了大约五倍。
    猜你喜欢
    • 2021-03-20
    • 2018-11-12
    • 2015-09-26
    • 1970-01-01
    • 2011-03-25
    • 2013-07-14
    • 1970-01-01
    • 2020-04-28
    • 2023-04-09
    相关资源
    最近更新 更多