I've run into a strange timewarp while doing some math with time, and it has left me stumped. Or, well, I've found the reason (or atleast a plausible one), but don't quite know what to do about it, or if there is indeed anything that can be done.
The issue in plain words is, that when adding time in larger units than 1 week (it's multipliers excluded) it seems to be impossible to be exact. And by exact I mean, that when I add 1 years worth of seconds to NOW, I end up 1 year and some hours from NOW.
I can understand that adding 1 (or more) months will do this, as a months length varies, but a year should be more or less the same, shouldn't it?
Now I know you'll want to know how I do this, so here follows (pseudoish) code:
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);
Doing some debug printouts of the interval length and before/after values show that it's all good, in the sense that "it works as expected and all checks out", but the issue stated above still stands: there is an anomaly in the exactness of it all which I'd very much would like to get right, if possible.
In so many words: How to do exact mathematics with time units greater than 1 week. (I.e. 1 month / 1 year).
PS. For those wondering why I'm using My_* classes is because PHP 5.3 just isn't widespread enough, but I'm trying to keep things in a way that migrating to built-in utility classes will be as smooth as possible.