※この記事にはアフィリエイト広告(プロモーション)が含まれます。
MySQL の日付の足し引きは DATE_ADD / INTERVAL、差は DATEDIFF / TIMESTAMPDIFF で計算します。この記事の出力は MySQL 8.4.11(Ubuntu 26.04)で採りました。
まず見る(結論)
SELECT DATE_ADD('2026-10-31', INTERVAL 1 DAY); -- 2026-11-01
SELECT '2026-10-31' - INTERVAL 7 DAY; -- 2026-10-24
SELECT DATEDIFF('2026-11-01', '2026-10-31'); -- 1(後 − 前)
SELECT TIMESTAMPDIFF(DAY, '2026-10-31', '2026-11-01'); -- 1(前, 後 の順)
SELECT TIMESTAMPDIFF(YEAR, '1990-11-01', CURDATE()); -- 年齢
CURDATE() + 1は日付ではなく数値。2026-10-31 には20261032が返った
足し算・引き算
| 式 | 結果 |
|---|---|
DATE_ADD('2026-01-31', INTERVAL 1 MONTH) |
2026-02-28 |
DATE_ADD('2026-01-31', INTERVAL 2 MONTH) |
2026-03-31 |
| 上の1か月後にさらに1か月を足す | 2026-03-28 |
DATE_SUB('2026-03-31', INTERVAL 1 MONTH) |
2026-02-28 |
DATE_ADD('2024-02-29', INTERVAL 1 YEAR) |
2025-02-28 |
DATE_ADD('2026-10-31 23:30:00', INTERVAL 45 MINUTE) |
2026-11-01 00:15:00 |
DATE_ADD('2026-10-31', INTERVAL 90 MINUTE) |
2026-10-31 01:30:00(日付に時刻が付く) |
DATE_ADD('2026-10-31 10:00', INTERVAL '1:30' HOUR_MINUTE) |
2026-10-31 11:30:00 |
月を足して存在しない日になると、その月の末日に丸められます。1か月ずつ足していくと 31 日が 28 日に変わったまま戻りません。毎月の末日が欲しいときは、起点の日付から INTERVAL n MONTH を足すか、LAST_DAY を使います。
+ 1 は日付の計算にならない
SELECT CURDATE() + 1, NOW() + 1, '2026-10-31' + 1;
20261032 | 20261031013507 | 2027
INTERVAL を付けない + は数値の足し算です。文字列の '2026-10-31' + 1 は先頭の 2026 だけを数値にして 2027 を返しました。出たのは警告 1292 だけです。
不正な日付は NULL になる
DATE_ADD('2026-02-30', INTERVAL 1 DAY) は NULL で、警告 1292 Incorrect datetime value: '2026-02-30' だけが出ました。'2026/10/31' と '20261031' は日付として通りましたが、/ 区切りには非推奨の警告 4095 が出ます。
DATEDIFF と TIMESTAMPDIFF
| 式 | 結果 |
|---|---|
DATEDIFF('2026-10-31 23:59:59', '2026-10-31 00:00:00') |
0 |
DATEDIFF('2026-11-01 00:00:01', '2026-10-31 23:59:59') |
1 |
TIMESTAMPDIFF(DAY, '2026-10-31 23:59:59', '2026-11-01 00:00:01') |
0 |
TIMESTAMPDIFF(SECOND, '2026-10-31 23:59:59', '2026-11-01 00:00:01') |
2 |
DATEDIFF('2026-10-01', '2026-10-31') |
-30 |
DATEDIFF(後, 前) は時刻を捨てて日付の差を返します。TIMESTAMPDIFF(単位, 前, 後) は時刻まで見て、単位に満たない分を切り捨てます。引数を逆にすると、どちらも負の値になります。
月数・年齢
| 式 | 結果 |
|---|---|
TIMESTAMPDIFF(MONTH, '2026-01-31', '2026-02-28') |
0 |
TIMESTAMPDIFF(MONTH, '2026-01-15', '2026-02-15') |
1 |
TIMESTAMPDIFF(YEAR, '1990-11-01', '2026-10-31') |
35(誕生日の前日) |
TIMESTAMPDIFF(YEAR, '1990-10-31', '2026-10-31') |
36(誕生日当日) |
TIMESTAMPDIFF(YEAR, '2000-02-29', '2026-02-28') |
25 |
TIMESTAMPDIFF(YEAR, '2000-02-29', '2026-03-01') |
26 |
時間の差(TIMEDIFF)
TIMEDIFF('2026-11-01 01:00:00', '2026-10-31 23:30:00') は 01:30:00 です。TIME 型の上限があり、TIMEDIFF('2026-12-31 00:00:00', '2026-01-01 00:00:00') は 838:59:59 で止まって警告 1292 が出ました。日付だけの TIMEDIFF('2026-11-01', '2026-10-31') は 00:00:00 でした(警告 1292)。長い期間は TIMESTAMPDIFF(SECOND, ...) で秒数として取ります。
月初・月末・週の初め
SELECT LAST_DAY('2026-02-10'); -- 2026-02-28
SELECT DATE_FORMAT('2026-10-31', '%Y-%m-01'); -- 2026-10-01(月初)
SELECT LAST_DAY('2026-10-31') + INTERVAL 1 DAY; -- 2026-11-01(翌月1日)
SELECT '2026-10-31' - INTERVAL WEEKDAY('2026-10-31') DAY; -- 2026-10-26(その週の月曜)
WEEKDAY は月曜が 0、DAYOFWEEK は日曜が 1 で、2026-10-31(土)はそれぞれ 5 と 7 でした。
WHERE で日付の範囲を指定する
45分おきに1000行入れた datetime 列で件数を比べました。
| 条件 | 件数 |
|---|---|
created BETWEEN '2026-10-02' AND '2026-10-03' |
33 |
created >= '2026-10-02' AND created < '2026-10-03' + INTERVAL 1 DAY |
64 |
DATE(created) BETWEEN '2026-10-02' AND '2026-10-03' |
64 |
BETWEEN の終わりの '2026-10-03' は 2026-10-03 00:00:00 として比べられるため、10月3日の分がほぼ抜けました(BETWEEN の記事)。DATE(created) で包むと、EXPLAIN では type が index(全件 1000 行)になりました。>= と < で書いた場合は range(32 行)でした。列を関数で包まず、範囲で書きます。
タイムゾーン
NOW() はサーバーの time_zone(既定は SYSTEM)の時刻で、JST のサーバーでは UTC_TIMESTAMP() と 9 時間ずれました。CONVERT_TZ('2026-10-31 00:00:00', '+00:00', '+09:00') は 2026-10-31 09:00:00 を返しましたが、'UTC'・'Asia/Tokyo' のような名前で書くと NULL でした。mysql.time_zone_name が0行で、タイムゾーン表が読み込まれていないためです。