Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 88 additions & 38 deletions src/edatetime.erl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
-module(edatetime).
-include_lib("eunit/include/eunit.hrl").

%% API
-export([date2ts/1, datetime2ts/1,
ts2date/1, ts2datetime/1,
now2us/1, now2ms/0, now2ms/1, now2ts/0, now2ts/1,
Expand All @@ -11,81 +12,96 @@
day_start/1, week_start/1, month_start/1,
second_diff/2, minute_diff/2, hour_diff/2
]).

-export([iso8601/1, iso8601_basic/1]).

-export([tomorrow/1, yesterday/1]).


%%
%% Types
%%

-type year() :: pos_integer().
-type month() :: 1..12.
-type day() :: 1..31.
-type date() :: {year(), month(), day()}.
-type hour() :: 0..23.
-type minute() :: 0..59.
-type second() :: 0..59.
-type time() :: {hour(), minute(), second()}.
-type datetime() :: {date(), time()}.
-type ts() :: pos_integer().
-type timestamp() :: {pos_integer(), pos_integer(), pos_integer()}.
-type period() :: hours | days | hour | day | minutes | minute | seconds.
-export_types([datetime/0, date/0, timestamp/0, ts/0]).


%%
%% API
%%

-spec date2ts(date()) -> ts().
date2ts({Y, M, D}) ->
calendar:datetime_to_gregorian_seconds({{Y, M, D}, {0, 0, 0}})
- calendar:datetime_to_gregorian_seconds({{1970, 1, 1}, {0,0,0}}).

-spec datetime2ts(datetime()) -> ts().
datetime2ts(Datetime) ->
calendar:datetime_to_gregorian_seconds(Datetime)
- calendar:datetime_to_gregorian_seconds({{1970, 1, 1}, {0,0,0}}).

-spec ts2date(ts()) -> date().
ts2date(Timestamp) ->
{Date, _Time} = ts2datetime(Timestamp),
Date.

-spec ts2datetime(ts()) -> datetime().
ts2datetime(Timestamp) ->
BaseDate = calendar:datetime_to_gregorian_seconds({{1970,1,1},{0,0,0}}),
Seconds = BaseDate + Timestamp,
calendar:gregorian_seconds_to_datetime(Seconds).


-spec now2us(timestamp()) -> ts().
now2us({MegaSecs,Secs,MicroSecs}) ->
(MegaSecs * 1000000 + Secs) * 1000000 + MicroSecs.

-spec now2ms() -> ts().
now2ms() ->
now2ms(os:timestamp()).

-spec now2ms(timestamp()) -> ts().
now2ms({MegaSecs,Secs,MicroSecs}) ->
(MegaSecs * 1000000 + Secs) * 1000000 + (MicroSecs div 1000).

-spec now2ts() -> ts().
now2ts() ->
now2ts(os:timestamp()).

-spec now2ts(timestamp()) -> ts().
now2ts({MegaSeconds, Seconds, _}) ->
MegaSeconds * 1000000 + Seconds.

range(Start, End, days) ->
map(fun (E) -> E end, Start, End, days).

-spec range(ts(), ts(), period()) -> list().
range(Start, End, Period) ->
map(fun (E) -> E end, Start, End, Period).

-spec map(fun(), ts(), ts(), hours | days) -> list().
map(F, Start, End, hours) when Start =< End ->
do_map(F, hour_start(Start), hour_start(End), hours, []);
map(F, Start, End, days) when Start =< End ->
do_map(F, day_start(Start), day_start(End), days, []);
map(_, _, _, _) ->
error(badarg).

-spec foldl(fun(), any(), ts(), ts(), hours | days)
-> any().
foldl(F, Acc0, Start, End, hours) when Start =< End ->
do_foldl(F, hour_start(Start), hour_start(End), hours, Acc0);
foldl(F, Acc0, Start, End, days) when Start =< End ->
do_foldl(F, day_start(Start), day_start(End), days, Acc0);
foldl(_, _, _, _, _) ->
error(badarg).

do_map(F, End, End, _Period, Acc) ->
lists:reverse([F(End) | Acc]);
do_map(F, Start, End, Period, Acc) ->
do_map(F, shift(Start, 1, Period), End, Period, [F(Start) | Acc]).



