# 機能設計書 57-日時ウィジェット

## 概要

本ドキュメントは、Decap CMSにおける日時ウィジェット（DateTime Widget）の設計を記述する。日付・時刻の入力をフォーマットカスタマイズとUTC対応付きで提供するウィジェットである。

### 本機能の処理概要

**業務上の目的・背景**：CMSで管理するコンテンツには公開日・更新日・イベント日時・期限日など日時型のフィールドが不可欠である。本ウィジェットはブラウザネイティブの日時入力（datetime-local、date、time）を使用し、カスタムフォーマットでの値の保存・表示をサポートする。UTCモードにも対応し、タイムゾーンの違いを考慮した日時管理が可能である。

**機能の利用シーン**：エントリ編集画面・エントリ新規作成画面において、`widget: datetime`が指定されたフィールドに対して日時入力フィールドをレンダリングする。「Now」ボタンで現在日時の自動入力、「Clear」ボタンで値のクリアも可能。

**主要な処理内容**：
1. ブラウザネイティブの日時入力（datetime-local/date/time）のレンダリング
2. dayjs によるフォーマット変換（入力フォーマット ↔ 保存フォーマット）
3. UTCモード対応（picker_utc設定時）
4. 「Now」ボタンと「Clear」ボタンの提供
5. `{{now}}`テンプレート値の自動展開
6. date_format/time_format/formatによる柔軟なフォーマット設定

**関連システム・外部連携**：dayjs ライブラリ（customParseFormat, localizedFormat, utcプラグイン）に依存。

**権限による制御**：isDisabled プロップで読み取り専用化が可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 6 | エントリ編集画面 | 主機能 | 日時フィールドの入力・編集 |
| 7 | エントリ新規作成画面 | 主機能 | 日時フィールドの入力・編集 |

## 機能種別

UI入力コンポーネント / データ変換

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| field | object | Yes | フィールド設定（format, date_format, time_format, picker_utc等） | - |
| forID | string | No | input要素のid属性 | - |
| onChange | function | Yes | 値変更時のコールバック関数 | - |
| classNameWrapper | string | Yes | ラッパーのCSSクラス名 | - |
| setActiveStyle | function | Yes | フォーカス時のスタイル設定関数 | - |
| setInactiveStyle | function | Yes | ブラー時のスタイル設定関数 | - |
| value | string / object | No | 現在の日時値 | - |
| t | function | Yes | ローカライズ関数 | - |
| isDisabled | boolean | No | 無効化フラグ（デフォルト: false） | - |

### フィールド設定（config.yml）

| 設定名 | 型 | 説明 |
|--------|-----|------|
| format | string | 保存フォーマット（dayjs形式） |
| date_format | string / boolean | 日付フォーマット（trueの場合は'YYYY-MM-DD'） |
| time_format | string / boolean | 時刻フォーマット（trueの場合は'HH:mm'） |
| picker_utc | boolean | UTCモード有効化 |

### 入力データソース

エディタのコントロールペインから提供されるprops。

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| value | string | フォーマットされた日時文字列（空文字列も可） |

### 出力先

onChangeコールバック経由でReduxストアのエントリドラフトに反映される。

## 処理フロー

### 処理シーケンス

```
1. コンポーネントマウント
   └─ PropTypesの手動バリデーション実行（React 19対応）
   └─ value === '{{now}}' の場合、getNow()で現在日時に展開
2. getFormat()でフォーマット情報を決定
   └─ date_formatとtime_formatの組合せでinputType（datetime-local/date/time）を決定
   └─ userFormat（format設定）があれば優先
   └─ picker_utc時はZをエスケープ（[Z]化）
3. ユーザーが日時入力
   └─ onInputChange → handleChange
4. handleChange内でバリデーション・フォーマット変換
   └─ isValidDateで日時の妥当性チェック
   └─ dayjs(datetime, inputFormat).format(format) で保存フォーマットに変換
   └─ onChange(formattedValue) で値を通知
5. 「Now」ボタン押下
   └─ handleChange(getNow()) で現在日時を設定
6. 「Clear」ボタン押下
   └─ handleChange('') で値をクリア
```

### フローチャート

