Re: Date: additional ISO format support

From: Date: Thu, 09 Oct 2003 04:48:54 +0000
Subject: Re: Date: additional ISO format support
References: 1  Groups: php.pear.dev 
Request: Send a blank email to pear-dev+get-22507@lists.php.net to get a copy of this message
Can you file a bug report - add a link to email on marc.theaimsgroup.org That way it wont get lost in the mailing list archives.. - if nobody has added this already. Regards Alan Marshall Roch wrote:
I've finally got working support for additional ISO 8601 formats for Date. It now can input and output:
DATE_FORMAT_ISO:          YYYY-MM-DD HH:MM:SS       (already exists)
DATE_FORMAT_ISO_BASIC:    YYYYMMDDTHHMMSS-HHMM      (new)
DATE_FORMAT_ISO_EXTENDED: YYYY-MM-DDTHH:MM:SS-HH:MM (new)
DATE_FORMAT_TIMESTAMP:    YYYYMMDDHHMMSS            (already exists)
DATE_FORMAT_UNIXTIME:     <secs>                    (already exists)
Several points: o All 3 ISO formats are basically the same, and as such, can be parsed with one regex (more efficient than testing for each format individually). o "Basic" and "Extended" come from the ISO spec. I couldn't find names for these formats in Perl. o Each DATE_FORMAT_* constant specifies a different output format. Even though the /parser/ doesn't differentiate between the 3 ISO formats, the builder does. o To support timezone offsets, I added a toUTCbyOffset() method. Since Date can't look up a timestamp by an offset, I had to convert the datetime to UTC. Unit Test: o To test my changes, I began a PHPUnit test for Date.php. It is not complete, but it checks enough methods to be sure that the new date formats work. Fixed bugs: o I found two bugs while writing the unit test. See the bug tracker[1] o Possible bug: the current Date's regexps do not make sure that the date string contains only a date. I've added ^ and $ to make sure of this. Attachments: o Date-ISO.diff - adds date formats and fixes 2 bugs o testunit_date.php - goes in the tests/ folder, PHPUnit test for Date o testunit.php - goes in tests/, displays Date and Date_Span unit tests -- Marshall Roch [1] http://pear.php.net/bugs/search.php?cmd=display&status=Open&bug_type[]=Date ------------------------------------------------------------------------ Index: Date.php =================================================================== RCS file: /repository/pear/Date/Date.php,v retrieving revision 1.13 diff -u -r1.13 Date.php --- Date.php 25 May 2003 18:59:38 -0000 1.13 +++ Date.php 5 Oct 2003 19:12:15 -0000 @@ -30,13 +30,21 @@ */ define('DATE_FORMAT_ISO', 1); /** + * "YYYYMMSSTHHMMSS(Z|(+/-)HHMM)?" + */ +define('DATE_FORMAT_ISO_BASIC', 2); +/** + * "YYYY-MM-SSTHH:MM:SS(Z|(+/-)HH:MM)?" + */ +define('DATE_FORMAT_ISO_EXTENDED', 3); +/** * "YYYYMMDDHHMMSS" */ -define('DATE_FORMAT_TIMESTAMP', 2); +define('DATE_FORMAT_TIMESTAMP', 4); /** * long int, seconds since the unix epoch */ -define('DATE_FORMAT_UNIXTIME', 3); +define('DATE_FORMAT_UNIXTIME', 5); /** * Generic date handling class for PEAR. @@ -95,7 +103,7 @@
      * Creates a new Date Object
      * initialized to the current date/time in the
      * system default time zone by default.  A date optionally
-     * passed in may be in the ISO, TIMESTAMP or UNIXTIME format,
+     * passed in may be in the ISO 8601, TIMESTAMP or UNIXTIME format,
      * or another Date object.
      *
      * @access public
