ZF-12141: Zend_Date::toString returns wrong date for some 'php' formats

Description

Simple example:


$date = new Zend_Date();
echo $date->toString(DateTime::ATOM, 'php'); // 2012-04-09\UTC12:21:48+00:00

This code display 2012-04-09\UTC12:21:48+00:00 but 2012-04-09T12:21:48+00:00 is expected instead.

But example bellow works as expected:


$date = new Zend_Date();
echo $date->toString(Zend_Date::ATOM); // 2012-04-09T12:21:48+00:00

The basic problem is that Zend_Date::toString uses Zend_Locale_Format::convertPhpToIsoFormat for php format type that works incorrectly with formats that contain symbols are escaped with backslash (\T in example). Zend_Locale_Format::convertPhpToIsoFormat just splits format string to array and convert each symbol to ISO separately. But it should be more complicated. The simplest way to fix it is to get escaped symbol right after backslash and put it into single quotes so Zend_Date will parse it as comment.

Comments

It might looks like below:


    public static function convertPhpToIsoFormat($format)
    {
        if ($format === null) {
            return null;
        }

        $convert = array('d' => 'dd'  , 'D' => 'EE'  , 'j' => 'd'   , 'l' => 'EEEE', 'N' => 'eee' , 'S' => 'SS'  ,
                         'w' => 'e'   , 'z' => 'D'   , 'W' => 'ww'  , 'F' => 'MMMM', 'm' => 'MM'  , 'M' => 'MMM' ,
                         'n' => 'M'   , 't' => 'ddd' , 'L' => 'l'   , 'o' => 'YYYY', 'Y' => 'yyyy', 'y' => 'yy'  ,
                         'a' => 'a'   , 'A' => 'a'   , 'B' => 'B'   , 'g' => 'h'   , 'G' => 'H'   , 'h' => 'hh'  ,
                         'H' => 'HH'  , 'i' => 'mm'  , 's' => 'ss'  , 'e' => 'zzzz', 'I' => 'I'   , 'O' => 'Z'   ,
                         'P' => 'ZZZZ', 'T' => 'z'   , 'Z' => 'X'   , 'c' => 'yyyy-MM-ddTHH:mm:ssZZZZ',
                         'r' => 'r'   , 'U' => 'U');
        $values = str_split($format);
        $escaped = false;
        $temp = array();
        foreach ($values as $key => $value) {
            if ($v == '\\') {
                $escaped = true;
                continue;
            }
            if ($escaped) {
                $temp[] = str_replace("'''", "''", "'$value'");
                $escaped = false;
            } else if ($value == "'") {
                $temp[] = "''";
            } else if (isset($convert[$value]) === true) {
                $temp[] = $convert[$value];
            } else {
                $temp[] = $value;
            }
        }

        return join($values);
    }