foldl(F, Acc0, Start, End, hours) ->
do_foldl(F, Start, End, hours, Acc0);
foldl(F, Acc0, Start, End, days) ->
do_foldl(F, day_start(Start), day_start(End), days, Acc0).

do_foldl(F, End, End, _Period, Acc) ->
F(End, Acc);
do_foldl(F, Start, End, Period, Acc) ->
do_foldl(F, shift(Start, 1, Period), End, Period, F(Start, Acc)).



day_start(Ts) when is_integer(Ts) ->
Ts - (Ts rem 86400).

hour_start(Ts) ->
Ts - (Ts rem 3600).

-spec shift(ts(), integer(), period()) -> ts().
shift(Ts, N, days) -> Ts + (N * 86400);
shift(Ts, N, day) -> Ts + (N * 86400);
shift(Ts, N, hours) -> Ts + (N * 3600);
Expand All @@ -94,45 +110,70 @@ shift(Ts, N, minutes) -> Ts + (N * 60);
shift(Ts, N, minute) -> Ts + (N * 60);
shift(Ts, N, seconds) -> Ts + N.

-spec hour_start(ts()) -> ts().
hour_start(Ts) ->
Ts - (Ts rem 3600).

-spec day_start(ts()) -> ts().
day_start(Ts) when is_integer(Ts) ->
Ts - (Ts rem 86400).

-spec week_start(ts()) -> ts().
week_start(Ts) when is_integer(Ts) ->
WeekDay = calendar:day_of_the_week(ts2date(Ts)),
day_start(shift(Ts, -WeekDay+1, days)).

-spec month_start(ts()) -> ts().
month_start(Ts) when is_integer(Ts) ->
{Y, M, _} = ts2date(Ts),
date2ts({Y, M, 1}).


-spec tomorrow(ts()) -> ts().
tomorrow(Ts) ->
shift(Ts, 1, days).

-spec yesterday(ts()) -> ts().
yesterday(Ts) ->
shift(Ts, -1, days).


second_diff(TsA, TsB) -> float((TsA - TsB)).
minute_diff(TsA, TsB) -> float((TsA - TsB) / 60).
hour_diff(TsA, TsB) -> float((TsA - TsB) / (60 * 60)).



%%
%% Serialization
%%

-spec iso8601(ts()) -> binary().
iso8601(Ts) ->
{{Year, Month, Day}, {Hour, Minute, Second}} = edatetime:ts2datetime(Ts),
list_to_binary(
io_lib:format("~4.10.0B-~2.10.0B-~2.10.0BT~2.10.0B:~2.10.0B:~2.10.0BZ",
[Year, Month, Day, Hour, Minute, Second])).

-spec iso8601_basic(ts()) -> binary().
iso8601_basic(Ts) ->
{{Year, Month, Day}, {Hour, Minute, Second}} = edatetime:ts2datetime(Ts),
list_to_binary(
io_lib:format("~4.10.0B~2.10.0B~2.10.0BT~2.10.0B~2.10.0B~2.10.0BZ",
[Year, Month, Day, Hour, Minute, Second])).


%%
%% Internals
%%

do_map(F, End, End, _Period, Acc) ->
lists:reverse([F(End) | Acc]);
do_map(F, Start, End, Period, Acc) ->
do_map(F, shift(Start, 1, Period), End, Period, [F(Start) | Acc]).

do_foldl(F, End, End, _Period, Acc) ->
F(End, Acc);
do_foldl(F, Start, End, Period, Acc) ->
do_foldl(F, shift(Start, 1, Period), End, Period, F(Start, Acc)).

second_diff(TsA, TsB) -> float((TsA - TsB)).
minute_diff(TsA, TsB) -> float((TsA - TsB) / 60).
hour_diff(TsA, TsB) -> float((TsA - TsB) / (60 * 60)).


%%
%% TESTS
Expand Down Expand Up @@ -201,6 +242,15 @@ map_hours_test() ->
datetime2ts({{2012, 12, 31}, {2, 5, 0}}),
hours)).

wrong_map_hours_test() ->
?assertEqual(
[{{2012,12,31},{0,0,0}}],
map(fun ts2datetime/1,
datetime2ts({{2012, 12, 31}, {0, 0, 0}}),
datetime2ts({{2012, 12, 31}, {0, 0, 1}}),
hours)
).

range_test() ->
?assertEqual([date2ts({2012, 12, 31}),
date2ts({2013, 1, 1}),
Expand Down