Я работаю над планировщиком платежей, который планирует n
платежей на основе произвольной даты начала и одного из набора частот (ежедневно, еженедельно, ежемесячно и т. Д.), И я ищу общий алгоритм для этого.
Я попытался использовать грубые силы для этого, обрезая частоту и добавляя определенное количество дней, недель, месяцев по мере необходимости. Это работает для большинства целей.
Там, где он терпит неудачу, это когда произвольная дата начала составляет 28 месяцев, а частота находится где-то между месяцами и ежегодно, особенно для частот, таких как «первый из каждого месяца» и «последний из каждого месяца». Поскольку дни 29, 30 и 31 не появляются в течение всех месяцев, добавление месяца, такого как date('2013-10-31')->addMonth(1)
имеет непредсказуемые результаты. Как и добавление месяцев, таких как date('2014-01-31')->addDays(30)
, опять же, из-за отсутствия необходимости в феврале.
Есть ли общее решение этой проблемы без ужасно сложных случаев, которые мне нужны для перемещения любой заданной частоты через какой-либо месяц?
Бонусные очки для PHP, но при необходимости я могу перевести.
«Добавить месяц» и т. Д., Раздражение из-за разных месячных длин, действительно, раздражает.
Решение, если у вас PHP> = 5.2, является классом DateTime .
Хотя для получения полного контроля просто использовать этот класс, он не является тривиальным.
Вот одна версия правильного кода, чтобы добавить месяц.
// Variables defining the start date // Example only - this could be any valid date $year = '2013'; $month = '01'; $day = '31'; // set to the desired starting date and time $the_date = new DateTime($year . '-' . $month . '-' . $day); // Jump to the first day of this month $the_date->modify("first day of this month"); // add 14 days, so we'll land on the 15th $the_date->add(new DateInterval("P14D")); // add 1 month - guaranteed to work! $the_date->add(new DateInterval("P1M")); // calculate how many days to add to 15 to get back to the **day** we started with... // (as an integer, regardless of whether it is a valid day of the current month) $number_days_to_add_back = intval($day) - 15; // determine the last day of the month stored in $the_date $test_last_date = clone $the_date; $test_last_date->modify("last day of this month"); $day_last = $test_last_date->format('j'); // This provides the day, 01-31 // Test if adding $number_days_to_add_back runs past // the end of the month; if so, adjust it so it won't run past // the last day of the month if (15 + $number_days_to_add_back > intval($day_last)) { $number_days_to_add_back = intval($day_last) - 15; } // Now make the final adjustment $the_date->modify("" . $number_days_to_add_back . " day"); // Test it - a month has been added $test = date_format($the_date, 'Ym-d');
Прежде всего вам нужно определить, как вы хотите, чтобы он работал. Это проблема бизнес-логики , а тем более техническая проблема. Как долго «один месяц»? Вы имеете в виду «один месяц» как промежуток времени примерно 30 дней (как долго точно?), Или «+ один месяц» означает «в тот же день в следующем месяце»? Как только вы определили, как «31st + 1 month» должен работать, это просто вопрос правильного его использования.
Мое предложение состояло бы в том, чтобы «+1 месяц» означало «увеличить число месяца на единицу, сохраняя номер дня одинаковым, если только день не существует в этом месяце, и в этом случае используйте последний день месяца» . Что можно реализовать, используя что-то вроде этого:
$date = mktime(0, 0, 0, 1, 31); // midnight Jan 31st $nextMonth = mktime(0, 0, 0, date('n', $date) + 1, 1, date('Y', $date)); // 1st of next month $newDate = mktime(0, 0, 0, date('n', $nextMonth), min(date('t', $nextMonth), date('j', $date)), date('Y', $nextMonth));
Это не очень, но расчеты даты / времени редко бывают, особенно если определение операции является неопределенным.