Как рассчитать разницу между двумя днями как форматированную строку?

Вот что у меня до сих пор:

/** * Parse a duration between 2 date/times in seconds * and to convert that duration into a formatted string * * @param integer $time_start start time in seconds * @param integer $time_end end time in seconds * @param string $format like the php strftime formatting uses %y %m %w %d %h or %i. * @param boolean $chop chop off sections that have 0 values */ public static function FormatDateDiff($time_start = 0, $time_end = 0, $format = "%s", $chop = false) { if($time_start > $time_end) list($time_start, $time_end) = array($time_end, $time_start); list($year_start,$month_start,$day_start) = explode('-',date('Ym-d',$time_start)); list($year_end,$month_end,$day_end) = explode('-',date('Ym-d',$time_end)); $years = $year_end - $year_start; $months = $month_end - $month_start; $days = $day_start - $day_end; $weeks = 0; $hours = 0; $mins = 0; $secs = 0; if(mktime(0,0,0,$month_end,$day_end) < mktime(0,0,0,$month_start,$day_start)) { $years -= 1; } if($days < 0) { $months -= 1; $days += 30; // this is an approximation...not sure how to figure this out } if($months < 0) $months += 12; if(strpos($format, '%y')===false) { $months += $years * 12; } if(strpos($format, '%w')!==false) { $weeks = floor($days/7); $days %= 7; } echo date('Ym-d',$time_start).' to '.date('Ym-d',$time_end).": {$years}y {$months}m {$weeks}w {$days}d<br/>"; } 

(Это неполное и неточное)

Кажется, я не верю в математику. Наивное разделение его не будет работать из-за високосных лет и разной продолжительности месяцев.

Логика также должна изменяться в зависимости от строки формата. Например, с 04 по февраль 2010 года по 28 июня 2011 года (как временные метки unix) со строкой формата %y year %m month %d day должен выводить 1 year 4 month 24 day но если %y year опущен, то это необходимо добавить 12 месяцев к месяцу, т. е. выход должен составлять 16 month 24 day .

Должен также обрабатывать времена … но я пока этого не добился.


Ни одно из этих решений date_diff не обрабатывает недели . И я не знаю, как я мог взломать его в date_diff , так что это не решение для меня.

Кроме того, $diff->format не выполняет то, что я просил … чтобы дать общее количество месяцев и дней, если «большие единицы» опущены. Пример:

 >>> $start = new DateTime('04-Feb-2010') >>> $end = new DateTime('28-Jun-2011') >>> $diff = $start->diff($end) >>> $diff->format('%m months, %d days') '4 months, 24 days' 

Должно быть 16 months, 24 days , как я сказал ранее. Пожалуйста, прекратите быть настолько быстрым, чтобы закрыть мой вопрос как обман, прежде чем вы его полностью поймете. Если решения по другим вопросам можно настроить, чтобы решить эту проблему, хорошо, но, пожалуйста, объясните, как, потому что я не понимаю.

Чтобы быть ясным,

  • если %y опущено, годы должны быть пересчитаны за месяцы
  • если %m опущено, месяцы должны быть свернуты в дни
  • если %w опущено, недели должны быть свернуты в дни
  • если %h опущено, часы должны быть свернуты в минуты
  • если %m опущено, минуты должны быть свернуты в секунды

Если «меньшие единицы» опущены, следующая самая большая единица может быть закруглена или перекрыта, где это имеет смысл.

Я не ожидаю принятого ответа, но вот как заставить date_diff делать недели.

 <?php $january = new DateTime('2010-01-01'); $february = new DateTime('2011-02-20 3:35:28'); $interval = $february->diff($january); $parts = $interval->format('%y %m %d %h %i %s %a'); $weeks = 0; list($years, $months, $days, $hours, $minutes, $seconds, $total_days) = explode(' ', $parts); if ($days >= 7) { $weeks = (int)($days / 7); $days %= 7; } echo "$years years, $months months, $weeks weeks, $days days, $hours hours, $minutes minutes $seconds seconds"; // 1 years, 1 months, 2 weeks, 5 days, 3 hours, 35 minutes 28 seconds 

Возможно, с этим вы можете интегрировать его в свою функцию, чтобы выполнить перекатывание и обработку заданного пользователем формата.

Если большие единицы не указаны, вы можете начать с самого большого блока и применить их обратно к следующему меньшему блоку. (т. е. 1 год 1 месяц без каких-либо лет, должен добавить 12 обратно в месяцы). Если «месяц» не включен в формат, вы можете использовать общие дни, чтобы справиться с тем, что месяцы имеют разное количество дней.

Для этого я использовал бы DateTime :: diff () :

 $start = new DateTime('2012-01-01 12:00:00'); $end = new DateTime('2012-01-20 06:59:59'); $diff = $start->diff($end); echo $diff->format('%d days, %h hours, %m minutes, %s seconds'); 

Подробнее о форматах см. В разделе DateInterval :: format () .

 $absolute = false; //default, returns years, months, days, etc. True would return seconds $difference = date_diff($dateTimeStart, $dateTimeEnd, $absolute); echo $difference->format("%y Year(s) %m Month(s) ..."); 

Я бы попробовал это первым, если это не сработает для ваших целей, тогда вы можете попытаться использовать функции Calendar для получения правильного количества дней в каждом месяце.

Думаю, вам нужно будет решить свою собственную логику расчета недель. Лично я бы сказал, что $weeks = $difference->d / 7; но нет строго правильного способа подсчета прошедших недель в течение месяца. Думайте о календаре, мы часто говорим о первой, второй, третьей неделе, но если месяц не начался в воскресенье (или в понедельник или субботу, в зависимости от компании, религии и т. Д.), То действительно мы не являемся абсолютными в этих описаниях , Вы можете сказать абсолютно, что было 3 воскресенья (например) с начала месяца, но не три недели. 28-го, прошло четыре недели? Что, если месяц начнется в среду, то это пять?

Кроме того, если вы хотите создать собственный формат (35 месяцев), вы всегда можете напрямую обращаться к членам и объединять строку формата.

Вот моя попытка интересной проблемы. Это не совсем работает 100%, но это очень близко.

Функция начинается с вычисления общего количества секунд и месяцев, которые требуется для разницы дат. Затем, используя массив $tokens который сортируется в порядке возрастания, он проходит через эти маркеры и пытается сопоставить его с $format ввода $format . Если найден $format , соответствующее значение вычитается из общего количества месяцев или секунд.

Тем не менее, существует возможность иметь значение больше нуля в течение месяцев или лет, но строка $format не указала ни один из этих параметров. Если это так, функция перекатывается через соответствующее количество дней, чтобы придумать правильные вычисления.

Я кратко протестировал его, и он работает для всех примеров в этом потоке. Вы можете проверить его на кодовом коде, чтобы узнать, можете ли вы его сломать! :) Меня бы интересовали улучшения.

 <?php function calc( $start, $end, $format = '%s', $chop = false) { $tokens = array( '%y', '%m', '%w', '%d', '%h', '%i', '%s'); if( !is_a( $start, 'DateTime') || !is_a( $end, 'DateTime')) { return; } $diff = $start->diff( $end); $months = ($diff->y * 12) + $diff->m; $secs = ($diff->d * 24 * 3600) + ($diff->h * 3600) + ($diff->i * 60) + ($diff->s); $output = array(); while( $token = array_shift( $tokens)) { $token_present = !(strpos( $format, $token) === false); switch( $token) { case '%y': if( ($months / 12) > 0 && $token_present) { $output[$token] = floor( $months / 12); $months -= $output[$token] * 12; } break; case '%m': if( $months > 0 && $token_present) { $output[$token] = $months; $months = 0; } break; case '%w': // Rollover between (months or years) and seconds if( (!isset( $output['%y']) || !isset( $output['%m'])) && $months > 0) { $days = $diff->format( '%a'); // Need a fix for leap year probably. $days -= (isset( $output['%y'])) ? ($output['%y'] * 365) : 0; $days -= $diff->d; $secs += ($days * 24 * 60 * 60); } $val = (7 * 24 * 60 * 60); if( ($secs / $val) > 0 && $token_present) { $output[$token] = floor( $secs / $val); $secs -= $output[$token] * $val; } break; case '%d': $val = (24 * 60 * 60); if( ($secs / $val) > 0 && $token_present) { $output[$token] = floor( $secs / $val); $secs -= $output[$token] * $val; } break; case '%h': $val = (60 * 60); if( ($secs / $val) > 0 && $token_present) { $output[$token] = floor( $secs / $val); $secs -= $output[$token] * $val; } break; case '%i': $val = (60); if( ($secs / $val) > 0 && $token_present) { $output[$token] = floor( $secs / $val); $secs -= $output[$token] * $val; } break; case '%s': if( $secs > 0 && $token_present) { $output[$token] = $secs; } break; } } // Filter out blank keys and replace their tokens in the $format string $filtered = $chop ? array_filter( $output) : $output; $format = str_replace( array_diff( array_keys($output), array_keys($filtered)), '', $format); return str_replace( array_keys( $filtered), array_values( $filtered), $format); } $start = new DateTime('04-Feb-2010'); $end = new DateTime('28-Jun-2011'); echo calc( $start, $end, "%m months %d days\n"); // 16 months 24 days $january = new DateTime('2010-01-01'); $february = new DateTime('2011-02-20 3:35:28'); echo calc( $january, $february, '%y years %m months %w weeks %d days %h hours %i minutes %s seconds'); // 1 years 1 months 2 weeks 5 days 3 hours 35 minutes 28 seconds 

Я думаю, у меня это есть:

 if($time_start > $time_end) list($time_start, $time_end) = array($time_end, $time_start); $start_dt = new DateTime(); $end_dt = new DateTime(); $start_dt->setTimestamp($time_start); $end_dt->setTimestamp($time_end); $has_time = preg_match('`%[his]`',$format) > 0; if(!$has_time) { $start_dt->setTime(0,0,0); $end_dt->setTime(0,0,0); } $interval = $end_dt->diff($start_dt); $parts = $interval->format('%y %m %d %h %i %s %a'); $weeks = 0; list($years, $months, $days, $hours, $mins, $secs, $total_days) = explode(' ',$parts); if(strpos($format,'%y')===false) { $months += $years * 12; } if(strpos($format,'%m')===false) { $days = $total_days; if(strpos($format,'%y')!==false) { $start_dt->add(new DateInterval('P'.$years.'Y')); $interval = $end_dt->diff($start_dt); $days = $interval->days; } } if(strpos($format,'%w')!==false) { $weeks = (int)($days/7); $days %= 7; } if(strpos($format,'%d')===false) { $hours += $days * 24; } if(strpos($format,'%h')===false) { $mins += $hours * 60; } if(strpos($format,'%i')===false) { $secs += $mins * 60; } 

(FYI, я просто делаю некоторую базовую str_replacing в конце, чтобы бросить ее в мою строку пользовательского формата)

Изменить: Есть еще один сценарий, который я не знаю, как обращаться … когда запрашиваются годы и дни, но не месяцы, результат будет неправильным. Я задаюсь вопросом, должен ли я просто модерировать общие дни с 365 … это своего рода редкий сценарий.

Edit2: Решил! Мы добавим много лет к дате начала, а затем пересчитаем общие дни.