PostgreSQL 14 min read

PostgreSQLの日付計算 ― intervalの足し引き・ageで年齢・月末はdate_trunc

※この記事にはアフィリエイト広告(プロモーション)が含まれます。

PostgreSQL の日付の足し引きは + / - と interval で書きます。この記事の出力は PostgreSQL 18.6(Ubuntu 26.04、timezone は Asia/Tokyo)で採りました。

まず見る(結論)

SELECT current_date + 1;                              -- 明日(date)
SELECT current_date - 7;                              -- 7日前(date)
SELECT now() + interval '90 minutes';                 -- 90分後
SELECT date '2026-11-08' - date '2026-10-31';         -- 8(日数・integer)
SELECT age(date '2026-11-08', date '1990-11-09');     -- 35 years 11 mons 29 days
SELECT date_trunc('month', current_date)::date;       -- 月初
SELECT (date_trunc('month', current_date) + interval '1 month - 1 day')::date;  -- 月末
  • datediff・date_diff・last_day は function ... does not exist になる
  • interval '1 M' は1か月ではなく1分。月は '1 month' か '1 mon' と書く

足し算・引き算と結果の型

式 結果 型
date '2026-11-08' + 1 2026-11-09 date
date '2026-11-08' + interval '1 day' 2026-11-09 00:00:00 timestamp
timestamp '2026-11-08 10:00' + 1 operator does not exist: timestamp without time zone + integer
'2026-11-08' + 1 invalid input syntax for type integer: "2026-11-08"
'2026-11-08'::date + 1 2026-11-09 date

date に interval を足した結果を日付で欲しいときは ::date で戻します。文字列には date '…' か '…'::date で型を付けます。

interval の書き方

SELECT timestamp '2026-11-08 23:30' + interval '45 minutes';    -- 2026-11-09 00:15:00
SELECT timestamp '2026-11-08' + interval '1 day 2 hours';       -- 2026-11-09 02:00:00
SELECT date '2026-11-08' + 3 * interval '1 week';               -- 2026-11-29 00:00:00
SELECT date '2026-11-08' + make_interval(days => 10);           -- 2026-11-18 00:00:00
SELECT date '2026-11-08' + interval '1 M';                      -- 2026-11-08 00:01:00

日数を変数や列で持っているときは n * interval '1 day' か make_interval(days => n) で組み立てます。(n || ' days')::interval でも通りますが、文字列を経由する必要はありません。

月末をまたぐ足し算

式 結果
date '2026-01-31' + interval '1 month' 2026-02-28
date '2026-01-31' + interval '2 months' 2026-03-31
上の1か月後にさらに1か月を足す 2026-03-28
date '2026-03-31' - interval '1 month' 2026-02-28
date '2024-02-29' + interval '1 year' 2025-02-28

1か月ずつ積み上げると 31 日が 28 日のまま戻りません。generate_series で月ごとの日付を作るときも同じです。

SELECT generate_series(date '2026-01-31', date '2026-06-30', interval '1 month')::date;
-- 2026-01-31 / 02-28 / 03-28 / 04-28 / 05-28 / 06-28

SELECT (date '2026-01-31' + n * interval '1 month')::date FROM generate_series(0, 4) n;
-- 2026-01-31 / 02-28 / 03-31 / 04-30 / 05-31

月末をそろえたいときは、上の2つ目のように起点に n か月を足します。

日数の差・時間の差

式 結果 型
date '2026-11-08' - date '2026-10-31' 8 integer
timestamp '2026-11-08 01:00' - timestamp '2026-10-31 23:30' 7 days 01:30:00 interval
timestamp '2027-03-01' - timestamp '2026-01-01' 424 days interval
extract(epoch FROM 上の 7 days 01:30:00) 610200(秒) numeric
extract(day FROM 上の 7 days 01:30:00) 7 numeric

timestamp の差は何日離れていても月や年に繰り上がらず、日数のまま返ります。秒や時間で比べたいときは extract(epoch FROM …) で秒に直します。

2026-11-08 00:00:01 と 2026-11-07 23:59:59 の差は、::date で日付にしてから引くと 1、そのまま引くと 00:00:02 です。暦の上で何日違うかを出すなら先に ::date にします。

