R语言 是否可以使用类似 'tz=NULL' 的东西.. 'as.POSIXct"默认为与区域设置相关的时区(



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

TL;DR:有没有办法在没有时区的情况下使用像POSIXct类这样的东西?我通常使用tz="UTC"而不考虑数据集的实际时区,但这是一个混乱的黑客IMO,我不是特别喜欢它。我想要的是类似于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

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

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

日期转换不正确,因为我的计算机区域设置采用澳大利亚东部时间,这意味着时间戳的数值已移动与我的区域设置相关的偏移量(在本例中为 -11hrs)。我们可以通过强制时区为 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

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


我的黑客解决方案

我不想要这种行为,所以我需要说服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

我所希望的

我真正想要的是一种无需指定时区即可使用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"

因此,对象上根本没有时区属性。日期转换的工作方式与打印值的预期相同。如果存在夏令时时移或任何其他特定于区域设置的问题,则用户(我或其他人)需要自己处理。

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

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

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

总结一下:

as.POSIXct确定系统中的tzas.Date具有类POSIXct的默认tz = "UTC"。因此,除非您在tz = "UTC",否则日期可能会更改;解决方案是将tzDate一起使用,或更改as.Date.POSIXct的行为(请参阅下面的更新)。

案例1

如果未指定显式tzwithas.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

案例2

如果使用as.POSIXct设置显式tz,则可以从POSIXct对象中提取tz,并将其传递给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

更新

在 Dirk Eddelbuettel 的anytimeGitHub 项目网站上存在相关的讨论。讨论结果有些循环,所以恐怕在理解为什么as.Date.POSIXct继承POSIXcttz方面并没有提供太多.我可能会称之为碱基R特质(或者像Dirk所说的那样:"这些是碱基R中已知的怪癖">)。

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

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

as.Date.POSIXct <- function(x) {
as.Date(as.POSIXlt(x, tz = attr(x, "tzone")))
}

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

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

您基本上希望as.POSIXct的默认值与提供的默认值不同。除了as.POSIXct.default之外,您真的不想修改任何内容,这是最终将处理字符值的函数。修改为没有多大意义。POSIXct.numer,因为这将始终是UCT的偏移量。tz参数仅确定format.POSIXct将显示的内容。因此,您可以修改已获得的正式列表。把它放在你的.Rprofile

formals(as.POSIXct.default) <- alist(x=, ...=, tz="UTC")

然后它通过了您的测试:

> x <- as.POSIXct( "2018-01-01 03:00:00" ); x
[1] "2018-01-01 03:00:00 UTC"
> attr( x, "tzone" )
[1] "UTC"
> 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"

另一种选择是定义一个全新的类,但这需要更广泛的努力。

关于时区的规范,还有一点要说明。随着"夏令时"的盛行,在(尽可能输入)和输出期间使用%z格式可能会更加明确:

dtm <- format( Sys.time(), format="%Y-%m-%d %H:%M:%S %z")
#output
format( Sys.time(), format="%Y-%m-%d %H:%M:%S %z")
[1] "2018-07-06 17:18:27 -0700"
#input and output without the formals change
as.POSIXct(dtm, format="%Y-%m-%d %H:%M:%S %z")
[1] "2018-07-06 17:21:41 PDT"
# after the formals change
as.POSIXct(dtm, format="%Y-%m-%d %H:%M:%S %z")
[1] "2018-07-07 00:21:41 UTC"

因此,当tz信息作为偏移量存在时,可以正确处理。

最新更新