Erlang: отметка времени с арифметикой часовых поясов - PullRequest
4 голосов
/ 29 марта 2011

Каков наилучший способ сложения / вычитания единиц в / из определенной временной отметки относительно часового пояса в Эрланге?

Из того, что я нашел, календарь stdlib может работать с часовым поясом местного или UTC, не более. Кроме того, арифметику рекомендуется выполнять только в часовом поясе UTC (причина очевидна).

Что мне делать, если, например, мне нужно добавить 1 месяц к {{2011,3,24}, {11,13,15}}, скажем, CET (центральноевропейское время) и местному ( система) часовой пояс не CET? Это даже не то же самое, что преобразование этой временной метки в UTC, добавление 31 * 24 * 60 * 60 секунд и обратное преобразование в CET (что даст {{2011,4,24}, {12,13,15}} вместо {{2011,4,24}, {11,13,15}}). Между прочим, мы не можем сделать даже такую ​​вещь, если CET не является местным часовым поясом с stdlib.

Ответы, которые я нашел, прибегая к помощи:

  1. setenv, чтобы сделать местный часовой пояс = необходимый часовой пояс (это очень уродливо в первую очередь; тогда он позволит только преобразовать необходимый часовой пояс в utc и выполнять арифметику в соответствии с utc, а не в нужный часовой пояс)
  2. open_port для linux date util и выполняйте там арифметику (не так страшно; довольно медленно; требуется некоторый анализ, потому что протокол между erlang и date будет текстовым)
  3. драйвер порта или erl_interface на C, использующий его стандартную библиотеку (совсем не страшно; но я не нашел готового решения, и я не настолько хорош в C, чтобы написать его)

Идеальным решением было бы что-то, написанное на Erlang с использованием информации о часовом поясе ОС, но я не нашел ничего.

Теперь я застрял в решении 2 (open_port to date util). Есть ли способ лучше?

Спасибо заранее.

P. S. Была похожая проблема, но нет хорошего ответа там Вопрос списка часовых поясов

1 Ответ

4 голосов
/ 30 марта 2011

Надеюсь, кому-то поможет.

Буду благодарен, если кто-то укажет, что можно улучшить.Заранее спасибо!

port_helper.erl

-module(port_helper).
-export([get_stdout/1]).
get_stdout(Port) ->
    loop(Port, []).
loop(Port, DataAcc) ->
    receive
        {Port, {data, Data}} ->
            loop(Port, DataAcc ++ Data);
        {Port, eof} ->
            DataAcc
    end.

timestamp_with_time_zone.erl

-module(timestamp_with_time_zone).
-export([to_time_zone/2, to_universal_time/1, modify/2]).
to_time_zone({{{Year, Month, Day}, {Hour, Minute, Second}}, TimeZone}, OutputTimeZone) ->
    InputPattern = "~4.10.0B-~2.10.0B-~2.10.0B ~2.10.0B:~2.10.0B:~2.10.0B",
    InputDeep = io_lib:format(InputPattern, [Year, Month, Day, Hour, Minute, Second]),
    Input = lists:flatten(InputDeep),
    {external_date(Input, TimeZone, OutputTimeZone), OutputTimeZone}.
to_universal_time({{{Year, Month, Day}, {Hour, Minute, Second}}, TimeZone}) ->
    {Timestamp, "UTC"} = to_time_zone({{{Year, Month, Day}, {Hour, Minute, Second}}, TimeZone}, "UTC"),
    Timestamp.
modify({{{Year, Month, Day}, {Hour, Minute, Second}}, TimeZone}, {Times, Unit}) ->
    if
        Times > 0 ->
            TimesModifier = "";
        Times < 0 ->
            TimesModifier = " ago"
    end,
    InputPattern = "~4.10.0B-~2.10.0B-~2.10.0B ~2.10.0B:~2.10.0B:~2.10.0B ~.10B ~s~s",
    InputDeep = io_lib:format(InputPattern, [Year, Month, Day, Hour, Minute, Second, abs(Times), Unit, TimesModifier]),
    Input = lists:flatten(InputDeep),
    external_date(Input, TimeZone, TimeZone).

external_date(Input, InputTimeZone, OutputTimeZone) ->
    CmdPattern = "date --date 'TZ=\"~s\" ~s' +%Y%m%d%H%M%S",
    CmdDeep = io_lib:format(CmdPattern, [InputTimeZone, Input]),
    Cmd = lists:flatten(CmdDeep),
    Port = open_port({spawn, Cmd}, [{env, [{"TZ", OutputTimeZone}]}, eof, stderr_to_stdout]),
    ResultString = port_helper:get_stdout(Port),
    case io_lib:fread("~4d~2d~2d~2d~2d~2d", ResultString) of
        {ok, [YearNew, MonthNew, DayNew, HourNew, MinuteNew, SecondNew], _LeftOverChars} ->
            {{YearNew, MonthNew, DayNew}, {HourNew, MinuteNew, SecondNew}}
    end.
...