1
\$\begingroup\$

I would like to get the current user's timezone name so it can be used with the PHP date_default_timezone_set(). After looking up a few things I've finally managed to come up with a solution for this. I'm not great with code though so any code checks/help will be appreciated.

As long as it is a fair guess of the user's timezone, it is good enough for me. It seems to work OK. Here it is:

/**
 * JS/jQuery - Set cookie of current user's UTC offset (format example: -06:00)
 *
 */
$(document).ready( function() {

        // Get UTC offset
        var offset = new Date().getTimezoneOffset(), o = Math.abs(offset);
        var offset = (offset < 0 ? "+" : "-") + ("00" + Math.floor(o / 60)).slice(-2) + ":" + ("00" + (o % 60)).slice(-2);

        if (document.cookie.indexOf("hp_time_offset") >= 0) {} else {

            // Set cookie
            $.cookie( 'hp_time_offset', offset, { expires : null, path:'/' } );     
        }   

});


/**
 * PHP - Get the UTC offset cookie and convert it to a timezone name
 *
 */
function hp_get_timezone() {

    if( isset( $_COOKIE['hp_time_offset'] ) ) {

        $time_offset = $_COOKIE['hp_time_offset'];

        // validate - exit if offset cookie contains anything other than "0-9 + - :",
        if ( preg_match('/[^0-9:\+\-]/', $time_offset) )
            return;

        // validate - exit if offset cookie is not in correct range
        if ( ! in_array($time_offset, range(-12, 14)))
            return;

        // Calculate seconds from offset
        list($hours, $minutes) = explode(':', $time_offset);
        $seconds = $hours * 60 * 60 + $minutes * 60;

        // Get timezone name from seconds
        $tz = timezone_name_from_abbr('', $seconds, 1);

        // bug fix
        if($tz === false) 
            $tz = timezone_name_from_abbr('', $seconds, 0);

        // if still false, set a default timezone name.
        if($tz === false) 
            $tz = 'America/New_York';

    } else {

        // If cookie doesn't exist, set a default timezone name.
        $tz = 'America/New_York';       
    }

    return $tz;
}


//set timezone 
date_default_timezone_set( hp_get_timezone() );

Any help appreciated.

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

On the javascript side:

  • I am not seeing any value in creating the string format for the offset that you are creating, as you just end up spending more code in PHP trying to get to a usable format. This is all more work than necessary. Consider keeping offset in minute-based javascript format or converting to second-based format.
  • You seem to use your cookie management library inconsistently here. You write using a jQuery extension like $.cookie() while reading the actual cookie string from document. Why not use your library for both (or does it not support that?) Also, not sure what you what your library does with expires: null. Hopefully this set to session-length cookie?

On PHP side:

I would strongly consider using PHP's DateTime and related classes (in this case specifically, DateTimeZone) rather than the procedural date_* functions. Over time I think you will find them much more powerful and flexible in usage. This also can help move you away from the concept of resetting your default system timezone, which I don't think you want to overwrite here, but rather use as fallback. There may be other parts of your code that do things like insert records into database datetime fields or something like that where you want to use a consistent system time, not the end users timezone. I don't think you have an appropriate use case for changing this setting (at least from the context shown).

Let's show an example of how this might look using DateTimeZone:

$system_tz = date_default_timezone_get();

// assume $_COOKIE['hp_time_offset'] contains a minute based offset like
// output by javascript Date().getTimezoneOffset()
if (!empty($_COOKIE('hp_time_offset') &&
    is_numeric($_COOKIE['hp_time_offset'])) {

    $offset_min = (int)$_COOKIE['hp_time_offset'];
    $offset_sec = $offset_min * 60;
    // is current system in daylight savings?
    $is_dst = (int)date('I');
    // get timezone name
    $tz = timezone_name_from_abbr('', $offset_sec, $is_dst);
    try {
        $date_timezone = new DateTimeZone($tz);
    } catch (Exception $e) {
        error_log('Invalid timezone string of "' . $tz . '" passed from cookie.');
        $tz = $system_tz;
    }
}

if(empty($timezone)) {
    // I am not wrapping this in try-catch as if there is a failure here
    // I don't know what you want to do to recover (as this should not happen).
    // Feel free to catch if needed for your implementation
    $date_timezone = new DateTimeZone($tz);
}

return $date_timezone;
// or if you just want to return the timezone string
// return $date_timezone->getName();

This proposed functionality gives you a valid DateTimeZone object representing either the cookie-derived timezone or, as a fallback, the system timezone. You can use this object to instantiate any DateTime object you may need on your system with those objects automatically taking on the expected timezone behavior.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.