@@ -109,16 +117,20 @@
             $this->setDate(date('Y-m-d H:i:s'));
         } elseif (is_object($date) && (get_class($date) == 'date')) {
             $this->copy($date);
-        } elseif (preg_match('/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/', $date)) {
+        // matches DATE_FORMAT_ISO, DATE_FORMAT_ISO_BASIC, and DATE_FORMAT_ISO_EXTENDED
+        } elseif (preg_match('/^(\d{4})-?(\d{2})-?(\d{2})[T\s](\d{2}):?(\d{2}):?(\d{2})(Z|[\+\-]\d{2}\d{2})?$/i', $date)) {
             $this->setDate($date);
-        } elseif (preg_match('/\d{14}/',$date)) {
+        } elseif (preg_match('/^\d{14}$/',$date)) {
             $this->setDate($date,DATE_FORMAT_TIMESTAMP);
-        } elseif (preg_match('/\d{4}-\d{2}-\d{2}/', $date)) {
+        } elseif (preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) {
             $this->setDate($date.' 00:00:00');
-        } elseif (preg_match('/\d{8}/',$date)) {
+        } elseif (preg_match('/^\d{8}$/',$date)) {
             $this->setDate($date.'000000',DATE_FORMAT_TIMESTAMP);
-        } else {
+        } elseif (is_numeric($date)) {
             $this->setDate($date,DATE_FORMAT_UNIXTIME);
+        } else {
+            include_once 'PEAR.php';
+            PEAR::raiseError("Invalid/unrecognized date format!");
         }
     }
 @@ -136,13 +148,25 @@
     {
         switch ($format) {
         case DATE_FORMAT_ISO:
-            if (ereg("([0-9]{4})-([0-9]{2})-([0-9]{2})[ ]([0-9]{2}):([0-9]{2}):([0-9]{2})",$date,$regs)) {
+        case DATE_FORMAT_ISO_BASIC:
+        case DATE_FORMAT_ISO_EXTENDED:
+            if (preg_match('/^(\d{4})-?(\d{2})-?(\d{2})[T\s](\d{2}):?(\d{2}):?(\d{2})(Z|[\+\-]\d{2}\d{2})?$/i', $date, $regs)) {
+
                 $this->year   = $regs[1];
                 $this->month  = $regs[2];
                 $this->day    = $regs[3];
                 $this->hour   = $regs[4];
                 $this->minute = $regs[5];
                 $this->second = $regs[6];
+
+                // if an offset is defined, convert time to UTC
+                // Date currently can't set a timezone only by offset,
+                // so it has to store it as UTC
+                if ($regs[7]) {
+                    $this->toUTCbyOffset($regs[7]);
+                }
+
+                             } else {
                 $this->year   = 0;
                 $this->month  = 1;
@@ -191,11 +215,25 @@
         case DATE_FORMAT_ISO:
             return $this->format("%Y-%m-%d %T");
             break;
+        case DATE_FORMAT_ISO_BASIC:
+            $format = "%Y%m%dT%H%M%S";
+            if ($this->tz->getID() == 'UTC') {
+                $format .= "Z";
+            }
+            return $this->format($format);
+            break;
+        case DATE_FORMAT_ISO_EXTENDED:
+            $format = "%Y-%m-%dT%H:%M:%S";
+            if ($this->tz->getID() == 'UTC') {
+                $format .= "Z";
+            }
+            return $this->format($format);
+            break;
         case DATE_FORMAT_TIMESTAMP:
             return $this->format("%Y%m%d%H%M%S");
             break;
         case DATE_FORMAT_UNIXTIME:
-            return mktime($this->hour, $this->minute, $this->second, $this->month, $this->day, $this->year, 0);
+            return mktime($this->hour, $this->minute, $this->second, $this->month, $this->day, $this->year);
             break;
         }
     }
@@ -293,7 +331,7 @@
                     $output .= sprintf("%02d/%02d/%02d",$this->month,$this->day,$this->year);
                     break;
                 case "e":
-                    $output .= $this->day;
+                    $output .= $this->day * 1; // get rid of leading zero
                     break;
                 case "E":
                     $output .= Date_Calc::dateToDays($this->day,$this->month,$this->year);
@@ -515,6 +553,37 @@
           $tz = Date_TimeZone::getDefault();
        }
        $this->convertTZ($tz);
+    }
+
+    function toUTCbyOffset($offset)
+    {
+
+        if ($offset == "Z" || $offset == "+00:00" || $offset == "+0000") {
+            $this->toUTC();
+            return true;
+        }
+
+        if (preg_match('/([\+\-])(\d{2}):?(\d{2})/', $offset, $regs)) {
+            // convert offset to seconds
+            $hours  = (int) $regs[2];
+            $mins   = (int) $regs[3];
+            $offset = ($hours * 3600) + ($mins * 60);
+
+            if ($regs[1] == "-") {
+                $offset *= -1;
+            }
+
+            if ($offset > 0) {
+                $this->subtractSeconds(intval($offset / 1000));
+            } else {
+                $this->addSeconds(intval(abs($offset) / 1000));
+            }
+
+            $this->tz = new Date_TimeZone('UTC');
+            return true;
+        }
+
+        return false;
     }
      /**
------------------------------------------------------------------------ <?php echo "<pre>"; require_once 'PHPUnit.php'; require_once 'testunit_date.php'; require_once 'testunit_date_span.php'; echo "</pre>"; ?> ------------------------------------------------------------------------ <?php // vim: set expandtab tabstop=4 softtabstop=4 shiftwidth=4: // +----------------------------------------------------------------------+
// | PHP Version 4                                                        |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2003 The PHP Group                                |
// +----------------------------------------------------------------------+
// | This source file is subject to version 2.02 of the PHP license,      |
// | that is bundled with this package in the file LICENSE, and is        |
// | available at through the world-wide-web at                           |
// | http://www.php.net/license/2_02.txt.                                 |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to          |
// | license@php.net so we can mail you a copy immediately.               |
// +----------------------------------------------------------------------+
// | Author: Marshall Roch <mroch@php.net>                                |
// +----------------------------------------------------------------------+ // // $Id$ // require_once '../Date.php'; require_once 'PHPUnit.php'; class Date_Test extends PHPUnit_TestCase {
    var $time;
    function Date_Test($name)
    {
        $this->PHPUnit_TestCase($name);
    }
    function setUp()
    {
        $this->time = new Date("2003-10-04 14:03:24");
    }
    function tearDown()
    {
        unset($this->time);
    }
    function testDateNull()
    {
        $time = new Date();
        $this->assertEquals(
            date('Y-m-d H:i:s'),
            sprintf('%04d-%02d-%02d %02d:%02d:%02d',
                $time->year, $time->month, $time->day,
                $time->hour, $time->minute, $time->second)
        );
    }
    function testDateCopy()
    {
        $temp = new Date($this->time);
        $this->assertEquals($temp, $this->time);
    }
    function testDateISO()
    {
        $temp = new Date("2003-10-04 14:03:24");
        $this->assertEquals(
            '2003-10-04 14:03:24',
            sprintf('%04d-%02d-%02d %02d:%02d:%02d',
                $temp->year, $temp->month, $temp->day,
                $temp->hour, $temp->minute, $temp->second)
        );
    }
    function testDateISOBasic()
    {
        $temp = new Date("20031004T140324", DATE_FORMAT_ISO_BASIC);
        $this->assertEquals(
            '2003-10-04 14:03:24',
            sprintf('%04d-%02d-%02d %02d:%02d:%02d',
                $temp->year, $temp->month, $temp->day,
                $temp->hour, $temp->minute, $temp->second)
        );
    }
    function testDateISOExtended()
    {
        $temp = new Date("2003-10-04T14:03:24", DATE_FORMAT_ISO_EXTENDED);
        $this->assertEquals(
            '2003-10-04 14:03:24',
            sprintf('%04d-%02d-%02d %02d:%02d:%02d',
                $temp->year, $temp->month, $temp->day,
                $temp->hour, $temp->minute, $temp->second)
        );
    }
    function testDateISOTimestamp()
    {
        $temp = new Date("20031004140324", DATE_FORMAT_ISO_TIMESTAMP);
        $this->assertEquals(
            '2003-10-04 14:03:24',
            sprintf('%04d-%02d-%02d %02d:%02d:%02d',
                $temp->year, $temp->month, $temp->day,
                $temp->hour, $temp->minute, $temp->second)
        );
    }
    function testDateUnixtime()
    {
        $temp = new Date(strtotime("2003-10-04 14:03:24"), DATE_FORMAT_UNIXTIME);
        $this->assertEquals(
            '2003-10-04 14:03:24',
            sprintf('%04d-%02d-%02d %02d:%02d:%02d',
                $temp->year, $temp->month, $temp->day,
                $temp->hour, $temp->minute, $temp->second)
        );
    }
    function testSetDateISO()
    {
        $this->time->setDate("2003-10-04 14:03:24", DATE_FORMAT_ISO);
        $this->assertEquals(
            '2003-10-04 14:03:24',
            sprintf('%04d-%02d-%02d %02d:%02d:%02d',
                $this->time->year, $this->time->month, $this->time->day,
                $this->time->hour, $this->time->minute, $this->time->second)
        );
    }
    function testSetDateISOBasic()
    {
        $this->time->setDate("20031004T140324", DATE_FORMAT_ISO_BASIC);
        $this->assertEquals(
            '2003-10-04 14:03:24',
            sprintf('%04d-%02d-%02d %02d:%02d:%02d',
                $this->time->year, $this->time->month, $this->time->day,
                $this->time->hour, $this->time->minute, $this->time->second)
        );
    }
    function testSetDateISOExtended()
    {
        $this->time->setDate("2003-10-04T14:03:24", DATE_FORMAT_ISO_EXTENDED);
        $this->assertEquals(
            '2003-10-04 14:03:24',
            sprintf('%04d-%02d-%02d %02d:%02d:%02d',
                $this->time->year, $this->time->month, $this->time->day,
                $this->time->hour, $this->time->minute, $this->time->second)
        );
    }
    function testSetDateTimestamp()
    {
        $this->time->setDate("20031004140324", DATE_FORMAT_TIMESTAMP);
        $this->assertEquals(
            '2003-10-04 14:03:24',
            sprintf('%04d-%02d-%02d %02d:%02d:%02d',
                $this->time->year, $this->time->month, $this->time->day,
                $this->time->hour, $this->time->minute, $this->time->second)
        );
    }
    function testSetDateUnixtime()
    {
        $this->time->setDate(strtotime("2003-10-04 14:03:24"), DATE_FORMAT_UNIXTIME);
        $this->assertEquals(
            '2003-10-04 14:03:24',
            sprintf('%04d-%02d-%02d %02d:%02d:%02d',
                $this->time->year, $this->time->month, $this->time->day,
                $this->time->hour, $this->time->minute, $this->time->second)
        );
    }
    function testGetDateISO()
    {
        $date = $this->time->getDate(DATE_FORMAT_ISO);
        $this->assertEquals('2003-10-04 14:03:24', $date);
    }
    function testGetDateISOBasic()
    {
        $date = $this->time->getDate(DATE_FORMAT_ISO_BASIC);
        $this->assertEquals('20031004T140324Z', $date);
    }
    function testGetDateISOExtended()
    {
        $date = $this->time->getDate(DATE_FORMAT_ISO_EXTENDED);
        $this->assertEquals('2003-10-04T14:03:24Z', $date);
    }
    function testGetDateTimestamp()
    {
        $date = $this->time->getDate(DATE_FORMAT_TIMESTAMP);
        $this->assertEquals('20031004140324', $date);
    }
    function testGetDateUnixtime()
    {
        $date = $this->time->getDate(DATE_FORMAT_UNIXTIME);
        $this->assertEquals(strtotime('2003-10-04 14:03:24'), $date);
    }
    function testFormat()
    {
        $codes = array(
            'a' => 'Sat',
            'A' => 'Saturday',
            'b' => 'Oct',
            'B' => 'October',
            'C' => '20',
            'd' => '04',
            'D' => '10/04/2003',
            'e' => '4',
            'H' => '14',
            'I' => '02',
            'j' => '277',
            'm' => '10',
            'M' => '03',
            'n' => "\n",
            'O' => '+00:00',
            'o' => '+00:00',
            'p' => 'pm',
            'P' => 'PM',
            'r' => '02:03:24 PM',
            'R' => '14:03',
            'S' => '24',
            't' => "\t",
            'T' => '14:03:24',
            'w' => '6',
            'U' => '40',
            'y' => '03',
            'Y' => '2003',
            '%' => '%'
        );
        foreach ($codes as $code => $expected) {
            $this->assertEquals(
                "$code: $expected", $this->time->format("$code: %$code")
            );
        }
    }
    function testToUTCbyOffset()
    {
        $this->time->setTZbyID('EST');
        $this->time->toUTC();
        $temp = new Date("2003-10-04 14:03:24");
        $temp->toUTCbyOffset("-05:00");
        $this->assertEquals($temp, $this->time);
    }
} // runs the tests $suite = new PHPUnit_TestSuite("Date_Test"); $result = PHPUnit::run($suite); // prints the tests echo $result->toString(); ?>
-- Can you help out? Need Consulting Services or Know of a Job? http://www.akbkhome.com

« previous php.pear.dev (#22507) next »