教程

介绍实现 omnilude-tools 时使用的部分 PRD 文档

AAnonymous
10分钟阅读

前言

在上一篇介绍 omnilude-tools 的文章里提到过,那个项目里有不少工具都直接建立在具体的 PRD 文档之上。这次公开的文档,就是其中之一: Unix Timestamp Converter PRD。

这份文档最初是为了实现真实的 timestamp-converter 工具而用韩文写成的,现在对应的实现痕迹仍然保留在 src/app/[locale]/(tools)/developer/timestamp-converter/ 目录下。也就是说,它并不是一份松散的点子备忘,而是一份把页面结构、状态管理、SEO 与测试设计一起绑住的工作文档。

这篇文章里我尤其想强调三点。

  • 这份文档,是前面介绍过的 omnilude-tools 里某个真实工具背后的 PRD 之一。
  • 它也是 prd-generator 技能在真实开发里很好的一次使用案例。
  • 这份 PRD 的原文是韩文,因此这篇文章在各个语言版本里都尽量提供整份文档的完整翻译,而不是只给一段摘要。

目前也可以直接通过 tools.omnilude.com/developer/timestamp-converter 访问这个工具。

如果可以的话,我也想一并提供生成这份 PRD 时使用的提示词,但由于磁盘空间限制,我已经删除了当时的会话数据,因此暂时无法提供,还请理解。

正文

简单说,这份文档把范围压缩到了实现者可以立刻动手的程度。

  • 做什么: 一个在 Unix 时间戳与人类可读日期之间双向转换的工具
  • 做到哪里: MVP、高级功能、时区、当前时间实时显示、代码片段、计算器
  • 怎么做: 文件结构、Zustand 状态管理、转换工具函数、SEO、测试、无障碍、性能要求
  • 如何验证: 单元测试、E2E 测试、实现清单

当文档细到这个程度时,真正的实现会简单很多。实际上,timestamp-converter 页面就是建立在这种结构化指引之上,如今也仍然作为 omnilude-tools 的 developer 分组工具存在。

这份文档由韩文翻译而来

这篇文章的基准原文是一份韩文 PRD。因此,各语言版本都优先保留整份文档,而不是把它压缩成一个简短说明。即使它本质上是 PRD 文档,我也更看重保留真实实现背景和技术语境,而不是单纯缩短篇幅。

PRD 原文

下面是整理后公开的 Unix Timestamp Converter PRD 全文。

PRD: Unix Timestamp Converter

基本信息

项目
工具 IDtimestamp-converter
分组developer
路径/developer/timestamp-converter
优先级P0 (必需)
预估工时1-2 天
状态Draft

1. 概述

1.1 目的

一个提供 Unix 时间戳与人类可读日期格式之间双向转换的工具。

1.2 目标用户

  • 后端/前端开发者
  • 数据分析师
  • 系统管理员

1.3 竞品分析

网站优点缺点
epochconverter.com格式丰富、带代码片段UI 较旧
unixtimestamp.com界面简单功能有限
timestamp-converter.com设计整洁时区支持有限

2. 功能需求

2.1 核心功能 (MVP)

F1: 时间戳 → 日期转换

Typescript
输入: Unix 时间戳 (自动识别秒/毫秒/微秒)
输出: 多种日期格式

自动识别逻辑:

Typescript
function detectTimestampUnit(value: number): 'seconds' | 'milliseconds' | 'microseconds' {
  if (value < 1e12) return 'seconds';       // 小于 10 位
  if (value < 1e15) return 'milliseconds';  // 13 位
  return 'microseconds';                    // 16 位
}

输出格式:

  • ISO 8601: 2026-01-29T13:45:30.000Z
  • RFC 2822: Wed, 29 Jan 2026 13:45:30 +0000
  • 本地格式: 2026年1月29日 22:45:30
  • 相对时间: 5 分钟前, 3 天后

F2: 日期 → 时间戳转换

Typescript
输入: 日期字符串或日期选择器
输出: Unix 时间戳 (秒、毫秒)

支持的输入格式:

  • ISO 8601 字符串
  • 日期/时间选择器 (DatePicker)
  • 自然语言 (可选): now, yesterday, next week

F3: 时区支持

Typescript
默认值: 浏览器本地时区
选项: UTC、主要城市时区选择

主要时区列表:

Typescript
const TIMEZONES = [
  { value: 'UTC', label: 'UTC' },
  { value: 'Asia/Seoul', label: '首尔 (KST, UTC+9)' },
  { value: 'Asia/Tokyo', label: '东京 (JST, UTC+9)' },
  { value: 'America/New_York', label: '纽约 (EST/EDT, UTC-5/-4)' },
  { value: 'America/Los_Angeles', label: '洛杉矶 (PST/PDT, UTC-8/-7)' },
  { value: 'Europe/London', label: '伦敦 (GMT/BST, UTC+0/+1)' },
  // ... 继续补充
];

F4: 当前时间实时显示

