Twigで日付を操る!PHP連携&カスタムフィルターで自由自在に表示

2025-05-26

  1. フィルターとしての date
    これは最も一般的に使用される形式で、日付や時刻をフォーマットするために使われます。PHPの date() 関数と非常に似ており、様々なフォーマット指定子を使って、日付や時刻を希望の文字列形式に変換することができます。

    使い方

    {{ your_date_variable|date("Y-m-d H:i:s") }}
    
    • your_date_variable: フォーマットしたい日付/時刻データ(PHPの DateTime オブジェクト、UNIXタイムスタンプ、日付文字列など)
    • "Y-m-d H:i:s": フォーマット文字列。Y は年、m は月、d は日、H は時、i は分、s は秒を表します。PHPの date() 関数で使用できるすべてのフォーマット指定子が利用可能です。
    {# 現在のタイムスタンプをフォーマット #}
    {{ "now"|date("Y年m月d日 H時i分s秒") }} {# 例: 2023年10月27日 15時30分45秒 #}
    
    {# 特定の日付文字列をフォーマット #}
    {{ "2023-01-15"|date("F j, Y") }} {# 例: January 15, 2023 #}
    
    {# DateTimeオブジェクトをフォーマット (PHPから渡された場合) #}
    {% set article_published_at = date("2024-05-20 10:00:00") %} {# Twigのdate関数でDateTimeオブジェクトを生成 #}
    <p>公開日: {{ article_published_at|date("Y/m/d") }}</p> {# 例: 2024/05/20 #}
    
  2. 関数としての date
    これは、日付/時刻を表す DateTime オブジェクトを生成するために使用されます。特に、Twigテンプレート内で特定の日付や現在の日付に基づいて計算や比較を行いたい場合に便利です。

    使い方

    {% set now = date() %} {# 現在の日時を示すDateTimeオブジェクトを生成 #}
    {% set specific_date = date("2023-12-31") %} {# 特定の日付のDateTimeオブジェクトを生成 #}
    {% set another_date = date("yesterday") %} {# 相対的な日付も指定可能 #}
    

    引数には、PHPの strtotime() 関数が解釈できるあらゆる日付/時刻文字列を指定できます。引数を省略すると、現在の日時を表す DateTime オブジェクトが生成されます。


    {% set event_date = date("2025-06-01") %}
    {% set today = date() %}
    
    {% if event_date > today %}
        <p>イベントはまだ開催されていません。</p>
    {% else %}
        <p>イベントは終了しました。</p>
    {% endif %}
    
    {% set one_week_later = date("+1 week") %}
    <p>1週間後: {{ one_week_later|date("Y-m-d") }}</p>
    
  • 関数としての date (date(...))
    Twigテンプレート内でDateTimeオブジェクトを生成し、日付/時刻の操作や比較を行いたいときに使います。
  • フィルターとしての date (|date)
    既存の日付/時刻データをフォーマットして文字列として表示したいときに使います。


フォーマット文字列の誤り

これは最も一般的なエラーの一つです。PHPの date() 関数と同じフォーマット指定子を使用しますが、間違った指定子を使ったり、大文字小文字を間違えたりすることがあります。

よくある間違いの例

  • H (24時間表記の時、例: 15) の代わりに h (12時間表記の時、例: 03) を使いたいのに間違える。
  • M (英語の月略称、例: Jan) の代わりに m (数字の月、例: 01) を使いたいのに間違える。
  • Y (年) の代わりに y (2桁の年) を使うべきなのに間違える。

エラーの症状

  • 日付が全く表示されない(通常は他のエラーが原因ですが)。
  • 期待と異なる形式で日付が表示される。

トラブルシューティング

  • 大文字小文字を注意する
    特に MmHh など、大文字小文字で意味が異なる指定子に注意してください。
  • シンプルなフォーマットでテストする
    Y-m-d H:i:s のように最も基本的なフォーマットで表示してみて、データが正しく渡されているか確認します。

無効な日付/時刻データが渡される

date フィルターに有効な日付/時刻データ(DateTimeオブジェクト、UNIXタイムスタンプ、解釈可能な日付文字列など)が渡されない場合、期待通りの結果が得られません。

よくある間違いの例

  • Twigの date() 関数に strtotime() で解釈できない文字列を渡している。
  • PHP側で日付の処理に失敗し、Twigに誤ったデータが渡されている。
  • データベースから取得した日付フィールドが null や空文字列である。

エラーの症状

  • InvalidArgumentException など、PHPレベルでのエラーが発生することがある。
  • 日付が 1970-01-01 のような初期値になる(UNIXタイムスタンプが0の場合)。
  • 日付が空欄になる。

トラブルシューティング

  • デフォルト値の指定
    もし日付データが null になる可能性があるなら、default フィルターで代替値を指定することを検討します。
    {{ your_date_variable|default('now')|date("Y-m-d") }}
    
    あるいは、条件分岐で表示を制御します。
    {% if your_date_variable %}
        {{ your_date_variable|date("Y-m-d") }}
    {% else %}
        <p>日付は未定です。</p>
    {% endif %}
    
  • PHP側のデータソースを確認する
    データベースからのデータであれば、SQLクエリやORMの設定を確認し、正しく日付が取得されているか確認します。
  • dump() 関数で変数の中身を確認する
    {{ dump(your_date_variable) }}
    
    これで、your_date_variable がどのようなデータ型でどのような値を持っているかを確認できます。null や空文字列、予期しない文字列がないかチェックしてください。

タイムゾーンの問題

Twigはサーバーのデフォルトタイムゾーンを使用するか、設定されたタイムゾーンに従います。明示的にタイムゾーンを指定しない場合、表示される時間が期待と異なることがあります。

よくある間違いの例

  • ユーザーごとに異なるタイムゾーンで時間を表示したい。
  • サーバーがUTCタイムゾーンに設定されているが、表示はJST(日本標準時)を期待している。

エラーの症状

  • 表示される時間が数時間ずれている。

トラブルシューティング

  • UTCで処理し、表示時に変換する
    データベースにはUTCで保存し、表示時のみユーザーのタイムゾーンに変換するというのが一般的なプラクティスです。
  • PHPのデフォルトタイムゾーンを確認/設定する
    PHPの php.inidate.timezone を設定するか、スクリプトの先頭で date_default_timezone_set() 関数を使用します。
    date_default_timezone_set('Asia/Tokyo');
    
  • Twigのタイムゾーン設定を確認/設定する
    アプリケーションのTwig環境設定で、デフォルトのタイムゾーンを設定できます。
    // Symfonyの場合 (config/packages/twig.yaml など)
    twig:
        date:
            timezone: 'Asia/Tokyo' # または 'UTC', 'America/Los_Angeles' など
    
    あるいは、個々の date フィルターで指定することもできます。
    {{ your_date_variable|date("Y-m-d H:i:s", "Asia/Tokyo") }}
    

date 関数と date フィルターの混同

前述の通り、date には関数とフィルターの2つの使い方があります。これらを混同するとエラーになります。

よくある間違いの例

  • 既に DateTime オブジェクトである変数に対して、再度 date() 関数を適用しようとする。
  • date() のように関数として呼び出すべきところで |date フィルターとして使ってしまう。

エラーの症状

  • Twig_Error_Syntax: Unknown "date" filter. のように、フィルターが見つからないというエラー。
  • Twig_Error_SyntaxTwig_Error_Runtime のようなエラーが発生する。

トラブルシューティング

  • エラーメッセージをよく読む
    エラーメッセージは、関数とフィルターのどちらで問題が発生しているかを示してくれることが多いです。
  • 目的を明確にする
    • 既存の日付データを表示/フォーマットしたいなら |date フィルター
    • Twig内で新しいDateTimeオブジェクトを生成したいなら date() 関数

Twigテンプレートのキャッシュが原因で、PHPコードやTwigテンプレートの変更が反映されないことがあります。

エラーの症状

  • 以前のエラーメッセージが残り続けている。
  • コードを修正したはずなのに、表示が変わらない。
  • デバッグモードを有効にする
    開発中はキャッシュを無効にするか、自動で再コンパイルされるように設定しておくと便利です。


現在の日付と時刻の表示

最も基本的な使い方です。"now" という文字列は、PHPの strtotime() 関数と同様に、現在の日時を表す特殊なキーワードとして認識されます。

{# 現在の年月日と時刻を表示 #}
<p>現在日時: {{ "now"|date("Y年m月d日 H時i分s秒") }}</p>

{# 短い形式で表示 #}
<p>今日の日付: {{ "now"|date("Y/m/d") }}</p>

{# 曜日を含めて表示 (ローカライズに依存する場合があります) #}
<p>今日の曜日: {{ "now"|date("l") }}</p> {# 例: Friday (英語) #}

出力例

現在日時: 2025年05月25日 22時29分37秒
今日の日付: 2025/05/25
今日の曜日: Saturday

変数に格納された日付のフォーマット

データベースから取得した日付や、PHP側で生成した DateTime オブジェクトなどをTwigで表示する際に使用します。

// PHP側 (例: Symfonyコントローラー)
// データベースから取得したと仮定
$article = [
    'title' => 'Twigの日付機能',
    'publishedAt' => new DateTime('2024-03-10 14:30:00'),
    'updatedAt' => null // 更新されていない場合
];

return $this->render('article/show.html.twig', [
    'article' => $article,
]);
{# Twigテンプレート (article/show.html.twig) #}

<h2>{{ article.title }}</h2>

<p>公開日: {{ article.publishedAt|date("Y年m月d日 H:i") }}</p>

{# 更新日がある場合のみ表示する例 #}
{% if article.updatedAt %}
    <p>最終更新日: {{ article.updatedAt|date("Y年m月d日 H:i") }}</p>
{% else %}
    <p>最終更新日: なし</p>
{% endif %}

{# もしpublishedAtがnullになる可能性があるなら、defaultフィルターで代替値を指定 #}
<p>公開日 (安全版): {{ article.publishedAt|default('未定')|date("Y/m/d") }}</p>
{# 上記の例では '未定' という文字列がdateフィルターに渡され、エラーになる可能性があります。
   より安全なのは、Twigのdate関数でデフォルトのDateTimeオブジェクトを生成することです。 #}
<p>公開日 (より安全版): {{ (article.publishedAt ?: date('now'))|date("Y/m/d") }}</p>

出力例

<h2>Twigの日付機能</h2>

<p>公開日: 2024年03月10日 14:30</p>

<p>最終更新日: なし</p>

<p>公開日 (安全版): 2024/03/10</p>
<p>公開日 (より安全版): 2024/03/10</p>

日付の比較と条件分岐

date() 関数を使って DateTime オブジェクトを生成し、それらを比較することで条件に応じた表示が可能です。

{% set event_date = date("2025-06-15 10:00:00") %}
{% set current_date = date() %} {# 現在の日時 #}

<h3>イベント情報</h3>

{% if event_date > current_date %}
    <p>イベント開催まで: {{ (event_date.timestamp - current_date.timestamp)|date("G時間i分") }}</p>
    <p>(イベントは{{ event_date|date("Y年m月d日") }}に開催予定です)</p>
{% elseif event_date == current_date %}
    <p>イベントは本日開催です!</p>
{% else %}
    <p>このイベントは終了しました ({{ event_date|date("Y年m月d日") }})。</p>
{% endif %}

出力例 (現在の時刻によって変動)

現在の時刻が 2025-05-25 22:29:37 の場合:

<h3>イベント情報</h3>

<p>イベント開催まで: 483時間30分</p>
<p>(イベントは2025年06月15日に開催予定です)</p>

timestampプロパティを使ってUNIXタイムスタンプを取得し、差分を計算しています。その差分をdateフィルターでフォーマットすることで、残り時間を表示しています。

相対的な日付の表示

date() 関数や date フィルターに "tomorrow", "yesterday", "+1 day", "-1 week" などの相対的な日付文字列を渡すことができます。

<p>明日の日付: {{ date("tomorrow")|date("Y-m-d") }}</p>
<p>先週の今日: {{ date("-1 week")|date("Y-m-d") }}</p>
<p>来月の1日: {{ date("first day of next month")|date("Y-m-d") }}</p>
<p>私の誕生日 (1990年5月25日) から何日経ったか: {{ (date("now").timestamp - date("1990-05-25").timestamp) / (60 * 60 * 24) | round(0, 'floor') }}</p>

出力例 (現在の時刻によって変動)

現在の時刻が 2025-05-25 の場合:

明日の日付: 2025-05-26
先週の今日: 2025-05-18
来月の1日: 2025-06-01
私の誕生日 (1990年5月25日) から何日経ったか: 12781 日

特定のタイムゾーンで日付を表示したい場合に、date フィルターの第2引数でタイムゾーンを指定できます。

{% set utc_time = date("2024-07-20 10:00:00 UTC") %}

<p>UTC 時間: {{ utc_time|date("Y/m/d H:i:s T") }}</p>
<p>東京時間: {{ utc_time|date("Y/m/d H:i:s T", "Asia/Tokyo") }}</p>
<p>ロンドン時間: {{ utc_time|date("Y/m/d H:i:s T", "Europe/London") }}</p>

出力例

UTC 時間: 2024/07/20 10:00:00 UTC
東京時間: 2024/07/20 19:00:00 JST
ロンドン時間: 2024/07/20 11:00:00 BST

Tはタイムゾーンの略称を表示するフォーマット指定子です。



PHP側での日付処理(推奨されるアプローチ)

Twig はプレゼンテーション層を担当すべきであり、複雑なロジックはPHP側(コントローラー、サービス、エンティティなど)で行うのがベストプラクティスです。日付の計算、フォーマット、比較などはPHPで実行し、Twigには整形済みの文字列や、Twigの date フィルターが容易に扱える DateTime オブジェクトを渡すのが一般的です。

メリット

  • 強力な日付API
    PHPの DateTime および DateTimeImmutable クラスは非常に強力で、Twig の date 関数が提供する機能以上の柔軟性があります(例: DateInterval を使った期間の計算、タイムゾーンの厳密な制御など)。
  • パフォーマンス
    複雑な日付計算はPHPで行う方が高速です。
  • テストの容易性
    PHP側で日付ロジックを単体テストできます。
  • ロジックとプレゼンテーションの分離
    コードが整理され、管理しやすくなります。


// PHP側 (例: Symfonyコントローラー、またはサービス)
use DateTime;
use DateTimeImmutable;
use DateInterval;

class ArticleController
{
    public function show($articleId)
    {
        // データベースから記事を取得 (仮定)
        $article = [
            'title' => 'Twigの日付機能',
            'publishedAt' => new DateTimeImmutable('2024-03-10 14:30:00'),
        ];

        // PHP側でフォーマット済みの文字列を渡す
        $formattedPublishedDate = $article['publishedAt']->format('Y年m月d日 H時i分');

        // PHP側で DateTime オブジェクトを渡し、Twigでフォーマット
        $futureDate = new DateTimeImmutable('+3 days');
        $pastDate = new DateTimeImmutable('-1 month');

        // 差分計算などの複雑なロジックもPHPで
        $interval = $article['publishedAt']->diff(new DateTimeImmutable());
        $daysAgo = $interval->days;

        return $this->render('article/show.html.twig', [
            'article' => $article,
            'formattedPublishedDate' => $formattedPublishedDate,
            'futureDate' => $futureDate,
            'pastDate' => $pastDate,
            'daysAgo' => $daysAgo,
        ]);
    }
}
{# Twigテンプレート #}

<h2>{{ article.title }}</h2>

<p>公開日 (PHPでフォーマット済み): {{ formattedPublishedDate }}</p>

{# PHPで作成したDateTimeオブジェクトをTwigでフォーマット #}
<p>3日後の日付: {{ futureDate|date("Y/m/d") }}</p>
<p>1ヶ月前の日付: {{ pastDate|date("Y/m/d") }}</p>

<p>公開から約 {{ daysAgo }} 日が経過しました。</p>

Twigのカスタムフィルター/関数

Twigの標準 date フィルターだけでは実現できない、特定の表示形式やロジックが必要な場合、カスタムフィルターや関数を作成することができます。これは、日付関連のロジックがTwigテンプレート内で頻繁に再利用される場合に特に有効です。

ユースケースの例

  • 多言語対応
    date フィルターだけでは難しい、各言語の文化に合わせた日付表示。
  • 特定の業務ロジックに基づいた日付表示
    例: 「承認期限まで残りN日」
  • 「X分前」「Y時間前」のような相対的な時間表示 (Human-friendly date)
    TwitterやFacebookのような表示。

作成方法 (Symfonyの例)

  1. Twig Extension クラスを作成

// src/Twig/AppExtension.php namespace App\Twig;

use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
use Twig\TwigFunction;
use DateTimeInterface;

class AppExtension extends AbstractExtension
{
    public function getFilters(): array
    {
        return [
            new TwigFilter('time_ago', [$this, 'timeAgoFilter']),
        ];
    }

    public function getFunctions(): array
    {
        return [
            new TwigFunction('get_localized_date', [$this, 'getLocalizedDate']),
        ];
    }

    public function timeAgoFilter(DateTimeInterface $dateTime): string
    {
        $now = new \DateTimeImmutable();
        $interval = $now->diff($dateTime);

        if ($interval->y >= 1) {
            return $interval->y . '年前';
        } elseif ($interval->m >= 1) {
            return $interval->m . 'ヶ月前';
        } elseif ($interval->d >= 1) {
            return $interval->d . '日前';
        } elseif ($interval->h >= 1) {
            return $interval->h . '時間前';
        } elseif ($interval->i >= 1) {
            return $interval->i . '分前';
        } else {
            return 'たった今';
        }
    }

    public function getLocalizedDate(DateTimeInterface $dateTime, string $locale = 'ja'): string
    {
        // ロケールに基づいたより複雑な日付フォーマットを実装
        // 例: IntlDateFormatter を使用
        $formatter = new \IntlDateFormatter(
            $locale,
            \IntlDateFormatter::FULL,
            \IntlDateFormatter::FULL,
            null, // タイムゾーンはTwig設定に任せるかここで指定
            \IntlDateFormatter::MEDIUM,
            'yyyy年M月d日 (E) HH時mm分'
        );
        return $formatter->format($dateTime);
    }
}
```
  1. Twigテンプレートで使用
    {% set article_published_at = date('2024-05-20 10:30:00') %}
    {% set comment_posted_at = date('-5 minutes') %}
    
    <p>記事の公開: {{ article_published_at|time_ago }}</p> {# 例: 5日前 #}
    <p>コメントの投稿: {{ comment_posted_at|time_ago }}</p> {# 例: 5分前 #}
    
    <p>ローカライズされた日付: {{ get_localized_date(article_published_at, app.request.locale) }}</p> {# 例: 2024年5月20日 (月) 10時30分 #}
    

表示される日付がユーザーのタイムゾーンに強く依存する場合や、動的な日付表示(例: カウントダウンタイマー)が必要な場合、JavaScript を利用することも有効な代替手段です。

メリット

  • サーバー負荷の軽減
    サーバー側で日付処理を行う必要が減る。
  • 動的な更新
    ページリロードなしに、日付や時間がリアルタイムで更新される。
  • ユーザーのタイムゾーン
    クライアントのブラウザ設定に基づいて時間を表示できる。

ユースケースの例

  • 日付ピッカーなどのUIコンポーネント。
  • イベントの開始時刻をユーザーのタイムゾーンで表示。
  • 「あとN時間N分」のようなリアルタイム更新される残り時間。
{# TwigテンプレートでISO形式の日付文字列を出力 #}
<p>イベント開始: <span id="event-start-time" data-iso-date="{{ event_date|date(constant('DATE_ATOM')) }}"></span></p>
<p>残り時間: <span id="countdown"></span></p>

<script>
    document.addEventListener('DOMContentLoaded', function() {
        const eventStartTimeSpan = document.getElementById('event-start-time');
        const isoDateString = eventStartTimeSpan.dataset.isoDate;
        const eventDate = new Date(isoDateString); // JavaScriptのDateオブジェクトに変換

        // ローカルタイムゾーンで表示
        eventStartTimeSpan.textContent = eventDate.toLocaleString();

        // カウントダウンの例
        const countdownSpan = document.getElementById('countdown');

        function updateCountdown() {
            const now = new Date();
            const diff = eventDate.getTime() - now.getTime(); // ミリ秒単位の差分

            if (diff <= 0) {
                countdownSpan.textContent = "イベントは終了しました!";
                clearInterval(countdownInterval);
                return;
            }

            const days = Math.floor(diff / (1000 * 60 * 60 * 24));
            const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
            const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
            const seconds = Math.floor((diff % (1000 * 60)) / 1000);

            countdownSpan.textContent = `${days}${hours}時間 ${minutes}${seconds}秒`;
        }

        const countdownInterval = setInterval(updateCountdown, 1000);
        updateCountdown(); // 初回表示
    });
</script>
  • ユーザーのタイムゾーン/リアルタイム更新
    JavaScriptを検討します。Twigは初期データを提供し、JSがクライアント側で動的に処理します。
  • 複雑なロジック/再利用性
    PHP側で処理を行うか、Twigのカスタムフィルター/関数を作成するのが最も良い方法です。これにより、コードの分離と再利用性が向上します。
  • 基本
    Twigの date フィルター/関数は、シンプルなフォーマットや基本的な比較に適しています。