Customizable DST Rules

2019-05-27 01:14发布

What's the task?
By project specification i need to calculate DST rules for specific time zone offsets. Rules cannot be applied via standard instruments like date_default_timezone_set() because we can't rely on software configuration and can't get it updated if some DST rules will be changed.

What i have?
In situations when DST switch date is on the last week and the switch day of the last week is on the next month i need to decrement the number of the last week. For now i have only one ugly solution:

 $week = 5;
 if (
     (1 + 7 * ($week - 1)) 
     + $start['day_of_week'] 
     - date('w', mktime(0, 0, 0, $start['month'], 1, date('Y', $start['timestamp']))) 
     > 
     date('t', $start['month'])) 
 {
     $week--;
 }

What am i looking for?
I'm looking for any ideas for a simplest solution for this, or links =)

1条回答
Fickle 薄情
2楼-- · 2019-05-27 01:51

You could use the system-wide timezone database and maintain custom updates to it.

Official TZ database repository at IANA

It is available as source files that you can modify when needed and recompile into binary files that can be used by the glibc time functions, and consequently by all programs that use those time functions. You might need to find a way to disable any automatic updates to that database, if your distro performs such updates, or you will lose your custom changes.

I'm not certain if PHP actually follows the system-wide TZ database. The PECL timezonedb package seems to indicate that it is a separate recompiled version of the IANA database, so it might not observe changes in the system-wide database.

If PHP disregards changes in the system-wide database, you could retrieve the zone offset by invoking any external program that uses the glibc timezone-aware time functions and that supports returning the timezone offset, e.g.:

putenv('TZ=America/New_York');
$offset = exec('date +%z');

$offset will be in format ±HHMM, so some additional parsing will be required.

You can also find the correct offset for specific timestamps, different from the current time:

$unix_timestamp = 1350757594;
$offset = exec('date +%z --date=@'.$unix_timestamp);

Of course, proper filtering through escapeshellarg would be required if the timestamp comes from an untrusted source.

UPDATE: Here's a solution for a single DST ruleset; no external resources are used

function calculate_offset($ts) {
    static $standard_offset = 2;    // = +2:00
    static $dst_offset      = 3;    // = +3:00
    static $dst_start       = 'last Sunday of March %d 1:00';
    static $dst_end         = 'last Sunday of October %d 1:00';

    date_default_timezone_set('UTC');

    $y = idate('Y', $ts);
    $start = strtotime(sprintf($dst_start, $y));
    $end = strtotime(sprintf($dst_end, $y));

    if ($start < $end) {
        // Northern hemisphere, DST starts earlier in the year than it ends
        $offset = ($ts >= $start && $ts < $end)? $dst_offset: $standard_offset;
    } else {
        // Southern hemisphere, DST ends earlier in the year than it starts
        $offset = ($ts >= $end && $ts < $start)? $standard_offset: $dst_offset;
    }
    $utc_time = strftime('%Y/%m/%d %H:%M:%S', $ts);
    $local_time = strftime('%Y/%m/%d %H:%M:%S', $ts + $offset * 60 * 60);
    printf("TS: %d; Offset: +%02d:00; UTC: %s; Local: %s\n", $ts, $offset, $utc_time, $local_time);
}

The example code uses the standard European DST rules and Eastern European Time offsets. You will need to find a way to express your DST start and end rules in the format understood by strtotime: Relative Formats. A %d in your DST start/end rule will be replaced by the year your timestamp falls in, so that weekday-in-month rules will find correct dates for the specific year. All calculations are done in UTC times, so your rules should refer to DST start/end times in UTC.

查看更多
登录 后发表回答