1-module(birl_ffi).
2
3-export([
4 now/0,
5 local_offset/0,
6 monotonic_now/0,
7 to_parts/2,
8 from_parts/2,
9 weekday/2,
10 local_timezone/0
11]).
12
13-define(DaysInMonths, [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]).
14-define(Regions, [
15 "Africa",
16 "America",
17 "Antarctica",
18 "Arctic",
19 "Asia",
20 "Atlantic",
21 "Australia",
22 "Brazil",
23 "Canada",
24 "Chile",
25 "Etc",
26 "Europe",
27 "Indian",
28 "Mexico",
29 "Pacific",
30 "US"
31]).
32
33now() -> os:system_time(microsecond).
34
35local_offset() ->
36 Timestamp = erlang:timestamp(),
37 {{_, LMo, LD}, {LH, LM, _}} = calendar:now_to_local_time(Timestamp),
38 {{_, UMo, UD}, {UH, UM, _}} = calendar:now_to_universal_time(Timestamp),
39 if
40 LD == UD ->
41 (LH - UH) * 60 + LM - UM;
42 (LD > UD andalso LMo == UMo) or (LD == 1 andalso LMo > UMo) ->
43 (23 - UH) * 60 + (60 - UM) + LH * 60 + LM;
44 true ->
45 -((23 - LH) * 60 + (60 - LM) + UH * 60 + UM)
46 end.
47
48local_timezone() ->
49 case os:type() of
50 {unix, _} ->
51 case os:getenv("TZ") of
52 ":" ++ Path ->
53 case timezone_from_path(Path) of
54 <<>> -> local_timezone_from_etc();
55 TZ -> {some, TZ}
56 end;
57 false ->
58 local_timezone_from_etc();
59 TZOrPath ->
60 case timezone_from_path(TZOrPath) of
61 <<>> -> local_timezone_from_etc();
62 TZ -> {some, TZ}
63 end
64 end;
65 {win32, _} ->
66 {ok, RegHandle} = win32reg:open([read]),
67 win32reg:change_key(
68 RegHandle,
69 "\\local_machine\\SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation"
70 ),
71 {ok, Values} = win32reg:values(RegHandle),
72 case lists:keyfind("TimeZoneKeyName", 1, Values) of
73 {"TimeZoneKeyName", WinZone} -> win_to_iana(string:trim(WinZone));
74 false -> none
75 end
76 end.
77
78monotonic_now() ->
79 StartTime = erlang:system_info(start_time),
80 CurrentTime = erlang:monotonic_time(),
81 Difference = (CurrentTime - StartTime),
82 erlang:convert_time_unit(Difference, native, microsecond).
83
84to_parts(Timestamp, Offset) ->
85 {Date, {Hour, Minute, Second}} = calendar:system_time_to_universal_time(
86 Timestamp + Offset, microsecond
87 ),
88 MilliSecond = (Timestamp rem 1_000_000) div 1_000,
89 {Date,
90 {Hour, Minute, Second,
91 if
92 MilliSecond == 0 -> MilliSecond;
93 Timestamp >= 0 -> MilliSecond;
94 true -> 1000 + MilliSecond
95 end}}.
96
97from_parts(Parts, Offset) ->
98 {{Year, Month, Day}, {Hour, Minute, Second, MilliSecond}} = Parts,
99 DaysInYears = calculate_days_from_year(Year - 1, 0),
100 DaysInMonths = calculate_days_from_month(Year, Month - 1, 0),
101 Days =
102 if
103 DaysInYears >= 0 -> DaysInYears + DaysInMonths + Day - 1;
104 true -> DaysInYears + DaysInMonths + Day
105 end,
106 Seconds = (Days * 3600 * 24) + (Hour * 3600) + (Minute * 60) + Second,
107 Seconds * 1_000_000 + MilliSecond * 1_000 - Offset.
108
109weekday(Timestamp, Offset) ->
110 {Date, _} = to_parts(Timestamp, Offset),
111 calendar:day_of_the_week(Date) - 1.
112
113local_timezone_from_etc() ->
114 case file:read_file("/etc/timezone") of
115 {ok, NewLinedTimezone} ->
116 {some, string:trim(NewLinedTimezone)};
117 {error, _} ->
118 case file:read_link("/etc/localtime") of
119 {ok, Path} ->
120 {some, timezone_from_path(Path)};
121 {error, _} ->
122 none
123 end
124 end.
125
126timezone_from_path(Path) ->
127 Split = string:split(Path, "/", all),
128 ZoneParts = lists:dropwhile(
129 fun(Segment) -> not lists:member(Segment, ?Regions) end, Split
130 ),
131 binary:list_to_bin(string:join(ZoneParts, "/")).
132
133calculate_days_from_year(1969, Days) ->
134 Days;
135calculate_days_from_year(Year, Days) when Year > 1969 ->
136 case calendar:is_leap_year(Year) of
137 true ->
138 calculate_days_from_year(Year - 1, Days + 366);
139 false ->
140 calculate_days_from_year(Year - 1, Days + 365)
141 end;
142calculate_days_from_year(Year, Days) when Year < 1969 ->
143 case calendar:is_leap_year(Year) of
144 true ->
145 calculate_days_from_year(Year + 1, Days - 366);
146 false ->
147 calculate_days_from_year(Year + 1, Days - 365)
148 end.
149
150calculate_days_from_month(_, 0, Days) ->
151 Days;
152calculate_days_from_month(Year, Month, Days) ->
153 case calendar:is_leap_year(Year) and (Month == 2) of
154 true ->
155 calculate_days_from_month(Year, 1, Days + 29);
156 false ->
157 calculate_days_from_month(Year, Month - 1, Days + lists:nth(Month, ?DaysInMonths))
158 end.
159
160win_to_iana("Afghanistan Standard Time") -> {some, <<"Asia/Kabul">>};
161win_to_iana("Alaskan Standard Time") -> {some, <<"America/Anchorage">>};
162win_to_iana("Aleutian Standard Time") -> {some, <<"America/Adak">>};
163win_to_iana("Altai Standard Time") -> {some, <<"Asia/Barnaul">>};
164win_to_iana("Arab Standard Time") -> {some, <<"Asia/Riyadh">>};
165win_to_iana("Arabian Standard Time") -> {some, <<"Asia/Dubai">>};
166win_to_iana("Arabic Standard Time") -> {some, <<"Asia/Baghdad">>};
167win_to_iana("Argentina Standard Time") -> {some, <<"America/Argentina/La_Rioja">>};
168win_to_iana("Astrakhan Standard Time") -> {some, <<"Europe/Astrakhan">>};
169win_to_iana("Atlantic Standard Time") -> {some, <<"Atlantic/Bermuda">>};
170win_to_iana("AUS Central Standard Time") -> {some, <<"Australia/Darwin">>};
171win_to_iana("Aus Central W. Standard Time") -> {some, <<"Australia/Eucla">>};
172win_to_iana("AUS Eastern Standard Time") -> {some, <<"Australia/Sydney">>};
173win_to_iana("Azerbaijan Standard Time") -> {some, <<"Asia/Baku">>};
174win_to_iana("Azores Standard Time") -> {some, <<"Atlantic/Azores">>};
175win_to_iana("Bahia Standard Time") -> {some, <<"America/Bahia">>};
176win_to_iana("Bangladesh Standard Time") -> {some, <<"Asia/Dhaka">>};
177win_to_iana("Belarus Standard Time") -> {some, <<"Europe/Minsk">>};
178win_to_iana("Bougainville Standard Time") -> {some, <<"Pacific/Bougainville">>};
179win_to_iana("Canada Central Standard Time") -> {some, <<"America/Regina">>};
180win_to_iana("Cape Verde Standard Time") -> {some, <<"Atlantic/Cape_Verde">>};
181win_to_iana("Caucasus Standard Time") -> {some, <<"Asia/Yerevan">>};
182win_to_iana("Cen. Australia Standard Time") -> {some, <<"Australia/Adelaide">>};
183win_to_iana("Central America Standard Time") -> {some, <<"America/Guatemala">>};
184win_to_iana("Central Asia Standard Time") -> {some, <<"Asia/Almaty">>};
185win_to_iana("Central Brazilian Standard Time") -> {some, <<"America/Campo_Grande">>};
186win_to_iana("Central Europe Standard Time") -> {some, <<"Europe/Budapest">>};
187win_to_iana("Central European Standard Time") -> {some, <<"Europe/Warsaw">>};
188win_to_iana("Central Pacific Standard Time") -> {some, <<"Pacific/Guadalcanal">>};
189win_to_iana("Central Standard Time") -> {some, <<"CST6CDT">>};
190win_to_iana("Central Standard Time (Mexico)") -> {some, <<"America/Mexico_City">>};
191win_to_iana("Chatham Islands Standard Time") -> {some, <<"Pacific/Chatham">>};
192win_to_iana("China Standard Time") -> {some, <<"Asia/Shanghai">>};
193win_to_iana("Cuba Standard Time") -> {some, <<"America/Havana">>};
194win_to_iana("Dateline Standard Time") -> {some, <<"Etc/GMT+12">>};
195win_to_iana("E. Africa Standard Time") -> {some, <<"Africa/Nairobi">>};
196win_to_iana("E. Australia Standard Time") -> {some, <<"Australia/Brisbane">>};
197win_to_iana("E. Europe Standard Time") -> {some, <<"Europe/Chisinau">>};
198win_to_iana("E. South America Standard Time") -> {some, <<"America/Sao_Paulo">>};
199win_to_iana("Easter Island Standard Time") -> {some, <<"Pacific/Easter">>};
200win_to_iana("Eastern Standard Time") -> {some, <<"EST5EDT">>};
201win_to_iana("Eastern Standard Time (Mexico)") -> {some, <<"America/Cancun">>};
202win_to_iana("Egypt Standard Time") -> {some, <<"Africa/Cairo">>};
203win_to_iana("Ekaterinburg Standard Time") -> {some, <<"Asia/Yekaterinburg">>};
204win_to_iana("Fiji Standard Time") -> {some, <<"Pacific/Fiji">>};
205win_to_iana("FLE Standard Time") -> {some, <<"Europe/Riga">>};
206win_to_iana("Georgian Standard Time") -> {some, <<"Asia/Tbilisi">>};
207win_to_iana("GMT Standard Time") -> {some, <<"Europe/London">>};
208win_to_iana("Greenland Standard Time") -> {some, <<"Etc/GMT+2">>};
209win_to_iana("Greenwich Standard Time") -> {some, <<"Africa/Monrovia">>};
210win_to_iana("GTB Standard Time") -> {some, <<"Europe/Bucharest">>};
211win_to_iana("Haiti Standard Time") -> {some, <<"America/Port-au-Prince">>};
212win_to_iana("Hawaiian Standard Time") -> {some, <<"Etc/GMT+10">>};
213win_to_iana("Iran Standard Time") -> {some, <<"Asia/Tehran">>};
214win_to_iana("Israel Standard Time") -> {some, <<"Asia/Jerusalem">>};
215win_to_iana("Jordan Standard Time") -> {some, <<"Asia/Amman">>};
216win_to_iana("Kaliningrad Standard Time") -> {some, <<"Europe/Kaliningrad">>};
217win_to_iana("Korea Standard Time") -> {some, <<"Asia/Seoul">>};
218win_to_iana("Libya Standard Time") -> {some, <<"Africa/Tripoli">>};
219win_to_iana("Line Islands Standard Time") -> {some, <<"Pacific/Kiritimati">>};
220win_to_iana("Lord Howe Standard Time") -> {some, <<"Australia/Lord_Howe">>};
221win_to_iana("Magadan Standard Time") -> {some, <<"Asia/Magadan">>};
222win_to_iana("Magallanes Standard Time") -> {some, <<"America/Punta_Arenas">>};
223win_to_iana("Marquesas Standard Time") -> {some, <<"Pacific/Marquesas">>};
224win_to_iana("Mauritius Standard Time") -> {some, <<"Indian/Mauritius">>};
225win_to_iana("Middle East Standard Time") -> {some, <<"Asia/Beirut">>};
226win_to_iana("Montevideo Standard Time") -> {some, <<"America/Montevideo">>};
227win_to_iana("Morocco Standard Time") -> {some, <<"Africa/Casablanca">>};
228win_to_iana("Mountain Standard Time") -> {some, <<"MST7MDT">>};
229win_to_iana("Mountain Standard Time (Mexico)") -> {some, <<"America/Mazatlan">>};
230win_to_iana("N. Central Asia Standard Time") -> {some, <<"Asia/Novosibirsk">>};
231win_to_iana("Namibia Standard Time") -> {some, <<"Africa/Windhoek">>};
232win_to_iana("New Zealand Standard Time") -> {some, <<"Pacific/Auckland">>};
233win_to_iana("Newfoundland Standard Time") -> {some, <<"America/St_Johns">>};
234win_to_iana("Norfolk Standard Time") -> {some, <<"Pacific/Norfolk">>};
235win_to_iana("North Asia East Standard Time") -> {some, <<"Asia/Irkutsk">>};
236win_to_iana("North Asia Standard Time") -> {some, <<"Asia/Krasnoyarsk">>};
237win_to_iana("North Korea Standard Time") -> {some, <<"Asia/Pyongyang">>};
238win_to_iana("Omsk Standard Time") -> {some, <<"Asia/Omsk">>};
239win_to_iana("Pacific SA Standard Time") -> {some, <<"America/Santiago">>};
240win_to_iana("Pacific Standard Time") -> {some, <<"PST8PDT">>};
241win_to_iana("Pacific Standard Time (Mexico)") -> {some, <<"America/Tijuana">>};
242win_to_iana("Pakistan Standard Time") -> {some, <<"Asia/Karachi">>};
243win_to_iana("Paraguay Standard Time") -> {some, <<"America/Asuncion">>};
244win_to_iana("Qyzylorda Standard Time") -> {some, <<"Asia/Qyzylorda">>};
245win_to_iana("Romance Standard Time") -> {some, <<"Europe/Paris">>};
246win_to_iana("Russia Time Zone 10") -> {some, <<"Asia/Srednekolymsk">>};
247win_to_iana("Russia Time Zone 11") -> {some, <<"Asia/Kamchatka">>};
248win_to_iana("Russia Time Zone 3") -> {some, <<"Europe/Samara">>};
249win_to_iana("Russian Standard Time") -> {some, <<"Europe/Moscow">>};
250win_to_iana("SA Eastern Standard Time") -> {some, <<"America/Santarem">>};
251win_to_iana("SA Pacific Standard Time") -> {some, <<"Etc/GMT+5">>};
252win_to_iana("SA Western Standard Time") -> {some, <<"America/Santo_Domingo">>};
253win_to_iana("Saint Pierre Standard Time") -> {some, <<"America/Miquelon">>};
254win_to_iana("Sakhalin Standard Time") -> {some, <<"Asia/Sakhalin">>};
255win_to_iana("Samoa Standard Time") -> {some, <<"Pacific/Apia">>};
256win_to_iana("Sao Tome Standard Time") -> {some, <<"Africa/Sao_Tome">>};
257win_to_iana("Saratov Standard Time") -> {some, <<"Europe/Saratov">>};
258win_to_iana("SE Asia Standard Time") -> {some, <<"Asia/Bangkok">>};
259win_to_iana("Singapore Standard Time") -> {some, <<"Asia/Singapore">>};
260win_to_iana("South Africa Standard Time") -> {some, <<"Africa/Johannesburg">>};
261win_to_iana("South Sudan Standard Time") -> {some, <<"Africa/Juba">>};
262win_to_iana("Sri Lanka Standard Time") -> {some, <<"Asia/Colombo">>};
263win_to_iana("Sudan Standard Time") -> {some, <<"Africa/Khartoum">>};
264win_to_iana("Syria Standard Time") -> {some, <<"Asia/Damascus">>};
265win_to_iana("Taipei Standard Time") -> {some, <<"Asia/Taipei">>};
266win_to_iana("Tasmania Standard Time") -> {some, <<"Australia/Hobart">>};
267win_to_iana("Tocantins Standard Time") -> {some, <<"America/Araguaina">>};
268win_to_iana("Tokyo Standard Time") -> {some, <<"Asia/Tokyo">>};
269win_to_iana("Tomsk Standard Time") -> {some, <<"Asia/Tomsk">>};
270win_to_iana("Tonga Standard Time") -> {some, <<"Pacific/Tongatapu">>};
271win_to_iana("Transbaikal Standard Time") -> {some, <<"Asia/Chita">>};
272win_to_iana("Turkey Standard Time") -> {some, <<"Europe/Istanbul">>};
273win_to_iana("Turks And Caicos Standard Time") -> {some, <<"America/Grand_Turk">>};
274win_to_iana("Ulaanbaatar Standard Time") -> {some, <<"Asia/Ulaanbaatar">>};
275win_to_iana("US Eastern Standard Time") -> {some, <<"America/Indiana/Marengo">>};
276win_to_iana("US Mountain Standard Time") -> {some, <<"Etc/GMT+7">>};
277win_to_iana("UTC") -> {some, <<"Etc/GMT">>};
278win_to_iana("UTC-02") -> {some, <<"Etc/GMT+2">>};
279win_to_iana("UTC-08") -> {some, <<"Pacific/Pitcairn">>};
280win_to_iana("UTC-09") -> {some, <<"Pacific/Gambier">>};
281win_to_iana("UTC-11") -> {some, <<"Etc/GMT+11">>};
282win_to_iana("UTC+12") -> {some, <<"Etc/GMT-12">>};
283win_to_iana("UTC+13") -> {some, <<"Pacific/Fakaofo">>};
284win_to_iana("Venezuela Standard Time") -> {some, <<"America/Caracas">>};
285win_to_iana("Vladivostok Standard Time") -> {some, <<"Asia/Vladivostok">>};
286win_to_iana("Volgograd Standard Time") -> {some, <<"Europe/Volgograd">>};
287win_to_iana("W. Australia Standard Time") -> {some, <<"Australia/Perth">>};
288win_to_iana("W. Central Africa Standard Time") -> {some, <<"Etc/GMT-1">>};
289win_to_iana("W. Europe Standard Time") -> {some, <<"Europe/Berlin">>};
290win_to_iana("W. Mongolia Standard Time") -> {some, <<"Asia/Hovd">>};
291win_to_iana("West Asia Standard Time") -> {some, <<"Asia/Tashkent">>};
292win_to_iana("West Bank Standard Time") -> {some, <<"Asia/Gaza">>};
293win_to_iana("West Pacific Standard Time") -> {some, <<"Pacific/Port_Moresby">>};
294win_to_iana("Yakutsk Standard Time") -> {some, <<"Asia/Yakutsk">>};
295win_to_iana("Yukon Standard Time") -> {some, <<"America/Whitehorse">>};
296win_to_iana(_) -> none.