JavaScript Dateオブジェクトに対して年月日時分秒それぞれ任意に加算減算した結果を返す

単にDateオブジェクトのset系メソッドが自動的に繰り上げ、繰り下げしてくれるのを流用しただけの簡単なものです。

```js:date_modify.js
'use strict';
Date.prototype.modify = function(obj) {
    if(isNaN(this) || typeof obj !== 'object') return this;
    const d = new Date(this);
    const b = {
        year  : d.getFullYear(),
        month : d.getMonth(),
        day   : d.getDate(),
        hour  : d.getHours(),
        minute: d.getMinutes(),
        second: d.getSeconds(),
    };
    for(let p in obj) {
        const n = parseInt(obj[p], 10);
        if(isNaN(n)) return new Date('');
        if(b[p] !== undefined) b[p] += n;
        else return new Date('');
    }
    d.setFullYear(b.year, b.month, b.day);
    d.setHours(b.hour, b.minute, b.second);
    return d;
}
```

####使用例####

```js:
// example

const date = new Date('2021/05/01 00:00:00'); // 元の日時
console.log(date);
    // Sat May 01 2021 00:00:00 GMT+0900 (日本標準時)

console.log(date.modify({month:-6})); // 6か月前
    // Sun Nov 01 2020 00:00:00 GMT+0900 (日本標準時)

console.log(date.modify({hour:300})); // 300時間後
    // Thu May 13 2021 12:00:00 GMT+0900 (日本標準時)

console.log(date.modify({year:2, day:50})); // 2年と50日後
    // Tue Jun 20 2023 00:00:00 GMT+0900 (日本標準時)

console.log(date.modify({minute:-35, second:-15})); // 35分15秒前
    // Fri Apr 30 2021 23:24:45 GMT+0900 (日本標準時)

// 6年5か月4日前 + 3時間2分1秒
const properties = {
    year  : -6,
    month : -5,
    day   : -4,
    hour  : 3,
    minute: 2,
    second: 1,
};
console.log(date.modify(properties));
    // Thu Nov 27 2014 03:02:01 GMT+0900 (日本標準時)

console.log(date); // ベースのDateオブジェクトは元のまま
    // Sat May 01 2021 00:00:00 GMT+0900 (日本標準時)

// 戻り値はDateオブジェクトなのでDateオブジェクトが持つ他のメソッドもそのまま繋げて使用可
console.log(date.modify({month:10, hour:5}).toISOString());
    // 2022-02-28T20:00:00.000Z

// 引数が無効な型の場合は
// 元のDateオブジェクトをそのまま返却
console.log(date.modify());
console.log(date.modify(''));
console.log(date.modify(12345));
    // Sat May 01 2021 00:00:00 GMT+0900 (日本標準時)

// 未定義のプロパティー名や有効なプロパティー名で値が不正な場合
console.log(date.modify({aaaa:1234}));
console.log(date.modify({year:'aaa'}));
    // Invalid Date

// 変数での指定は可
for(let i = 1; i <= 24; i++) {
    console.log(date.modify({month:i}));
}
/*
Tue Jun 01 2021 00:00:00 GMT+0900 (日本標準時)
Thu Jul 01 2021 00:00:00 GMT+0900 (日本標準時)
Sun Aug 01 2021 00:00:00 GMT+0900 (日本標準時)
Wed Sep 01 2021 00:00:00 GMT+0900 (日本標準時)
                :
                :
Wed Feb 01 2023 00:00:00 GMT+0900 (日本標準時)
Wed Mar 01 2023 00:00:00 GMT+0900 (日本標準時)
Sat Apr 01 2023 00:00:00 GMT+0900 (日本標準時)
Mon May 01 2023 00:00:00 GMT+0900 (日本標準時)
*/
```

