JS怎么将时间戳转换为“刚刚”、“几分钟前”等相对时间

作者:小满时间:2026-11-05 12:55:14浏览:632

详细讲解在 JavaScript 中如何将秒级或毫秒级时间戳转换为“刚刚”、“几分钟前”、“几小时前”等相对时间语义。涵盖核心阶梯差值计算算法、现代浏览器原生 Intl.RelativeTimeFormat 接口、时钟偏差容错与动态定时刷新实战。

在社交评论、文章发布或即时通知等场景中,将生硬的毫秒时间戳转换成“刚刚”、“5分钟前”、“3小时前”等人类可读的相对时间,能极大提升界面的可读性。解决这一需求的核心逻辑在于:以当前时刻(Date.now())为基准计算时间差,并通过分级阈值(秒、分、时、天)匹配相应的语义文案。本文将通过手写阶梯函数与现代原生 Intl 接口两种方式,提供可以直接用于生产的完整实现。
JS怎么将时间戳转换为“刚刚”、“几分钟前”等相对时间
图示:相对时间转换的整体架构(从时间戳输入、差值运算到语义渲染)

一、核心计算原理与时间单位划分

计算机内部记录的时间通常是自 1970 年 1 月 1 日 00:00:00 UTC 以来的毫秒数(或秒数)。要生成相对时间描述,本质上是在做减法运算:

差值(diffInSeconds) = Math.floor((当前时间毫秒 - 目标时间毫秒) / 1000)

得到相差的秒数后,程序依据人类日常感知习惯设定阶梯阈值进行分支判断:

  • 不足 60 秒:事件刚刚发生,统一显示为“刚刚”;
  • 1 分钟至 60 分钟内:除以 60 取整,得到“X 分钟前”;
  • 1 小时至 24 小时内:除以 3600 取整,得到“X 小时前”;
  • 1 天至 30 天内:除以 86400 取整,得到“X 天前”;
  • 超过 30 天(或跨年):相对时间的参考意义降低,通常降级回退显示完整绝对日期(如 2024-03-15)。
JS怎么将时间戳转换为“刚刚”、“几分钟前”等相对时间
图 2:时间差阶梯划分与语义文案映射原理

二、通用且轻量的转换函数实现

下面是一个经过防御性编程处理的通用转换函数。它兼顾了 10 位(秒级)与 13 位(毫秒级)时间戳的输入兼容,并对客户端时钟超前等边缘情况做了兜底容错:

/**
 * 将时间戳转换为人性化的相对时间
 * @param {number|string|Date} timestamp - 时间戳或日期对象
 * @returns {string} 格式化后的相对时间文本
 */
function formatRelativeTime(timestamp) {
  if (!timestamp) return '';

  // 1. 统一归一化为毫秒时间戳数值
  let targetTime;
  if (timestamp instanceof Date) {
    targetTime = timestamp.getTime();
  } else {
    const num = Number(timestamp);
    if (isNaN(num)) return '';
    // 兼容 10 位秒级时间戳(如服务端 Unix Timestamp)
    targetTime = String(timestamp).length === 10 ? num * 1000 : num;
  }

  const now = Date.now();
  const diffSec = Math.floor((now - targetTime) / 1000);

  // 2. 防御性处理:时钟抖动导致当前时间小于目标时间
  if (diffSec < 0) {
    return '刚刚';
  }

  // 3. 阶梯区间比对
  if (diffSec < 60) {
    return '刚刚';
  }

  const diffMin = Math.floor(diffSec / 60);
  if (diffMin < 60) {
    return `${diffMin}分钟前`;
  }

  const diffHour = Math.floor(diffMin / 60);
  if (diffHour < 24) {
    return `${diffHour}小时前`;
  }

  const diffDay = Math.floor(diffHour / 24);
  if (diffDay < 30) {
    return `${diffDay}天前`;
  }

  // 4. 超过一个月,回退格式化为具体年月日
  const d = new Date(targetTime);
  const year = d.getFullYear();
  const month = String(d.getMonth() + 1).padStart(2, '0');
  const day = String(d.getDate()).padStart(2, '0');
  return `${year}-${month}-${day}`;
}

// === 运行验证示例 ===
console.log(formatRelativeTime(Date.now() - 25 * 1000));       // 示例输出: "刚刚"
console.log(formatRelativeTime(Date.now() - 6 * 60 * 1000));   // 示例输出: "6分钟前"
console.log(formatRelativeTime(Date.now() - 5 * 3600 * 1000)); // 示例输出: "5小时前"
console.log(formatRelativeTime(Date.now() - 3 * 86400 * 1000));// 示例输出: "3天前"
console.log(formatRelativeTime(1672531199000));                // 示例输出: "2023-01-01"
JS怎么将时间戳转换为“刚刚”、“几分钟前”等相对时间
图 1:相对时间转换处理全流程(操作步骤与逻辑分支)
注意事项: 计算分钟和小时必须使用 Math.floor 向下取整,而不能使用 Math.round。例如 50 秒如果四舍五入会被算作 1 分钟,这会导致刚刚发布 50 秒的内容显示为“1分钟前”,在强调即时感的产品中会引发用户困惑。

三、利用浏览器原生 Intl.RelativeTimeFormat 接口

如果你的项目需要同时支持英语、日语等多语言国际化,或者希望直接借助浏览器标准库能力,现代 JavaScript 提供了原生的 Intl.RelativeTimeFormat 对象。

