随着时间的推移,我在做一些数学运算时遇到了一个奇怪的时间扭曲,这让我感到难过。或者,我已经找到了原因(或者至少是一个合理的原因),但是我不知道该怎么办,或者确实有什么可以做的。
用简单的话来说,问题是,当以比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还不够广泛,但我试图保持一种迁移到内置实用程序类的方式尽可能顺利。
答案 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;
}
}