できない.dev

日付に日数を足すには

日付に日数を足したり引いたりして、月末や年末をまたいだ日付を求める基本形を各言語で示す。
元の値を書き換える言語と新しい値を返す言語の違い、月を足すときの月末の扱い、夏時間で「24 時間後」と「1 日後」がずれる点までを扱う。

公開:

各言語見出しの横のバッジは検証状態を表す。実行確認済みはコードを実際に実行して確認したもの、静的確認は構文と公式 API ドキュメントで確認したものである。

Python 実行確認済み

from datetime import date, datetime, timedelta
 
d = date(2026, 8, 28)
print(d + timedelta(days=30))   # -> 2026-09-27(月末を超えた分は翌月へ繰り上がる)
print(d - timedelta(days=60))   # -> 2026-06-29
print(d)                        # -> 2026-08-28(元の値は変わらない)
 
t = datetime(2026, 8, 28, 21, 30)
print(t + timedelta(days=1, hours=6))  # -> 2026-08-30 03:30:00

date や datetime に timedelta を足すと、月末や年末をまたいだ繰り上がりを含めた新しい値が返る。
引くときは timedelta を引けばよく、元の値は変わらない。
timedelta には月や年の単位が無いため、「1 か月後」は標準ライブラリの timedelta だけでは表せない点に注意する。

JavaScript 実行確認済み

const d = new Date(Date.UTC(2026, 7, 28)); // 月は 0 始まり(7 = 8 月)
 
const later = new Date(d);                 // setUTCDate は元の Date を書き換えるので複製する
later.setUTCDate(later.getUTCDate() + 30); // 月末を超えた分は翌月へ繰り上がる
console.log(later.toISOString().slice(0, 10)); // -> 2026-09-27
 
const before = new Date(d);
before.setUTCDate(before.getUTCDate() - 60);
console.log(before.toISOString().slice(0, 10)); // -> 2026-06-29
console.log(d.toISOString().slice(0, 10));      // -> 2026-08-28

Date に日数を足す専用のメソッドは無く、setUTCDate(getUTCDate() + n) で日の値を増やす。
範囲を超えた分は翌月へ繰り上がる。
setUTCDate は呼び出した Date 自体を書き換えるので、元の日付を残したいときは new Date(d) で複製してから操作する。

TypeScript 実行確認済み

function addDays(base: Date, days: number): Date {
  const result = new Date(base.getTime()); // 引数の Date を書き換えない
  result.setUTCDate(result.getUTCDate() + days);
  return result;
}
 
const d = new Date("2026-08-28T00:00:00Z");
console.log(addDays(d, 30).toISOString().slice(0, 10));  // -> 2026-09-27
console.log(addDays(d, -60).toISOString().slice(0, 10)); // -> 2026-06-29
console.log(d.toISOString().slice(0, 10));               // -> 2026-08-28

計算は JavaScript と同じだが、引数で受け取った Date を直接書き換えると呼び出し元の値まで変わる。
関数の中で getTime() から新しい Date を作ってから setUTCDate を呼び、戻り値として返す形にしておくと副作用が残らない。

Go 実行確認済み

package main
 
import (
	"fmt"
	"time"
)
 
func main() {
	d := time.Date(2026, time.August, 28, 0, 0, 0, 0, time.UTC)
	fmt.Println(d.AddDate(0, 0, 30).Format("2006-01-02"))  // -> 2026-09-27
	fmt.Println(d.AddDate(0, 0, -60).Format("2006-01-02")) // -> 2026-06-29
 
	end := time.Date(2026, time.January, 31, 0, 0, 0, 0, time.UTC)
	fmt.Println(end.AddDate(0, 1, 0).Format("2006-01-02")) // -> 2026-03-03(2 月 31 日を正規化した結果)
}

time.Time の AddDate(年, 月, 日) に日数を渡すと新しい time.Time が返る。
月末を超える日付は正規化されるため、1 月 31 日に 1 か月を足すと 2 月 31 日ではなく 3 月 3 日になる。
月の加算で月末に揃えたい場合は自分で調整する必要がある。

Rust 実行確認済み

use chrono::{Days, Months, NaiveDate};
 
