将字符串转换为 ISO 8601 持续时间格式

Converting a string to ISO 8601 duration format

我需要允许用户输入这样的字符串:

1m 3w 4d 22h 6m 3s

...然后将其转换为 ISO 8601 时长格式,如下所示:

P1M3W4DT22H6M3S

他们甚至应该被允许写更短的符号,比如:

10d 12h

我什至不知道从哪里开始,而且似乎找不到任何有助于这些转换的库。我可以使用 Moment 将 ISO 8601 持续时间格式转换为 .

建议?

我试着只删除空格,但当然我错过了时间之前的 T:

`P${duration.toUpperCase().replace(/\s/g, '')}`

如果您只缺少 D 标记后的 T 分隔符,只需添加 .replace('D', 'DT')}.

let duration = '1m 3w 4d 22h 6m 3s'
`P${duration.toUpperCase().replace(/\s/g, '').replace('D', 'DT')}`
// gives 'P1M3W4D22H6M3S'

但这只适用于静态输入格式。


要解析完整和不完整的输入,您需要按类型解析它们。

const parseDuration = duration => {
  const keys = ['Y', 'M', 'W', 'D', 'H', 'MIN', 'S']
  if (!duration || typeof duration !== 'string')
    throw "Empty input"
  let parsed = {}
  // split entries by space and process them
  duration.split(/\s+/).map(r => {
    // get length (number) and type for each entry
    let [_, len, type] = r.match(/^(\d+)(\w+)$/)
    type = type.toUpperCase()
    // assume M means minutes if either of W, D or H is set
    if (type === 'M' && (parsed['W'] || parsed['D'] || parsed['H']))
      type = 'MIN'
    // asign length by type
    if (len && keys.includes(type))
      parsed[type] = len
  })
  
  if (!Object.keys(parsed).length)
    throw new Error("Empty input")
  
  // set all undefined types to zero
  for (const k of keys)
    parsed[k] = parseInt(parsed[k]) || 0
  
  // format to ISO 8601 duration
  return `P${parsed.Y}Y${parsed.M}M${parsed.W}W${parsed.D}DT${parsed.H}H${parsed.MIN}M${parsed.S}S`
}

try {
  console.log(parseDuration('1m 3w 4d 22h 6m 3s'))
} catch {
  console.warn('failed to parse')
}
// P0Y1M3W4DT22H6M3S

try {
  console.log(parseDuration('3w 12h 10m'))
} catch {
  console.warn('failed to parse')
}
// P0Y0M3W0DT12H10M0S

try {
  console.log(parseDuration('1bullshit'))
} catch {
  console.warn('failed to parse')
}
// fails

try {
  console.log(parseDuration(''))
} catch {
  console.warn('failed to parse')
}
// fails

编辑示例以包括基本语法验证

去掉空格和字母大写后,可以使用正则表达式\d+H?\d*M?\d*S?$来捕获输入的时间部分,如果有的话。这决定了“T”定界符的插入点:

const toDuration = s => "P" + s.toUpperCase(s).replace(/\s/g, "")
                               .replace(/\d+H?\d*M?\d*S?$/, "T$&");

console.log(toDuration("1m 3w 4d 22h 6m 3s"));
console.log(toDuration("10d 12h"));
console.log(toDuration("10m"));
console.log(toDuration("1y 8w 3d"));
console.log(toDuration("2m 12h"));

做了一些假设:

  • 如果输入对“m”(月或分钟)的含义产生歧义,则将其解释为分钟。
  • 不需要更改输入中各部分的顺序即可导出有效的句点符号。
  • 输入遵循示例中给出的格式。这里没有输入验证。

如果你想包括验证,那么扩展如下:

const toDuration = s => /^(\d+y\s*)?(\d+m\s*)?(\d+w\s*)?(\d+d\s*)?(\d+h\s*)?(\d+m\s*)?(\d+s\s*)?$/i.test(s)
                ? "P" + s.toUpperCase(s).replace(/\s/g, "")
                         .replace(/\d+H?\d*M?\d*S?$/, "T$&")
                : ""; // indicates bad format

console.log(toDuration("1m 3w 4d 22h 6m 3s"));
console.log(toDuration("10d 12h"));
console.log(toDuration("10m"));
console.log(toDuration("1y 8w 3d"));
console.log(toDuration("2m 12h"));
console.log(toDuration("bad12 format"));