将时间方程式转换为 R 日期时间的正则表达式 (POSIXct)

Regex to convert time equations to R date-time (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() 但随后 Sys.time() 中的 m 被替换为 *lubridate::minutes(1).

我计划使用 eval(parse(text = ...)) 将(预期的)输出转换为 R 日期时间 - 目前在函数中被注释掉了。

我愿意使用其他包或方法。

更新

稍加修改后,我发现以下版本有效 - 我按顺序替换字符串,这样新替换的字符就不会再次被替换:

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)。

您可以像这样在替换中使用反向引用:

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 demo online

参见,例如,'(\d+)mo' = '\1*lubridate::months(1)'。这里,(\d+)mo 匹配和 捕获到组 1 一个或多个数字,而 mo 只是匹配。然后,当找到匹配项时,</code> in <code>*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()', '*lubridate::months(1)', '*lubridate::days(1)', '*lubridate::hours(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)"

另一个直接生成时间的选项如下:

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"