0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JSTとテストの時刻固定:TypeScriptで“時間”を正しく扱う実践録

Posted at

ゴール

  • JSTの一貫取得UTC→JST変換テストの時刻固定をシンプルに実装
  • 時間起因の不具合(タイムゾーン差、月/年度またぎの揺れ、テストの不安定化)を防ぐ

よくある問題

  • サーバーTZとユーザー期待のズレ(UTC基準で日付が前日/翌日になる)
  • 境界月の不安定(3月末/4月初、9月末/10月初、年跨ぎ)
  • new Date() 直書きで再現性がなく、テストが時期で壊れる

設計の原則

  • Pure関数 + 依存注入: now?: Date を引数で受ける
  • 変換をユーティリティに集約: DRYと責務分離
  • 境界条件を先に決める: 例) 4–9月=H1、10–3月=H2

実装パターン(最小セット)

  • JST要素を取得(汎用)
export function formatJst(now: Date = new Date()) {
  const s = now.toLocaleString('ja-JP', { timeZone: 'Asia/Tokyo' });
  const [d, t] = s.split(' ');
  const [year, month, day] = d.split('/').map(Number);
  const [hour, minute, second] = t.split(':').map(Number);
  return { year, month, day, hour, minute, second };
}

export const formatJstYear = (now?: Date) => formatJst(now).year;
export const formatJstMonth = (now?: Date) => formatJst(now).month;
  • 会計期(H1/H2)のDB形式を算出(例: 20251/20252)
export function getCurrentFiscalPeriodDb(now?: Date) {
  const y = formatJstYear(now);
  const m = formatJstMonth(now);
  if (m >= 4 && m <= 9) return `${y}1`;    // 4–9月: 当年H1
  const fy = m >= 10 ? y : y - 1;          // 10–3月: 当年H2(1–3月は前年)
  return `${fy}2`;
}
  • 表示形式 ⇄ DB形式の相互変換
const FISCAL_REGEX = /^FY(\d{4})H([12])$/;

export function toDbPeriod(fiscal: string) {
  const m = fiscal.match(FISCAL_REGEX);
  if (!m) throw new Error('無効な会計年度形式');
  return `${m[1]}${m[2]}`;                 // FY2024H1 -> 20241
}

export function toDisplayPeriod(db: string) {
  const year = db.slice(0, 4);
  const half = db.slice(4, 5);
  return `FY${year}H${half}`;              // 20241 -> FY2024H1
}

テストの時刻固定(Vitest)

  • Vitest
vi.useFakeTimers();
vi.setSystemTime(new Date('2025-08-01T00:00:00Z'));
// ...assert...
vi.useRealTimers();
  • afterEachで必ず実時間に戻すと安全
afterEach(() => {
  vi.useRealTimers(); // Jestなら jest.useRealTimers()
});

ありがちな落とし穴と対策

  • OS/コンテナのTZ依存: toLocaleString({ timeZone: 'Asia/Tokyo' })で明示
  • 文字列パースの不安定: .split(...).map(Number)で安定化(ロケール差の影響最小化)
  • 実行時刻依存のテスト: 時刻固定 + Pure関数化(now?: Date)で再現性担保
  • 境界の取りこぼし: 3/31, 4/1, 9/30, 10/1, 年跨ぎケースを必ずテスト

レシピ集(ショート)

  • “今日のJST要素”を取る
const { year, month, day } = formatJst();
  • “今の会計期(DB)”を得る
const period = getCurrentFiscalPeriodDb(); // 例: '20251'
``**
- **表示DBの変換**
```ts
toDbPeriod('FY2024H1');   // '20241'
toDisplayPeriod('20252');  // 'FY2025H2'

チェックリスト

  • 時間取得はユーティリティ経由(直にnew Date()参照しない)
  • 変換ロジックは1か所に集約(重複・分散を避ける)
  • テストは時刻固定 + 境界月ケース(揺れを潰す)

まとめ

  • 設計原則: Pure関数化・依存注入・変換の集約
  • 最小コード: JST要素取得・会計期計算・形式変換
  • テスト: useFakeTimers + setSystemTime + useRealTimers
  • これだけで、UTC/JSTずれや境界月の不具合、時間依存で壊れるテストを大幅に削減できます。
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?