 +      } else {
 +        $this->_gmtofs= (int)date('Z');
 +      }
-       
++
+       if (is_string($in)) {
+-        $this->_utime(self::_strtotime($in));
++        $this->parse($in, $gmtOffset);
+       } else if (is_int($in) || is_float($in)) {
+         $this->_utime($in);
+       } else if (is_null($in)) {
+         $this->_utime(time());
+       } else {
+-        $this->_utime(time());
+-        throw(new IllegalArgumentException(
++        throw new IllegalArgumentException(
+           'Given argument is neither a timestamp nor a well-formed timestring'
+-        ));
++        );
+       }
+     }
+     
+     /**
+-     * Get local time zones' offset from GMT (Greenwich main time). 
+-     * Caches the result.
+-     *
+-     * @return  int offset in seconds
+-     */
+-    protected function _getGMTOffset() {
+-      static $o;
+-      
 -      if (!isset($o)) $o= mktime(0, 0, 0, 1, 2, 1970, 0)- gmmktime(0, 0, 0, 1, 2, 1970, 0);
 -      return $o;
-+      if ($this->_gmtofs === '7200') throw new Error('FICKL');
-     }
-     
-     /**
-@@ -106,9 +114,6 @@
-         'Dec' => 12
-       );
+-    }
+-    
+-    /**
+      * Returns whether a year is a leap year
+      *
+      * @param   int year
+@@ -85,51 +90,54 @@
+     }
+     
+     /**
+-     * Overflow-safe replacement for PHP's strtotime() function.
++     * Wrapper around PHP's date_parse() function
+      *
+      * @param   string in
+-     * @return  int
++     * @see     php://date_parse
++     * @param   mixed usetz
++     * @throws  lang.IllegalArgumentException in case the date is unparseable
+      */
+-    protected static function _strtotime($in) {
+-      static $month_names= array(
+-        'Jan' => 1,
+-        'Feb' => 2,
+-        'Mar' => 3,
+-        'Apr' => 4,
+-        'May' => 5,
+-        'Jun' => 6,
+-        'Jul' => 7,
+-        'Aug' => 8,
+-        'Sep' => 9,
+-        'Oct' => 10,
+-        'Nov' => 11,
+-        'Dec' => 12
+-      );
++    protected function parse($in, $supplied) {
        
 -      // Try to use builtin function strtotime()
 -      if (-1 != ($stamp= strtotime($in)) && FALSE !== $stamp) return $stamp;
 -      
-       // European date format (dd.mm.yyyy hh:mm:ss). At least two values
-       // need to be found
-       if (2 < sscanf($in, '%d.%d.%d %d:%d:%d', $d, $m, $y, $h, $i, $s)) {
-@@ -127,9 +132,7 @@
-         return self::mktime($h, $i, 0, $month_names[$n], $d, $y);
+-      // European date format (dd.mm.yyyy hh:mm:ss). At least two values
+-      // need to be found
+-      if (2 < sscanf($in, '%d.%d.%d %d:%d:%d', $d, $m, $y, $h, $i, $s)) {
+-        return self::mktime($h, $i, $s, $m, $d, $y);
++      // If parsing fails generically, throw an exception
++      if (!($r= date_parse($in))) {
++        throw new IllegalArgumentException('Cannot parse "'.$in.'"');
+       }
+ 
+-      // "2006-05-04 11:59:00"
+-      if (2 < sscanf($in, '%4d-%02d-%02d %02d:%02d:%02d', $y, $m, $d, $h, $i, $s)) {
+-        return self::mktime($h, $i, $s, $m, $d, $y);
++      // Check for warnins and errors
++      if (0 != $r['warning_count']+ $r['error_count']) {
++        if (0 === @$r['zone_type']) {
++          // date_parse("1969-12-31 00:00AM") e.g. causes this and sets 
++          // errors => [
++          //   XX => "The timezone could not be found in the database"
++          // ]
++          // Ignore this case
++        } else {
++          throw new IllegalArgumentException('Cannot parse "'.$in.'": '.xp::stringOf($r));
++        }
+       }
+       
+-      // "Dec 31 2070 11:59PM"
+-      if (2 < sscanf($in, '%3s %02d %04d %02d:%02d%[AP]M', $n, $d, $y, $h, $i, $m)) {
+-        ($m == 'A' && $h == 12) && $h= 0;
+-        ($m == 'A') || ($m == 'P' && $h == 12) || $h+= 12;
+-        return self::mktime($h, $i, 0, $month_names[$n], $d, $y);
++      // If no timezone was given to constructor and the in string contains a
++      // timezone, use that. If timezones collide (e.g. when a Date is created
++      // via something like new Date('10.01.2000 13:14 +200', '+0300'))), throw
++      // an exception.
++      if (isset($r['zone'])) {
++        $ofs= $r['zone'] * -60;
++        if (NULL === $supplied) {
++          $this->_gmtofs= $ofs;
++        } else if ($this->_gmtofs != $ofs) {
++          throw new IllegalArgumentException('Colliding timezones in '.$in.' and '.$supplied);
++        }
        }
        
 -      // FIXME: Support more formats
 -      
 -      throw(new IllegalArgumentException('Cannot parse "'.$in.'"'));
-+      throw new IllegalArgumentException('Cannot parse "'.$in.'"');
-     }
-     
-     /**
-@@ -152,13 +155,6 @@
++      $this->_utime(self::mktime(
++        $r['hour'],
++        $r['minute'],
++        $r['second'],
++        $r['month'],
++        $r['day'],
++        $r['year']
++      ));
+     }
+     
+     /**
+@@ -151,19 +159,16 @@
+         array(NULL, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31),
          array(NULL, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) // Leap years
        );
-     
+-    
 -      // Use builtin?
 -      if (1971 < $year && $year < 2038) {
 -        return mktime($hour, $minute, $second, $month, $day, $year, $is_dst);
 -      }
 -      
 -      $gmt= 0;  // FIXME
--
+ 
        // Check for month overflow and advance into next year
        if ($month > 12) {
          $y= floor($month / 12);
-@@ -181,7 +177,7 @@
+         $year+= $y;
+         $month-= $y * 12;
++      } else if ($month < 1) {
++        $y= floor($month / 12)+ 1;
++        $year-= $y;
++        $month+= $y * 12;
+       }
+ 
+       $days= 0;
+@@ -181,7 +186,7 @@
          $days+= $day- 1;
          
          // Calculate stamp

      */
-    public function __construct($in= NULL) {
+    public function __construct($in= NULL, $gmtOffset= NULL) {
       if (is_string($in)) {
         $this->_utime(self::_strtotime($in));
       } else if (is_int($in) || is_float($in)) {
@@ -55,23 +65,21 @@
         $this->_utime(time());
       } else {
         $this->_utime(time());
-        throw(new IllegalArgumentException(
+        throw new IllegalArgumentException(
           'Given argument is neither a timestamp nor a well-formed timestring'
-        ));
+        );
       }
-    }
-    
-    /**
-     * Get local time zones' offset from GMT (Greenwich main time). 
-     * Caches the result.
-     *
-     * @return  int offset in seconds
-     */
-    protected function _getGMTOffset() {
-      static $o;
+
+      if (is_string($gmtOffset)) {
+        sscanf($gmtOffset, '%3d%2d', $h, $m);
+        $this->_gmtofs= $h * 3600 + $m * 60;

           case 'F': $return.= $this->month; break;
           case 'g': $return.= $this->hours == 0 ? 12 : ($this->hours > 12 ? $this->hours - 12 : $this->hours); break;
           case 'G': $return.= $this->hours; break;
@@ -536,33 +533,43 @@
           case 'l': $return.= $this->weekday; break;
           case 'L': $return.= (int)$this->leap; break;
           case 'm': $return.= sprintf('%02d', $this->mon); break;
