How to detect Ambiguous and Invalid DateTime in PHP? -


when dealing local datetime values provided user, it's quite possible have time either invalid or ambiguous, due daylight saving time transitions.

in other languages , frameworks, there methods such isambiguous , isvalid, on representation of time zone. example in .net, there timezoneinfo.isambiguoustime , timezoneinfo.isinvalidtime.

plenty of other time zone implementations have similar methods, or functionality address concern. example, in python, pytz library throw ambiguoustimeerror or invalidtimeerror exception can trap.

php has excellent time zone support, can't seem find address this. closest thing can find datetimezone::gettransitions. provides raw data, can see methods written on top of this. exist somewhere? if not, can provide implementation? expect them work this:

$tz = new datetimezone('america/new_york'); echo $tz->isvalidtime(new datetime('2013-03-10 02:00:00'));       # false echo $tz->isambiguoustime(new datetime('2013-11-03 01:00:00'));   # true 

i 'm not aware of existing implementations , haven't had cause use advanced date/time features such these yet, here clean room implementation.

to enable syntax illustrated in question going extend datetimezone follows:

class datetimezoneex extends datetimezone {     const max_dst_shift = 7200; // let's generous      // datetime instead of datetimeinterface php < 5.5     public function isvalidtime(datetimeinterface $date);      public function isambiguoustime(datetimeinterface $date); } 

to keep distracting details cluttering implementation, going assume $date arguments have been created proper time zone; in contrast example code given in question.

that say, correct result not produced this:

$tz = new datetimezoneex('america/new_york'); echo $tz->isvalidtime(new datetime('2013-03-10 02:00:00')); 

but instead this:

$tz = new datetimezoneex('america/new_york'); echo $tz->isvalidtime(new datetime('2013-03-10 02:00:00', $tz)); 

of course since $tz known object $this, should easy extend methods requirement removed. in case, making interface super user friendly out of scope of answer; going forward focus on technical details.

isvalidtime

the idea here use gettransitions see if there transitions around date/time interested in. gettransitions return array either 1 or 2 elements; timezone situation "begin" timestamp there, , element exist if transition occurs shortly after it. value of max_dst_shift small enough there no chance of getting second transition/third element.

let's see code:

public function isvalidtime(datetime $date) {     $ts = $date->gettimestamp();     $transitions = $this->gettransitions(         $ts - self::max_dst_shift,         $ts + self::max_dst_shift     );      if (count($transitions) == 1) {         // no dst changes around here, $date valid         return true;     }      $shift = $transitions[1]['offset'] - $transitions[0]['offset'];      if ($shift < 0) {         // clock moved backward, $date valid         // (although might ambiguous)         return true;     }      $compare = new datetime($date->format('y-m-d h:i:s'), $this);      return $compare->modify("$shift seconds")->gettimestamp() != $ts; } 

the final point of code depends on fact php's date functions calculate timestamps invalid date/times if wall clock time had not shifted. is, timestamps calculated 2013-03-10 02:30:00 , 2013-03-10 03:30:00 identical on new york timezone.

it's not difficult see how take advantage of fact: create new datetime instance equal input $date, shift forward in wall clock time terms amount equal dst shift in seconds (it imperative dst not taken account make adjustment). if timestamp of result (here dst rules come play) equal timestamp of input, input invalid date/time.

isambiguoustime

the implementation quite similar isvalidtime, few details change:

public function isambiguoustime(datetime $date) {     $ts = $date->gettimestamp();     $transitions = $this->gettransitions(         $ts - self::max_dst_shift,          $ts + self::max_dst_shift);      if (count($transitions) == 1) {         return false;     }      $shift = $transitions[1]['offset'] - $transitions[0]['offset'];      if ($shift > 0) {         // clock moved forward, $date not ambiguous         // (although might invalid)         return false;     }      $shift = -$shift;     $compare = new datetime($date->format('y-m-d h:i:s'), $this);     return $compare->modify("$shift seconds")->gettimestamp() - $ts > $shift; } 

the final point depends on implementation detail of php's date functions: when asked produce timestamp ambiguous date/time, php produces timestamp of first (in absolute time terms) occurrence. means the timestamps of latest ambiguous time , earliest non-ambiguous time given dst change differ amount larger dst offset (specifically, difference in range [offset + 1, 2 * offset], offset absolute value).

the implementation takes advantage of again doing "wall clock shift" forward , checking timestamp difference between result , input $date.

see code in action.


Comments

Popular posts from this blog

java - Run a .jar on Heroku -

java - Jtable duplicate Rows -

validation - How to pass paramaters like unix into windows batch file -