- Published on
Intl.RelativeTimeFormat与相对时间
日常开发中经常会用到一些处理相对时间的场景,如输入一个标准时间和标准时间的偏移,返回相对标准时间的时间偏移描述,如"明天","1秒后","5天前","1年前"等类似的描述。
一般的,我们会使用一些处理时间的第三方包来协助:
import moment from 'moment';
function Relative(props) {
return moment(props.date).fromNow();
}
可是,我们真的需要为简单的一个功能"花费20kb吗?
但这个问题其实也并不简单,它需要解决几个复杂的点
- 关于缩写
对于“一天后”,大多数人会说"明天",“昨天”大部分场景也不会说成“一天前”,“后天“,”明年“等等也同理。
- 本地化
这是比较困难的一件事,跨多种语言的翻译不仅很复杂(包括符号的本地化差异),如果将这些处理到囊括到函数内,毫无疑问代码量会非常巨大。
这也是为什么moment.js看起来很大的原因,因为处理所有标准时间和日期非常复杂。
尝试写一下?
function nativeGetRelativeTime(locale, unit, amount) {
if (locale === 'en-US') {
const isPlural = amount !== 1 && amount !== -1
const isPast = amount < 0
if (amount === 1 && unit === 'day') return '明天'
if (amount === -1 && unit === 'day') return '昨天'
if (amount === 1 && unit === 'year') return '明年'
if (isPast) {
return `${amount} ${unit}${isPlural} 前`
}
return `${amount} ${day}${isPlural} 后`
}
if (locale === 'es-US') {
}
if (locale === 'cn') {
}
}
但是有一个解决方案,内置在浏览器内。
Intl.RelativeTimeFormat
Intl 对象提供精确的字符串对比、数字格式化,和日期时间格式化等功能。对于上述场景,我们需要使用Intl.RelativeTimeFormat
。
const rtf = new Intl.RelativeTimeFormat('cn', {
numeric: 'auto'
})
rtf.format(1, 'day') // 明天
rtf.format(-2, 'year') // 2年前
rtf.format(10, 'minute') // 10分钟后
你甚至可以只传递当前的navigator.language
作为第一个参数:
const rtf = new Intl.RelativeTimeFormat(
navigator.language // ✅
)
支持的单位包括: year
、 quarter
、 month
、 week
、 day
、 hour
和 second
。
回到我们最初的示例,我们想要的是能够自动选择要使用的合适单位,它将打印 1 天前的日期("昨天") ,和一年后的日期("明年")。
我们可以抽象一个简单的包装函数,例如:
function Relative(props) {
const timeString = getRelativeTimeString(props.date)
return <>
{timeString}
</>
}
现在有了Intl.RelativeTimeFormat
,实现就非常简单了。剩下的问题是我们应该为给定的时间增量选择什么单位( "hour" , "day" 等)
/**
* 将日期转换为相对时间字符串
*/
function getRelativeTimeString(date, lang) {
const rtf = new Intl.RelativeTimeFormat(lang, { numeric: "auto" });
const timeMs = typeof date === "number" ? date : date.getTime();
// 获取给定日期距离现在的秒数
const deltaSeconds = Math.round((timeMs - Date.now()) / 1000); // 四舍五入,确保计算出的秒数能够在cutoffs数组内找到
// 以秒为单位表示一分钟、小时、天、周、月、年等,并保存在数组内
const cutoffs = [60, 3600, 86400, 86400 * 7, 86400 * 30, 86400 * 365, Infinity];
// 说需要的单位使用数组保存
const units = ["second", "minute", "hour", "day", "week", "month", "year"];
// 首先计算出距离当前的秒数,然后在数组中匹配一个与这个秒数最接近的元素
const unitIndex = cutoffs.findIndex(cutoff => cutoff > Math.abs(deltaSeconds));
const divisor = unitIndex ? cutoffs[unitIndex - 1] : 1;
return rtf.format(Math.floor(deltaSeconds / divisor), units[unitIndex]);
}
const result = getRelativeTimeString(+new Date() + 60000, 'zh-Hans-cn') // 1分钟后
const result2 = getRelativeTimeString(+new Date() - 58888, 'zh-Hans-cn') // 59秒钟前
End.