Saya setuju dengan sentimen OP bahwa ini kontra-intuitif dan membuat frustrasi, tetapi begitu juga menentukan apa +1 month
artinya dalam skenario di mana ini terjadi. Pertimbangkan contoh berikut:
Anda mulai dengan 31-01-2015 dan ingin menambahkan sebulan 6 kali untuk mendapatkan siklus penjadwalan untuk mengirim buletin email. Dengan mempertimbangkan ekspektasi awal OP, ini akan menghasilkan:
- 2015-01-31
- 2015-02-28
- 2015-03-31
- 2015-04-30
- 2015-05-31
- 2015-06-30
Segera, perhatikan bahwa +1 month
maksud kami adalahlast day of month
atau, sebagai alternatif, menambahkan 1 bulan per iterasi tetapi selalu mengacu pada titik awal. Alih-alih menafsirkan ini sebagai "hari terakhir bulan itu", kita dapat membacanya sebagai "hari ke-31 bulan depan atau hari terakhir tersedia dalam bulan itu". Ini berarti kami melompat dari 30 April ke 31 Mei, bukan ke 30 Mei. Perhatikan bahwa ini bukan karena ini adalah "hari terakhir bulan" tetapi karena kami ingin "yang terdekat dengan tanggal mulai bulan."
Jadi, misalkan salah satu pengguna kami berlangganan buletin lain untuk memulai 2015-01-30. Untuk apa tanggal intuitif itu +1 month
? Satu interpretasi adalah "hari ke-30 bulan depan atau paling dekat tersedia" yang akan menghasilkan:
- 2015-01-30
- 2015-02-28
- 2015-03-30
- 2015-04-30
- 2015-05-30
- 2015-06-30
Ini akan baik-baik saja kecuali jika pengguna kami mendapatkan kedua buletin pada hari yang sama. Anggaplah ini adalah masalah sisi penawaran, bukan sisi permintaan. Kami tidak khawatir pengguna akan terganggu dengan mendapatkan 2 buletin di hari yang sama, tetapi server email kami tidak mampu membayar bandwidth untuk pengiriman dua kali lipat. banyak buletin. Dengan mengingat hal itu, kami kembali ke interpretasi lain dari "+1 bulan" sebagai "kirim pada hari kedua hingga terakhir setiap bulan" yang akan menghasilkan:
- 2015-01-30
- 2015-02-27
- 2015-03-30
- 2015-04-29
- 2015-05-30
- 2015-06-29
Sekarang kami telah menghindari tumpang tindih dengan set pertama, tetapi kami juga berakhir dengan April dan 29 Juni, yang tentunya cocok dengan intuisi asli kami yang +1 month
seharusnya kembali m/$d/Y
atau yang menarik dan sederhana m/30/Y
untuk semua bulan yang memungkinkan. Jadi sekarang mari kita pertimbangkan interpretasi ketiga +1 month
menggunakan kedua tanggal:
31 Januari
- 2015-01-31
- 2015-03-03
- 2015-03-31
- 2015-05-01
- 2015-05-31
- 01-07-2015
30 Januari
- 2015-01-30
- 2015-03-02
- 2015-03-30
- 2015-04-30
- 2015-05-30
- 2015-06-30
Di atas memiliki beberapa masalah. Februari dilewati, yang bisa menjadi masalah pada supply-end (katakanlah jika ada alokasi bandwidth bulanan dan Feb terbuang percuma dan Maret bertambah dua kali lipat) dan demand-end (pengguna merasa dicurangi pada Feb dan merasakan Maret ekstra sebagai upaya untuk memperbaiki kesalahan). Di sisi lain, perhatikan bahwa dua set tanggal:
- tidak pernah tumpang tindih
- selalu pada tanggal yang sama ketika bulan itu memiliki tanggal (jadi set 30 Januari terlihat cukup bersih)
- semuanya dalam 3 hari (dalam banyak kasus 1 hari) dari apa yang dapat dianggap sebagai tanggal yang "benar".
- semuanya setidaknya 28 hari (satu bulan lunar) dari penerus dan pendahulunya, sehingga didistribusikan dengan sangat merata.
Mengingat dua set terakhir, tidak akan sulit untuk hanya memutar kembali salah satu tanggal jika jatuh di luar bulan berikutnya yang sebenarnya (jadi putar kembali ke 28 Feb dan 30 April di set pertama) dan tidak kehilangan waktu tidur selama set pertama. tumpang tindih dan perbedaan sesekali dari pola "hari terakhir bulan" vs "hari kedua hingga hari terakhir bulan". Tapi mengharapkan perpustakaan untuk memilih antara "paling cantik / alami", "interpretasi matematis dari 31/02 dan bulan lainnya melimpah", dan "relatif terhadap bulan pertama atau bulan lalu" selalu akan berakhir dengan harapan seseorang tidak terpenuhi dan beberapa jadwal perlu menyesuaikan tanggal yang "salah" untuk menghindari masalah dunia nyata yang disebabkan oleh interpretasi yang "salah".
Jadi sekali lagi, sementara saya juga berharap +1 month
untuk mengembalikan tanggal yang sebenarnya ada di bulan berikutnya, itu tidak sesederhana intuisi dan diberi pilihan, menggunakan matematika daripada ekspektasi pengembang web mungkin adalah pilihan yang aman.
Berikut adalah solusi alternatif yang masih kikuk tetapi menurut saya memiliki hasil yang bagus:
foreach(range(0,5) as $count) {
$new_date = clone $date;
$new_date->modify("+$count month");
$expected_month = $count + 1;
$actual_month = $new_date->format("m");
if($expected_month != $actual_month) {
$new_date = clone $date;
$new_date->modify("+". ($count - 1) . " month");
$new_date->modify("+4 weeks");
}
echo "* " . nl2br($new_date->format("Y-m-d") . PHP_EOL);
}
Ini tidak optimal tetapi logika yang mendasarinya adalah: Jika menambahkan 1 bulan menghasilkan tanggal selain yang diharapkan bulan depan, hapus tanggal itu dan tambahkan 4 minggu sebagai gantinya. Berikut hasil dengan dua tanggal tes:
31 Januari
- 2015-01-31
- 2015-02-28
- 2015-03-31
- 2015-04-28
- 2015-05-31
- 2015-06-28
30 Januari
- 2015-01-30
- 2015-02-27
- 2015-03-30
- 2015-04-30
- 2015-05-30
- 2015-06-30
(Kode saya berantakan dan tidak akan berfungsi dalam skenario multi-tahun. Saya menyambut siapa pun untuk menulis ulang solusi dengan kode yang lebih elegan selama premis yang mendasarinya tetap utuh, yaitu jika +1 bulan mengembalikan tanggal yang funky, gunakan +4 minggu sebagai gantinya.)