该 API 接受语言标签(如 zh-CN 或 en-US),并根据传入的数值与时间单位自动合成地道的相对时间语句:

/**
 * 基于原生 Intl 规范实现的相对时间转换
 * @param {number} timestamp - 毫秒时间戳
 * @param {string} lang - 语言代码,默认 zh-CN
 * @returns {string}
 */
function formatWithIntl(timestamp, lang = 'zh-CN') {
  const rtf = new Intl.RelativeTimeFormat(lang, { numeric: 'auto' });
  const diffSec = Math.floor((Date.now() - timestamp) / 1000);

  // 原生 API 对 0 秒会输出“现在”或“0秒前”,如果需要“刚刚”可前置拦截
  if (diffSec < 60) {
    return lang.startsWith('zh') ? '刚刚' : rtf.format(-diffSec, 'second');
  }

  const diffMin = Math.floor(diffSec / 60);
  if (diffMin < 60) {
    return rtf.format(-diffMin, 'minute'); // 例如: "5分钟前" 或 "5 minutes ago"
  }

  const diffHour = Math.floor(diffMin / 60);
  if (diffHour < 24) {
    return rtf.format(-diffHour, 'hour'); // 例如: "3小时前"
  }

  const diffDay = Math.floor(diffHour / 24);
  if (diffDay < 30) {
    return rtf.format(-diffDay, 'day'); // 例如: "昨天"、"2天前"
  }

  const diffMonth = Math.floor(diffDay / 30);
  if (diffMonth < 12) {
    return rtf.format(-diffMonth, 'month'); // 例如: "2个月前"
  }

  const diffYear = Math.floor(diffDay / 365);
  return rtf.format(-diffYear, 'year'); // 例如: "1年前"
}

// === 运行验证示例 ===
console.log(formatWithIntl(Date.now() - 86400 * 1000, 'zh-CN')); // 示例输出: "昨天"
console.log(formatWithIntl(Date.now() - 86400 * 1000, 'en-US')); // 示例输出: "yesterday"
console.log(formatWithIntl(Date.now() - 3600 * 1000, 'en-US'));  // 示例输出: "1 hour ago"
JS怎么将时间戳转换为“刚刚”、“几分钟前”等相对时间
图 3:手写阶梯函数与原生 Intl.RelativeTimeFormat 方案对比

四、进阶实战:页面动态自更新与定时器策略

相对时间与绝对时间最大的区别在于它的时效动态性:用户停留在页面 10 分钟后,原本加载时显示的“刚刚”应该自动流转为“10分钟前”。

在实际 DOM 渲染时,切忌为页面上的每个时间节点分别启动一个 setInterval,否则长列表中成百上千个微型定时器会严重抢占主线程并导致电量消耗与内存泄漏。正确的做法是:集中式轮询 + 批量属性刷新:

// 1. 在 HTML 中将时间戳保存在 dataset 属性中
// <span class="time-ago" data-timestamp="1711923456000">刚刚</span>

class RelativeTimeRefresher {
  constructor(selector = '.time-ago', intervalMs = 30000) {
    this.selector = selector;
    this.intervalMs = intervalMs;
    this.timerId = null;
  }

  // 批量更新所有节点
  update() {
    const elements = document.querySelectorAll(this.selector);
    elements.forEach((el) => {
      const ts = el.dataset.timestamp;
      if (ts) {
        el.textContent = formatRelativeTime(ts);
      }
    });
  }

  // 启动统一定时器
  start() {
    this.update(); // 立即执行一次
    this.timerId = setInterval(() => this.update(), this.intervalMs);
  }

  // 页面离开或组件销毁时停止
  stop() {
    if (this.timerId) {
      clearInterval(this.timerId);
      this.timerId = null;
    }
  }
}

// 初始化实例并启动
const refresher = new RelativeTimeRefresher('.time-ago', 30000); // 每 30 秒统一对齐刷新
refresher.start();

// 当处于单页面应用切换路由时执行停止
// window.addEventListener('beforeunload', () => refresher.stop());

五、生产环境避坑指南

在将代码部署至生产环境时,务必核查以下四个容易引发偶发 Bug 的细节:

  1. 时间戳长度混淆(10 位与 13 位): 后端接口在不同语言技术栈中常返回 10 位 Unix 时间戳(以秒为单位),而 JavaScript 的 Date.now() 和 new Date(ts) 期望 13 位毫秒。若直接用 10 位时间戳运算,计算出的差值相当于距离 1970 年仅过了数十秒,会通通判定为“几十年前”。
  2. 本地客户端时钟偏差防护: 用户手机或操作系统的本地时钟可能不准,甚至被用户人为调慢几分钟。此时 Date.now() < targetTime,差值小于 0。代码中务必设定 if (diffSec < 0) return '刚刚';,避免出现“-3分钟前”的荒谬现象。
  3. 时区一致性无需过度担忧: 时间戳本质上是全球统一的绝对毫秒计数值,无论是东八区还是西五区,同一瞬间的 Date.now() 差值运算不受时区影响。只有在超过 30 天需要格式化为 YYYY-MM-DD 字符串时,才需确认是否采用用户所在本地时区显示。
  4. 单页面应用内存回收: Vue/React 组件卸载时,绑定的更新定时器必须在 beforeUnmount 或 useEffect 清理函数中注销,防止离开页面后后台仍然在无效轮询。
JS怎么将时间戳转换为“刚刚”、“几分钟前”等相对时间
图 4:生产环境实战落地与避坑指南知识全景
作者最新文章
相关文章
更多