Je partage le sentiment du PO selon lequel cela est contre-intuitif et frustrant, mais il en va de même pour déterminer ce que +1 month
signifie les scénarios où cela se produit. Considérez ces exemples:
Vous commencez avec le 31/01/2015 et souhaitez ajouter un mois 6 fois pour obtenir un cycle de planification pour l'envoi d'une newsletter par e-mail. Compte tenu des attentes initiales du PO, cela reviendrait:
- 31/01/2015
- 28/02/2015
- 31/03/2015
- 30/04/2015
- 31/05/2015
- 30/06/2015
Tout de suite, notez que nous nous attendons +1 month
à vouloir dire last day of month
ou, alternativement, à ajouter 1 mois par itération mais toujours en référence au point de départ. Au lieu d'interpréter cela comme "le dernier jour du mois", nous pourrions le lire comme "le 31e jour du mois prochain ou le dernier jour disponible au cours de ce mois". Cela signifie que nous sautons du 30 avril au 31 mai au lieu du 30 mai. Notez que ce n'est pas parce que c'est "le dernier jour du mois", mais parce que nous voulons "le plus proche disponible à la date du mois de début".
Supposons donc qu'un de nos utilisateurs s'abonne à une autre newsletter pour commencer le 30/01/2015. À quoi sert la date intuitive +1 month
? Une interprétation serait "30e jour du mois suivant ou le plus proche disponible" qui renverrait:
- 30/01/2015
- 28/02/2015
- 30/03/2015
- 30/04/2015
- 30/05/2015
- 30/06/2015
Ce serait bien sauf lorsque notre utilisateur reçoit les deux newsletters le même jour. Supposons qu'il s'agit d'un problème du côté de l'offre plutôt que du côté de la demande. de nombreuses newsletters. Dans cet esprit, nous revenons à l'autre interprétation de "+1 mois" comme "envoyer l'avant-dernier jour de chaque mois" qui renverrait:
- 30/01/2015
- 27/02/2015
- 30/03/2015
- 29/04/2015
- 30/05/2015
- 29/06/2015
Nous avons maintenant évité tout chevauchement avec le premier set, mais nous nous retrouvons également avec avril et 29 juin, ce qui correspond certainement à nos intuitions originales qui +1 month
devraient simplement revenir m/$d/Y
ou à l'attrait et au simple m/30/Y
pour tous les mois possibles. Considérons maintenant une troisième interprétation de l' +1 month
utilisation des deux dates:
31 janvier
- 31/01/2015
- 03/03/2015
- 31/03/2015
- 01/05/2015
- 31/05/2015
- 01/07/2015
30 janvier
- 30/01/2015
- 02/03/2015
- 30/03/2015
- 30/04/2015
- 30/05/2015
- 30/06/2015
Ce qui précède a quelques problèmes. Février est ignoré, ce qui pourrait être un problème à la fois en termes d'approvisionnement (par exemple, s'il y a une allocation de bande passante mensuelle et que février est gaspillé et mars est doublé) et de demande (les utilisateurs se sentent trompés par rapport à février et perçoivent le mois de mars supplémentaire. comme tentative de corriger l'erreur). En revanche, notez que les deux dates définissent:
- ne jamais se chevaucher
- sont toujours à la même date lorsque ce mois a la date (donc l'ensemble du 30 janvier semble assez propre)
- sont tous dans les 3 jours (1 jour dans la plupart des cas) de ce qui pourrait être considéré comme la date «correcte».
- sont tous au moins 28 jours (un mois lunaire) de leur successeur et prédécesseur, donc très uniformément répartis.
Compte tenu des deux derniers sets, il ne serait pas difficile de simplement revenir en arrière sur l'une des dates si elle tombe en dehors du mois suivant (donc revenez au 28 février et au 30 avril dans le premier set) et ne perdez pas de sommeil sur le chevauchement occasionnel et divergence entre le modèle «dernier jour du mois» et «avant-dernier jour du mois». Mais s'attendre à ce que la bibliothèque choisisse entre "la plus jolie / naturelle", "l'interprétation mathématique du 31/02 et des autres débordements du mois" et "par rapport au premier du mois ou au mois dernier" se terminera toujours par le non-respect des attentes de quelqu'un et certains horaires doivent ajuster la «mauvaise» date pour éviter le problème du monde réel que la «mauvaise» interprétation introduit.
Donc, encore une fois, même si je m'attendrais également +1 month
à renvoyer une date qui est en fait le mois suivant, ce n'est pas aussi simple que l'intuition et étant donné les choix, aller avec les mathématiques sur les attentes des développeurs Web est probablement le choix sûr.
Voici une solution alternative qui est toujours aussi maladroite que toute autre mais qui, à mon avis, donne de bons résultats:
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);
}
Ce n'est pas optimal, mais la logique sous-jacente est la suivante: si l'ajout d'un mois entraîne une date autre que le mois prochain prévu, supprimez cette date et ajoutez 4 semaines à la place. Voici les résultats avec les deux dates de test:
31 janvier
- 31/01/2015
- 28/02/2015
- 31/03/2015
- 28/04/2015
- 31/05/2015
- 28/06/2015
30 janvier
- 30/01/2015
- 27/02/2015
- 30/03/2015
- 30/04/2015
- 30/05/2015
- 30/06/2015
(Mon code est un gâchis et ne fonctionnerait pas dans un scénario pluriannuel. Je souhaite à quiconque de réécrire la solution avec un code plus élégant tant que le principe sous-jacent est conservé intact, c'est-à-dire si +1 mois renvoie une date géniale, utilisez +4 semaines à la place.)