Регулярное выражение для преобразования уравнений времени в дату и время R (POSIXct)

Я читаю данные с другой платформы, где комбинация строк, перечисленных ниже, используется для выражения временных меток:

\* = current time 
t = current day (00:00)
mo = month 
d = days 
h = hours
m = minutes 

Например, *-3d — это текущее время минус 3 дня, t-3h — это три часа до сегодняшнего утра (вчера полночь).

Я хотел бы иметь возможность вводить эти уравнения в R и получать соответствующее значение POSIXct. Я пытаюсь использовать регулярное выражение в приведенной ниже функции, но теряю числовой множитель для каждой строки:

strTimeConverter <- function(z){
  ret <- stringi::stri_replace_all_regex(
    str = z, 
    pattern = c('^\\*', 
                '^t', 
                '([[:digit:]]{1,})mo', 
                '([[:digit:]]{1,})d', 
                '([[:digit:]]{1,})h',
                '([[:digit:]]{1,})m'),
    replacement = c('Sys.time()', 
                    'Sys.Date()', 
                    '*lubridate::months(1)', 
                    '*lubridate::days(1)', 
                    '*lubridate::hours(1)', 
                    '*lubridate::minutes(1)'),
    vectorize_all = F
  )
  return(ret)
  # return(eval(expr = parse(text = ret)))
}

> strTimeConverter('*-5mo+3d+4h+2m')
[1] "Sys.time()-*lubridate::months(1)+*lubridate::days(1)+*lubridate::hours(1)+*lubridate::minutes(1)"

> strTimeConverter('t-5mo+3d+4h+2m')
[1] "Sys.Date()-*lubridate::months(1)+*lubridate::days(1)+*lubridate::hours(1)+*lubridate::minutes(1)"

Ожидаемый результат:

# *-5mo+3d+4h+2m
"Sys.time()-5*lubridate::months(1)+3*lubridate::days(1)+4*lubridate::hours(1)+4*lubridate::minutes(1)"

# t-5mo+3d+4h+2m
"Sys.Date()-5*lubridate::months(1)+3*lubridate::days(1)+4*lubridate::hours(1)+4*lubridate::minutes(1)"

Я предполагал, что заключение [[:digit]]{1,} в круглые скобки () сохранит их, но очевидно, что это не работает. Я определил шаблон следующим образом, иначе код заменяет повторяющиеся вхождения, например. * преобразуется в Sys.time(), но затем m в Sys.time() заменяется на *lubridate::minutes(1).

Я планирую преобразовать (ожидаемый) вывод в дату-время R, используя eval(parse(text = ...)) - в настоящее время закомментировано в функции.

Я открыт для использования других пакетов или подходов.

Обновить

Немного поработав, я обнаружил, что приведенная ниже версия работает — я заменяю строки в таком порядке, чтобы вновь замененные символы не заменялись снова:

strTimeConverter <- function(z){
  ret <- stringi::stri_replace_all_regex(
    str = z, 
    pattern = c('y', 'd', 'h', 'mo', 'm', '^t', '^\\*'),
    replacement = c('*years(1)',
                    '*days(1)', 
                    '*hours(1)', 
                    '*days(30)',
                    '*minutes(1)',
                    'Sys.Date()', 
                    'Sys.time()'),
    vectorize_all = F
  )
  ret <- gsub(pattern = '\\*', replacement = '*lubridate::', x = ret)
  rdate <- (eval(expr = parse(text = ret)))
  attr(rdate, 'tzone') <- 'UTC'
  return(rdate)
}
sample_string <- '*-5mo+3d+4h+2m'
strTimeConverter(sample_string)

Это работает, но не очень элегантно и, скорее всего, потерпит неудачу, поскольку я вынужден включать другие выражения (например, yd для дня года, например, 124).


person Gautam    schedule 18.12.2020    source источник


Ответы (2)


Вы можете использовать обратные ссылки в заменах следующим образом:

