前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >什么,你还在用 momentJs 处理相对时间

什么,你还在用 momentJs 处理相对时间

作者头像
前端修罗场
发布2023-10-07 19:56:42
1880
发布2023-10-07 19:56:42
举报
文章被收录于专栏:Web 技术

我想,下面这段代码,你是不是在开发中常常这样使用来计算距离现在过去了多长时间:

代码语言:javascript
复制
import moment from 'moment' // 61k (gzipped:19.k)
function Relative(props) {
	const timeString = moment(props.date).fromNow()
	return <>
		{timeString}
	</>
}

可是,你竟然用一个大小为 20k (还是压缩过的,没压缩 61k)的包,只用来做日期的转换? really? 你想要的只是进行一个日期上的转换啊。

现在,经过我这么一吓唬,你可能会想,能不能这么做:

代码语言:javascript
复制
function nativeGetRelativeTime(unit = 'days', amount = 2) {
  return `in ${amount} ${unit}s`
}

别,请别这么做。虽然相对时间暂时看起来像是一个简单的问题,但你应该要意识到相对时间有很多复杂的问题需要解决,比如:

  • 缩写:一般不会显示 “1天前”? 一般会显示 “昨天”、“明天” 或 “明年” 这样的词
  • 将来和过去: 比如我们不会显示“在 -2 天内”,而是显示 “2天前”

还可能存在其他问题,例如时区问题。这些复杂的问题一旦来到,往往开发者会采用像 momentJsdayjs 这样的库来解决问题。虽然,下面这段代码也可以试着解决你的问题:

代码语言:javascript
复制
function nativeGetRelativeTime(locale, unit, amount) {
  if (locale === 'zh') {
    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 ? 's' : ''} 前`
    }
    return `in ${amount} ${day}${isPlural ? 's' : ''}`
  }

}

但是,还是请你别这么做。因为这看起来似乎变得复杂了。而我向你推荐的一个内置对象能帮助你解决相对时间的问题。

Intl.RelativeTimeFormat

重申一遍,当你遇到这些情况时,要记住,目前现代前端中已经有有很多解决常见问题的内置解决方案了,可以方便的进行使用。

而面对本文提到的相对时间问题,我要说的就是 Intl.RelativeTimeFormat 这个对象。

我先用下面一段代码来样式它的便捷性:

代码语言:javascript
复制
const rtf = new Intl.RelativeTimeFormat('en', {
  numeric: 'auto'
})

rtf.format(1, 'day') // 'tomorrow'
rtf.format(-2, 'year') // '2 years ago'
rtf.format(10, 'minute') // 'in 10 minutes'

而且,它能很好地处理不同时区的问题:

代码语言:javascript
复制
const rtf = new Intl.RelativeTimeFormat('es-ES', {
  numeric: 'auto'
})
rtf.format(1, 'day') // 'mañana'
rtf.format(-2, 'year') // 'hace 2 años'
rtf.format(10, 'minute') // 'dentro de 10 minutos'

你甚至可以只传入navigator.language 作为第一个参数:

代码语言:javascript
复制
const rtf = new Intl.RelativeTimeFormat(
  navigator.language // ✅
)

除此之外,Intl.RelativeTimeFormat 支持的单位包括: "year", "quarter", "month", "week", "day", "hour", "minute", 和 "second"

现在,回到我们最初的例子,我们用 Intl.RelativeTimeFormat 来改写一下:

首先,我们先写一个简单的包装函数来处理相对时间的转换:

代码语言:javascript
复制
function Relative(props) {
  const timeString = getRelativeTimeString(props.date)
  
  return <>
    {timeString}
  </>
}

接着,我们使用 Intl.RelativeTimeFormat 对象来实现 getRelativeTimeString 函数:

代码语言:javascript
复制
export function getRelativeTimeString(
  date: Date | number,
  lang = navigator.language
): string {
  const timeMs = typeof date === "number" ? date : date.getTime();
  const deltaSeconds = Math.round((timeMs - Date.now()) / 1000);
  const cutoffs = [60, 3600, 86400, 86400 * 7, 86400 * 30, 86400 * 365, Infinity];

  const units: Intl.RelativeTimeFormatUnit[] = ["second", "minute", "hour", "day", "week", "month", "year"];

  const unitIndex = cutoffs.findIndex(cutoff => cutoff > Math.abs(deltaSeconds));
  const divisor = unitIndex ? cutoffs[unitIndex - 1] : 1;
  const rtf = new Intl.RelativeTimeFormat(lang, { numeric: "auto" });
  return rtf.format(Math.floor(deltaSeconds / divisor), units[unitIndex]);
}

Using date-fns

但是如果你不想自己的解决方案,那么也有一个开源的解决方案。date-fns 是一个很棒的 JavaScript 日期工具库,每个日期都支持以 树摇 的方式单独导出。

其中,date-fns 中内置了一个 intlFormatDistance 函数,它是 Intl.RelativeTimeFormat 的一个小包装器,这个函数做的正是我们需要的。并且,它的大小在2kb以下。

看下面的代码,是不是代码简单了许多:

Intl.DateTimeFormat

除此之前,Intl.DateTimeformat 还提供格式化日期和时间:

代码语言:javascript
复制
new Intl.DateTimeFormat('en-US').format(new Date())
// -> 1/23/2023

new Intl.DateTimeFormat('en-US', {
  dateStyle: 'full'
}).format(new Date())
// -> Monday, January 23, 2023

new Intl.DateTimeFormat('en-US', {
  timeStyle: 'medium'
}).format(new Date())
// -> 4:17:23 PM

new Intl.DateTimeFormat('en-US', {
  dayPeriod: 'short', hour: 'numeric'
}).format(new Date())
// -> 4 in the afternoon

Intl.NumberFormat

同时,Intl.NumberFormat 这个对象还能为你格式化数字:

代码语言:javascript
复制
new Intl.NumberFormat('en', { 
  style: 'currency', currency: 'USD' 
}).format(123456.789)
// -> $123,456.79

new Intl.NumberFormat('de-DE', { 
  style: 'currency', currency: 'EUR' 
}).format(123456.789)
// -> 123.456,79 €

new Intl.NumberFormat('pt-PT', {
  style: 'unit', unit: 'kilometer-per-hour'
}).format(50));
// -> 50 km/h

(16).toLocaleString('en-GB', {
  style: 'unit', unit: 'liter', unitDisplay: 'long',
}));
// -> 16 litres

目前,所有主流浏览器以及 Node.js 和 Deno 都支持 Intl.RelativeTimeFormat。 如果你还在使用像 momentJs 这样的大型数据处理库,不妨考虑考虑Intl.RelativeTimeFormat, Intl.DateTimeFormat 这些对象,能不能帮你解决你面临的问题。

这里是编程轨迹,下篇文章再见~

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Intl.RelativeTimeFormat
  • Using date-fns
  • Intl.DateTimeFormat
  • Intl.NumberFormat
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档