Можно ли использовать что-то вроде `tz = NULL`? ...` as.POSIXct` по умолчанию зависит от часового пояса, зависящего от локали (в отличие от `as.Date`), что вызывает проблемы - PullRequest
0 голосов
/ 06 июля 2018

Я знаю, что это давняя, глубоко укоренившаяся проблема, но с этим я сталкиваюсь так регулярно, и я вижу, как начинающие 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

Даты были преобразованы неправильно, потому что языковой стандарт моего компьютера указан по восточному времени Австралии, а это означает, что числовые значения временных меток были смещены на смещение, относящееся к моему языковому стандарту (в данном случае -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

Это только один пример проблем, вызванных часовыми поясами. Есть и другие, но я не буду вдаваться в них здесь.


Мое хакерское решение

Я не хочу такого поведения, поэтому мне нужно убедить 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::. POSIXct объект с tz="UTC" - это именно то, что мне нужно, я просто не хочу лгать о часовых поясах, чтобы заставить его вести себя так, как я хочу (и я полагаю, что большинство новичков ожидают R) .

Так что же здесь делают другие? Есть ли простой способ использовать POSIXct без часового пояса, который я пропустил? Есть ли лучший обходной путь, чем tz="UTC"? Это то, что делают другие?

Ответы [ 2 ]

0 голосов
/ 06 июля 2018

Вы в основном хотите другое значение по умолчанию для as.POSIXct, чем предусмотрено. Вы действительно не хотите ничего изменять, кроме as.POSIXct.default, который является функцией, которая в конечном итоге будет обрабатывать значения символов. Не имеет смысла изменять как .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 информация представлена ​​в виде смещения, она может обрабатываться правильно.

0 голосов
/ 06 июля 2018

Я не уверен, что понимаю вашу проблему. После (повторного) прочтения вашего поста и последующих комментариев я понимаю вашу точку зрения.

Подведем итог:

as.POSIXct определяет tz из вашей системы. as.Date имеет значение по умолчанию tz = "UTC" для класса POSIXct. Так что если вы не в tz = "UTC", даты могут измениться; решение состоит в том, чтобы использовать tz с Date, или изменить поведение as.Date.POSIXct (см. обновление ниже).

Дело 1

Если вы не указываете явное tz с помощью as.POSIXct, вы можете просто указать tz = "" с помощью as.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

Если вы делаете , устанавливаете явное tz с помощью as.POSIXct, вы можете извлечь tz из объекта POSIXct и передать его в 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 anytime GitHub существует обсуждение . Обсуждение получается несколько круглым, поэтому я боюсь, что оно не предлагает слишком много с точки зрения понимания , почему as.Date.POSIXct делает не наследующим tz от POSIXct. Я бы, вероятно, назвал это базовой R-идиосинкразией (или, как Дирк называет это: "[T], эти известные причуды в Base R" ).

Что касается решения: я бы изменил поведение as.Date.POSIXct, а не поведение по умолчанию as.POSIXct.

Мы могли бы просто переопределить as.Date.POSIXct для наследования tz от объекта POSIXct.

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...