是否可以使用像“tz=NULL”这样的东西?...“as.POSIXct”默认为依赖于语言环境的时区(与“as.Date”不同),这会导致问题

ros*_*ova 4 timezone datetime r

我知道这是一个长期存在的、根深蒂固的问题,但这是我经常遇到的问题,而且我看到初学者经常遇到这个问题R,我希望有一个令人满意的解决方案。到目前为止,我的谷歌和 SO 搜索都是空的,但如果在其他地方重复,请指出正确的方向。

TL;DR:有没有办法使用类似POSIXct没有时区的类?我通常使用tz="UTC"无论数据集的实际时区如何,但在我看来这是一个混乱的黑客,我不是特别喜欢它。我想要的是类似 的东西tz=NULL,它的行为方式与 UTC 相同,但实际上没有添加“UTC”作为属性tzone


问题

我将从一个典型时区问题的示例(有很多)开始。创建具有值的对象POSIXct

df <- data.frame( timestamp = as.POSIXct( c( "2018-01-01 03:00:00",
                                             "2018-01-01 12:00:00" ) ),
                  a = 1:2 )
df

#             timestamp a
# 1 2018-01-01 03:00:00 1
# 2 2018-01-01 12:00:00 2
Run Code Online (Sandbox Code Playgroud)

一切都很好,但随后我尝试将时间戳转换为日期:

df$date <- as.Date( df$timestamp )
df

#             timestamp a       date
# 1 2018-01-01 03:00:00 1 2017-12-31
# 2 2018-01-01 12:00:00 2 2018-01-01
Run Code Online (Sandbox Code Playgroud)

日期转换不正确,因为我的计算机区域设置为澳大利亚东部时间,这意味着时间戳的数值已移动了与我的区域设置相关的偏移量(在本例中为 -11 小时)。我们可以通过将时区强制为 UTC,然后比较之前和之后的值来看到这一点:

df$timestamp[1]
# [1] "2018-01-01 03:00:00 AEDT"

x <- lubridate::force_tz( df$timestamp[1], "UTC" ); x
# [1] "2018-01-01 03:00:00 UTC"

difftime( df$timestamp[1], x )
# Time difference of -11 hours
Run Code Online (Sandbox Code Playgroud)

这只是时区引起的问题的一个例子。还有其他的,但我不会在这里讨论。


我的黑客解决方案

我不想要这种行为,所以我需要说服as.POSIXct不要弄乱我的时间戳。我通常通过使用 来完成此操作tz="UTC",效果很好,只是我向不真实的数据添加了信息。这些时间不是 UTC,我只是说为了避免时移问题。这是一种黑客行为,每当我将数据提供给其他人时,他们可能会认为时间戳是 UTC,但实际上并非如此,这是可以原谅的。为了避免这种情况,我通常将实际时区添加到对象/列名称中,并希望我传递数据的任何人都能理解为什么有人会使用与对象本身不同的时区来标记对象:

df <- data.frame( timestamp.AET = as.POSIXct( c( "2018-01-01 03:00:00",
                                                 "2018-01-01 12:00:00" ),
                                              tz = "UTC" ),
                  a = 1:2 )
df$date <- as.Date( df$timestamp )
df

#         timestamp.AET a       date
# 1 2018-01-01 03:00:00 1 2018-01-01
# 2 2018-01-01 12:00:00 2 2018-01-01
Run Code Online (Sandbox Code Playgroud)

我所希望的是什么

POSIXct我真正想要的是一种无需指定时区的使用方式。我不想让时间以任何方式混乱。执行所有操作,就好像这些值采用 UTC 格式一样,并将任何时区详细信息(例如偏移量、夏令时等)留给用户。只是不要假装它们实际上是 UTC。这是我的理想:

x <- as.POSIXct( "2018-01-01 03:00:00" ); x
# [1] "2018-01-01 03:00:00"

attr( x, "tzone" )
# [1] NULL

