※この記事にはアフィリエイト広告(プロモーション)が含まれます。
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の日付計算