age で年齢・月数

式 結果
age(date '2026-11-08', date '1990-11-09') 35 years 11 mons 29 days(誕生日の前日)
age(date '2026-11-08', date '1990-11-08') 36 years(誕生日当日)
age(date '2026-02-28', date '2026-01-31') 28 days
age(date '2026-03-01', date '2026-01-31') 1 mon 1 day
age(date '2026-02-28', date '2000-02-29') 25 years 11 mons 28 days
age(date '2026-03-01', date '2000-02-29') 26 years 1 day
age(date '2026-01-01', date '2026-11-08') -10 mons -7 days

age(後, 前) は年・月・日に分けた interval を返します。満年齢は extract(year FROM age(…)) で取り出します。上の1行目では 35 でした。経過月数は、年を 12 倍して月を足します。

SELECT extract(year FROM age(d2, d1)) * 12 + extract(month FROM age(d2, d1));
-- d1 = 2025-01-15, d2 = 2026-11-08 で 21

引数を1つだけ渡すと current_date の 0 時からの差になります。age(timestamp '2026-11-08 10:00') を 11 月 8 日に実行すると -10:00:00 でした。

月初・月末・週の初め

SELECT date_trunc('month', date '2026-11-08')::date;                         -- 2026-11-01
SELECT (date_trunc('month', date '2026-02-10') + interval '1 month - 1 day')::date;  -- 2026-02-28
SELECT (date_trunc('month', date '2024-02-10') + interval '1 month - 1 day')::date;  -- 2024-02-29
SELECT date_trunc('week', date '2026-11-08')::date;                          -- 2026-11-02(月曜)

date を date_trunc に渡すと、戻り値は timestamptz(2026-11-01 00:00:00+09)でした。日付が欲しいときは ::date を付けます。date_trunc('week', …) は ISO 週で月曜始まりです。2026-11-08(日)は extract(dow …) が 0、extract(isodow …) が 7 を返しました。

15分ごとのように任意の幅で丸めるときは date_bin('15 minutes', ts, timestamp '2000-01-01') を使います。10:22:30 は 10:15:00 になりました。

WHERE で日付の範囲を指定する

45分おきに10万行入れた timestamp 列(索引あり)で比べました。

条件 件数 EXPLAIN
created >= date '2026-10-02' AND created < date '2026-10-03' + 1 64 Index Only Scan
created::date BETWEEN '2026-10-02' AND '2026-10-03' 64 Seq Scan
date_trunc('day', created) = '2026-10-02' — Seq Scan
created BETWEEN '2026-10-02' AND '2026-10-03' 33 —

>= と <(終わりの日の翌日)で範囲を書きます。BETWEEN で時刻付きの列を日付だけで挟むと終わりの日がほぼ抜けることは timestamptz の記事に書きました。

UTC の日付・夏時間をまたぐ足し算

now() AT TIME ZONE 'UTC' は UTC の時刻を timestamp(タイムゾーンなし)で返します。JST の朝 5 時に実行すると、(now() AT TIME ZONE 'UTC')::date は前日の 2026-11-07、current_date は 2026-11-08 でした。

timestamptz に '1 day' と '24 hours' を足した結果は、夏時間が終わる日だけ違います。timezone が America/New_York のとき、2026-11-01 00:00-04 に足すと 1 day は 2026-11-02 00:00:00-05 でした。24 hours では 2026-11-01 23:00:00-05 です。Asia/Tokyo では両方同じです。

関連: PostgreSQLのtimestamptzとは / PostgreSQLの日付フォーマット / MySQLの日付計算

PostgreSQL をもっと深く知るための本

PostgreSQL実践入門 ──アーキテクチャ、運用監視、性能改善

技術評論社・2026年2月

アーキテクチャから運用監視・性能改善までを扱う。付録に PostgreSQL 18 の新機能と設定項目の差分がある。

Amazonで見る

[改訂3版]内部構造から学ぶPostgreSQL ―設計・運用計画の鉄則

技術評論社・2022年11月・PostgreSQL 14 ベース

内部構造の理解から、設計と運用計画の立て方を学ぶ。

Amazonで見る

Amazonのアソシエイトとして、FIRE SE は適格販売により収入を得ています。