library(stringr)
x <- c("*-5mo+3d+4h+2m", "t-5mo+3d+4h+2m")
repl <- c('^\\*' = 'Sys.time()', '^t' = 'Sys.Date()', '(\\d+)mo' = '\\1*lubridate::months(1)', '(\\d+)d' = '\\1*lubridate::days(1)',  '(\\d+)h' =  '\\1*lubridate::hours(1)', '(\\d+)m' = '\\1*lubridate::minutes(1)')
stringr::str_replace_all(x, repl)
## => [1] "Sys.time()-5*lubridate::months(1)+3*lubridate::days(1)+4*lubridate::hours(1)+2*lubridate::minutes(1)"
##    [2] "Sys.Date()-5*lubridate::months(1)+3*lubridate::days(1)+4*lubridate::hours(1)+2*lubridate::minutes(1)"

См. демонстрацию R в Интернете.

См., например, '(\\d+)mo' = '\\1*lubridate::months(1)'. Здесь (\d+)mo соответствует и захватывает в группу 1 одну или несколько цифр, а mo просто соответствует. Затем, когда совпадение найдено, \1 в \1*lubridate::months(1) вставляет содержимое группы 1 в результирующую строку.

Обратите внимание, что замена может быть более безопасной, если вы закроете совпадение периода времени границей слова (\b) справа:

repl <- c('^\\*' = 'Sys.time()', '^t' = 'Sys.Date()', '(\\d+)mo\\b' = '\\1*lubridate::months(1)', '(\\d+)d\\b' = '\\1*lubridate::days(1)',  '(\\d+)h\\b' =  '\\1*lubridate::hours(1)', '(\\d+)m\\b' = '\\1*lubridate::minutes(1)')

Это не сработает, если интервалы времени склеены друг с другом без каких-либо разделителей, отличных от слов, но у вас есть + в ваших примерах строк, так что здесь это безопасно.

На самом деле, вы можете заставить его работать и с той функцией, которую вы использовали. Просто убедитесь, что обратные ссылки имеют синтаксис $n:

x <- c("*-5mo+3d+4h+2m", "t-5mo+3d+4h+2m")
pattern = c('^\\*', '^t', '(\\d+)mo', '(\\d+)d', '(\\d+)h', '(\\d+)m')
replacement = c('Sys.time()', 'Sys.Date()', '$1*lubridate::months(1)', '$1*lubridate::days(1)', '$1*lubridate::hours(1)', '$1*lubridate::minutes(1)')
stringi::stri_replace_all_regex(x, pattern, replacement, vectorize_all=FALSE)

Выход:

[1] "Sys.time()-5*lubridate::months(1)+3*lubridate::days(1)+4*lubridate::hours(1)+2*lubridate::minutes(1)"
[2] "Sys.Date()-5*lubridate::months(1)+3*lubridate::days(1)+4*lubridate::hours(1)+2*lubridate::minutes(1)"
person Wiktor Stribiżew    schedule 18.12.2020
comment
Спасибо! Я попробую - мне было интересно, есть ли у stringr лучший метод, чем stringi для моего варианта использования - похоже, он есть! - person Gautam; 19.12.2020
comment
@Gautam Да, я на самом деле взломал его: вы можете использовать stringi::stri_replace_all_regex, но синтаксис обратной ссылки - $n, а не \n. - person Wiktor Stribiżew; 19.12.2020

Другой вариант прямого производства времени будет следующим:

strTimeConvert <- function(base=Sys.time(), delta="-5mo+3d+4h+2m"){
  mo <- gsub(".*([+-]\\d+)mo.*", "\\1", x)
  ds <- gsub(".*([+-]\\d+)d.*", "\\1", x)
  hs <- gsub(".*([+-]\\d+)h.*", "\\1", x)
  ms <- gsub(".*([+-]\\d+)m.*", "\\1", x)
  out <- base + months(as.numeric(mo)) + days(as.numeric(ds)) + 
          hours(as.numeric(hs)) + minutes(as.numeric(ms))
  out
}
strTimeConvert()
# [1] "2020-07-21 20:32:19 EDT"
person DaveArmstrong    schedule 18.12.2020