如何将带有字符串的混合时间格式转换为军事时间?

How to convert mixed time formats with strings to military time?

我有一个变量需要转换成军用时间。这个变量很乱,因为它在格式上缺乏一致性。

下面是变量中可能存在的内容的示例。

x <- c("0.9305555555555547", "15:20 Found", "10:00:00 AM Found", "0.125", "Found 1525")

到目前为止,我已经取得了一些成功,使所有内容的格式与 RegEx 更加一致:

x <- str_extract(x, "[0-9]+.+[0-9]|[0-9][0-9][:][0-9][0-9]")
x <- str_remove(x, "[:]") 
x <- str_remove(x, "[:][0-9][0-9]$")

如您所见,我得到:“0.9305555555555547”、“1520”、“1000”、“0.125”、“1525”

问题是小数点需要乘以 2400 才能回到军事时间,但我也不想乘以整数(因为那些已经在军事时间)。

x 本质上是数据帧中的一个变量。

我正在考虑使用 if/else 逻辑,但我不知道如何实现它。

为了澄清,我想要:

输入:“0.9305555555555547”、“15:20 找到”、“10:00:00 AM 找到”、“0.125”、“找到 15:25”

输出:“2233”、“1520”、“1000”、“0300”、“1525”

在使用正则表达式完成 pre-processing 之后,您可以在此处使用 str_detect()

实现 if/else 逻辑
x <- ifelse(str_detect(x, "\."),
            as.integer(as.numeric(x) * 2400),
            as.integer(x)) %>% 
  sprintf("%04d", .)

这将return你想要的输出字符

然后你可以像这样将它解析为 POSIXct

x <- as.POSIXct(x,
         format = "%H%M",
         origin = "1970-01-01",
         tz = "UTC")

我从字面上按照你的逻辑得到了完全相同的结果。

  1. 数值转换
  2. 乘以 2400 并返回字符
  3. for 循环检测字符中的点并在之后删除
  4. for 循环将 0 放在少于 4 个字符的数字前面
x <- c("0.9305555555555547", "15:20 Found", "10:00:00 AM Found", "0.125", "Found 1525")

x <- str_extract(x, "[0-9]+.+[0-9]|[0-9][0-9][:][0-9][0-9]")
x <- str_remove(x, "[:]") 
x <- str_remove(x, "[:][0-9][0-9]$")

x <- as.numeric(x)

x <- as.character(ifelse(x<1,x*2400,x))

for(i in 1:length(x)){
  
  ii <- stri_locate_first_regex(x[i],"\.")[1]
  
  if(!is.na(ii)){
    
    x[i] <- str_sub(x[i],1,ii-1)
    
  }
  
}

for(i in 1:length(x)){
  
  while (nchar(x[i])<4) {
    
    x[i] <- paste0("0",x[i])
    
  }
  
}

x
[1] "2233" "1520" "1000" "0300" "1525"
>

编辑: 我意识到了这一点:

the decimals need to be multiplied by 2400 to get back to military time

不太对。 “2400”不是十进制数(技术上它是六十进制,以 60 为底),因此十进制乘法不会给出正确的结果。我已经相应地更改了我的代码。


我不会在所有内容上都使用相同的正则表达式,而是首先确定 x 的每个元素的格式,然后 相应地处理该元素。

我喜欢 hms 库用于处理时间并在下面使用它,但您也可以使用基础 POSIXctPOSIXlt class.

library(tidyverse)
library(hms)

x <- c("0.9305555555555547", "15:20 Found", "10:00:00 AM Found", "0.125", "Found 1525")

# ----
# define functions for parsing each possible format in `x`
parse_decimal <- function(x) {
  hrs <- as.numeric(str_extract(x, "^0\.\d+")) * 24
  min <- (hrs %% 1) * 60
  hrs <- floor(hrs)
  hms(hours = hrs, minutes = min)
}
  
parse_timestring <- function(x) {
  out <- str_remove_all(x, "\D")
  hr_digits <- if_else(str_length(out) == 3, 1, 2)
  hrs <- as.numeric(str_sub(out, end = hr_digits))
  hrs <- if_else(str_detect(str_to_upper(x), "P\.?M"), hrs + 12, hrs)
  min <- as.numeric(str_sub(out, start = hr_digits + 1))
  hms(hours = hrs, minutes = min)
}

# ----
# test each element of x, and pass to appropriate parsing Fx
time <- case_when(
  str_detect(x, "^[01]$|^0\.\d") ~ parse_decimal(x),
  str_detect(x, "\d{1,2}:?\d{2}") ~ parse_timestring(x),
  TRUE ~ NA_real_
)

time
# 22:20:00.000000
# 15:20:00.000000
# 10:00:00.000000
# 03:00:00.000000
# 15:25:00.000000

我们应该扩展 AM/PM 指标的正则表达式,这样力量就不会错过彼此。接下来,在子集中我们处理十进制时间、英制时间、24 小时制时间和 return 结果。

milt <- function(x) {
  u <- trimws(gsub('\D*(\d*\W?[AP]?M?)\D*', '\1', x))
  u[grep('\.', u)] <- sprintf('%04d', round(as.double(u[grep('\.', u)])*2400))
  u[grep('[AP]M', u)] <- strftime(strptime(u[grep('[AP]M', u)], '%I:%M:%S %p'), '%H%M')
  u[grep(':', u)] <- gsub(':', '', u[grep(':', u)] )
  return(u)
}

milt(x)
# [1] "2233" "1520" "1000" "2200" "0300" "1525" "0000" "1020"

数据:

x <- c("0.9305555555555547", "15:20 Found", "10:00:00 AM Found",
       "10:00:00 PM Found", "0.125", "Found 1525", "0000", "10:20")