----
####値の小数の扱いについて####
プロパティーに渡す値に小数が含まれる場合は小数部を単純に取り除いた整数として扱われるようにしています。
set系メソッドはもともと小数を渡しても小数部は無視された整数として扱われるので絶対値として渡すのであれば小数を含んだままでもとくに問題はないのですが、相対値として指定する場合は小数部が残っていると元の値によっては影響が出るケース、出ないケースが混在してしまうためです。

```js:
const d1 = new Date('2021/05/01 00:10:00'); // どちらも基準時刻は00:10:00
const d2 = new Date('2021/05/01 00:10:00');
d1.setMinutes(10 -15);   // -5
d2.setMinutes(10 -15.5); // -5.5
console.log(d1.toString()); // Fri Apr 30 2021 23:55:00 GMT+0900 (日本標準時)
console.log(d2.toString()); // Fri Apr 30 2021 23:55:00 GMT+0900 (日本標準時)
    // set系メソッドでは小数部は考慮されないので
    // どちらも-5として扱われ23:55:00となる

const d3 = new Date('2021/05/01 00:20:00'); // どちらも基準時刻は00:20:00
const d4 = new Date('2021/05/01 00:20:00');
d3.setMinutes(20 -15);   // 5
d4.setMinutes(20 -15.5); // 4.5
console.log(d3.toString()); // Sat May 01 2021 00:05:00 GMT+0900 (日本標準時)
console.log(d4.toString()); // Sat May 01 2021 00:04:00 GMT+0900 (日本標準時)
    // 相対値として渡した値の計算結果が負数にならない場合は
    // 小数部の有無が整数部の差異となって出てしまうので
    // 00:05:00と00:04:00で結果にも差異が出てしまう
```
 
----
####月の日数の違いによる繰り上がり繰り下がり動作について####

大の月の月末(例として5月31日)から1か月後や1か月前の日付を取得する場合、1か月後の場合は6月30日と7月1日の2通り、1か月前の場合は4月30日と5月1日の2通りの動作が考えられると思いますが、スクリプト内で使用しているsetMonth()メソッドでの動作結果は以下の通りでした。

```js:test.js
console.log(new Date('2021/05/31').modify({month:1}));
    // Thu Jul 01 2021 00:00:00 GMT+0900 (日本標準時)

console.log(new Date('2021/05/31').modify({month:-1}));
    // Sat May 01 2021 00:00:00 GMT+0900 (日本標準時)
```

PHPのDateTimeImmutableクラスのmodifyメソッドで同様の確認をしたところ、同じ結果が返ってきました。

```php:test.php
echo (new DateTimeImmutable('2021/05/31'))->modify('1 month')->format('Y-m-d H:i:s'),"\n";
    // 2021-07-01 00:00:00

echo (new DateTimeImmutable('2021/05/31'))->modify('-1 month')->format('Y-m-d H:i:s'),"\n";
    // 2021-05-01 00:00:00
```

1月31日の1か月後、3月31日の1か月前は共に3月3日となるようでした。
2月末は30日にも満たない分、繰越日数として影響が出るようですので気にかけておいたほうがいいかもしれません。

```js:test.js
console.log(new Date('2021/01/31').modify({month:1}));
    // Wed Mar 03 2021 00:00:00 GMT+0900 (日本標準時)

console.log(new Date('2021/03/31').modify({month:-1}));
    // Wed Mar 03 2021 00:00:00 GMT+0900 (日本標準時)
```

PHPも同様にどちらも3月3日となるようです。

```php:test.php
echo (new DateTimeImmutable('2021/01/31'))->modify('1 month')->format('Y-m-d H:i:s'),"\n";
    // 2021-03-03 00:00:00

echo (new DateTimeImmutable('2021/03/31'))->modify('-1 month')->format('Y-m-d H:i:s'),"\n";
    // 2021-03-03 00:00:00
```

PHPでも同様の結果になるのであればそのままの動作にしておいたほうが無難かと思いますので独自の補正処理の追加などは考えていません。