First stab at a time abstraction.
authorSiraaj Khandkar <siraaj@khandkar.net>
Sun, 8 Feb 2015 06:15:54 +0000 (01:15 -0500)
committerSiraaj Khandkar <siraaj@khandkar.net>
Sun, 8 Feb 2015 06:15:54 +0000 (01:15 -0500)
src/hope_time.erl [new file with mode: 0644]

diff --git a/src/hope_time.erl b/src/hope_time.erl
new file mode 100644 (file)
index 0000000..0970aa7
--- /dev/null
@@ -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(<<Bin/binary>>) ->
+    % 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(<<Bin/binary>>) ->
+    << 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(<<RegExp/binary>>) ->
+    fun (<<String/binary>>) ->
+        case re:run(String, RegExp)
+        of  nomatch    -> false
+        ;   {match, _} -> true
+        end
+    end.
This page took 0.030047 seconds and 4 git commands to generate.