11 October 2011

Magento - working with BST

If you run a Magento store in the UK, you have probably run into the problem of store times being wrong for half of the year as the timezone switches between GMT and BST. Unfortunately Magento has no support for BST at all and the only choice for store owners in the UK is to use standard GMT for the whole year. This of course results in all of the store times being an hour behind over the summer months.

If you have ever looked into the way Magento actually generates times for your store, you probably know that it works from a base of using UTC throughout, and then calculates the time difference between this universal time and the timezone you have selected in admin. The idea behind this is to allow you to set different timezones per store or website and have them all work happily together because in fact they are all actually stored in UTC.

Unfortunately the only way to allow proper support for BST is to change this default timezone from UTC to Europe/London. So a word of warning, if you are in any way planning to use timezones other than Europe/London in your install, then you should not implement the changes here - you are unfortunately probably just going to have to put up with your store being an hour out for half of the year. If that's not you however, read on...

All of the changes here are very simple, start by copying the following core files:
app/code/core/Mage/Core/Model/Locale.php
app/code/core/Mage/Core/Model/Date.php
app/code/core/Mage/Cron/Model/Schedule.php
to their equivalent location under the local branch if they don't already exist there:
app/code/local/Mage/Core/Model/Locale.php
app/code/local/Mage/Core/Model/Date.php
app/code/local/Mage/Cron/Model/Schedule.php
Editing the first two files will change the storing of most timestamps in the database and backend to allow for BST (although we will be making another edit later on), and the third file will correct cron schedule timings (otherwise you will find jobs only queue if they scheduled an hour out).

So, open app/code/local/Mage/Core/Model/Locale.php and find the following line:
const DEFAULT_TIMEZONE  = 'UTC';
Change it to:
const DEFAULT_TIMEZONE  = 'Europe/London';
In the same file, find the following lines in the storeTimeStamp() method:
@date_default_timezone_set($timezone);
$date = date('Y-m-d H:i:s');
@date_default_timezone_set($currentTimezone);
Change them to the following:
@date_default_timezone_set(self::DEFAULT_TIMEZONE);
$date = date('Y-m-d H:i:s');
@date_default_timezone_set(self::DEFAULT_TIMEZONE);
Next open app/code/local/Mage/Core/Model/Date.php and find the following lines in the calculateOffset() method:
$result = date_default_timezone_set($timezone);
and
date_default_timezone_set($oldzone);
Change them to:
$result = date_default_timezone_set(Mage_Core_Model_Locale::DEFAULT_TIMEZONE);
and
date_default_timezone_set(Mage_Core_Model_Locale::DEFAULT_TIMEZONE);
As you can probably see, all we have done is change the class constant for the timezone to Europe/London, and then referenced this in the other edits we have made.

Now you need to make sure cron continues to work correctly, so open app/code/local/Mage/Cron/Model/Schedule.php and find the following line:
$d = getdate(Mage::getSingleton('core/date')->timestamp($time));
This is where cron decides what the current time is, and therefore whether or not to queue your jobs. If we leave this as it is, your jobs won't queue when you schedule them to - instead we just want to pull the server time here without an offset, so change it to the following:
$d = getdate();
Obviously you need to make sure that the server is set to Europe/London also or this won't work.

Thats pretty much all the changes that are needed except that the guys at Varien decided they would put one more timezone reference in a file that you can't override in the local branch. So unfortunately to complete the process we need to make a change in, what I would consider to be a core file. Doing that is definitely not good practice and should be avoided unless absolutely necessary. Unfortunately I don't think there is an alternative in this case, so open app/Mage.php and find the following line:
date_default_timezone_set('UTC');
Change it to:
date_default_timezone_set('Europe/London');
Your store should now be running correctly in the Europe/London timezone and all new content should be recorded with this timestamp. Note however that existing content with a timestamp will not have changed from UTC (being the same as GMT) so will still show as an hour behind during the summer months.

