Как рассчитать разницу между двумя днями в виде форматированной строки?
Вот что у меня есть до сих пор:
/**
* 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('Y-m-d',$time_start));
list($year_end,$month_end,$day_end) = explode('-',date('Y-m-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('Y-m-d',$time_start).' to '.date('Y-m-d',$time_end).": {$years}y {$months}m {$weeks}w {$days}d<br/>";
}
(оно неполное и неточное)
Я, кажется, не могу правильно рассчитать. Наивно разделять его не получится из-за високосных лет и разной длины месяцев.
Логика также должна изменяться в зависимости от строки формата. Например, переход от 04-Feb-2010 к 28-Jun-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 не делает того, что я asked...to дайте общее количество месяцев и дней, если "большие единицы" опущены. Пример:
>>> $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опущено, минуты должны быть свернуты в секунды
Если "меньшие единицы" опущены, следующая самая большая единица может быть округлена или выровнена там, где это имеет смысл.
5 ответов:
Я не ожидаю приемлемого ответа, но вот как получить 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) ...");Я бы попробовал это сначала, если это не работает для ваших целей, то вы можете попробовать использовать функции календаря, чтобы получить правильное количество дней в каждом месяце.
Я думаю, что вам нужно было бы решить свою собственную логику для расчета недель. Лично я бы сказал
$weeks = $difference->d / 7;, но нет строго правильного способа подсчета прошедших недель в части месяца. Подумайте о календаре, мы часто говорим о первой, второй, третьей неделе, но если месяц не начался в воскресенье (или понедельник, или в субботу, в зависимости от компании, религии и т. д.) тогда действительно мы не абсолютны в этих описаниях. Вы можете абсолютно точно сказать, что с начала месяца было 3 воскресенья (например), но не три недели. 28-го, прошло уже четыре недели? Что, если месяц начался с женитьбы, то это пять?Кроме того, если вам нужен пользовательский формат (35 месяцев), вы всегда можете получить прямой доступ к членам и объединить строку формата.
Вот моя попытка решить интересную проблему. Это не совсем работает на 100%, но это очень близко.
Функция начинается с вычисления общего количества секунд и месяцев, которое требуется для разницы дат. Затем, используя массив$tokens, отсортированный в порядке возрастания, он перебирает эти маркеры и пытается сопоставить их с входными данными$format. Если$formatнайдено, то соответствующее значение вычитается из общего числа месяцев или секунд.Однако существует возможность иметь значение больше нуля Для месяцев или лет, но строка
Я кратко протестировал его, и он работает для всех примеров в этом потоке. Вы можете проверить его на codepad , чтобы увидеть, если вы можете сломать его!$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; }(к вашему сведению, я просто делаю некоторые базовые str_replacing в конце, чтобы бросить его в мою пользовательскую строку формата)
Edit: есть еще один сценарий, с которым я не знаю, как справиться... когда запрашиваются годы и дни, а не месяцы, результат будет неверным. Мне интересно, если я просто модулирую общее количество дней с 365.... это довольно редкий сценарий.
Edit2: решил ее! Мы просто добавим, что много лет к дате начала и затем пересчитайте общее количество дней.
Comments