fecha - PHP strtotime+1 mes de comportamiento
strtotime to date (8)
Sé sobre el comportamiento no deseado de la función de PHP
tiempo de juego
Por ejemplo, al agregar un mes (+1 mes) a fechas como: 31.01.2011 -> 03.03.2011
Sé que no es oficialmente un error de PHP , y que esta solución tiene algunos argumentos detrás, pero al menos para mí, este comportamiento ha causado una gran pérdida de tiempo (en el pasado y en el presente) y personalmente lo odio.
Lo que encontré aún más extraño es que, por ejemplo, en:
MySQL: DATE_ADD (''2011-01-31'', INTERVAL 1 MES) devuelve 2011-02-28 o
C # donde nuevo DateTime (2011, 01, 31) .AddMonths (1); volverá 28.02.2011
wolframalpha.com con 31.01.2013 + 1 month
como entrada; volverá el jueves 28 de febrero del 2013
Me parece que otros han encontrado una solución más decente a la pregunta estúpida que vi en muchos informes de errores de PHP "qué día será, si digo que nos encontraremos dentro de un mes a partir de ahora" o algo así. La respuesta es: si 31 no existe en el próximo mes, consígame el último día de ese mes, pero por favor, siga el próximo mes.
Entonces, MI PREGUNTA ES: ¿existe una función de PHP (escrita por alguien) que resuelva este error no reconocido oficialmente? Como no creo que sea el único que quiere otro comportamiento al sumar / restar meses.
Estoy particularmente interesado en las soluciones que también funcionan no solo para el fin de mes, sino también un reemplazo completo de strtotime
. También debe tratarse el caso strotime +n months
.
¡Feliz codificación!
Algo similar a la respuesta de Juhana pero más intuitiva y menos complicaciones esperadas. La idea es así:
- Almacenar la fecha original y la fecha de + n mes (s) en variables
- Extraer la parte del día de ambas variables.
- Si los días no coinciden, reste el número de días de la fecha futura
El lado positivo de esta solución es que funciona para cualquier fecha (no solo las fechas del borde) y también para restar meses (al poner - en lugar de +). Aquí hay una implementación de ejemplo:
$start = mktime(0,0,0,1,31,2015);
for ($contract = 0; $contract < 12; $contract++) {
$end = strtotime(''+ '' . $contract . '' months'', $start);
if (date(''d'', $start) != date(''d'', $end)) {
$end = strtotime(''- '' . date(''d'', $end) . '' days'', $end);
}
echo date(''d-m-Y'', $end) . ''|'';
}
Y la salida es la siguiente:
31-01-2015|28-02-2015|31-03-2015|30-04-2015|31-05-2015|30-06-2015|31-07-2015|31-08-2015|30-09-2015|31-10-2015|30-11-2015|31-12-2015|
Aquí está el algoritmo que puedes usar. Debe ser lo suficientemente simple como para implementarse.
- Tener la fecha original y la fecha del mes +1 en variables
- Extraer la parte del mes de ambas variables.
- Si la diferencia es mayor que 1 mes (o si el original es diciembre y el otro no es enero), cambie la última variable al último día del mes siguiente. Puede usar, por ejemplo,
t
endate()
para obtener el último día:date( ''tmY'' )
Aquí hay una implementación de una versión mejorada de la respuesta de Juhana :
<?php
function sameDateNextMonth(DateTime $createdDate, DateTime $currentDate) {
$addMon = clone $currentDate;
$addMon->add(new DateInterval("P1M"));
$nextMon = clone $currentDate;
$nextMon->modify("last day of next month");
if ($addMon->format("n") == $nextMon->format("n")) {
$recurDay = $createdDate->format("j");
$daysInMon = $addMon->format("t");
$currentDay = $currentDate->format("j");
if ($recurDay > $currentDay && $recurDay <= $daysInMon) {
$addMon->setDate($addMon->format("Y"), $addMon->format("n"), $recurDay);
}
return $addMon;
} else {
return $nextMon;
}
}
Esta versión toma $createdDate
bajo la presunción de que está lidiando con un período mensual recurrente, como una suscripción, que comenzó en una fecha específica, como la 31a. Siempre se necesitan $createdDate
fechas "recurrentes" tardías no cambiarán a valores más bajos a medida que se adelanten a través de meses de menor valor (por ejemplo, todas las fechas recurrentes 29, 30 o 31 no se quedarán atascadas el 28). después de pasar por un año no bisiesto en febrero).
Aquí hay un código de controlador para probar el algoritmo:
$createdDate = new DateTime("2015-03-31");
echo "created date = " . $createdDate->format("Y-m-d") . PHP_EOL;
$next = sameDateNextMonth($createdDate, $createdDate);
echo " next date = " . $next->format("Y-m-d") . PHP_EOL;
foreach(range(1, 12) as $i) {
$next = sameDateNextMonth($createdDate, $next);
echo " next date = " . $next->format("Y-m-d") . PHP_EOL;
}
Qué salidas:
created date = 2015-03-31
next date = 2015-04-30
next date = 2015-05-31
next date = 2015-06-30
next date = 2015-07-31
next date = 2015-08-31
next date = 2015-09-30
next date = 2015-10-31
next date = 2015-11-30
next date = 2015-12-31
next date = 2016-01-31
next date = 2016-02-29
next date = 2016-03-31
next date = 2016-04-30
Lo he resuelto de esta manera:
$startDate = date("Y-m-d");
$month = date("m",strtotime($startDate));
$nextmonth = date("m",strtotime("$startDate +1 month"));
if((($nextmonth-$month) > 1) || ($month == 12 && $nextmonth != 1))
{
$nextDate = date( ''t.m.Y'',strtotime("$initialDate +1 week"));
}else
{
$nextDate = date("Y-m-d",strtotime("$initialDate +1 month"));
}
echo $nextDate;
Los desarrolladores de PHP seguramente no consideran esto como un error . Pero en los documentos de strtotime
hay pocos comentarios con soluciones para su problema (busque los ejemplos del 28 de febrero;)), es decir, este extenderá la clase DateTime :
<?php
// this will give us 2010-02-28 ()
echo PHPDateTime::DateNextMonth(strftime(''%F'', strtotime("2010-01-31 00:00:00")), 31);
?>
Clase PHPDateTime:
<?php
/**
* IA FrameWork
* @package: Classes & Object Oriented Programming
* @subpackage: Date & Time Manipulation
* @author: ItsAsh <ash at itsash dot co dot uk>
*/
final class PHPDateTime extends DateTime {
// Public Methods
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/**
* Calculate time difference between two dates
* ...
*/
public static function TimeDifference($date1, $date2)
$date1 = is_int($date1) ? $date1 : strtotime($date1);
$date2 = is_int($date2) ? $date2 : strtotime($date2);
if (($date1 !== false) && ($date2 !== false)) {
if ($date2 >= $date1) {
$diff = ($date2 - $date1);
if ($days = intval((floor($diff / 86400))))
$diff %= 86400;
if ($hours = intval((floor($diff / 3600))))
$diff %= 3600;
if ($minutes = intval((floor($diff / 60))))
$diff %= 60;
return array($days, $hours, $minutes, intval($diff));
}
}
return false;
}
/**
* Formatted time difference between two dates
*
* ...
*/
public static function StringTimeDifference($date1, $date2) {
$i = array();
list($d, $h, $m, $s) = (array) self::TimeDifference($date1, $date2);
if ($d > 0)
$i[] = sprintf(''%d Days'', $d);
if ($h > 0)
$i[] = sprintf(''%d Hours'', $h);
if (($d == 0) && ($m > 0))
$i[] = sprintf(''%d Minutes'', $m);
if (($h == 0) && ($s > 0))
$i[] = sprintf(''%d Seconds'', $s);
return count($i) ? implode('' '', $i) : ''Just Now'';
}
/**
* Calculate the date next month
*
* ...
*/
public static function DateNextMonth($now, $date = 0) {
$mdate = array(0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
list($y, $m, $d) = explode(''-'', (is_int($now) ? strftime(''%F'', $now) : $now));
if ($date)
$d = $date;
if (++$m == 2)
$d = (($y % 4) === 0) ? (($d <= 29) ? $d : 29) : (($d <= 28) ? $d : 28);
else
$d = ($d <= $mdate[$m]) ? $d : $mdate[$m];
return strftime(''%F'', mktime(0, 0, 0, $m, $d, $y));
}
}
?>
Tuvo el mismo problema recientemente y terminó escribiendo una clase que se encarga de agregar / restar varios intervalos de tiempo a objetos DateTime.
Aquí está el código:
https://gist.github.com/pavlepredic/6220041#file-gistfile1-php
He estado usando esta clase por un tiempo y parece funcionar bien, pero estoy realmente interesado en una revisión por pares. Lo que hace es crear un objeto TimeInterval (en su caso, especificaría 1 mes como el intervalo) y luego llamar al método addToDate (), asegurándose de establecer el argumento $ preventMonthOverflow en true. El código se asegurará de que la fecha resultante no se derrame en el próximo mes.
Uso de la muestra:
$int = new TimeInterval(1, TimeInterval::MONTH);
$date = date_create(''2013-01-31'');
$future = $int->addToDate($date, true);
echo $future->format(''Y-m-d'');
La fecha resultante es: 2013-02-28
Lo que necesitas es decirle a PHP que sea más inteligente
$the_date = strtotime(''31.01.2011'');
echo date(''r'', strtotime(''last day of next month'', $the_date));
$the_date = strtotime(''31.03.2011'');
echo date(''r'', strtotime(''last day of next month'', $the_date));
Asumiendo que solo son interesantes el último día del próximo mes.
referencia - http://www.php.net/manual/en/datetime.formats.relative.php
function ldom($m,$y){
//return tha last date of a given month based on the month and the year
//(factors in leap years)
$first_day= strtotime (date($m.''/1/''.$y));
$next_month = date(''m'',strtotime ( ''+32 day'' , $first_day)) ;
$last_day= strtotime ( ''-1 day'' , strtotime (date($next_month.''/1/''.$y)) ) ;
return $last_day;
}