是否可以使用类似tz = NULL的东西?as.POSIXct默认为与语言环境相关的时区(与as.Date不同),这会导致问题

时间:2018-07-05 23:20:40

标签: r datetime timezone

我知道这是一个长期存在且根深蒂固的问题,但这是我经常遇到的问题,我发现R的初学者经常遇到这样的困扰,以至于我很喜欢有一个令人满意的解决方案。到目前为止,我的Google和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

那只是时区造成的问题的一个示例。还有其他人,但我在这里不再赘述。


我的hack-y解决方案

我不希望这种行为,所以我需要说服as.POSIXct不要弄乱我的时间戳。我通常使用tz="UTC"来执行此操作,除了我向不真实的数据中添加信息外,它可以正常工作。这些时间不在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"

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

我相信something similar to this is possible in POSIXlt,但我真的不想转向这一点。 chron或其他面向时间序列的程序包可能是另一种解决方案,但是我认为POSIXct的使用和接受程度更高,这似乎应该在base::中实现。 POSIXct的{​​{1}}对象正是我所需要的,我只是不想为了使它以我想要的方式工作而不得不撒谎时区(而且我相信大多数{ {1}}期望)。

那么其他人在这里做什么?是否有一种简便的方法来使用tz="UTC"而又没有我错过的时区?是否有比R更好的解决方法?那是别人在做什么吗?

2 个答案:

答案 0 :(得分:3)

我不确定我是否理解您的问题。 已经(重新)阅读了您的帖子并发表了评论,我明白您的意思了。

总结:

x-fossil-markdown从您的系统中确定as.POSIXcttz的类as.Date具有默认的tz = "UTC"。因此,除非您在POSIXct中,否则日期可能会改变;解决方案是将tz = "UTC"tz配合使用,或更改Date的行为(请参见下面的更新)。

案例1

如果您as.Date.POSIXct指定显式tz,则只需用as.POSIXct指定tz = ""来实施系统-特定时区。

as.Date

案例2

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

as.Date

更新

在Dirk Eddelbuettel的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 GitHub项目站点上有一个related discussion。讨论的结果有些循环,因此恐怕就理解为什么 anytime 继承as.Date.POSIXct而言,它提供的内容不多来自tz。我可能会称其为基本R特质(或如Dirk所称:“ [T] hese是基本R中的古怪”“ )。

作为一种解决方案:我将更改POSIXct的行为,而不是as.Date.POSIXct的默认行为。

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

POSIXct

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

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

答案 1 :(得分:2)

您基本上希望as.POSIXct的默认值与提供的默认值不同。除了as.POSIXct.default之外,您实际上并不想修改任何东西,该函数最终将处理字符值。修改as.POSIXct.numeric并没有多大意义,因为这始终是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信息作为偏移量出现时,就可以正确处理。