diff --git a/.gitignore b/.gitignore index e9d9cf7..1a67641 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ ebin/ _build/ +rebar3 +doc/* +!doc/style.css +!doc/overview.edoc \ No newline at end of file diff --git a/Makefile b/Makefile index 6b97d11..a810a39 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,44 @@ -all: include/tz_index.hrl - ./rebar3 compile +REBAR := ./rebar3 +REBAR_URL := https://s3.amazonaws.com/rebar3/rebar3 +ERL ?= erl -check: - ./rebar3 eunit + + +all: include/tz_index.hrl compile include/tz_index.hrl: src/ibuild.erl include/tz_database.hrl - cd include && ln -s ../src/ibuild.erl && escript ibuild.erl; EV=$$?; rm ibuild.erl; exit $$EV + cd include && cp ../src/ibuild.erl ibuild.erl && escript ibuild.erl; EV=$$?; rm ibuild.erl; exit $$EV + +compile: $(REBAR) + $(REBAR) compile + +check: $(REBAR) + $(REBAR) eunit + + +clean: $(REBAR) clean_doc + $(REBAR) clean + +clean_doc: + @rm -f doc/*.html + @rm -f doc/erlang.png + @rm -f doc/edoc-info + +xref: $(REBAR) + $(REBAR) as test xref + +dialyzer: $(REBAR) + $(REBAR) as check dialyzer + +doc: $(REBAR) doc/overview.edoc + $(REBAR) edoc + +doc_private: $(REBAR) + $(REBAR) as doc_private edoc + +./rebar3: + $(ERL) -noshell -s inets -s ssl \ + -eval '{ok, saved_to_file} = httpc:request(get, {"$(REBAR_URL)", []}, [], [{stream, "./rebar3"}])' \ + -s init stop + chmod +x ./rebar3 \ No newline at end of file diff --git a/README.md b/README.md index 8286bb0..d6dc962 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,8 @@ +[![Hex.pm version][hexpm version]][hexpm] +[![Hex.pm Downloads][hexpm downloads]][hexpm] +[![Hex.pm Documentation][hexdocs documentation]][hexdocs] +[![License][license]](https://www.apache.org/licenses/LICENSE-2.0) + #### Public exports * utc_to_local(DateTime, Timezone) - converts UTC time to local according to specified Timezone @@ -6,9 +11,14 @@ * tz_name(DateTime, Timezone) - returns a timezone name (E.g. MSK, MSD, etc) * tz_shift(DateTime, Timezone) - returns time difference between local datetime and GMT * tz_shift(DateTime, TimezoneFrom, TimezoneTo) - returns time difference between local datetime and required timezone + Where +``` DateTime = {date(), time()} -TimeZone(To, From) = String(). E.g. “Europe/Moscow”, “America/NewYork”. Or abbreviations "MSK", "MSD", etc. Note: +TimeZone(To, From) = String(). +``` +E.g. “Europe/Moscow”, “America/NewYork”. Or abbreviations "MSK", "MSD", etc. Note: + abbreviation is just used to find appropriate timezone name. If you want to convert "MSK" -> "PDT", but source timezone is not in daylight saving, it will be corrected by library and "MSK" -> "PST" conversion will be made. @@ -51,3 +61,11 @@ Calculates time difference between two local timezones >localtime:tz_shift({{2013, 01, 22}, {18, 17, 00}}, "America/New York", "Europe/Moscow"). > >{'+',9,0} + + +[hexpm]: https://hex.pm/packages/erlang_localtime +[hexpm version]: https://img.shields.io/hexpm/v/erlang_localtime.svg?style=flat-curcle "Hex version" +[hexpm downloads]: https://img.shields.io/hexpm/dt/erlang_localtime.svg?style=flat-curcle +[hexdocs documentation]: https://img.shields.io/badge/hex-docs-purple.svg?style=flat-curcle +[hexdocs]: https://hexdocs.pm/erlang_localtime +[license]: https://img.shields.io/badge/License-Apache_2.0-blue.svg?logo=apache&logoColor=red "Apache-2.0" \ No newline at end of file diff --git a/doc/overview.edoc b/doc/overview.edoc new file mode 100644 index 0000000..13e94a9 --- /dev/null +++ b/doc/overview.edoc @@ -0,0 +1,7 @@ +@author Dmitry S. Melnikov +@author Jesse Gumm +@author Heinz N. Gies +@title erlang_localtime +@doc Erlang library for conversion from one local time to another +@copyright Apache-2.0, 2010 Dmitry S. Melnikov +@since 2010 \ No newline at end of file diff --git a/include/tz_index.hrl b/include/tz_index.hrl index 95bd35e..03b0034 100644 --- a/include/tz_index.hrl +++ b/include/tz_index.hrl @@ -202,7 +202,7 @@ "US/Alaska"], ["GYT","America/Guyana"], ["PMST","America/Miquelon"], - ["UTC","Etc/UTC","Antarctica/Troll", + ["UTC","Antarctica/Troll","Etc/UTC", "Etc/Universal","Etc/Zulu","UTC","Universal", "Zulu"], ["WIT","Asia/Jayapura"], diff --git a/rebar.config b/rebar.config index 0f5d40e..eb24680 100644 --- a/rebar.config +++ b/rebar.config @@ -1 +1,35 @@ {erl_opts, [debug_info]}. + +{profiles, [ + {test, [ + {xref_checks, [ + undefined_function_calls, + locals_not_used, + deprecated_function_calls + ]}, + {xref_ignores, [ + ]}, + {dialyzer, [ + {warnings, [ + no_return + ]} + ]} + ]}, + {doc_private, [ + {edoc_opts, [ + {private, true} + ]} + ]}, + {check, [ + {dialyzer, [ + {warnings, [ + no_return, + no_opaque + ]} + ]}, + + {erl_opts, [ + debug_info + ]} + ]} +]}. \ No newline at end of file diff --git a/rebar3 b/rebar3 deleted file mode 100755 index 8a36476..0000000 Binary files a/rebar3 and /dev/null differ diff --git a/src/ibuild.erl b/src/ibuild.erl index 2530004..2aef950 100755 --- a/src/ibuild.erl +++ b/src/ibuild.erl @@ -1,3 +1,4 @@ +%% @private %% @author Dmitry S. Melnikov (dmitryme@gmail.com) %% @copyright 2010 Dmitry S. Melnikov diff --git a/src/localtime.erl b/src/localtime.erl index d6f18ed..e432d87 100644 --- a/src/localtime.erl +++ b/src/localtime.erl @@ -23,12 +23,22 @@ ,fmt_shift/1 ]). -% utc_to_local(UtcDateTime, Timezone) -> LocalDateTime | [LocalDateTime, DstLocalDateTime] | {error, ErrDescr} -% UtcDateTime = DateTime() -% Timezone = String() -% LocalDateTime = DateTime() -% DstLocalDateTime = DateTime() -% ErrDescr = atom(), unknown_tz +-export_type[timezone/0]. + +-type timezone() :: string(). % TimeZone + +%% @doc Converts UTC time +%% to local according to specified Timezone.
+%% `TimeList' is `[LocalDateTime, DstLocalDateTime]'. + +-spec utc_to_local(UtcDateTime, Timezone) -> Result when + UtcDateTime :: calendar:datetime(), + Timezone :: timezone(), + Result :: LocalDateTime | TimeList | Error, + TimeList :: [calendar:datetime()], + Error :: {error, ErrDescr}, + LocalDateTime :: calendar:datetime(), + ErrDescr :: atom() | unknown_tz. utc_to_local(UtcDateTime, Timezone) -> case lists:keyfind(get_timezone(Timezone), 1, ?tz_database) of false -> @@ -47,12 +57,17 @@ utc_to_local(UtcDateTime, Timezone) -> end end. -% local_to_utc(LocalDateTime, Timezone) -> UtcDateTime | [UtcDateTime, DstUtcDateTime] | time_not_exists | {error, ErrDescr} -% LocalDateTime = DateTime() -% Timezone = String() -% UtcDateTime = DateTime() -% DstUtcDateTime = DateTime() -% ErrDescr = atom(), unknown_tz +%% @doc Converts local time to UTC.
+%% `TimeList' is `[UtcDateTime, DstUtcDateTime]'. + +-spec local_to_utc(LocalDateTime, Timezone) -> Result when + LocalDateTime :: calendar:datetime(), + Timezone :: timezone(), + Result :: UtcDateTime | TimeList | time_not_exists | Error, + UtcDateTime :: calendar:datetime(), + TimeList :: [calendar:datetime()], + Error :: {error, ErrDescr}, + ErrDescr :: atom() | unknown_tz. local_to_utc(LocalDateTime, Timezone) -> case lists:keyfind(get_timezone(Timezone), 1, ?tz_database) of false -> @@ -73,11 +88,14 @@ local_to_utc(LocalDateTime, Timezone) -> end end. -% local_to_local(LocalDateTime, TimezoneFrom, TimezoneTo) -> LocalDateTime | ambiguous | time_not_exists | {error, ErrDescr} -% LocalDateTime = DateTime() -% TimezoneFrom = String() -% TimezoneTo = String() -% ErrDescr = atom(), unknown_tz +%% @doc Converts local time to local. +-spec local_to_local(LocalDateTime, TimezoneFrom, TimezoneTo) -> Result when + LocalDateTime :: calendar:datetime(), + TimezoneFrom :: timezone(), + TimezoneTo :: timezone(), + Result :: LocalDateTime | ambiguous | time_not_exists | Error, + Error :: {error, ErrDescr}, + ErrDescr :: atom() | unknown_tz. local_to_local(LocalDateTime, TimezoneFrom, TimezoneTo) -> case local_to_utc(LocalDateTime, TimezoneFrom) of UtcDateTime = {{_,_,_},{_,_,_}} -> @@ -94,11 +112,13 @@ local_to_local(LocalDateTime, TimezoneFrom, TimezoneTo) -> Other end. -% local_to_local_dst(LocalDateTime, TimezoneFrom, TimezoneTo) -> LocalDateTime | ambiguous | time_not_exists | {error, ErrDescr} -% LocalDateTime = DateTime() -% TimezoneFrom = String() -% TimezoneTo = String() -% ErrDescr = atom(), unknown_tz +-spec local_to_local_dst(LocalDateTime, TimezoneFrom, TimezoneTo) -> Result when + LocalDateTime :: calendar:datetime(), + TimezoneFrom :: timezone(), + TimezoneTo :: timezone(), + Result :: LocalDateTime | ambiguous | time_not_exists | Error, + Error :: {error, ErrDescr}, + ErrDescr :: atom() | unknown_tz. local_to_local_dst(LocalDateTime, TimezoneFrom, TimezoneTo) -> case local_to_utc(LocalDateTime, TimezoneFrom) of UtcDateTime = {{_,_,_},{_,_,_}} -> @@ -115,15 +135,22 @@ local_to_local_dst(LocalDateTime, TimezoneFrom, TimezoneTo) -> Other end. -% tz_name(DateTime(), Timezone) -> {Abbr, Name} | {{StdAbbr, StdName}, {DstAbbr, DstName}} | unable_to_detect | {error, ErrDesc} -% Timezone = String() -% Abbr = String() -% Name = String() -% StdAbbr = String() -% StdName = String() -% DstAbbr = String() -% DstName = String() -% ErrDesc = atom(), unknown_tz +%% @doc +%% @returns a timezone name (E.g. MSK, MSD, etc). +-spec tz_name(DateTime, Timezone) -> Result when + DateTime :: calendar:datetime(), + Timezone :: timezone(), + Result :: AbbrTuple | StdTuple | unable_to_detect | Error, + AbbrTuple :: {Abbr, Name}, + StdTuple :: {{StdAbbr, StdName}, {DstAbbr, DstName}}, + Abbr :: string(), + Name :: string(), + StdAbbr :: string(), + StdName :: string(), + DstAbbr :: string(), + DstName :: string(), + Error :: {error, ErrDesc}, + ErrDesc :: atom() | unknown_tz. tz_name(_UtcDateTime, "UTC") -> {"UTC", "UTC"}; tz_name(LocalDateTime, Timezone) -> @@ -145,15 +172,21 @@ tz_name(LocalDateTime, Timezone) -> end end. -% tz_shift(LocalDateTime, Timezone) -> Shift | {Shift, DstSift} | unable_to_detect | {error, ErrDesc} -% returns time shift from GMT -% LocalDateTime = DateTime() -% Timezone = String() -% Shift = DstShift = {Sign, Hours, Minutes} -% Sign = term(), '+', '-' -% Hours = Minutes = Integer(), -% {Shift, DstShift} - returns, when shift is ambiguous -% ErrDesc = atom(), unknown_tz +%% @doc Shifts local datetime. +%% @returns time difference between local datetime and GMT. +%% Returns `{Shift, DstShift}' when shift is ambiguous. +-spec tz_shift(LocalDateTime, Timezone) -> Result when + LocalDateTime :: calendar:datetime(), + Timezone :: timezone(), + Result :: 0 | Shift | ShiftTuple | unable_to_detect | Error, + ShiftTuple :: {Shift, DstShift}, + Shift :: DstShift, + DstShift :: {Sign, Hours, Minutes}, + Sign :: term() | '+' | '-', + Hours :: integer(), + Minutes :: integer(), + Error :: {error, ErrDesc}, + ErrDesc :: atom() | unknown_tz. tz_shift(_UtcDateTime, "UTC") -> 0; tz_shift(LocalDateTime, Timezone) -> @@ -175,10 +208,25 @@ tz_shift(LocalDateTime, Timezone) -> end end. -% the same as tz_shift/2, but calculates time difference between two local timezones +%% @doc The same as {@link tz_shift/2}, but calculates time difference between two local timezones. +%% @throws term() + +-spec tz_shift(LocalDateTime, TimezoneFrom, TimezoneTo) -> Result when + LocalDateTime :: calendar:datetime(), + TimezoneFrom :: timezone(), + TimezoneTo :: timezone(), + Result :: 0 | Shift | ShiftTuple | unable_to_detect | Error, + ShiftTuple :: {Shift, DstShift}, + Shift :: DstShift, + DstShift :: {Sign, Hours, Minutes}, + Sign :: term() | '+' | '-', + Hours :: integer(), + Minutes :: integer(), + Error :: {error, ErrDesc}, + ErrDesc :: atom() | unknown_tz. tz_shift(LocalDateTime, TimezoneFrom, TimezoneTo) -> F = fun() -> - FromShift = fmt_shift(tz_shift(LocalDateTime, TimezoneFrom)), + FromShift = fmt_shift(tz_shift(LocalDateTime, TimezoneFrom)), DateTimeTo = localtime:local_to_local(LocalDateTime, TimezoneFrom, TimezoneTo), ToShift = fmt_shift(tz_shift(DateTimeTo, TimezoneTo)), fmt_min(ToShift-FromShift) @@ -189,13 +237,24 @@ tz_shift(LocalDateTime, TimezoneFrom, TimezoneTo) -> Err end. +-spec adjust_datetime(DateTime, Minutes) -> Result when + DateTime :: calendar:datetime(), + Minutes :: integer(), + Result :: calendar:datetime(). adjust_datetime(DateTime, Minutes) -> Seconds = calendar:datetime_to_gregorian_seconds(DateTime) + Minutes * 60, calendar:gregorian_seconds_to_datetime(Seconds). +%% @doc Get TimeZone + +-spec get_timezone(TimeZone) -> Result when + TimeZone :: timezone(), + Result :: timezone(). get_timezone(TimeZone) -> get_timezone_inner(TimeZone). +-spec list_timezones() -> Result when + Result :: [timezone()]. list_timezones() -> dict:fetch_keys(?tz_index). @@ -203,14 +262,29 @@ list_timezones() -> % privates % ======================================================================= +-spec invert_shift(Minutes) -> Result when + Minutes :: integer(), + Result :: integer(). invert_shift(Minutes) -> -Minutes. +-spec fmt_min(Shift) -> Result when + Shift :: integer(), + Result :: {Sign, Hours, Minutes}, + Sign :: '+' | '-', + Hours :: integer(), + Minutes :: integer(). fmt_min(Shift) when Shift < 0 -> {'-', abs(Shift) div 60, abs(Shift) rem 60}; fmt_min(Shift) -> {'+', Shift div 60, Shift rem 60}. +-spec fmt_shift(any() | Shift | 0) -> Return when + Shift :: {Sign, Hours, Minutes}, + Sign :: '+' | '-', + Hours :: integer(), + Minutes :: integer(), + Return :: integer(). fmt_shift({'+', H, M}) -> H * 60 + M; fmt_shift({'-', H, M}) -> diff --git a/src/localtime_dst.erl b/src/localtime_dst.erl index ca4d3e6..7a64bce 100644 --- a/src/localtime_dst.erl +++ b/src/localtime_dst.erl @@ -13,12 +13,11 @@ check/2 ]). --compile([export_all]). - -% check(DateTime, TimeZone) -> is_in_dst | is_not_in_dst | ambiguous_time | time_not_exists -% DateTime = DateTime() -% TimeZone = tuple() +-spec check(DateTime, Timezone) -> Result when + DateTime :: calendar:datetime(), + Timezone :: tuple(), + Result :: is_in_dst | is_not_in_dst | ambiguous_time | time_not_exists. check(DateTime, Timezone) when is_list(Timezone) -> case lists:keyfind(localtime:get_timezone(Timezone), 1, ?tz_database) of false ->