Как рассчитать разницу между двумя днями в виде отформатированной строки? - PullRequest
5 голосов
/ 24 января 2012

Вот что у меня так далеко:

/**
 * 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 февраля 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 не делает то, что я просил ... давать общее количество месяцев и дней, если "большие единицы" опущены. Пример: * 1 027 *

>>> $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 ]

1 голос
/ 25 января 2012

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

Функция начинается с вычисления общего количества секунд и месяцев, которое требуется для разницы дат.Затем, используя массив $tokens, отсортированный в порядке возрастания, он просматривает эти токены и пытается сопоставить его с входными данными $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
1 голос
/ 25 января 2012

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

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 в конце, чтобы бросить его в мою строку пользовательского формата)

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

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

1 голос
/ 25 января 2012

Я бы использовал 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 () для получения дополнительной информации о форматах.

1 голос
/ 25 января 2012

Я не ожидаю, что принятый ответ, но вот как заставить 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 назад к месяцам).Если «месяц» не включен в формат, то вы можете использовать общее количество дней, чтобы учесть тот факт, что месяцы имеют разное количество дней.

1 голос
/ 24 января 2012
$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 месяцев), вы всегда можете напрямую получить доступ к членам и объединить строку формата.

...