代码之家  ›  专栏  ›  技术社区  ›  nikc.org

增加时间的问题

  •  1
  • nikc.org  · 技术社区  · 16 年前

    我在计算时间的时候遇到了一个奇怪的时间偏差,这让我很困惑。或者,好吧,我已经找到了原因(或者至少是一个似是而非的原因),但不知道该怎么办,或者是否真的有什么可以做的。

    我可以理解,增加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年)。

    对于那些想知道为什么我要使用我的类的人来说,是因为PHP5.3还不够普及,但我正试图保持迁移到内置实用程序类时尽可能平滑的方式。

    3 回复  |  直到 16 年前
        1
  •  2
  •   djna    16 年前

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

    月份的长度是可变的。

    因此,添加一年和添加一个月的语义可能并不对应于添加固定的秒数。

    你找不到这方面的图书馆吗?

        2
  •  1
  •   Seth    16 年前

    平均的 年份(即365.2421..*24 * 60 * 60). 因此,您的计算隐含地将一年定义为特定天数。

    根据这一定义,12月31日的时间比6小时稍长一些。所以你的“时钟”从12月31日23:59到29:59(不管是什么),然后在1月1日滚动到00:00。月也会发生类似的事情,因为你也将它们定义为特定的秒数,而不是28-31天。

    如果您的计算目的是在通用“平均”日历上对事件之间的差异进行计时,那么您的方法可以正常工作。但是如果你需要一个真正的日历,它会关闭。

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

    当你想加减的时候,手动计算。。。每次递增时,检查“溢出”。如果您在当月的某一天溢出,请重置它并增加该月的值(然后检查该月是否溢出,等等)。

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

    正如你所看到的,这相当于365天的毫秒。没有混乱,没有大惊小怪。

    顺便说一句,有时间吗 这很难。数学以60为基数,都是特殊情况(闰年、夏令时、时区)。

    玩得高兴

        3
  •  0
  •   nikc.org    16 年前

    为了以防万一有人感兴趣,工作解决方案是如此简单(像往常一样),让我觉得自己很愚蠢。与将间隔长度转换为秒不同,更冗长的版本可以正确地使用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;
        }
    }