Home » Php » php – Why date() works twice as fast if we set time zone from code?

php – Why date() works twice as fast if we set time zone from code?

Posted by: admin April 23, 2020 Leave a comment

Questions:

Have you noticed that date() function works 2x faster than usual if you set actual timezone inside your script before any date() call? I’m very curious about this.

Look at this simple piece of code:

<?php

  $start = microtime(true);
  for ($i = 0; $i < 100000; $i++) date('Y-m-d H:i:s');
  echo (microtime(true) - $start);

?>

It just calls date() function using for loop 100,000 times. The result I’ve got is always around 1.6 seconds (Windows, PHP 5.3.5) but…

If I set same time zone again adding one absurd line before start:

date_default_timezone_set(date_default_timezone_get());

I get a time below 800ms; ~2x faster (same server).

I was looking around to find any reasonable explanation for this behavior but did not have any success. From my angle, this additional line is useless but PHP doesn’t agree with me.

I have tried this test on two linux servers (different PHP versions) and got different resulting times but in proportion ~6:1.

Note: date.timezone property in php.ini has been properly set (Europe/Paris).

I was searching for related questions here and did not find anything similar. I’ve also checked manual for date_default_time_zone() function @ php.net and found that I’m not only one who noticed this, but still can’t understand why that happens?

Anyone?

How to&Answers:

Update for PHP 5.4:

As documented in the description of date_default_timezone_get, starting from PHP 5.4.0 the algorithm to guess the timezone from system information has been removed from the code (contrast with the PHP 5.3 source) so this behavior no longer exists.

Running the timing test on my dev server to see it in action, I got:

  • PHP 5.3.11: ~720ms
  • PHP 5.4.3: ~470ms

Original answer:

I ‘ve just looked into PHP source. Specifically, all relevant code is in /ext/date/php_date.c.

I started with the assumption that if you don’t provide a timezone for date, date_default_timezone_get is called to get one. Here’s that function:

PHP_FUNCTION(date_default_timezone_get)
{
    timelib_tzinfo *default_tz;

    default_tz = get_timezone_info(TSRMLS_C);
    RETVAL_STRING(default_tz->name, 1);
}

OK, so what does get_timezone_info look like? This:

PHPAPI timelib_tzinfo *get_timezone_info(TSRMLS_D)
{
    char *tz;
    timelib_tzinfo *tzi;

    tz = guess_timezone(DATE_TIMEZONEDB TSRMLS_CC);
    tzi = php_date_parse_tzfile(tz, DATE_TIMEZONEDB TSRMLS_CC);
    if (! tzi) {
        php_error_docref(NULL TSRMLS_CC, E_ERROR, "Timezone database is corrupt - this should *never* happen!");
    }
    return tzi;
}

What about guess_timezone? Here it is:

static char* guess_timezone(const timelib_tzdb *tzdb TSRMLS_DC)
{
    char *env;

    /* Checking configure timezone */
    if (DATEG(timezone) && (strlen(DATEG(timezone)) > 0)) {
        return DATEG(timezone);
    }
    /* Check environment variable */
    env = getenv("TZ");
    if (env && *env && timelib_timezone_id_is_valid(env, tzdb)) {
        return env;
    }
    /* Check config setting for default timezone */
    /*  ..... code omitted ....... */
#if HAVE_TM_ZONE
    /* Try to guess timezone from system information */
    /*  ..... code omitted ....... */
#endif
#ifdef PHP_WIN32
    /*  ..... code omitted ....... */
#elif defined(NETWARE)
    /*  ..... code omitted ....... */
#endif
    /* Fallback to UTC */
    php_error_docref(NULL TSRMLS_CC, E_WARNING, DATE_TZ_ERRMSG "We had to select 'UTC' because your platform doesn't provide functionality for the guessing algorithm");
    return "UTC";
}

OK, so how does that interact with date_default_timezone_set? Let’s look at that function:

PHP_FUNCTION(date_default_timezone_set)
{
    char *zone;
    int   zone_len;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &zone, &zone_len) == FAILURE) {
        RETURN_FALSE;
    }
    if (!timelib_timezone_id_is_valid(zone, DATE_TIMEZONEDB)) {
        php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Timezone ID '%s' is invalid", zone);
        RETURN_FALSE;
    }
    if (DATEG(timezone)) {
        efree(DATEG(timezone));
        DATEG(timezone) = NULL;
    }
    DATEG(timezone) = estrndup(zone, zone_len);
    RETURN_TRUE;
}

Long story short: if you call date_default_timezone_set once, then guess_timezone takes the fast path of reading from the timezone variable (the very first conditional is satisfied, and it returns immediately). Otherwise it takes some time to work out the default timezone, which is not cached (I guess for simplicity), and if you do that in a loop the delay starts to show.

Answer:

I’d imagine it has to determine the timezone for itself each time it’s called unless explicitly specified, which adds to the function runtime.

But really, does it matter? How many scripts are you likely to make that call date() 100,000 times per run?