Zend_Date: New Year Problems

With Christmas over and the New Year approaching the world is getting some rest between the two holidays. The same goes on at AITOC’s support department: there are very few tickets, there’s time to look back at the past year and draft some resolutions for the upcoming year. But on December 30 three customers submitted tickets with complaints on Delivery Date extension.

You may read more about the extension itself at https://www.aitoc.com/en/magentomods_delivery_date.html. To summarize, it adds a date selection field at the shipping method step. It uses a simple input and a slightly modified calendar from Magento back end.

All customers described a very similar problem. Some use the module for more than one year. Here’s the problem: it is impossible to select any date in the calendar, and the default date is set to December 30, 2014. Windows calendar still shows 2013.

calendar

If the date was just skipping a year, it wouldn’t be so critical. The problem is enhanced by the fact the dates in the vicinity of December 2013 and January 2014 are inactive.

First we assumed that the date format in the module was set up incorrectly. Maybe this causes the day and the month switch places and the wrong final date is displayed. But this is unlikely, because in this case the problem would have been evident earlier. We still check, the format is mm/dd/yyyy, as it should be.

In one of the tickets the customers says the same problem occurred last year, but it was fixed. Great, we can apply the same fix now. We search the support archive. Indeed, there are a few complaints submitted on December 31, 2012. Also Monday. Also right before the New Year. But back then it was a country-wide day off, so our support department could only address the problem on January 2, 2013. The error was no longer reproduced by that time.

So there was no solution last time the error occured. We start looking for what causes the problem using debug. JS is responsible for displaying the calendar. It is the same one as in Magento back end. It’s unlikely that Magento would have such an obvious bug with dates. So the problem should be somewhere in the settings. We try to manually change the date in input, but it is still being changed to the default December 30, 2014.

Again we turn to Firebug to see the server requests.

2

There are a few requests to check the date. As we can see, the date is invalid (sure thing, delivery in a year from the order date is not the best idea). The first valid date is indicated as December 30, 2014. Great, we found the trace of the problem.

We check the controller to find out where this date is set up.

public function dateValidateAction()

{

$result = $this->_getValidator();

if (isset($result['error']))

{

$result['valid_date'] = $this->_getValidDate();

}

$this->_sendResponse($result);

}

Nothing deals with dates here, so we’re moving on to $this->_getValidDate();

private function _getValidDate()

{

$format = Mage::getStoreConfig('checkout/adjdeliverydate/format');

$date = Mage::getModel('adjdeliverydate/holiday')->getFirstAvailableDate('unix');

$dateFormer = new Zend_Date($date);

$formatedDate = $dateFormer ->toString($format);

return $formatedDate;

}

There are a few actions. We’ve already checked the format. So the problem must be at

$date = Mage::getModel('adjdeliverydate/holiday')->getFirstAvailableDate('unix');

So we open the Holidays model and look into the method:

public function getFirstAvailableDate($format = 'Y-m-d')

{

$d = (int)$currentDate->toString('d');//date('j');

$m = (int)$currentDate->toString('M');//date('n');

$y = (int)$currentDate->toString('Y');//date('Y');

 

$maxInterval = Mage::getStoreConfig('checkout/adjdeliverydate/max');

 

$dayMove = $this->_getMoveDaysIntervalDependesHolidays($d, $m, $y);

 

for ($i = $dayMove; $i < 365; /*days*/ $i++)

{

if ($i > $maxInterval)

return;

 

if ($i < 2)

{

if ($this->_checkTodayTomorrowDeliveryEnabled($i))

{

continue;

}

}

 

if($this->isDayoff($d+$i, $m, $y))

continue;

 

$time = mktime(0,0,0, $m, $d+$i, $y);

 

if ($format == 'unix')

{

return $time;

}

 

return date($format, $time);

}

}

 

This function uses the current date. Then we check each date from the current to the next one throughout the year to check if it’s a holiday. If it is a holiday, we move on to the next date. If it’s not, we leave it. So maybe the holidays are detected poorly? Debug showed this was not the case. The problem then must be in the following line:

 

$y = (int)$currentDate->toString('Y');//date('Y');

Strange. We’d never suspect a Magento’s standard function to be the problem. Especially given that it has no problem with day and month.

In theory, it should return the same result as date (“Y”), but take into account the time zone of the Magento installation and the server. Could it be that it adds a year when adjusting for time zone? We look deeper into Zend Date.

toString function is relatively big (over 40 lines), so we won’t include it into this article. With debug we find out that the timestamp works fine, and the problem occurs in the following line:

 

return $this->date($this->_toToken($format, $locale), $this->getUnixTimestamp(), false);

We can even say more. When adjusting the format to locale (_toToken) this function is in the same class. We use debug to find the following line:

 

$format .= $this->_parseIsoToDate($orig, $locale);

