Dużo pracowałem z DateTime class
i ostatnio napotkałem coś, co uważałem za błąd podczas dodawania miesięcy. Po krótkich badaniach okazuje się, że nie był to błąd, ale działał zgodnie z przeznaczeniem. Zgodnie z dokumentacją znajdującą się tutaj :
Przykład # 2 Uważaj podczas dodawania lub odejmowania miesięcy
<?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
Czy ktoś może uzasadnić, dlaczego nie jest to uważane za błąd?
Co więcej, czy ktoś ma jakieś eleganckie rozwiązania, aby rozwiązać problem i sprawić, by +1 miesiąc działał zgodnie z oczekiwaniami, a nie zgodnie z przeznaczeniem?
strtotime()
stackoverflow.com/questions/7119777/ ...Odpowiedzi:
Dlaczego to nie jest błąd:
Obecne zachowanie jest poprawne. Następujące rzeczy mają miejsce wewnętrznie:
+1 month
zwiększa numer miesiąca (pierwotnie 1) o jeden. To tworzy datę2010-02-31
.Drugi miesiąc (luty) ma tylko 28 dni w 2010 roku, więc PHP automatycznie koryguje to, kontynuując liczenie dni od 1 lutego. Następnie kończysz 3 marca.
Jak zdobyć to, czego chcesz:
Aby uzyskać to, czego chcesz, musisz: ręcznie sprawdzić następny miesiąc. Następnie dodaj liczbę dni, które ma następny miesiąc.
Mam nadzieję, że możesz sam to zakodować. Po prostu daję co robić.
Sposób PHP 5.3:
Aby uzyskać poprawne zachowanie, możesz użyć jednej z nowych funkcji PHP 5.3, która wprowadza sekcję czasu względnego
first day of
. Ta strofa może być stosowany w połączeniu znext month
,fifth month
lub+8 months
udać się do pierwszego dnia danego miesiąca. Zamiast+1 month
tego, co robisz, możesz użyć tego kodu, aby uzyskać pierwszy dzień następnego miesiąca w następujący sposób:Ten skrypt poprawnie wypisze
February
. Następujące rzeczy mają miejsce, gdy PHP przetwarza tęfirst day of next month
sekcję:next month
zwiększa numer miesiąca (pierwotnie 1) o jeden. To sprawia, że data 2010-02-31.first day of
ustawia numer dnia na1
, co daje datę 2010-02-01.źródło
Oto kolejne kompaktowe rozwiązanie całkowicie wykorzystujące metody DateTime, modyfikujące obiekt w miejscu bez tworzenia klonów.
Wyprowadza:
źródło
$dt->modify()->modify()
. Działa równie dobrze.Może to być przydatne:
źródło
$dateTime->modify('first day of next month')->modify('-1day')
Moje rozwiązanie problemu:
źródło
Zgadzam się z opinią OP, że jest to sprzeczne z intuicją i frustrujące, ale tak samo jest z określeniem, co
+1 month
oznacza w scenariuszach, w których tak się dzieje. Rozważ te przykłady:Rozpoczynasz od 31.01.2015 i chcesz dodać miesiąc 6 razy, aby uzyskać cykl planowania wysyłania biuletynu e-mail. Biorąc pod uwagę początkowe oczekiwania PO, powróci to:
Od razu zauważ, że spodziewamy się, że będziemy
+1 month
oznaczaćlast day of month
lub, alternatywnie, dodawać 1 miesiąc na iterację, ale zawsze w odniesieniu do punktu początkowego. Zamiast interpretować to jako „ostatni dzień miesiąca”, możemy go odczytać jako „31 dzień następnego miesiąca lub ostatni dostępny w tym miesiącu”. Oznacza to, że skaczemy od 30 kwietnia do 31 maja zamiast do 30 maja. Zauważ, że nie dzieje się tak dlatego, że jest to „ostatni dzień miesiąca”, ale dlatego, że chcemy mieć „najbliższą dostępną datę miesiąca początkowego”.Załóżmy więc, że jeden z naszych użytkowników subskrybuje inny biuletyn, który rozpocznie się 30.01.2015. Po co jest intuicyjna data
+1 month
? Jedna interpretacja to „30 dzień następnego miesiąca lub najbliższy dostępny”, co zwróci:Byłoby to w porządku, z wyjątkiem sytuacji, gdy nasz użytkownik otrzyma oba biuletyny tego samego dnia. Załóżmy, że jest to problem po stronie podaży, a nie po stronie popytu Nie martwimy się, że użytkownik będzie zirytowany otrzymaniem 2 biuletynów tego samego dnia, ale zamiast tego nasze serwery pocztowe nie mogą pozwolić sobie na przepustowość do wysyłania dwa razy więcej wiele biuletynów. Mając to na uwadze, wrócimy do innej interpretacji „+1 miesiąc” jako „wyślij przedostatniego dnia każdego miesiąca”, która zwróci:
Teraz uniknęliśmy jakiegokolwiek nakładania się pierwszego zestawu, ale kończymy również na 29 kwietnia i czerwca, co z pewnością pasuje do naszych pierwotnych intuicji, które
+1 month
po prostu powinny powrócićm/$d/Y
lub są atrakcyjne i prostem/30/Y
na wszystkie możliwe miesiące. Rozważmy teraz trzecią interpretację+1 month
użycia obu dat:31 stycznia
30 stycznia
Powyższe ma pewne problemy. Luty jest pomijany, co może stanowić problem zarówno pod koniec podaży (powiedzmy, że miesięczny przydział przepustowości, a luty się marnuje, a marzec się podwoi) i popyt (użytkownicy czują się oszukani po lutym i dostrzegają dodatkowy marzec jako próba naprawienia błędu). Z drugiej strony zwróć uwagę, że te dwa zestawy dat:
Biorąc pod uwagę ostatnie dwa zestawy, nie byłoby trudno po prostu cofnąć jedną z dat, jeśli przypada ona poza faktyczny następny miesiąc (więc cofnij się do 28 lutego i 30 kwietnia w pierwszym zestawie) i nie stracić snu w ciągu sporadyczne nakładanie się i rozbieżność między wzorcem „ostatni dzień miesiąca” a „od drugiego do ostatniego dnia miesiąca”. Ale oczekiwanie, że biblioteka wybierze między „najpiękniejszą / naturalną”, „matematyczną interpretacją danych z 31 lutego i innych przepełnień miesiąca” oraz „w stosunku do pierwszego lub ostatniego miesiąca” zawsze kończy się niespełnieniem czyichś oczekiwań i jakiś harmonogram wymaga dostosowania „złej” daty, aby uniknąć rzeczywistego problemu, który wprowadza „zła” interpretacja.
Więc znowu, chociaż spodziewałbym
+1 month
się zwrócić datę, która faktycznie przypada w następnym miesiącu, nie jest to tak proste, jak intuicja, a biorąc pod uwagę możliwości, przejście z matematyką ponad oczekiwania twórców stron internetowych jest prawdopodobnie bezpiecznym wyborem.Oto alternatywne rozwiązanie, które nadal jest tak samo niezgrabne, jak inne, ale myślę, że ma dobre wyniki:
Nie jest to optymalne, ale podstawowa logika jest taka: jeśli dodanie 1 miesiąca skutkuje datą inną niż oczekiwany następny miesiąc, wyrzuć tę datę i zamiast tego dodaj 4 tygodnie. Oto wyniki z dwoma datami testu:
31 stycznia
30 stycznia
(Mój kod jest bałaganem i nie działałby w scenariuszu wieloletnim. Zapraszam każdego do przepisania rozwiązania z bardziej eleganckim kodem, o ile podstawowa przesłanka pozostanie nienaruszona, tj. Jeśli +1 miesiąc zwraca funkową datę, użyj +4 tygodnie zamiast tego.)
źródło
Stworzyłem funkcję, która zwraca DateInterval, aby upewnić się, że dodanie miesiąca pokazuje następny miesiąc i usuwa kolejne dni.
źródło
W połączeniu z odpowiedzią Shamittomara mogłoby to oznaczać „bezpieczne” dodanie miesięcy:
źródło
Znalazłem krótszy sposób obejścia tego za pomocą następującego kodu:
źródło
Oto implementacja ulepszonej wersji odpowiedzi Juhany na powiązane pytanie:
W tej wersji
$createdDate
zakłada się, że masz do czynienia z powtarzającym się okresem miesięcznym, takim jak subskrypcja, który rozpoczął się w określonym dniu, na przykład 31. Zawsze trwa$createdDate
tak późno, że daty „powtarzające się” nie zmieniają się na niższe wartości, ponieważ są przesuwane do przodu przez miesiące o niższej wartości (np. Aby wszystkie 29, 30 lub 31 daty powtarzania się nie utknęły w 28 dniu po upływie przez luty niebędący rokiem przestępnym).Oto kod sterownika do testowania algorytmu:
Które wyjścia:
źródło
To jest ulepszona wersja odpowiedzi Kasihasi na powiązane pytanie. Spowoduje to poprawne dodanie lub odjęcie dowolnej liczby miesięcy do daty.
Na przykład:
wyświetli:
źródło
Jeśli chcesz po prostu uniknąć pomijania miesiąca, możesz wykonać coś takiego, aby uzyskać datę i uruchomić pętlę w następnym miesiącu, zmniejszając datę o jeden i sprawdzając ponownie, aż do poprawnej daty, gdzie $ start_calculated jest prawidłowym ciągiem dla strtotime (tj. mysql datetime lub „now”). To wyszukuje sam koniec miesiąca na 1 minutę przed północą zamiast pomijania miesiąca.
źródło
Rozszerzenie dla klasy DateTime rozwiązujące problem dodawania lub odejmowania miesięcy
https://gist.github.com/66Ton99/60571ee49bf1906aaa1c
źródło
Jeśli używasz,
strtotime()
po prostu użyj$date = strtotime('first day of +1 month');
źródło
Musiałem znaleźć datę na „ten miesiąc w zeszłym roku” i dość szybko staje się nieprzyjemna, gdy ten miesiąc jest luty w roku przestępnym. Uważam jednak, że to działa ...: - / Wydaje się, że sztuczka polega na tym, aby oprzeć swoją zmianę na pierwszym dniu miesiąca.
źródło
źródło
wyjdzie
2
(luty). będzie działać również przez inne miesiące.źródło
Dniami:
Ważny:
Metoda
add()
klasy DateTime modyfikuje wartość obiektu, więc po wywołaniuadd()
obiektu DateTime zwraca nowy obiekt daty, a także modyfikuje sam obiekt.źródło
możesz to zrobić za pomocą po prostu date () i strtotime (). Na przykład, aby dodać 1 miesiąc do dzisiejszej daty:
jeśli chcesz użyć klasy datetime, to też jest w porządku, ale jest to równie łatwe. więcej szczegółów tutaj
źródło
Przyjęta odpowiedź już wyjaśnia, dlaczego to nie jest ale, a niektóre inne odpowiedzi stanowią zgrabne rozwiązanie z wyrażeniami php, takimi jak
first day of the +2 months
. Problem z tymi wyrażeniami polega na tym, że nie są one automatycznie uzupełniane.Rozwiązanie jest jednak dość proste. Najpierw powinieneś znaleźć przydatne abstrakcje, które odzwierciedlają twoją przestrzeń problemową. W tym przypadku jest to plik
ISO8601DateTime
. Po drugie, powinno być wiele implementacji, które mogą przynieść pożądaną reprezentację tekstową. Na przykładToday
,Tomorrow
,The first day of this month
,Future
- wszystkie stanowią realizację specyficznejISO8601DateTime
koncepcji.Tak więc w twoim przypadku implementacja, której potrzebujesz, to
TheFirstDayOfNMonthsLater
. Łatwo to znaleźć, patrząc na listę podklas w dowolnym IDE. Oto kod:To samo z ostatnimi dniami miesiąca. Więcej przykładów takiego podejścia, to przeczytać .
źródło
źródło