Управление временными зонами в веб-приложениях

Задача заключается в том, что каждый пользователь может работать в своей собственной временной зоне, более того, эта зона может изменяться. Широко распространенным вариантом решения проблемы является установка временной зоны непосредственно в настройках каждого пользователя. Минусы этого решения очевидны - эту настройку большинство пользователей никогда не найдет и не выставит в нужное значение, а при перемещении в другой город/страну и тем более никто об этом не вспомнит и не сменит свою временную зону. Другой вариант - использовать настройки ОС. Но увы, в стандартных HTTP-заголовках параметры временной зоны не передаются. Получить временную зону из яваскрипта напрямую тоже не удастся - можно узнать лишь временное смещение, которым могут соответствовать много зон. Что же делать? "Остается одно - только лечь помереть" (с). Или использовать смещение для локального времени, полученное на яваскрипт, в пределах одной сессии, а на стороне сервера применять соответствующее смещение. Например, для Москвы мы можем получить часовой пояс MSK или MSD (зимнее и летнее московское время), которым соответствуют смещения 3 или 4 часа. Чтобы было еще смешнее, в 1921-м году московское летнее время (MSD) имело смещение 5 часов от Гринвича, а в 2010-м году изменены часовые пояса для части субъектов РФ. Так что необходимо еще и текущую дату учитывать.

Код на яваскрипт тривиален:
getZone = function() {
tz = new Date().toString().replace(/^.*\(|\)$/g, "");
return tz;
}
alert(getZone());
Записать текущее значение часового пояса в кукисы также не представляет сложности. А вот на сервере все интереснее - нам нужна таблица всех временных зон и правил получения временного смещения для них. В линуксе есть пакет tzdata с нужной нам информацией, из которого несложно извлечь нужную информацию. В том числе, в тикле 8.5 есть соответствующие утилиты для преобразования в текстовые файлы. Подробнее см. здесь: Sources for Time Zone and Daylight Saving Time Data
Для OpenACS нашелся вот такой скрипт: http://pedroliska.com/files/load-timezones.tcl.txt Чтобы не лезть в код самого скрипта, запускаем его через враппер:
#!/usr/bin/tclsh8.5

proc ad_page_contract {args} {}

proc acs_root_dir {} {
  exec mkdir -p ./packages/ref-timezones/sql/common
  return "."
}
proc ReturnHeaders {args} {}
proc ns_write {line} {
  puts -nonewline $line
}
proc with_catch {errmsg if else} {
  uplevel 1 $if
}

proc ad_decode { args } {
#    this procedure is analogus to sql decode procedure. first parameter is
#    the value we want to decode. this parameter is followed by a list of
#    pairs where first element in the pair is convert from value and second
#    element is convert to value. last value is default value, which will
#    be returned in the case convert from values matches the given value to
#    be decoded
    set num_args [llength $args]
    set input_value [lindex $args 0]

    set counter 1

    while { $counter < [expr $num_args - 2] } {
 lappend from_list [lindex $args $counter]
 incr counter
 lappend to_list [lindex $args $counter]
 incr counter
    }

    set default_value [lindex $args $counter]

    if { $counter < 2 } {
 return $default_value
    }

    set index [lsearch -exact $from_list $input_value]
    
    if { $index < 0 } {
 return $default_value
    } else {
 return [lindex $to_list $index]
    }
}

source load-timezones.tcl.txt
В результате получаем два файла с нужной нам информацией: timezones.dat и timezone-rules.dat В файлике timezones.dat содержатся наименования временных зон - из яваскрипта мы эту информацию получить не можем, разве что предложим пользователю список выбора для ручного указания зоны, так что нам этот файл в настоящий момент не интересен (притом, если мы знаем наименование временной зоны, достаточно назвать ее тиклю или постгресу для пересчета, так что временное смещение уже не требуется, все совершенно тривиально). А вот timezone-rules.dat предоставляет все правила пересчета, с ним нам и придется работать. Как пример:
$ grep 2010 timezone-rules.dat |grep MSD
#tz_id, abbrev, utc_start, utc_end, local_start, local_end, gmt_offset, isdst
391,MSD,Sat Mar 27 23:00:00 2010,Sat Oct 30 22:59:59 2010,Sun Mar 28 03:00:00 2010,Sun Oct 31 02:59:59 2010,14400,t
428,MSD,Sat Mar 27 23:00:00 2010,Sat Oct 30 22:59:59 2010,Sun Mar 28 03:00:00 2010,Sun Oct 31 02:59:59 2010,14400,t
Здесь строчку комментария я добавил для удобства просмотра. Мы видим 2 временные зоны (а для зимнего времени MSK и более того - найдутся 4 зоны), несложно получить и их названия:
$ grep 391 timezones.dat 
391,W-SU,+030000
veter@veter-eeepc:/srv/work/tcl_tz/packages/ref-timezones/sql/common$ grep 428 timezones.dat 
428,Europe/Moscow,+030000
Нужная нам информация о временном смещении содержится в предпоследнем поле файла timezone-rules.dat, в данном случае это 14400 секунд от UTC. Флаг в последнем поле этого же файла подсказывает, что время летнее. Значит, на серевере для текущей сессии пользователя следует использовать временное смещение +4 часа, что и требовалось узнать. Upd. На практике, раз нам все равно не удается узнать временную зону, достаточно воспользоваться вот таким яваскрипт-кодом для получения временного смещения в секундах:
getZone = function() {
  return -(new Date()).getTimezoneOffset()*60;
}
alert(getZone());
Полученное значение следует прибавить на сервере ко всем значениям времени в UTC, передаваемым клиенту. Можно также хранить в кукисах и аббевиатуру, чтобы выводить время в виде "2010-06-01 12:00:00 MSD" (заметим, что в России не принято отображать время в подобном формате). Upd. В очередной попсовой статейке в тему: Автоматическое определение часового пояса пользователя попался интересный комментарий:
Все даты у меня возвращаются пользователю, как Unixtime в UTC. После, скармливаю этот таймштамп объекту new Date(timestamp), а Javascript знает, что Unixtime должен быть в UTC и сам корректирует дату с учетом текущего часового пояса пользователя. И в конце можно отформатировать дату как угодно, перед отображением пользователю. Таким образом, я никогда у пользователя не спрашиваю его часовой пояс, но даты ему отображаются корректно.

За неимением повсеместной поддержки HTML5 тоже решение.

Comments

Popular posts from this blog

Открытый софт для научных расчетов

Кольцевые структуры в геофизике

Модем Huawei E1550 в debian