Wrong return in my example, it should be: ``` Sorry for typo.

Hi Mustafin,

thanks for the code snippet. However I found two issues that still exist: 1. Inserting of consecutive escaped characters like "\a\t" result in "a't" 2. A masked backslash was not displayed

My suggestion is to improve it like this:


public static function convertPhpToIsoFormat($format)
    {
        if ($format === null) {
            return null;
        }

        $convert = array('d' => 'dd'  , 'D' => 'EE'  , 'j' => 'd'   , 'l' => 'EEEE', 'N' => 'eee' , 'S' => 'SS'  ,
                         'w' => 'e'   , 'z' => 'D'   , 'W' => 'ww'  , 'F' => 'MMMM', 'm' => 'MM'  , 'M' => 'MMM' ,
                         'n' => 'M'   , 't' => 'ddd' , 'L' => 'l'   , 'o' => 'YYYY', 'Y' => 'yyyy', 'y' => 'yy'  ,
                         'a' => 'a'   , 'A' => 'a'   , 'B' => 'B'   , 'g' => 'h'   , 'G' => 'H'   , 'h' => 'hh'  ,
                         'H' => 'HH'  , 'i' => 'mm'  , 's' => 'ss'  , 'e' => 'zzzz', 'I' => 'I'   , 'O' => 'Z'   ,
                         'P' => 'ZZZZ', 'T' => 'z'   , 'Z' => 'X'   , 'c' => 'yyyy-MM-ddTHH:mm:ssZZZZ',
                         'r' => 'r'   , 'U' => 'U');
        $values = str_split($format);
        $escaped = false;
        $lastescaped = false;
        $temp = array();
        foreach ($values as $key => $value) {
            if (!$escaped && $value == '\\') {
                $escaped = true;
                continue;
            }
            if ($escaped) {
                if (!$lastescaped) {
                    $temp[] = "'";
                    $lastescaped = true;
                } 
                $temp[] = $value;
                $escaped = false;
            } else { 
                if ($lastescaped) {
                    $temp[] = "'";
                    $lastescaped = false;
                }
                if ($value == "'") {
                    $temp[] = "''";
                } else if (isset($convert[$value]) === true) {
                    $temp[] = $convert[$value];
                } else {
                    $temp[] = $value;
                }
            }
        }
        return join($temp);
    }

I just realized that the string "\a\'\b" wouldn't work properly with this code. The result would be "a''b" instead of "a'b". To fix this the escaped sequence should only be terminated if the current character is != "'".


    public static function convertPhpToIsoFormat($format)
    {
        if ($format === null) {
            return null;
        }

        $convert = array('d' => 'dd'  , 'D' => 'EE'  , 'j' => 'd'   , 'l' => 'EEEE', 'N' => 'eee' , 'S' => 'SS'  ,
                         'w' => 'e'   , 'z' => 'D'   , 'W' => 'ww'  , 'F' => 'MMMM', 'm' => 'MM'  , 'M' => 'MMM' ,
                         'n' => 'M'   , 't' => 'ddd' , 'L' => 'l'   , 'o' => 'YYYY', 'Y' => 'yyyy', 'y' => 'yy'  ,
                         'a' => 'a'   , 'A' => 'a'   , 'B' => 'B'   , 'g' => 'h'   , 'G' => 'H'   , 'h' => 'hh'  ,
                         'H' => 'HH'  , 'i' => 'mm'  , 's' => 'ss'  , 'e' => 'zzzz', 'I' => 'I'   , 'O' => 'Z'   ,
                         'P' => 'ZZZZ', 'T' => 'z'   , 'Z' => 'X'   , 'c' => 'yyyy-MM-ddTHH:mm:ssZZZZ',
                         'r' => 'r'   , 'U' => 'U');
        $values = str_split($format);
        $escaped = false;
        $lastescaped = false;
        $temp = array();
        foreach ($values as $key => $value) {
            if (!$escaped && $value == '\\') {
                $escaped = true;
                continue;
            }
            if ($escaped) {
                if (!$lastescaped) {
                    $temp[] = "'";
                    $lastescaped = true;
                } 
                $temp[] = $value;
                $escaped = false;
            } else { 
                if ($value == "'") {
                    $temp[] = "''";
                } else {
                    if ($lastescaped) {
                        $temp[] = "'";
                        $lastescaped = false;
                    }
                    if (isset($convert[$value]) === true) {
                        $temp[] = $convert[$value];
                    } else {
                        $temp[] = $value;
                    }
                }
            }
        }
        echo join($temp) . "
"; return join($temp); }

Hi Heck,

{quote} 2. A masked backslash was not displayed {quote} If you try to get an output of


you'll get a string

For this output the valid iso date format is
You could check it with this code

So masked backslash in php date format should be converted to a single backslash for iso date format. No needs to escape it in iso, but with your last snippet it will.


will return

`

\ With small additional condition we can get the expected behaviour:


...
            if ($escaped) {
                if ($v != '\\' && !$lastescaped) { // added *$v != '\\' &&* condition here allows us to get correct backslashes for iso date format
                    $temp[] = "'";
                    $lastescaped = true;
                } 
                $temp[] = $value;
                $escaped = false;
            }
...

Yet another typo please use '$value' instead of '$v' in my examples. Sorry for that.