The logs show that ‘Y’ is used prior to this line. And after this line it’s ‘o’. We google this weird year format. On https://www.php.net/manual/en/function.date.php all possible date formats. Here’s what we find:

o ISO-8601 year number. This has the same value as Y, except that if the ISO week number (W) belongs to the previous or next year, that year is used instead. (added in PHP 5.1.0)

Great, we found the source of the problem. Now it’s clear why the error occurred on this specific day. This week is assigned to 2014. Now we have to figure out why the substitution happens. Now we need to find the place in parseIsoToDate method where ‘o’ is introduced.

case self::YEAR_8601 :

return 'o';

break;

What does YEAR_8601 mean? We use Y, which is seen by PHP as regular year, not the 8601 format. We look into class constants and find all the year constants.

 

const YEAR              = 'y';

const YEAR_SHORT        = 'yy';

const YEAR_8601         = 'Y';

const YEAR_SHORT_8601   = 'YY';

So Zend sees Y as 8610 format. Lowercase “y” stands for a regular year (and it is interpreted by PHP as short year). And to get the short year, Zend needs “yy”. This is quite illogical. And it differs greatly from the syntax of the date format of the date function.

The solution seems obvious now. We go a few steps up and change the capital “Y” to a lowercase “y” in toString. And rejoice to have a well-functioning module again! During the debug there were a few more tickets reporting the same problem. We change one letter and report the issue as fixed.

Next we decided to investigate why there’s such an illogical format system. Here’s what we found on the Zend website: https://framework.zend.com/manual/1.12/en/zend.date.constants.html#zend.date.constants.selfdefinedformats

The description there is quite strange, there are a few symbol sets which belong to the same constant (y, yy, yyy,  and yyyy, for example). This concerns the latest version of the framework. Magento uses veriosn 1.11.1, and the documentation described 1.12. Here’s a quote with the toString function description in Zend_Date class:

 

Supported format tokens are:

     * G - era, y - year, Y - ISO year, M - month, w - week of year, D - day of year, d - day of month

     * E - day of week, e - number of weekday (1-7), h - hour 1-12, H - hour 0-23, m - minute, s - second

     * A - milliseconds of day, z - timezone, Z - timezone offset, S - fractional second, a - period of day

     *

     * Additionally format tokens but non ISO conform are:

     * SS - day suffix, eee - php number of weekday(0-6), ddd - number of days per month

     * l - Leap year, B - swatch internet time, I - daylight saving time, X - timezone offset in seconds

     * r - RFC2822 format, U - unix timestamp

     *

     * Not supported ISO tokens are

     * u - extended year, Q - quarter, q - quarter, L - stand alone month, W - week of month

     * F - day of week of month, g - modified julian, c - stand alone weekday, k - hour 0-11, K - hour 1-24

     * v - wall zone

So we see that date(‘G’) will return hours in 24-hour format, while Zend will return an era (A.D. or B.C.) Here’s a comparative table:

Date() Zend_Date::toString() Description
d dd Day of the month, 2 digits with leading zeros
D EEE A textual representation of a day, three letters
j d Day of the month without leading zeros
l (lowercase L) EEEE A full textual representation of the day of the week
N e ISO-8601 numeric representation of the day of the week (added in PHP 5.1.0)
z D The day of the year (starting from 0)
W w ISO-8601 week number of year, weeks starting on Monday (added in PHP 4.1.0)
F MMMM A full textual representation of a month, such as January or March
m MM Numeric representation of a month, with leading zeros
M MMM A short textual representation of a month, three letters
n M Numeric representation of a month, without leading zeros
o Y ISO-8601 year number. This has the same value as Y, except that if the ISO week number (W) belongs to the previous or next year, that year is used instead. (added in PHP 5.1.0)
Y y A full numeric representation of a year, 4 digits
y yy A full numeric representation of a year, 4 digits
a a (sort of, but likely to be uppercase) Lowercase Ante meridiem and Post meridiem
A a (sort of, but no guarantee that the format is uppercase) Uppercase Ante meridiem and Post meridiem
g h 12-hour format of an hour without leading zeros
G H 24-hour format of an hour without leading zeros
h hh 12-hour format of an hour with leading zeros
H HH 24-hour format of an hour with leading zeros
i m Minutes with leading zeros
s ss Seconds, with leading zeros
e zzzz Timezone identifier (added in PHP 5.1.0)
O Z or ZZ or ZZZ Difference to Greenwich time (GMT) in hours
P ZZZZ Difference to Greenwich time (GMT) with colon between hours and minutes (added in PHP 5.1.3)
T z or zz or zzz Timezone abbreviation

 

Conclusion.  Third-party libraries don’t always simplify the developers’ work. Often libraries introduce their own formats that differ from standard PHP formats. Zend recommends not to set date format by string, but instead use class constants with ready formats.

 

Magento and PayPal Sandbox Setup Previous Post
Migrating the Mail System to a New Server: Unexpected Problems. Next Post