Erlang: timestamp with timezone arithmetic

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

From what I found, stdlib calendar can work with local or UTC time zone, no more. Moreover, arithmetic is recommended only in the UTC time zone (the reason is obvious).

What if, for example, I need to add 1 month to {{2011,3,24}, {11,13,15}}, for example, CET (Central European Time) and local (system) time zone is not CET? It's not even the same as converting this timestamp to UTC, adding 31 * 24 * 60 * 60 seconds and switching back to CET (which will give {{2011,4,24}, {12,13,15}} instead {{2011,4,24}, {11,13,15}}). By the way, we cannot even do this if CET is not a local time zone with stdlib.

The answers I found are as follows:

  • setenv make a local time zone = necessary time zone (this is very ugly in the first place, then it will only allow you to convert the necessary time zone to utc and do arithmetic corresponding to utc, and not the necessary time zone)
  • open_port to linux date util and do arithmetic there (not so ugly but slower; some parsing is needed because the protocol between erlang and date will be textual)
  • port driver or erl_interface in C using its standard library (not ugly at all, but I didn't find a ready-to-use solution, and I'm not so good in C to write it)

The ideal solution would be written in Erlang using the OS time zone information, but I did not find.

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

Thanks in advance.

R. S. There was a similar problem, but there is no good answer. Time zone problem.

+4
source share
1 answer

Hope will help someone.

We will be grateful if someone indicates what can be improved. Thank you in advance!

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. 
+4
source

Source: https://habr.com/ru/post/1345663/


All Articles