开发者

Erlang: timestamp with time zone arithmetics

开发者 https://www.devze.com 2023-02-20 23:23 出处:网络
What is the best way to add/subtract units to/from specific timestamp with respect to time zone in Erlang?

What is the best way to add/subtract units to/from specific timestamp with respect to time zone in Erlang?

From what I've found, calendar of stdlib can work with either local or UTC time zone, no more. Moreover, arithmetics is recommended to do in UTC time zone only (the reason is obvious).

What should I do if, for instance, I need to add 1 month to {{2011,3,24},{11,13,15}} in, let's say, CET (Central European Time) and local (system) time zone is not CET? That is not even the same as converting this timestamp to UTC, adding 31 * 24 * 60开发者_如何学Go * 60 seconds and converting back to CET (that will give {{2011,4,24},{12,13,15}} instead of {{2011,4,24},{11,13,15}}). By the way we can't do even such a thing if CET is not local time zone with stdlib.

The answers I found googling are:

  1. setenv to make local time zone = needed time zone (that is very ugly first of all; then it will only allow to convert needed time zone to utc and do arithmetics respective to utc, not the needed time zone)
  2. open_port to linux date util and do arithmetics there (not that ugly; rather slow; needs some parsing, because the protocol between erlang and date will be textual)
  3. port driver or erl_interface to C using its standard library (not ugly at all; but I didn't find ready to use solution and I'm not that good at C to write one)

The ideal solution would be something written in Erlang using OS timezone info, but I didn't find any.

Now I'm stuck to solution 2 (open_port to date util). Is there a better way?

Thanks in advance.

P. S. There was a similar issue, but no good answer there Time zone list issue


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.
0

精彩评论

暂无评论...
验证码 换一张
取 消