Saya telah banyak bekerja dengan itu DateTime class
dan baru-baru ini menemukan apa yang saya pikir sebagai bug ketika menambahkan bulan. Setelah sedikit penelitian, tampaknya itu bukan bug, tetapi berfungsi sebagaimana mestinya. Menurut dokumentasi yang ditemukan di sini :
Contoh # 2 Berhati-hatilah saat menambah atau mengurangi bulan
<?php
$date = new DateTime('2000-12-31');
$date->modify('+1 month');
echo $date->format('Y-m-d') . "\n";
$date->modify('+1 month');
echo $date->format('Y-m-d') . "\n";
?>
The above example will output: 2001-01-31 2001-03-03
Adakah yang bisa membenarkan mengapa ini tidak dianggap bug?
Selain itu, apakah ada yang punya solusi elegan untuk memperbaiki masalah ini dan membuatnya jadi 1 bulan akan berfungsi seperti yang diharapkan, bukan sebagaimana yang dimaksudkan?
strtotime()
stackoverflow.com/questions/7119777/…Jawaban:
Mengapa ini bukan bug:
Perilaku saat ini benar. Berikut ini terjadi secara internal:
+1 month
meningkatkan nomor bulan (awalnya 1) satu. Ini membuat tanggal2010-02-31
.Bulan kedua (Februari) hanya memiliki 28 hari di tahun 2010, jadi PHP secara otomatis mengoreksinya dengan terus menghitung hari dari tanggal 1 Februari. Anda kemudian berakhir pada 3 Maret.
Cara mendapatkan yang Anda inginkan:
Untuk mendapatkan apa yang diinginkan adalah dengan cara: mengecek bulan berikutnya secara manual. Kemudian tambahkan jumlah hari yang dimiliki bulan depan.
Saya harap Anda sendiri dapat membuat kode ini. Saya hanya memberikan apa yang harus dilakukan.
Cara PHP 5.3:
Untuk mendapatkan perilaku yang benar, Anda dapat menggunakan salah satu fungsionalitas baru PHP 5.3 yang memperkenalkan stanza waktu relatif
first day of
. Bait ini dapat digunakan dalam kombinasi dengannext month
,fifth month
atau+8 months
untuk pergi ke hari pertama bulan yang ditentukan. Alih-alih+1 month
dari apa yang Anda lakukan, Anda dapat menggunakan kode ini untuk mendapatkan hari pertama bulan depan seperti ini:Skrip ini akan menghasilkan keluaran dengan benar
February
. Hal-hal berikut terjadi ketika PHP memprosesfirst day of next month
syair ini :next month
meningkatkan nomor bulan (awalnya 1) satu. Ini membuat tanggal 2010-02-31.first day of
menetapkan nomor hari menjadi1
, menghasilkan tanggal 2010-02-01.sumber
Berikut adalah solusi ringkas lainnya yang sepenuhnya menggunakan metode DateTime, memodifikasi objek di tempat tanpa membuat klon.
Ini menghasilkan:
sumber
$dt->modify()->modify()
. Bekerja dengan baik.Ini mungkin berguna:
sumber
$dateTime->modify('first day of next month')->modify('-1day')
Solusi saya untuk masalah ini:
sumber
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:
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: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:
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 kembalim/$d/Y
atau yang menarik dan sederhanam/30/Y
untuk semua bulan yang memungkinkan. Jadi sekarang mari kita pertimbangkan interpretasi ketiga+1 month
menggunakan kedua tanggal:31 Januari
30 Januari
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:
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:
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
30 Januari
(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.)
sumber
Saya membuat fungsi yang mengembalikan DateInterval untuk memastikan bahwa menambahkan bulan menunjukkan bulan berikutnya, dan menghapus hari-hari setelahnya.
sumber
Sehubungan dengan jawaban shamittomar, bisa jadi ini untuk menambahkan bulan "dengan aman":
sumber
Saya menemukan cara yang lebih pendek dengan menggunakan kode berikut:
sumber
Berikut adalah implementasi dari versi perbaikan dari jawaban Juhana dalam pertanyaan terkait:
Versi ini
$createdDate
mengasumsikan bahwa Anda berurusan dengan periode bulanan berulang, seperti langganan, yang dimulai pada tanggal tertentu, seperti tanggal 31. Itu selalu membutuhkan waktu yang$createdDate
terlambat "berulang pada" tanggal tidak akan bergeser ke nilai yang lebih rendah karena mereka didorong ke depan melalui bulan-bulan dengan nilai yang lebih rendah (misalnya, jadi semua tanggal berulang ke-29, 30 atau 31 pada akhirnya tidak akan macet pada tanggal 28 setelah lewat melalui bulan Februari bukan tahun kabisat).Berikut beberapa kode driver untuk menguji algoritme:
Output mana:
sumber
Ini adalah versi perbaikan dari jawaban Kasihasi dalam pertanyaan terkait. Ini akan menambah atau mengurangi jumlah bulan yang berubah-ubah dengan benar.
Sebagai contoh:
akan mengeluarkan:
sumber
Jika Anda hanya ingin menghindari melewatkan satu bulan, Anda dapat melakukan sesuatu seperti ini untuk mengeluarkan tanggal dan menjalankan loop pada bulan berikutnya mengurangi tanggal satu per satu dan memeriksa ulang hingga tanggal yang valid di mana $ starting_calculated adalah string yang valid untuk strtotime (mis. mysql datetime atau "sekarang"). Ini menemukan akhir bulan pada 1 menit hingga tengah malam alih-alih melewatkan bulan.
sumber
Perpanjangan untuk kelas DateTime yang memecahkan masalah menambah atau mengurangi bulan
https://gist.github.com/66Ton99/60571ee49bf1906aaa1c
sumber
Jika menggunakan
strtotime()
gunakan saja$date = strtotime('first day of +1 month');
sumber
Saya perlu mendapatkan tanggal untuk 'bulan ini tahun lalu' dan itu menjadi tidak menyenangkan dengan cepat ketika bulan ini adalah Februari dalam tahun kabisat. Namun, saya yakin ini berhasil ...: - / Triknya tampaknya mendasarkan perubahan Anda pada hari pertama setiap bulan.
sumber
sumber
akan keluar
2
(februari). akan bekerja untuk bulan-bulan lain juga.sumber
Untuk hari-hari:
Penting:
Metode
add()
kelas DateTime memodifikasi nilai objek sehingga setelah memanggiladd()
Objek DateTime ia mengembalikan objek tanggal baru dan juga memodifikasi objek itu sendiri.sumber
Anda sebenarnya dapat melakukannya hanya dengan date () dan strtotime () juga. Misalnya untuk menambahkan 1 bulan ke tanggal hari ini:
jika Anda ingin menggunakan kelas datetime tidak apa-apa juga tapi ini sama mudahnya. lebih jelasnya disini
sumber
Jawaban yang diterima sudah menjelaskan mengapa ini bukan tetapi, dan beberapa jawaban lain memberikan solusi yang rapi dengan ekspresi php seperti
first day of the +2 months
. Masalah dengan ekspresi tersebut adalah bahwa mereka tidak dilengkapi secara otomatis.Solusinya cukup sederhana. Pertama, Anda harus menemukan abstraksi berguna yang mencerminkan ruang masalah Anda. Dalam hal ini, ini adalah
ISO8601DateTime
. Kedua, harus ada beberapa implementasi yang dapat menghadirkan representasi tekstual yang diinginkan. Sebagai contoh,Today
,Tomorrow
,The first day of this month
,Future
- semua merupakan implementasi khusus dariISO8601DateTime
konsep.Jadi, dalam kasus Anda, implementasi yang Anda butuhkan adalah
TheFirstDayOfNMonthsLater
. Sangat mudah untuk menemukannya hanya dengan melihat daftar subclass di IDE manapun. Berikut kodenya:Hal yang sama dengan hari-hari terakhir dalam sebulan. Untuk lebih banyak contoh dari pendekatan ini, baca ini .
sumber
sumber