如何以格式化字符串的形式计算两天之间的差异


How to calculate the difference between two days as a formatted string?

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

/**
 * 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/>";
}

(不完整不准确(

我似乎无法正确计算数学。天真地将其划分是行不通的,因为闰年和不同的月份长度。

逻辑也需要根据格式字符串进行更改。例如,使用格式字符串%y year %m month %d day将 04-Feb-2010 传递到 28-Jun-2011(作为 unix 时间戳(应该输出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::d iff((来做这件事:

$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,则从月数或秒总数中减去适当的值。

但是,有可能在数

月或数年内具有大于零的值,但$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,将其放入我的自定义格式字符串中(

编辑:还有1个我不知道如何处理的情况...当请求年和日而不是月时,输出将是错误的。我想知道我是否应该用 365 修改总天数......这是一种罕见的情况。

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