将具有本地时间的向量转换为UTC

4

我有一个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时间 - 在某种意义上,这是与这里这里所面临的相反问题 - 但我遇到了困难。第二个链接中的解决方案的变体可行:

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万行的数据框来说速度非常慢。有没有更加优雅和快速的方法来完成这个任务呢?

2个回答

10

我知道的最“官方”的方法需要格式化和重新解析;David Smith在REvolutions博客上曾经发过一篇文章,介绍了这个方法。

时间序列库,特别是那些具有时区意识的库,也可以实现。以下是一种使用RcppCCTZ的方法,它是我对CCTZ(由谷歌员工编写但不是官方谷歌库)的包装器——它计算两个时区之间的差异(默认以小时为单位)。

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"])

这将得到以下数据框:

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更快,同时返回更有用的信息。将第三列重新整理回数据框(或类似结构)会耗费更多时间。

所需翻译内容已经被翻译成中文。

这是否考虑了夏令时,还是UTC偏移量在全年内固定不变? - RoyalTS
2
如果您查看函数签名,它会接受“从”和“到”时区作为日期(时间)对象来计算差异。也就是说,对于我在芝加哥的这里,它可以正确确定一年中三个星期,“您”的柏林夏令时与我的不同。实际上,example(tzDiff) 恰好展示了这一点(尽管是在纽约和伦敦之间)。 - Dirk Eddelbuettel
终于有机会试一下了。在一个大约有500k行的数据框上仍然非常慢。虽然比我的原始版本快了一些,但仍然比一月份的糖浆慢。 - RoyalTS
感谢您费心提供另一种解决方案!我怀疑使用lubridate是导致我的解决方案有些额外开销的原因。可能可以不用它,但我的解决方案已经足够快了,我并不太在意额外的速度提升。 - RoyalTS

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

请注意,结果中的datetime_local是以AEST格式显示的,可能与您预期的完全不同。我怀疑这是由于R中的限制,即POSIXct向量中的所有元素必须具有相同的时区。因此,在ungroup时,datetime_local被强制转换为AEST

2
看一下我刚刚添加到我的答案中的编辑。只使用data.table似乎可以使它快五倍左右。 - Dirk Eddelbuettel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接