时间格式化
时间在界面上有三种截然不同的形态,把它们混为一谈是常见的困惑来源。ranuts 为每一种提供独立的函数:
| 读者真正想知道的 | 函数 | 输出示例 |
|---|---|---|
| 这件事具体发生在什么时候? | formatDate |
2026-07-25 14:05:09 |
| 这段时间有多长? | formatDuration |
01:01:01 |
| 距离现在多久之前? | formatRelative |
3 天前、5m |
formatDuration
把经过的秒数格式化成冒号分隔的时钟时长,也就是播放器进度条上常见的那种形态。不足一小时用 mm:ss,超过则展开为 hh:mm:ss。
参数
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
seconds |
经过的秒数;负数会被夹到 0 | number |
必填 |
返回值
string:时长字符串;输入不是有限数时返回 ''。
import { formatDuration } from 'ranuts/utils';
formatDuration(0); // '00:00'
formatDuration(65); // '01:05'
formatDuration(3661); // '01:01:01'
formatDuration(NaN); // ''NaN 返回空串是刻意的:播放器在元数据加载完成前读 video.duration 拿到的就是 NaN,此时显示空白比 NaN:NaN 得体。
formatRelative
描述某个时间点相对于另一个时间点的位置,比如「3 天前」「2 小时后」。
本地化交给平台的
Intl.RelativeTimeFormat,它自 2020 年起在所有主流浏览器可用,且已经掌握各语言的复数与词形规则。formatRelative 只补上 Intl 有意留白的那部分:决定用哪个单位来表达这段间隔。
和 Intl 一样,它只报告单一单位:3 天 6 小时的间隔算作「3 天前」,不会说成「3 天 6 小时前」。
参数
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
value |
要描述的时间点 | number | string | Date |
必填 |
options |
见下表 | FormatRelativeOptions |
{} |
| 选项 | 说明 | 类型 | 默认值 |
|---|---|---|---|
now |
参照的时间点 | number | string | Date |
当前时间 |
locale |
BCP 47 语言标签;compact 风格会忽略它 |
string | string[] |
运行时语言 |
style |
'long' | 'short' | 'narrow' | 'compact' |
RelativeStyle |
'long' |
numeric |
'auto' 会换用「昨天」这类习惯说法,'always' 保留数字 |
'always' | 'auto' |
'auto' |
返回值
string:描述文本;两端任一无法解析时返回 ''。
import { formatRelative } from 'ranuts/utils';
const twoHoursAgo = Date.now() - 2 * 3600_000;
formatRelative(twoHoursAgo, { locale: 'zh-CN' }); // '2 小时前'
formatRelative(twoHoursAgo, { locale: 'en-US' }); // '2 hours ago'
formatRelative(twoHoursAgo, { locale: 'en-US', style: 'short' }); // '2 hr. ago'
formatRelative(Date.now() + 60_000, { locale: 'zh-CN' }); // '1 分钟后'
formatRelative(Date.now() - 86_400_000, { locale: 'zh-CN' }); // '昨天'
formatRelative(Date.now() - 86_400_000, { locale: 'zh-CN', numeric: 'always' }); // '1 天前'compact 风格
compact 是列表条目旁边那种紧凑角标:
formatRelative(Date.now() - 30_000, { style: 'compact' }); // '30s'
formatRelative(Date.now() - 5 * 60_000, { style: 'compact' }); // '5m'
formatRelative(Date.now() - 3 * 3600_000, { style: 'compact' }); // '3h'
formatRelative(Date.now() - 2 * 86_400_000, { style: 'compact' }); // '2d'parseVttTimestamp / parseVttCueTiming
解析 WebVTT 字幕的时间信息,即 .vtt 文件里 hh:mm:ss.mmm --> hh:mm:ss.mmm 这样的行。
parseVttTimestamp 把单个时间戳(hh: 部分可选)解析成秒数;parseVttCueTiming 解析一整行 cue 时间信息,即用 --> 分隔的两端,并忽略结尾附带的 cue 设置(比如 align:start line:0),返回 { start, end }。
import { parseVttTimestamp, parseVttCueTiming } from 'ranuts/utils';
parseVttTimestamp('00:00:05.000'); // 5
parseVttTimestamp('01:05.250'); // 65.25
parseVttTimestamp('不是时间戳'); // undefined
parseVttCueTiming('00:00:00.000 --> 00:00:05.000'); // { start: 0, end: 5 }
parseVttCueTiming('00:00:05.000 --> 00:00:10.000 align:start line:0'); // { start: 5, end: 10 }两者在输入不匹配时都返回 undefined,不会抛出异常,这样字幕文件里格式错误的一行可以直接跳过,不会中断整个解析过程。
注意事项
- 单位选择:
formatRelative取间隔真正填满的最粗单位,再在该单位内取整。当取整结果正好达到下一个单位的临界点(比如 59.6 分钟会取整成「60 分钟」)时,会自动进位,于是显示为「1 小时前」。 - 对称取整:先对绝对值取整再补回符号。因为 JavaScript 里
Math.round(-1.5)是-1,否则 90 分钟前会显示「1 小时前」,而 90 分钟后却显示「2 小时后」。 - 格式化器复用:
Intl.RelativeTimeFormat实例按 locale/style/numeric 组合缓存,因此渲染一百条时间戳的列表只会构造一个格式化器,而不是一百个。 - 降级:在没有
Intl.RelativeTimeFormat的运行时上会回退到 compact 形态,而不是抛错。