# 日期时间展示规范（小程序前端）

> **文档编号**：DS-DATETIME-001  
> **版本**：v1.1.0  
> **适用范围**：NeoZQYY 小程序所有前端页面  
> **最后更新**：2026-03-18

---

## 一、设计原则

- **就近优先**：越近的时间用越自然的语言表达，减少认知负担。
- **精度递减**：随时间距离增大，展示精度逐步降低（秒 → 分 → 时 → 天 → 日期）。
- **一致性**：全端统一规则，禁止页面间自行实现差异逻辑。
- **可读性优先**：格式选择以用户直觉理解为准，而非技术精确。

---

## 二、展示规则总表

| 时间距当前的范围 | 展示格式 | 示例 |
|---|---|---|
| 0 秒 ～ 119 秒（不足 2 分钟） | `刚刚` | 刚刚 |
| 2 分钟 ～ 59 分钟 | `N分钟前` | 2分钟前 / 59分钟前 |
| 1 小时 ～ 23 小时 59 分 | `N小时前` | 1小时前 / 23小时前 |
| 24 小时 ～ 71 小时 59 分（1天～3天内） | `N天前` | 1天前 / 3天前 |
| 超过 72 小时（3天前以上） | `YYYY-MM-DD` | 2026-03-10 |

> **注**：所有时间差计算以**服务端返回的 UTC 时间戳**为基准，前端根据设备本地时区渲染，不得使用客户端本地时间作为「当前时间」基准。

---

## 三、逐级规则详解

### 3.1 刚刚（0 ～ 119 秒）

- **触发条件**：`now - timestamp < 120 秒`
- **展示文案**：`刚刚`
- **说明**：2 分钟内的时间差对用户无感知意义，统一收敛为「刚刚」，不展示具体秒数或分钟数。

```
示例：发布于 5 秒前   → 刚刚
      发布于 90 秒前  → 刚刚
      发布于 119 秒前 → 刚刚
```

---

### 3.2 N 分钟前（2 ～ 59 分钟）

- **触发条件**：`120 秒 ≤ now - timestamp < 3600 秒`
- **展示格式**：`Math.floor(diff / 60) + '分钟前'`
- **最小值**：2分钟前
- **最大值**：59分钟前

```
示例：
  diff = 120s  → 2分钟前
  diff = 3540s → 59分钟前
```

> **不得** 展示「1分钟前」（119 秒内均为「刚刚」）；**不得** 展示「60分钟前」，3600 秒起跳进入「小时」维度。

---

### 3.3 N 小时前（1 ～ 23 小时）

- **触发条件**：`3600 秒 ≤ now - timestamp < 86400 秒`
- **展示格式**：`Math.floor(diff / 3600) + '小时前'`
- **最小值**：1小时前
- **最大值**：23小时前

```
示例：
  diff = 3600s  → 1小时前
  diff = 82800s → 23小时前
```

> **不得** 展示「24小时前」，86400 秒起跳进入「天」维度。

---

### 3.4 N 天前（1 ～ 3 天）

- **触发条件**：`86400 秒 ≤ now - timestamp < 259200 秒`（即 1 天 ～ 不足 72 小时）
- **展示格式**：`Math.floor(diff / 86400) + '天前'`
- **最小值**：1天前
- **最大值**：3天前

```
示例：
  diff = 86400s  → 1天前
  diff = 259199s → 3天前
```

> **临界说明**：72小时（259200秒）整数点，即 diff ≥ 259200 秒，切换为日期格式。

---

### 3.5 日期展示（超过 3 天）

- **触发条件**：`now - timestamp ≥ 259200 秒`（≥ 72 小时）
- **展示格式**：
  - **同年**：`MM-DD`（省略年份，如 `03-10`）
  - **跨年**：`YYYY-MM-DD`（保留年份，如 `2025-08-21`）
- **分隔符**：使用连字符 `-`，不使用斜杠 `/` 或中文「年月日」
- **不展示时分秒**：日期维度下不展示具体时刻。

```
示例（当前日期 2026-03-18）：
  时间戳 2026-03-10 → 03-10
  时间戳 2025-08-21 → 2025-08-21（跨年展示完整年份）
```

---

## 四、判断流程图

```
输入：服务端时间戳 timestamp
         │
         ▼
   diff = now - timestamp（单位：秒）
         │
   ┌─────┴───────┐
   │ diff < 120  │──► 刚刚
   └─────┬───────┘
         │
   ┌─────┴──────────┐
   │ diff < 3600    │──► N分钟前（N = floor(diff/60)，2≤N≤59）
   └─────┬──────────┘
         │
   ┌─────┴──────────┐
   │ diff < 86400   │──► N小时前（N = floor(diff/3600)，1≤N≤23）
   └─────┬──────────┘
         │
   ┌─────┴──────────┐
   │ diff < 259200  │──► N天前（N = floor(diff/86400)，1≤N≤3）
   └─────┬──────────┘
         │
         ▼
    日期格式（同年MM-DD / 跨年YYYY-MM-DD）
```

---

## 五、前端实现参考（JavaScript）

```javascript
/**
 * 格式化时间展示
 * @param {number} timestamp - 服务端返回的 Unix 时间戳（毫秒）
 * @returns {string} 格式化后的时间文案
 */
function formatRelativeTime(timestamp) {
  const now = Date.now();
  const diff = Math.floor((now - timestamp) / 1000); // 转换为秒

  if (diff < 120) {
    return '刚刚';
  }

  if (diff < 3600) {
    return `${Math.floor(diff / 60)}分钟前`;
  }

  if (diff < 86400) {
    return `${Math.floor(diff / 3600)}小时前`;
  }

  if (diff < 259200) { // 72小时 = 3天
    return `${Math.floor(diff / 86400)}天前`;
  }

  // 超过3天，展示日期
  const date = new Date(timestamp);
  const nowDate = new Date(now);
  const year = date.getFullYear();
  const month = String(date.getMonth() + 1).padStart(2, '0');
  const day = String(date.getDate()).padStart(2, '0');

  if (year === nowDate.getFullYear()) {
    return `${month}-${day}`; // 同年省略年份
  }
  return `${year}-${month}-${day}`; // 跨年显示完整年份
}
```

> **微信小程序说明**：可直接将此函数挂载到 `utils/time.js`，在 WXML 中通过 `wxs` 模块或 `Page.data` 绑定使用。

---

## 六、边界与特殊情况处理

| 场景 | 处理方式 |
|---|---|
| `timestamp` 为空 / undefined | 展示 `--`，不报错 |
| `timestamp` 为未来时间（服务端时钟偏差） | 统一展示 `刚刚`（diff < 0 时按 0 处理） |
| 用户设备时区异常 | 以服务端返回的格式化日期字段（fallback）为准 |
| 时间戳单位（秒 vs 毫秒） | 统一由服务端约定为**毫秒**，前端不做猜测 |

---

## 七、禁止事项

- ❌ 禁止展示「1分钟前」「60分钟前」「24小时前」「0分钟前」等临界歧义文案（2分钟内统一展示「刚刚」）
- ❌ 禁止在「天」维度之后继续展示相对文案（如「7天前」「30天前」）
- ❌ 禁止使用中文「年月日」格式（如「2026年3月10日」），统一使用 `-` 分隔符
- ❌ 禁止在超3天后展示时分秒
- ❌ 禁止各页面自行定义时间格式化函数，必须引用 `utils/time.js` 统一工具

---

## 八、关联文档

- [VI 设计规范](./VI-DESIGN-SYSTEM.md)
- [组件库规范](../components/)
- `apps/miniprogram/miniprogram/utils/time.js`（工具函数实现位置）
