MySQL 10 min read

MySQLの日付計算 ― DATE_ADDの月末・DATEDIFFとTIMESTAMPDIFFの違い

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

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行で、タイムゾーン表が読み込まれていないためです。

関連: MySQLのDATE_FORMAT / MySQLのBETWEEN / MySQLのEXPLAIN

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

MySQL徹底入門 第5版 MySQL 8.4 LTS対応

翔泳社・2025年6月・MySQL 8.4 LTS 対応

MySQL 8.4 に対応した入門書。日本MySQLユーザ会のメンバーが執筆している。

Amazonで見る

MySQL運用・管理[実践]入門 〜安全かつ高速にデータを扱う内部構造・動作原理を学ぶ

技術評論社・2024年5月

内部構造と動作原理から、運用・管理の考え方を学ぶ。

Amazonで見る

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