He estado trabajando mucho con el DateTime class
y recientemente encontré lo que pensé que era un error al agregar meses. Después de investigar un poco, parece que no fue un error, sino que funcionó como se esperaba. Según la documentación que se encuentra aquí :
Ejemplo n. ° 2 Tenga cuidado al sumar o restar meses
<?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
¿Alguien puede justificar por qué esto no se considera un error?
Además, ¿alguien tiene alguna solución elegante para corregir el problema y hacer que +1 mes funcione como se esperaba en lugar de como se esperaba?
strtotime()
stackoverflow.com/questions/7119777/…Respuestas:
Por qué no es un error:
El comportamiento actual es correcto. Lo siguiente sucede internamente:
+1 month
aumenta el número de mes (originalmente 1) en uno. Esto hace la fecha2010-02-31
.El segundo mes (febrero) solo tiene 28 días en 2010, por lo que PHP corrige esto automáticamente al continuar contando los días desde el 1 de febrero. Luego terminas el 3 de marzo.
Cómo conseguir lo que quieres:
Para obtener lo que desea es: verificando manualmente el mes siguiente. Luego suma el número de días que tiene el próximo mes.
Espero que puedas codificar esto tú mismo. Solo estoy dando qué hacer.
PHP 5.3 forma:
Para obtener el comportamiento correcto, puede utilizar una de las nuevas funciones de PHP 5.3 que introduce la estrofa de tiempo relativo
first day of
. Esta estrofa se puede usar en combinación connext month
,fifth month
o+8 months
para ir al primer día del mes especificado. En lugar de+1 month
lo que está haciendo, puede usar este código para obtener el primer día del próximo mes así:Este script se generará correctamente
February
. Las siguientes cosas suceden cuando PHP procesa estafirst day of next month
estrofa:next month
aumenta el número de mes (originalmente 1) en uno. Esto hace que la fecha 2010-02-31.first day of
establece el número de día en1
, lo que da como resultado la fecha 2010-02-01.fuente
Aquí hay otra solución compacta que usa completamente métodos DateTime, modificando el objeto en el lugar sin crear clones.
Produce:
fuente
$dt->modify()->modify()
. Funciona igual de bien.Esto puede resultar útil:
fuente
$dateTime->modify('first day of next month')->modify('-1day')
Mi solución al problema:
fuente
Estoy de acuerdo con el sentimiento del OP de que esto es contraintuitivo y frustrante, pero también lo es determinar qué
+1 month
significa en los escenarios en los que esto ocurre. Considere estos ejemplos:Comienza con 2015-01-31 y desea agregar un mes 6 veces para obtener un ciclo de programación para enviar un boletín por correo electrónico. Con las expectativas iniciales del OP en mente, esto regresaría:
De inmediato, observe que estamos esperando
+1 month
querer decirlast day of month
o, alternativamente, agregar 1 mes por iteración, pero siempre en referencia al punto de inicio. En lugar de interpretar esto como "último día del mes", podríamos leerlo como "día 31 del próximo mes o último disponible dentro de ese mes". Esto significa que saltamos del 30 de abril al 31 de mayo en lugar del 30 de mayo. Tenga en cuenta que esto no se debe a que sea el "último día del mes", sino a que queremos "el más cercano disponible a la fecha del mes de inicio".Entonces, supongamos que uno de nuestros usuarios se suscribe a otro boletín para comenzar el 30-01-2015. ¿Para qué es la fecha intuitiva
+1 month
? Una interpretación sería "día 30 del próximo mes o el más cercano disponible" que devolvería:Esto estaría bien, excepto cuando nuestro usuario reciba ambos boletines el mismo día. Supongamos que se trata de un problema del lado de la oferta y no del lado de la demanda.No nos preocupa que el usuario se moleste al recibir 2 boletines el mismo día, sino que nuestros servidores de correo no pueden pagar el ancho de banda para enviar el doble de muchos boletines. Con eso en mente, volvemos a la otra interpretación de "+1 mes" como "enviar el penúltimo día de cada mes", que devolvería:
Ahora hemos evitado cualquier superposición con el primer conjunto, pero también terminamos con el 29 de abril y junio, lo que ciertamente coincide con nuestras intuiciones originales que
+1 month
simplemente deberían regresarm/$d/Y
o lo atractivo y simplem/30/Y
para todos los meses posibles. Así que ahora consideremos una tercera interpretación del+1 month
uso de ambas fechas:31 de enero
30 de enero
Lo anterior tiene algunos problemas. Se omite febrero, lo que podría ser un problema tanto para el final de la oferta (por ejemplo, si hay una asignación mensual de ancho de banda y febrero se desperdicia y marzo se duplica) como el final de la demanda (los usuarios se sienten estafados en febrero y perciben el marzo adicional como intento de corregir el error). Por otro lado, observe que los dos conjuntos de fechas:
Dados los dos últimos conjuntos, no sería difícil simplemente revertir una de las fechas si cae fuera del mes siguiente real (así que retroceda al 28 de febrero y al 30 de abril en el primer conjunto) y no perder el sueño durante el superposición ocasional y divergencia del patrón del "último día del mes" frente al patrón del "segundo al último día del mes". Pero esperar que la biblioteca elija entre "más bonito / natural", "interpretación matemática del 31/02 y otros desbordamientos del mes" y "relativo al primero o al último mes" siempre terminará con las expectativas de alguien que no se cumplan y algunos programas necesitan ajustar la fecha "incorrecta" para evitar el problema del mundo real que introduce la interpretación "incorrecta".
Entonces, nuevamente, aunque también esperaría
+1 month
devolver una fecha que en realidad es el mes siguiente, no es tan simple como la intuición y, dadas las opciones, ir con las matemáticas sobre las expectativas de los desarrolladores web es probablemente la opción segura.Aquí hay una solución alternativa que sigue siendo tan torpe como cualquier otra, pero creo que tiene buenos resultados:
No es óptimo, pero la lógica subyacente es: si agregar 1 mes da como resultado una fecha diferente a la prevista para el próximo mes, elimine esa fecha y agregue 4 semanas en su lugar. Aquí están los resultados con las dos fechas de prueba:
31 de enero
30 de enero
(Mi código es un desastre y no funcionaría en un escenario de varios años. Le doy la bienvenida a cualquiera para que reescriba la solución con un código más elegante siempre que la premisa subyacente se mantenga intacta, es decir, si +1 mes devuelve una fecha original, use +4 semanas en su lugar.)
fuente
Hice una función que devuelve un DateInterval para asegurarme de que al agregar un mes se muestre el mes siguiente y se eliminen los días posteriores.
fuente
Junto con la respuesta de shamittomar, podría ser esto para agregar meses "de forma segura":
fuente
Encontré una forma más corta de evitarlo usando el siguiente código:
fuente
Aquí hay una implementación de una versión mejorada de la respuesta de Juhana en una pregunta relacionada:
Esta versión asume
$createdDate
que se trata de un período mensual recurrente, como una suscripción, que comenzó en una fecha específica, como el 31. Siempre es necesario$createdDate
que las fechas "recurrentes" tardías no cambien a valores más bajos a medida que avanzan a través de meses de menor valor (p. Ej., Para que todas las fechas recurrentes 29, 30 o 31 finalmente no se atasquen el día 28 después de pasar hasta febrero no bisiesto).Aquí hay un código de controlador para probar el algoritmo:
Qué salidas:
fuente
Esta es una versión mejorada de la respuesta de Kasihasi en una pregunta relacionada. Esto sumará o restará correctamente una cantidad arbitraria de meses a una fecha.
Por ejemplo:
dará salida:
fuente
Si solo desea evitar omitir un mes, puede realizar algo como esto para obtener la fecha y ejecutar un bucle en el mes siguiente reduciendo la fecha en uno y volviendo a verificar hasta una fecha válida donde $ start_calculated es una cadena válida para strtotime (es decir mysql datetime o "ahora"). Esto encuentra el final del mes 1 minuto antes de la medianoche en lugar de omitir el mes.
fuente
Extensión para la clase DateTime que resuelve el problema de sumar o restar meses
https://gist.github.com/66Ton99/60571ee49bf1906aaa1c
fuente
Si usa
strtotime()
solo use$date = strtotime('first day of +1 month');
fuente
Necesitaba obtener una fecha para 'este mes el año pasado' y se vuelve desagradable bastante rápido cuando este mes es febrero en un año bisiesto. Sin embargo, creo que esto funciona ...: - / El truco parece estar en basar el cambio en el primer día del mes.
fuente
fuente
saldrá
2
(febrero). también funcionará durante otros meses.fuente
Por dias:
Importante:
El método
add()
de la clase DateTime modifica el valor del objeto, por lo que después de llamaradd()
a un objeto DateTime, devuelve el nuevo objeto de fecha y también modifica el objeto en sí.fuente
en realidad, puede hacerlo con solo date () y strtotime () también. Por ejemplo, para agregar 1 mes a la fecha de hoy:
Si desea utilizar la clase datetime, eso también está bien, pero es igual de fácil. más detalles aquí
fuente
La respuesta aceptada ya explica por qué esto no es un pero, y algunas otras respuestas plantean una buena solución con expresiones php como
first day of the +2 months
. El problema con esas expresiones es que no se autocompletan.Sin embargo, la solución es bastante simple. Primero, debe encontrar abstracciones útiles que reflejen el espacio de su problema. En este caso, es un
ISO8601DateTime
. En segundo lugar, debe haber múltiples implementaciones que puedan traer una representación textual deseada. Por ejemplo,Today
,Tomorrow
,The first day of this month
,Future
- todos representan una implementación específica delISO8601DateTime
concepto.Entonces, en su caso, una implementación que necesita es
TheFirstDayOfNMonthsLater
. Es fácil de encontrar con solo mirar las subclases llist en cualquier IDE. Aquí está el código:Lo mismo ocurre con los últimos días de un mes. Para obtener más ejemplos de este enfoque, lea esto .
fuente
fuente