fn main() {
    let d = NaiveDate::from_ymd_opt(2026, 8, 28).unwrap();
    println!("{}", d.checked_add_days(Days::new(30)).unwrap()); // -> 2026-09-27
    println!("{}", d.checked_sub_days(Days::new(60)).unwrap()); // -> 2026-06-29
 
    let end = NaiveDate::from_ymd_opt(2026, 1, 31).unwrap();
    println!("{}", end.checked_add_months(Months::new(1)).unwrap()); // -> 2026-02-28(月末に丸める)
}

標準ライブラリには暦の日付型が無いため chrono を使う。
NaiveDate の checked_add_days と checked_sub_days は結果が範囲外になると None を返すので Option で受け取る。
checked_add_months は存在しない日付を月末に丸めるため、1 月 31 日の 1 か月後は 2 月 28 日になる。

Java 実行確認済み

import java.time.LocalDate;
 
public class Main {
    public static void main(String[] args) {
        LocalDate d = LocalDate.of(2026, 8, 28);
        d.plusDays(30);                        // 戻り値を捨てると何も起きない(LocalDate は不変)
        System.out.println(d);                 // -> 2026-08-28
        System.out.println(d.plusDays(30));    // -> 2026-09-27
        System.out.println(d.minusDays(60));   // -> 2026-06-29
 
        LocalDate end = LocalDate.of(2026, 1, 31);
        System.out.println(end.plusMonths(1)); // -> 2026-02-28(月末に丸める)
    }
}

LocalDate は不変オブジェクトで、plusDays や minusDays は新しい LocalDate を返す。
戻り値を受け取らずに d.plusDays(30); と書いても d は変わらないため、結果は必ず変数に代入する。
plusMonths は存在しない日付を月末に丸める。

C# 実行確認済み

var d = new DateTime(2026, 8, 28);
d.AddDays(30);                                            // 戻り値を捨てると d は変わらない
Console.WriteLine(d.ToString("yyyy-MM-dd"));              // -> 2026-08-28
Console.WriteLine(d.AddDays(30).ToString("yyyy-MM-dd"));  // -> 2026-09-27
Console.WriteLine(d.AddDays(-60).ToString("yyyy-MM-dd")); // -> 2026-06-29
 
var end = new DateTime(2026, 1, 31);
Console.WriteLine(end.AddMonths(1).ToString("yyyy-MM-dd")); // -> 2026-02-28(月末に丸める)

DateTime は値型で、AddDays は新しい DateTime を返す。
戻り値を捨てると元の変数は変わらないので、Java と同じく結果を代入して使う。
負の数を渡せば日数を引ける。
AddMonths は存在しない日付を月末に丸めるため、1 月 31 日の 1 か月後は 2 月 28 日になる。

つまずき

元の値が変わるかどうかは言語で分かれる。
JavaScript の setUTCDate は呼び出した Date 自体を書き換える一方、Java の plusDays と C# の AddDays は新しい値を返すだけで元の変数は変わらない。
後者で戻り値を捨てると、コンパイルエラーも警告も出ないまま日付が進まない。
Python の timedelta の加算、Go の AddDate、Rust の checked_add_days も新しい値を返す。

月を足すと月末の扱いが言語で違う

1 月 31 日に 1 か月を足した結果は言語によって異なる。
Go の AddDate(0, 1, 0) と JavaScript の setUTCMonth は 2 月 31 日を正規化して 3 月 3 日にするのに対し、Java の plusMonths、C# の AddMonths、Rust の checked_add_months は 2 月 28 日に丸める。
Python の timedelta には月の単位が無い。
締め日や請求日のように月単位で進める処理では、どちらの挙動が必要かを先に決めておく。

24 時間後と 1 日後は同じとは限らない

夏時間のある地域のローカル時刻で計算すると、切り替え日をまたぐ「24 時間後」と「1 日後」がずれる。
America/New_York で 2026 年 3 月 7 日 12:00 に 24 時間を足すと翌日の 13:00 になるが、日付として 1 日を足すと 12:00 のままである。
Go の Add(24 * time.Hour) と AddDate(0, 0, 1)、JavaScript のミリ秒の加算と setDate の違いがこれにあたる。
上のコードが UTC で計算しているのは、この差を持ち込まないためである。

この記事は役立ちましたか?