Omnilude-tools을 구현한 PRD 문서 중 일부를 소개합니다
들어가며
앞서 소개한 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
기본 정보
| 항목 | 값 |
|---|---|
| 도구 ID | timestamp-converter |
| 그룹 | developer |
| 경로 | /developer/timestamp-converter |
| 우선순위 | P0 (필수) |
| 예상 공수 | 1-2일 |
| 상태 | Draft |
1. 개요
1.1 목적
Unix 타임스탬프와 사람이 읽을 수 있는 날짜 형식 간의 양방향 변환을 제공하는 도구
1.2 대상 사용자
- 백엔드/프론트엔드 개발자
- 데이터 분석가
- 시스템 관리자
1.3 경쟁사 분석
| 사이트 | 장점 | 단점 |
|---|---|---|
| epochconverter.com | 다양한 형식, 코드 스니펫 | 구식 UI |
| unixtimestamp.com | 심플한 UI | 기능 제한적 |
| timestamp-converter.com | 깔끔한 디자인 | 타임존 제한 |
2. 기능 요구사항
2.1 핵심 기능 (MVP)
F1: 타임스탬프 → 날짜 변환
입력: Unix 타임스탬프 (초/밀리초/마이크로초 자동 감지)
출력: 다양한 날짜 형식
자동 감지 로직:
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일 오후 10:45:30 - 상대 시간:
5분 전,3일 후
F2: 날짜 → 타임스탬프 변환
입력: 날짜 문자열 또는 날짜 선택기
출력: Unix 타임스탬프 (초, 밀리초)
지원 입력 형식:
- ISO 8601 문자열
- 날짜/시간 선택기 (DatePicker)
- 자연어 (선택적):
now,yesterday,next week
F3: 타임존 지원
기본값: 브라우저 로컬 타임존
옵션: UTC, 주요 도시별 타임존 선택
주요 타임존 목록:
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: 현재 시간 실시간 표시
화면 상단에 현재 Unix 타임스탬프 실시간 갱신 (1초 간격)
2.2 고급 기능
F5: 코드 스니펫 생성
선택한 타임스탬프를 다양한 프로그래밍 언어 코드로 제공
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: 타임스탬프 계산기
기준 시간 + 일/시/분/초 = 결과 타임스탬프
예: 현재 + 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+):
- 2컬럼 레이아웃 (입력 | 결과)
Tablet/Mobile (< lg):
- 1컬럼 레이아웃 (입력 위, 결과 아래)
3.3 인터랙션
- 실시간 변환: 입력 시 자동 변환 (디바운스 300ms)
- 복사 피드백: 복사 버튼 클릭 시 toast 알림
- 키보드 단축키: 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)
// _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 핵심 변환 로직
// 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 의존성
{
"dependencies": {
"date-fns": "^3.x",
"date-fns-tz": "^3.x"
}
}
이미 프로젝트에 설치되어 있음
5. 다국어 지원
5.1 번역 키
// messages/ko/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 메타데이터
// 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 단위 테스트
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 테스트
test('타임스탬프 변환 플로우', async ({ page }) => {
await page.goto('/ko/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 스토어 구현
- 변환 유틸리티 함수 구현
- 메인 페이지 컴포넌트 구현
- 현재 시간 표시 컴포넌트
- 입력 섹션 컴포넌트
- 결과 섹션 컴포넌트
- 코드 스니펫 섹션 컴포넌트
- 다국어 번역 키 추가 (7개 언어)
- tools.ts에 도구 등록
- 반응형 스타일링
- 접근성 검토
- 테스트 작성