20 comments:

  1. This is the first time am hearing about bst and its really interesting to know best about it thru ur post.
    E-Commerce Software

    ReplyDelete
  2. Just to confirm setting 'Europe/London' will take care of British summer time issue right? Thanks in advance

    ReplyDelete
  3. Using Europe/London as your time zone will result in your store following BST during the summer months and GMT during the winter months - so yes your store will then be keeping correct time throughout the year.

    ReplyDelete
  4. Bleh... annoyingly this also exists in EE.

    Was driving me up the wall and couldn't remember how I fixed in 1.3x.

    Cheers for the post.

    ReplyDelete
    Replies
    1. Glad the solution enabled you to resolve the issue in EE also.

      Delete
    2. Is there a setting for New Zealand?

      Delete
    3. Yes there is a New Zealand timezone setting. The UK is only a special case because it operates on 2 different time zones during the year.

      Delete
  5. I am having this problem on the east coast of the US during Daylight Savings Time. What would my default time zone need to be? We are GMT+5.

    ReplyDelete
    Replies
    1. Have a look here for a list of PHP timezones.

      http://php.net/manual/en/timezones.php

      Delete
  6. Following your fix the Mage::getModel('core/date')->date() method still seems to return the wrong time.

    e.g. Everything appears correct on the orders page but when used to print the order created date in my own module it outputs the date and time but 1 hour ahead still:

    echo Mage::getModel('core/date')->date('Ymd_His', $order->getCreatedAt());

    This date method still calls the date object's timestamp method which adjusts the timestamp by the current timezone offset in seconds.

    Does this mean there could be other knockon effects as the Core/Model/Date method sounds like it would be used in a lot of places?

    ---

    Also shouldn't the amend to getdate() in Schedule.php be this instead?

    $d = getdate($time);

    To take the method's $time argument into account.

    Thoughts?

    ReplyDelete
    Replies
    1. This will alter timestamps throughout the store, whether the date is queried from core or custom methods. The fix has been reliably running for a number of years on a few different stores with consistently correct results, so if you are not getting the correct date returned I would suggest double checking the implementation of the solution and also looking for other explanations such as rewrites which could be altering the returned date. Also check your server has the time correctly set.

      The call to getdate() does not require a $time argument as by default it will use time(), being the current time.

      Delete
  7. I have used this solution and now i get correct database Cron_schedule times (no 1 our delay anymore)
    But now my cron Jobs are scheduled 9 times for each occurrence, i believe it's because i have set schedule ahead = 10 (it seams to schedule one occurrence for each minute)!!!

    I have changed the schedule ahead = 5 and get 4 occurrences
    and if i make it =1 i stop getting cron jobs :-(

    Any ideas why this happens now?

    Thank You

    ReplyDelete
    Replies
    1. If you get jobs queue with a higher schedule ahead setting then you probably have it configured incorrectly now - perhaps the missed timing is too short? Check your timings...

      Also it doesn't matter if you have schedule ahead set to a higher value, you will notice that the scheduled start time for each of these scheduled jobs is set according to the regularity you have set cron to run, not all the at the same time.

      Delete
  8. Thank you for your help

    One example of what is happening now, I have catalog Product Alert Schedulled to run at 12:15 and this is what tasks planned in the cron_schedule table look like.

    Code Created Scheduled Executed Finished Status
    catalog_product_alert 12:15:02 12:19:00 12:20:04 12:20:04 success
    catalog_product_alert 12:15:02 12:18:00 12:20:04 12:20:04 success
    catalog_product_alert 12:15:02 12:17:00 12:20:04 12:20:04 success
    catalog_product_alert 12:15:02 12:16:00 12:20:04 12:20:04 success
    catalog_product_alert 12:15:02 12:15:00 12:20:02 12:20:04 success

    Before i implemented the solution i only get something like the last line.
    catalog_product_alert 12:15:02 12:15:00 12:20:02 12:20:04 success

    I have this Cron (Scheduled Tasks)
    Generate Schedules Every 5
    Schedule Ahead for 5
    Missed If not running 10

    and my cron.php runs every 5 min.

    I'm guessing it´s got something to do with the time functions i have changed following this solution, i´m using magento 1.81

    Thanks

    ReplyDelete
    Replies
    1. That looks completely normal and isn't anything to do with changes here. if you look at the third column each job is scheduled to run one minute after the last so the jobs are getting queued in advance but do not execute at the same time. You do however have the catalog_product_alert job set to run every minute, but as cron is only executing every 5 minutes so the job will be running 5 every 5 minutes, one for each queued job. You need to adjust you timings for the job so it only runs every 5 minutes or more rather than every minute.

      Delete
    2. I really dont know, the job is scheduled under: System>Configuration>Catalog>Product Alerts Run Settings
      Frequency: Daily
      Start Time: 11_15_00 (one hour gap, corrected in database entry by your implementation)

      So it runs once a day, the effect i´m getting is that the job gets scheduled 5 times (one for each minute of the 5 min Schedule ahead).

      And it´s not the only job doing this, this is just one example.I have another job called "Low Stock Notifications" that gets me a 6 a.m email with low stock products, and i´m getting 5 copies of the same email now!

      I am using a staging enviroment, that mimics my live site, i will reset it (make a fresh copy) and i'll get back to you latter. In the live version, i´m not getting jobs repeated like this.
      I will re-implement your solution, and get back with more info, thanks.

      Delete
  9. Hello, i have done what i proposed in last comment, and it does the same, for most situations it is not an issue, the only problem is my low stock notifications, but thats probably an issue related to the way the extension is built :-)
    Your solution helped me solve this issue i had going for weeks now, with catalog price rules that were not applied from midnight to one o'clock, leaving a big gap to my costumers.

    First i found out i could change the timings of the catalogrule_apply_all in here: http://www.solvingmagento.com/quick-tip-magento-catalog-price-rules-dont-work/ but it did not solved it because when i changed the rule application from 1:00 am to 0:05 am, what i was telling the database was to run this job at 23:05 because in Portugal we have this same summer diference (as in UK) of +1 UTC (this problem does not happen in winter time because then GMT = UTC) and the main problem is that catalog price rule are created daily, and expire at midnight, and when i just changed the cron job what i was really telling the database was to refresh the rule at 23:05 so it stopped my catalog rules from working at 0:00 ;-P

    Hope this big text helps someone else out there, from me a BIG THANK YOU to Hussey Codding and all the other wonderful people that share info like you do.

    Cheers

    ReplyDelete
  10. Thanks for this fix. However, while testing it we noticed serious issues in regards to the scheduler's behaviour after applying the above changes.

    As @Miguel Tristao mentioned, as a result of the change on app/code/local/Mage/Cron/Model/Schedule.php, the jobs will be scheduled and EXECUTED as many times as the value of the cron settings Schedule ahead For. In our case it is set to 200 hundred, so we quickly noticed that something wasn't working quite well.

    After debugging and researching the way the scheduler works, we noticed that the line changed:
    $d = getdate(Mage::getSingleton('core/date')->timestamp($time));

    takes $time and adds the 1 hour offset to the timestamp, which would lead to jobs to be scheduled and executed one hour later than expected. However, replacing it by the proposed:
    $d = gettime();
    Would completely modify the behaviour of the scheduler, as it completely ignores the value of $time.

    The way it works it's a bit hard to explain:
    If "Schedule Ahead for" is 10, then Magento will call the method trySchedule() ten times, with the $time increasing progressively (ie. now - 21:30, 21:31, 21:32, and so on), and will try to match that time with the cronjob expression, if it matches, it will schedule the job, otherwise it will ignore it. Then, if we ignore $time and use the current time (getdate()), if the current time matches the cronjob expression, the job will be scheduled as many times as the method is called, with 1 minute difference in between, and they will all be executed.

    Anyway, in case anyone is facing this issue, our solution was to replace:
    $d = getdate(Mage::getSingleton('core/date')->timestamp($time));
    by:
    $d = getdate($time);

    Which takes into account the $time parameter, but doesn't call the method that adds the offset, because Magento is already working on the desired timezone, therefore the timestamp of $time is fine as it is.

    I hope it helps.

    Regards,
    Javier

    ReplyDelete
  11. Thank you for the fix. I can confirm that it only works properly with $d = getdate($time); for me, too.

    ReplyDelete