```mermaid
flowchart TD
    A[コンポーネント初期化] --> B{value === 'now'?}
    B -->|Yes| C[getNow で現在日時設定]
    B -->|No| D[通常表示]
    C --> D
    D --> E[getFormat でフォーマット決定]
    E --> F[input要素レンダリング]
    F --> G[ユーザー入力/ボタン操作]
    G --> H[handleChange]
    H --> I{日時妥当?}
    I -->|No| J[処理中断]
    I -->|Yes| K{空文字?}
    K -->|Yes| L["onChange('')"]
    K -->|No| M[dayjs変換]
    M --> N["onChange(formattedValue)"]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-57-01 | テンプレート展開 | 初期値が`{{now}}`の場合、現在日時に自動展開する | componentDidMount時 |
| BR-57-02 | フォーマット優先順位 | format設定 > date_format + time_formatの組合せ > デフォルト（YYYY-MM-DDTHH:mm:ss.SSSZ） | 常時 |
| BR-57-03 | inputType決定 | date_format=falseならtime、time_format=falseならdate、それ以外はdatetime-local | レンダリング時 |
| BR-57-04 | UTCモード | picker_utc=trueの場合、dayjs.utc()を使用し、「UTC」ラベルを表示する | picker_utc設定時 |
| BR-57-05 | Zエスケープ | UTC時にフォーマット文字列中のZを[Z]にエスケープ（dayjsのリテラル表記） | picker_utc && フォーマットにZ含む場合 |
| BR-57-06 | boolean型のフォーマット | date_format=trueなら'YYYY-MM-DD'、time_format=trueなら'HH:mm'に変換 | boolean型設定時 |

### 計算ロジック

- デフォルトフォーマット: `YYYY-MM-DDTHH:mm:ss.SSS[Z]`（UTC時）/ `YYYY-MM-DDTHH:mm:ss.SSSZ`（ローカル時）
- 入力フォーマット: datetime-local → `YYYY-MM-DDTHH:mm`、date → `YYYY-MM-DD`、time → `HH:mm`
- 変換: `dayjs(datetime, inputFormat).format(format)`

## データベース操作仕様

### 操作別データベース影響一覧

該当なし。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | 無効な日時 | isValidDateがfalseを返す場合 | handleChange処理を中断（値変更しない） |
| - | パースフォールバック | 指定フォーマットでのパース失敗 | dayjs(value)でフォーマットなしパースを試行 |

### リトライ仕様

該当なし。

## トランザクション仕様

該当なし。

## パフォーマンス要件

特になし。

## セキュリティ考慮事項

ブラウザネイティブのdate/time入力要素を使用しているため、不正な文字列入力のリスクは低い。

## 備考

- dayjsプラグイン（customParseFormat, localizedFormat, utc）が必要
- スキーマ定義でformat, date_format, time_format, picker_utcのプロパティ型を定義
- isDisabled=trueの場合、Now/Clearボタンは非表示になる

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | schema.js | `packages/decap-cms-widget-datetime/src/schema.js` | フィールドスキーマ定義（format: string, date_format: string/boolean, time_format: string/boolean, picker_utc: boolean） |
| 1-2 | index.js | `packages/decap-cms-widget-datetime/src/index.js` | Widget関数がname: 'datetime'とschemaを含む |

#### Step 2: フォーマットロジックを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | DateTimeControl.js | `packages/decap-cms-widget-datetime/src/DateTimeControl.js` | getFormat（84-123行目）: date_format/time_format/formatの優先順位とinputType決定ロジック |

**主要処理フロー**:
1. **5-8行目**: dayjsプラグイン読み込み（customParseFormat, localizedFormat, utc）
2. **11-13行目**: プラグイン適用
3. **75行目**: isUtcプロパティ（picker_utcからの初期化）
4. **77-82行目**: escapeZ - Zを[Z]にエスケープ
5. **84-123行目**: getFormat - フォーマット決定ロジックの核心部分
6. **125行目**: isValidDate - dayjs検証
7. **127-130行目**: getNow - UTC/ローカル現在日時取得
8. **132-144行目**: formatInputValue - 保存値→入力値への変換（フォールバック付き）
9. **146-157行目**: handleChange - 入力値→保存値への変換

#### Step 3: UIコンポーネントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | DateTimeControl.js | `packages/decap-cms-widget-datetime/src/DateTimeControl.js` | render（164-203行目）: input, UTCラベル, Now/Clearボタンの構成。Buttons関数コンポーネント（15-46行目） |

### プログラム呼び出し階層図

```
DecapCmsWidgetDatetime (index.js)
    |
    +-- Widget()
    |       └── { name: 'datetime', controlComponent, previewComponent, schema }
    |
    +-- DateTimeControl (DateTimeControl.js)
    |       +-- componentDidMount() - {{now}} テンプレート展開
    |       +-- getFormat() - フォーマット決定
    |       +-- formatInputValue() - 保存値→表示値変換
    |       +-- handleChange() - 表示値→保存値変換
    |       +-- getNow() - 現在日時取得
    |       +-- Buttons - Now/Clearボタン
    |       └── render() - <input type="datetime-local/date/time">
    |
    └── DateTimePreview (DateTimePreview.js)
            └── WidgetPreviewContainer で値を文字列表示
```

### データフロー図

```
[入力]                        [処理]                         [出力]

保存フォーマット値  ──> formatInputValue()              表示フォーマット値
(YYYY-MM-DDTHH:mm:ss.SSSZ)    dayjs変換                  (YYYY-MM-DDTHH:mm)
                                   |
                           ユーザー入力/Now/Clear
                                   |
                                   v
                            handleChange()
                            dayjs変換
                                   |
                                   v
                            onChange(保存フォーマット値)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| index.js | `packages/decap-cms-widget-datetime/src/index.js` | ソース | ウィジェットエクスポート定義 |
| DateTimeControl.js | `packages/decap-cms-widget-datetime/src/DateTimeControl.js` | ソース | 日時入力コントロール |
| DateTimePreview.js | `packages/decap-cms-widget-datetime/src/DateTimePreview.js` | ソース | プレビューコンポーネント |
| schema.js | `packages/decap-cms-widget-datetime/src/schema.js` | ソース | フィールドスキーマ定義 |
