From 299cfa166810626a9f58187779b060df06786705 Mon Sep 17 00:00:00 2001 From: Siraaj Khandkar Date: Sun, 8 Feb 2015 01:15:54 -0500 Subject: [PATCH] First stab at a time abstraction. --- src/hope_time.erl | 107 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 src/hope_time.erl diff --git a/src/hope_time.erl b/src/hope_time.erl new file mode 100644 index 0000000..0970aa7 --- /dev/null +++ b/src/hope_time.erl @@ -0,0 +1,107 @@ +-module(hope_time). + +-export_type( + [ t/0 + ]). + +-export( + [ now/0 + , of_timestamp/1 + , to_unix_time/1 + , of_iso8601/1 + + % Floatable + , of_float/1 + , to_float/1 + + % TODO: Stringable + ]). + + +-define(T, #?MODULE). + + +-record(?MODULE, + { unix_time :: float() + }). + +-opaque t() :: + ?T{}. + + +-spec now() -> + t(). +now() -> + Timestamp = os:timestamp(), + of_timestamp(Timestamp). + +-spec of_timestamp(erlang:timestamp()) -> + t(). +of_timestamp({MegasecondsInt, SecondsInt, MicrosecondsInt}) -> + Million = 1000000.0, + Megaseconds = float(MegasecondsInt), + Seconds = float(SecondsInt), + Microseconds = float(MicrosecondsInt), + UnixTime = (Megaseconds * Million) + Seconds + (Microseconds / Million), + ?T{unix_time = UnixTime}. + +-spec to_unix_time(t()) -> + float(). +to_unix_time(?T{unix_time=UnixTime}) -> + UnixTime. + +-spec of_float(float()) -> + t(). +of_float(Float) when is_float(Float) -> + ?T{unix_time = Float}. + +-spec to_float(t()) -> + float(). +to_float(?T{unix_time=Float}) -> + Float. + +-spec of_iso8601(binary()) -> + hope_result:t(t(), {unrecognized_as_iso8601, binary()}). +of_iso8601(<>) -> + % We use regexp rather than just simple binary pattern match, because we + % also want to validate character ranges, i.e., that components are + % integers. + ValidPatterns = + [ {zoneless, <<"\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\d">>} + ], + ValidPatternMatchers = + [{Tag, make_regexp_bool(RegExp)} || {Tag, RegExp} <- ValidPatterns], + case hope_list:first_match(ValidPatternMatchers, Bin) + of none -> {error, {unrecognized_as_iso8601, Bin}} + ; {some, zoneless} -> {ok, of_iso8601_zoneless(Bin)} + end. + +-spec of_iso8601_zoneless(binary()) -> + t(). +of_iso8601_zoneless(<>) -> + << YearBin:4/binary, "-", MonthBin:2/binary, "-", DayBin:2/binary + , "T" + , HourBin:2/binary, ":", MinBin:2/binary , ":", SecBin:2/binary + >> = Bin, + Year = binary_to_integer(YearBin), + Month = binary_to_integer(MonthBin), + Day = binary_to_integer(DayBin), + Hour = binary_to_integer(HourBin), + Min = binary_to_integer(MinBin), + Sec = binary_to_integer(SecBin), + DateTime = {{Year, Month, Day}, {Hour, Min, Sec}}, + SecondsGregorian = calendar:datetime_to_gregorian_seconds(DateTime), + SecondsFromZeroToUnixEpoch = 62167219200, + SecondsUnixEpochInt = SecondsGregorian - SecondsFromZeroToUnixEpoch, + SecondsUnixEpoch = float(SecondsUnixEpochInt), + of_float(SecondsUnixEpoch). + +-spec make_regexp_bool(binary()) -> + fun((binary()) -> boolean()). +make_regexp_bool(<>) -> + fun (<>) -> + case re:run(String, RegExp) + of nomatch -> false + ; {match, _} -> true + end + end. -- 2.20.1