shifted <- lubridate::force_tz( x, "UTC" )
shifted == x
# [1] TRUE

as.numeric( shifted ) == as.numeric( x )
# [1] TRUE

as.Date( x )
# [1] "2018-01-01"
Run Code Online (Sandbox Code Playgroud)

所以对象上根本没有时区属性。日期转换按照打印​​值的预期进行。如果存在夏令时时移或任何其他特定于区域设置的问题,用户(我或其他人)需要自己处理。

我相信类似的事情在 中是可能的POSIXlt,但我真的不想转向那个。chron或另一个面向时间序列的包可能是另一种解决方案,但我认为POSIXct更广泛地使用和接受,这似乎应该在base::. 一个POSIXct对象tz="UTC"正是我所需要的,我只是不想为了让它按照我想要的方式运行而对时区撒谎(我相信大多数初学者都期望R)。

那么其他人在这里做什么呢?有没有一种简单的方法可以使用POSIXct而无需我错过的时区?还有比 更好的解决方法吗tz="UTC"?其他人也是这么做的吗?

Mau*_*ers 5

我不确定我是否理解你的问题。 在(重新)阅读您的帖子和随后的评论后,我明白您的观点。

总结一下:

as.POSIXct由您的系统决定tzas.Date默认tz = "UTC"为 class POSIXct。因此,除非您在tz = "UTC",否则日期可能会发生变化;解决方案是使用tzwithDate来更改 的行为as.Date.POSIXct(请参阅下面的更新)。

情况1

如果您没有明确指定tzwith as.POSIXct,则可以简单地指定tz = ""withas.Date来强制执行系统特定的时区。

df <- data.frame(
    timestamp = as.POSIXct(c("2018-01-01 03:00:00", "2018-01-01 12:00:00")),
    a = 1:2)

df$date <- as.Date(df$timestamp, tz = "")
df;
#           timestamp a       date
#1 2018-01-01 03:00:00 1 2018-01-01
#2 2018-01-01 12:00:00 2 2018-01-01
Run Code Online (Sandbox Code Playgroud)

案例2

如果您确实设置了显式tzas.POSIXct可以tzPOSIXct对象中提取,并将其传递给as.Date

df <- data.frame(
    timestamp = as.POSIXct(c("2018-01-01 03:00:00", "2018-01-01 12:00:00"), tz = "UTC"),
    a = 1:2)

tz <- attr(df$timestamp, "tzone")
tz
#[1] "UTC"

df$date <- as.Date(df$timestamp, tz = tz)
df
#    timestamp a       date
#1 2018-01-01 03:00:00 1 2018-01-01
#2 2018-01-01 12:00:00 2 2018-01-01
Run Code Online (Sandbox Code Playgroud)

更新

Dirk Eddelbuettel 的 GitHub 项目网站上存在相关讨论anytime。讨论结果有些循环,所以恐怕它在理解为什么不继承 as.Date.POSIXct自方面没有提供太多信息。我可能会将其称为 Base R 特质(或者如 Dirk 所说:“这些是 Base R 中已知的怪癖”)。tzPOSIXct

至于解决方案:我会更改 的行为as.Date.POSIXct而不是默认行为as.POSIXct

我们可以简单地重新定义以从对象as.Date.POSIXct继承。tzPOSIXct

as.Date.POSIXct <- function(x) {
    as.Date(as.POSIXlt(x, tz = attr(x, "tzone")))
}
Run Code Online (Sandbox Code Playgroud)

然后您将获得示例案例的一致结果:

df <- data.frame(
    timestamp = as.POSIXct(c("2018-01-01 03:00:00", "2018-01-01 12:00:00")),
    a = 1:2)
df$date <- as.Date(df$timestamp)
df
#timestamp a       date
#1 2018-01-01 03:00:00 1 2018-01-01
#2 2018-01-01 12:00:00 2 2018-01-01
Run Code Online (Sandbox Code Playgroud)