5

这是我到目前为止所得到的:

/**
 * 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如果省略“更大的单位”,则不会按照我的要求...给出总月数和天数。例子:

>>> $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省略,分钟应该滚动到秒

如果省略“较小的单元”,则下一个最大的单元可以在有意义的地方进行四舍五入或地板化。

4

5 回答 5

1
$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 个月),您始终可以直接访问成员并连接格式字符串。

于 2012-01-24T19:57:32.990 回答
1

我会为此使用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()

于 2012-01-24T20:01:39.883 回答
1

我不希望得到一个公认的答案,但这里是如何让 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 加回月份)。如果格式中不包含“月”,那么您可以使用总天数来处理月份具有不同天数的事实。

于 2012-01-24T21:58:30.260 回答
1

我明白了:

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 修改总天数……这是一种罕见的情况。

编辑2:解决了!我们只需将那么多年添加到开始日期,然后重新计算总天数。

于 2012-01-24T22:54:38.307 回答
1

这是我对一个有趣问题的尝试。它不是 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
于 2012-01-24T23:37:56.383 回答