튜토리얼

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심플한 UI기능 제한적
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일 오후 10: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+):

  • 2컬럼 레이아웃 (입력 | 결과)

Tablet/Mobile (< lg):

  • 1컬럼 레이아웃 (입력 위, 결과 아래)

3.3 인터랙션

  1. 실시간 변환: 입력 시 자동 변환 (디바운스 300ms)
  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/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 메타데이터

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('/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에 도구 등록
  • 반응형 스타일링
  • 접근성 검토
  • 테스트 작성