Typescript
在页面顶部实时显示当前 Unix 时间戳,并每 1 秒更新一次

2.2 高级功能

F5: 生成代码片段

为选中的时间戳提供多种编程语言的代码片段。

Typescript
const CODE_SNIPPETS = {
  javascript: (ts: number) => `new Date(${ts * 1000})`,
  python: (ts: number) => `datetime.fromtimestamp(${ts})`,
  java: (ts: number) => `Instant.ofEpochSecond(${ts}L)`,
  go: (ts: number) => `time.Unix(${ts}, 0)`,
  php: (ts: number) => `date('Y-m-d H:i:s', ${ts})`,
  ruby: (ts: number) => `Time.at(${ts})`,
  csharp: (ts: number) => `DateTimeOffset.FromUnixTimeSeconds(${ts})`,
};

F6: 时间戳计算器

Typescript
基准时间 + 天/小时/分钟/秒 = 结果时间戳
例: 当前时间 + 7 天 = 下周的时间戳

3. UI/UX 设计

3.1 布局

┌─────────────────────────────────────────────────────────────┐
│  当前 Unix 时间戳: 1738150800 (实时更新)          [复制]     │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────────────┐  ┌─────────────────────────────┐  │
│  │  输入               │  │  结果                        │  │
│  │                     │  │                              │  │
│  │  [输入时间戳]       │  │  ISO 8601: ...      [复制]   │  │
│  │  或                 │  │  RFC 2822: ...      [复制]   │  │
│  │  [选择日期时间]     │  │  本地: ...          [复制]   │  │
│  │                     │  │  相对: ...          [复制]   │  │
│  │  时区: [选择 ▼]     │  │                              │  │
│  │                     │  │  时间戳 (秒): ...            │  │
│  │  [转换] [清空]      │  │  时间戳 (ms): ...            │  │
│  └─────────────────────┘  └─────────────────────────────┘  │
│                                                             │
├─────────────────────────────────────────────────────────────┤
│  代码片段                                                   │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  [JS] [Python] [Java] [Go] [PHP] [Ruby] [C#]        │   │
│  │  ─────────────────────────────────────────────────  │   │
│  │  new Date(1738150800000)                   [复制]   │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

3.2 响应式设计

Desktop (lg+):

  • 双栏布局 (输入 | 结果)

Tablet/Mobile (< lg):

  • 单栏布局 (输入在上,结果在下)

3.3 交互

  1. 实时转换: 输入时自动转换 (300ms debounce)
  2. 复制反馈: 点击复制按钮时显示 toast 提示
  3. 键盘快捷键: Enter 执行转换,Ctrl+C 复制结果

4. 技术规格

4.1 文件结构

src/app/[locale]/(tools)/developer/timestamp-converter/
├── page.tsx                              # 页面入口 + SEO
├── _store/
│   └── timestamp-store.ts                # Zustand 状态管理
└── _components/
    ├── timestamp-page.tsx                # 主客户端组件
    ├── current-time-display.tsx          # 当前时间显示
    ├── timestamp-input.tsx               # 输入区域
    ├── conversion-result.tsx             # 结果区域
    └── code-snippets.tsx                 # 代码片段区域

4.2 状态管理 (Zustand)

Typescript
// _store/timestamp-store.ts
import { create } from 'zustand';

type InputMode = 'timestamp' | 'datetime';

interface TimestampState {
  // 输入
  inputMode: InputMode;
  timestampInput: string;
  dateInput: Date | null;
  timezone: string;

  // 结果
  result: ConversionResult | null;

  // 代码片段
  selectedLanguage: string;

  // 动作
  setInputMode: (mode: InputMode) => void;
  setTimestampInput: (value: string) => void;
  setDateInput: (date: Date | null) => void;
  setTimezone: (tz: string) => void;
  setSelectedLanguage: (lang: string) => void;
  convert: () => void;
  clear: () => void;
}

interface ConversionResult {
  timestampSeconds: number;
  timestampMillis: number;
  iso8601: string;
  rfc2822: string;
  localFormat: string;
  relative: string;
}

export const useTimestampStore = create<TimestampState>((set, get) => ({
  inputMode: 'timestamp',
  timestampInput: '',
  dateInput: null,
  timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
  result: null,
  selectedLanguage: 'javascript',

  setInputMode: (mode) => set({ inputMode: mode }),
  setTimestampInput: (value) => set({ timestampInput: value }),
  setDateInput: (date) => set({ dateInput: date }),
  setTimezone: (tz) => set({ timezone: tz }),
  setSelectedLanguage: (lang) => set({ selectedLanguage: lang }),

  convert: () => {
    const { inputMode, timestampInput, dateInput, timezone } = get();
    // 转换逻辑
  },

  clear: () => set({
    timestampInput: '',
    dateInput: null,
    result: null,
  }),
}));

4.3 核心转换逻辑

Typescript
// lib/timestamp-utils.ts
import { format, formatDistanceToNow } from 'date-fns';
import { formatInTimeZone } from 'date-fns-tz';
import { ko, en, ja } from 'date-fns/locale';

export function convertTimestamp(
  timestamp: number,
  timezone: string,
  locale: string
): ConversionResult {
  // 自动识别单位
  const unit = detectTimestampUnit(timestamp);
  const timestampMs = unit === 'seconds'
    ? timestamp * 1000
    : unit === 'microseconds'
      ? Math.floor(timestamp / 1000)
      : timestamp;

  const date = new Date(timestampMs);
  const localeObj = { ko, en, ja }[locale] || ko;

  return {
    timestampSeconds: Math.floor(timestampMs / 1000),
    timestampMillis: timestampMs,
    iso8601: date.toISOString(),
    rfc2822: format(date, 'EEE, dd MMM yyyy HH:mm:ss xx', { locale: localeObj }),
    localFormat: formatInTimeZone(date, timezone, 'PPpp', { locale: localeObj }),
    relative: formatDistanceToNow(date, { addSuffix: true, locale: localeObj }),
  };
}

export function dateToTimestamp(date: Date): { seconds: number; millis: number } {
  const millis = date.getTime();
  return {
    seconds: Math.floor(millis / 1000),
    millis,
  };
}

4.4 依赖项

JSON
{
  "dependencies": {
    "date-fns": "^3.x",
    "date-fns-tz": "^3.x"
  }
}

项目中已经安装。

5. 多语言支持

5.1 翻译键

JSON
// messages/zh/tools/developer/timestamp-converter.json
{
  "tools": {
    "developer": {
      "timestampConverter": "时间戳转换器",
      "timestampConverterDesc": "在 Unix 时间戳与日期之间相互转换",
      "timestamp": {
        "currentTime": "当前 Unix 时间戳",
        "inputTimestamp": "输入时间戳",
        "inputDatetime": "输入日期/时间",
        "timezone": "时区",
        "convert": "转换",
        "clear": "清空",
        "copy": "复制",
        "copied": "已复制",
        "results": "转换结果",
        "iso8601": "ISO 8601",
        "rfc2822": "RFC 2822",
        "localFormat": "本地格式",
        "relative": "相对时间",
        "timestampSeconds": "时间戳 (秒)",
        "timestampMillis": "时间戳 (毫秒)",
        "codeSnippets": "代码片段",
        "invalidTimestamp": "无效的时间戳",
        "autoDetected": "自动识别: {unit}"
      }
    }
  }
}

6. SEO 元数据

Typescript
// page.tsx
export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { locale } = await params;
  const t = await getTranslations({ locale });

  return generateSeoMetadata({
    locale,
    title: t('tools.developer.timestampConverter'),
    description: t('tools.developer.timestampConverterDesc'),
    path: '/developer/timestamp-converter',
    keywords: [
      'unix timestamp',
      'epoch converter',
      '时间戳转换',
      '日期转换',
      'timestamp to date',
      'date to timestamp',
    ],
  });
}

