添加时间的问题

时间:2010-01-08 08:20:49

标签: php datetime time

随着时间的推移,我在做一些数学运算时遇到了一个奇怪的时间扭曲,这让我感到难过。或者,我已经找到了原因(或者至少是一个合理的原因),但是我不知道该怎么办,或者确实有什么可以做的。

用简单的话来说,问题是,当以比1周更大的单位增加时间时(排除乘数),似乎不可能是准确的。确切地说,我的意思是,当我现在增加1年的秒数时,我现在从现在开始1年和几个小时。

我可以理解,加上1个月(或更长)的月份可以做到这一点,因为月份长度不同,但是一年应该或多或少相同,不是吗?

现在我知道你会想知道我是怎么做的,所以接下来是(伪)代码:

class My_DateInterval {
    public $length; // Interval length in seconds

    public function __construct($interval) {
        $this->length = 0;

        preg_match(
            '/P(((?<years>([0-9]{1,}))Y)|((?<months>([0-9]{1,}))M)|((?<weeks>([0-9]{1,}))W)|((?<days>([0-9]{1,}))D)){0,}(T((?<hours>([0-9]{1,2})){1}H){0,1}((?<minutes>([0-9]{1,2}){1})M){0,1}((?<seconds>([0-9]{1,2}){1})S){0,1}){0,1}/', 
            $interval, $timeparts
        );

        if (is_numeric($timeparts['years'])) $this->length += intval($timeparts['years']) * 31556926; // 1 year in seconds
        if (is_numeric($timeparts['months'])) $this->length += intval($timeparts['months']) * 2629743.83; // 1 month in seconds
        if (is_numeric($timeparts['weeks'])) $this->length += intval($timeparts['weeks']) * 604800; // 1 week in seconds
        if (is_numeric($timeparts['days'])) $this->length += intval($timeparts['days']) * 86400; // 1 day in seconds
        if (is_numeric($timeparts['hours'])) $this->length += intval($timeparts['hours']) * 3600; // 1 hour in seconds
        if (is_numeric($timeparts['minutes'])) $this->length += intval($timeparts['minutes']) * 60; // 1 minute in seconds
        if (is_numeric($timeparts['seconds'])) $this->length += intval($timeparts['seconds']);

        $this->length = round($this->length);
    }
}

class My_DateTime extends DateTime {
    public function __construct($time, $tz = null) {
        parent::__contruct($time, $tz);
    }

    public function add(My_DateInterval $i) {
        $this->modify($i->length . ' seconds');
    }
}

$d = new My_DateTime();
$i = new My_DateInterval('P1Y');
$d->add($i);

对区间长度和值之前/之后进行一些调试打印输出显示它一切都很好,从某种意义上说“它按预期工作并且所有检出”,但上述问题仍然存在:存在异常现象如果可能的话,我非常希望得到正确的一切。

用这么多的话说:如何用大于1周的时间单位进行精确的数学运算。 (即1个月/ 1年)。

PS。对于那些想知道我为什么使用My_ *类的人来说,因为PHP 5.3还不够广泛,但我试图保持一种迁移到内置实用程序类的方式尽可能顺利。

3 个答案:

答案 0 :(得分:2)

一年是365.25天,大概是。因此,我们有闰年。

月份的长度可变。

因此,添加一年和添加月份的语义可能与添加固定秒数无关。

例如,2007年2月14日增加一个月可能预计将于2007年3月14日到2008年2月14日到2008年3月14日,分别增加28或29天。

这些东西变得粗糙,特别是当我们添加不同的日历时,世界上很多人甚至没有拥有二月!然后添加“七个工作日” - 您需要考虑公共假期日历。

您是否找不到可以找到的图书馆?

答案 1 :(得分:1)

  

我可以理解,加上1个月(或更长)的月份可以做到这一点,因为月份长度不同,但是一年应该或多或少相同,不是吗?

上面,当您添加一年的秒数时,您将从(我猜的是)和平均年的天数开始(即365.2421 .. * 24 * 60 * 60)。因此,您的计算隐含地将一年定义为特定天数。

根据这个定义,12月31日的时间不会超过6小时。所以你的“时钟”从12月31日23:59到29:59(无论是什么)在1月1日滚动到00:00之前。几个月会发生类似的事情,因为你也将它们定义为一定的秒数而不是28-31天。

如果计算的目的是计算通用“平均”日历上事件之间的差异,那么您的方法将正常工作。但是,如果你需要与真实日历进行通信,那么它将会被取消。

最简单的方法是使用假朱利安日历。跟踪每个时间段(年,月,日等)。将天数定义为24小时。将一年定义为365天。如果年份可以被4整除,则添加1天,但是除非它也可以被400整除,否则不会被100整除。

当您想要加或减时,手动执行“数学运算”...每次递增时,检查“溢出”。如果您在该月的某一天溢出,请将其重置并增加月份(然后检查月份是否有溢出等)。

您的日历几乎可以与真实日历完全对应。

大多数计算机日期实现基本上都是这样(不同程度的复杂性)。例如,在javascript中(因为我的Web检查器非常方便):

> new Date(2010,0,1,0,0,0) - new Date(2009,0,1,0,0,0)
31536000000

正如您所看到的,这正是365天的毫秒。没有乱,没有大惊小怪。

按照他们的方式,时间正确很难。数学基数为60.这是所有特殊情况(闰年,夏令时,时区)。

玩得开心。

答案 2 :(得分:0)

万一有人感兴趣,工作解决方案非常简单(像往常一样)让我感到愚蠢。而不是将间隔长度转换为秒,而wordier版本可以正常使用PHP内部。

class My_DateInterval {
    public $length; // strtotime supported string format
    public $years;
    public $months;
    public $weeks;
    public $days;
    public $hours;
    public $minutes;
    public $seconds;

    public function __construct($interval) {
        $this->length = 0;

        preg_match(
            '/P(((?<years>([0-9]{1,}))Y)|((?<months>([0-9]{1,}))M)|((?<weeks>([0-9]{1,}))W)|((?<days>([0-9]{1,}))D)){0,}(T((?<hours>([0-9]{1,2})){1}H){0,1}((?<minutes>([0-9]{1,2}){1})M){0,1}((?<seconds>([0-9]{1,2}){1})S){0,1}){0,1}/', 
            $interval, $timeparts
        );

        $this->years = intval($timeparts['years']);
        $this->months = intval($timeparts['months']);
        $this->weeks = intval($timeparts['weeks']);
        $this->days = intval($timeparts['days']);
        $this->hours = intval($timeparts['hours']);
        $this->minutes = intval($timeparts['minutes']);
        $this->seconds = intval($timeparts['seconds']);

        $length = $this->toString();
    }

    public function toString() {
        if (empty($this->length)) {
            $this->length =  sprintf('%d years %d months %d weeks %d days %d hours %d minutes %d seconds',
                $this->years,
                $this->months,
                $this->weeks,
                $this->days,
                $this->hours,
                $this->minutes,
                $this->seconds
            );
        }

        return $this->length;
    }
}