7. 测试用例

7.1 单元测试

Typescript
describe('detectTimestampUnit', () => {
  it('should detect seconds', () => {
    expect(detectTimestampUnit(1738150800)).toBe('seconds');
  });

  it('should detect milliseconds', () => {
    expect(detectTimestampUnit(1738150800000)).toBe('milliseconds');
  });

  it('should detect microseconds', () => {
    expect(detectTimestampUnit(1738150800000000)).toBe('microseconds');
  });
});

describe('convertTimestamp', () => {
  it('should convert timestamp to ISO 8601', () => {
    const result = convertTimestamp(1738150800, 'UTC', 'en');
    expect(result.iso8601).toBe('2025-01-29T09:00:00.000Z');
  });
});

7.2 E2E 测试

Typescript
test('时间戳转换流程', async ({ page }) => {
  await page.goto('/zh/developer/timestamp-converter');

  // 输入时间戳
  await page.fill('[data-testid="timestamp-input"]', '1738150800');

  // 检查结果
  await expect(page.locator('[data-testid="iso8601-result"]'))
    .toContainText('2025-01-29');

  // 点击复制按钮
  await page.click('[data-testid="copy-iso8601"]');
  await expect(page.locator('.toast')).toContainText('已复制');
});

8. 无障碍要求

  • 所有输入字段都要有合适的 label
  • 复制按钮需要 aria-label
  • 支持键盘导航
  • 支持屏幕阅读器

9. 性能要求

  • 输入后显示转换结果: < 100ms
  • 当前时间更新: 每秒准确刷新
  • 包体积增加: < 5KB (gzip)

10. 实现清单

  • 创建文件结构
  • 实现 Zustand store
  • 实现转换工具函数
  • 实现主页面组件
  • 实现当前时间显示组件
  • 实现输入区域组件
  • 实现结果区域组件
  • 实现代码片段区域组件
  • 添加 7 种语言的翻译键
  • tools.ts 中注册工具
  • 完成响应式样式
  • 做无障碍检查
  • 编写测试