-module(ephp_lib_date). -author('manuel@altenwald.com'). -behaviour(ephp_lib). -export([init_func/0, init_config/0, init_const/0, handle_error/3, time/2, microtime/2, microtime/3, date/3, date/4, gmdate/3, gmdate/4, date_default_timezone_set/3, date_default_timezone_get/2, timezone_abbreviations_list/2]). -include("ephp.hrl"). -spec init_func() -> ephp_lib:php_function_results(). init_func() -> [time, date, gmdate, microtime, date_default_timezone_get, date_default_timezone_set, timezone_abbreviations_list]. -spec init_config() -> ephp_lib:php_config_results(). init_config() -> [{<<"date.timezone">>, undefined}]. -spec init_const() -> ephp_lib:php_const_results(). init_const() -> []. -spec handle_error(ephp_error:error_type(), ephp_error:error_level(), Args :: term()) -> string() | ignore. handle_error(etimezone, _Level, {Function, TZ}) -> io_lib:format("~s(): Timezone ID '~s' is invalid", [Function, TZ]); handle_error(enotimezone, _Level, {Function}) -> io_lib:format("~s(): It is not safe to rely on the system's timezone " "settings. You are *required* to use the date.timezone " "setting or the date_default_timezone_set() function. " "In case you used any of those methods and you are still " "getting this warning, you most likely misspelled the " "timezone identifier. We selected the timezone 'UTC' for " "now, but please set date.timezone to select your " "timezone.", [Function]); handle_error(_Type, _Level, _Args) -> ignore. -spec time(ephp:context_id(), line()) -> integer(). time(_Context, _Line) -> ephp_datetime:posix_time(). -spec microtime(ephp:context_id(), line(), GetAsFloat :: {variable() | undefined, boolean()}) -> float() | binary(). microtime(_Context, _Line, {_, true}) -> {MS, S, US} = ephp_datetime:timestamp(), MS * 1000000 + S + US div 1000 / 1000; microtime(_Context, _Line, {_, false}) -> {MS, S, US} = ephp_datetime:timestamp(), Posix = ephp_data:to_bin(MS * 1000000 + S), MiliSec = ephp_data:to_bin(US div 1000 / 1000), <>; microtime(Context, Line, {Var, Val}) -> microtime(Context, Line, {Var, ephp_data:to_boolean(Val)}). -spec microtime(ephp:context_id(), line()) -> float() | binary(). microtime(Context, Line) -> microtime(Context, Line, {undefined, false}). -spec date(ephp:context_id(), line(), Format :: var_value()) -> binary(). date(Context, Line, Format) -> {MS, S, US} = ephp_datetime:timestamp(), date(Context, Line, Format, {undefined, MS * 1000000 + S + trunc(US / 1000000)}). -spec date(ephp:context_id(), line(), Format :: var_value(), Timestamp :: var_value()) -> binary(). date(Context, Line, {_, Format}, {_, Timestamp}) -> {M, S, U} = ephp_datetime:get_timestamp(Timestamp), TZ = ephp_datetime:get_tz(Context, Line), Date = ephp_datetime:to_zone({M, S, U}, TZ), date_format(Format, <<>>, {Timestamp, Date, TZ}). -spec gmdate(ephp:context_id(), line(), Format :: var_value()) -> binary(). gmdate(Context, Line, {_, Format}) -> {MS, S, US} = ephp_datetime:timestamp(), gmdate(Context, Line, {undefined, Format}, {undefined, MS * 1000000 + S + trunc(US / 1000000)}). -spec gmdate(ephp:context_id(), line(), Format :: var_value(), Timestamp :: var_value()) -> binary(). gmdate(_Context, _Line, {_, Format}, {_, Timestamp}) -> {M, S, U} = ephp_datetime:get_timestamp(Timestamp), TZ = <<"GMT">>, Date = calendar:now_to_universal_time({M, S, U}), date_format(Format, <<>>, {Timestamp, Date, TZ}). -spec date_default_timezone_get(ephp:context_id(), line()) -> binary(). date_default_timezone_get(Context, Line) -> ephp_datetime:get_tz(Context, Line). -spec date_default_timezone_set(ephp:context_id(), line(), TZ :: var_value()) -> undefined. date_default_timezone_set(Context, Line, {_, TZ}) -> case ephp_datetime:set_tz(TZ) of true -> undefined; false -> Function = <<"date_default_timezone_set">>, File = ephp_context:get_active_file(Context), Params = {error, etimezone, Line, File, ?E_NOTICE, {Function, TZ}}, ephp_error:handle_error(Context, Params), undefined end. -spec timezone_abbreviations_list(ephp:context_id(), line()) -> ephp_array:ephp_array(). timezone_abbreviations_list(_Context, _Line) -> lists:foldl(fun(Abbr, Array) -> Info = lists:foldl(fun({Dst, Offset, TZID}, SubArray) -> A0 = ephp_array:new(), A1 = ephp_array:store(<<"dst">>, Dst, A0), A2 = ephp_array:store(<<"offset">>, Offset, A1), A3 = ephp_array:store(<<"timezone_id">>, TZID, A2), ephp_array:store(auto, A3, SubArray) end, ephp_array:new(), ephp_timezone:timezone_info(Abbr)), ephp_array:store( ephp_string:to_lower(Abbr), Info, Array) end, ephp_array:new(), ephp_timezone:abbreviations()). %% ---------------------------------------------------------------------------- %% Internal functions %% ---------------------------------------------------------------------------- -spec date_format(ToAnalyze :: binary(), Result :: binary(), {Timestamp :: integer(), Date :: calendar:datetime(), TZ :: binary()}) -> binary(). date_format(<<>>, Result, _Date) -> Result; date_format(<<"d", R/binary>>, Result, {_, {{_, _, D}, _}, _} = Date) -> Day = ephp_data:pad_to_bin(D, 2), date_format(R, <>, Date); date_format(<<"D", R/binary>>, Result, {_, {D, _}, _} = Date) -> Day = ephp_datetime:get_abbr_weekday( calendar:day_of_the_week(D)), date_format(R, <>, Date); date_format(<<"j", R/binary>>, Result, {_, {{_, _, D}, _}, _} = Date) -> Month = ephp_data:to_bin(D), date_format(R, <>, Date); date_format(<<"l", R/binary>>, Result, {_, {D, _}, _} = Date) -> Day = ephp_datetime:get_weekday( calendar:day_of_the_week(D)), date_format(R, <>, Date); date_format(<<"N", R/binary>>, Result, {_, {D, _}, _} = Date) -> Day = calendar:day_of_the_week(D), DayBin = ephp_data:to_bin(Day), date_format(R, <>, Date); date_format(<<"S", R/binary>>, Result, {_, {{_, _, D}, _}, _} = Date) -> Suffix = if D =:= 1 orelse D =:= 21 orelse D =:= 31 -> <<"st">>; D =:= 2 orelse D =:= 22 -> <<"nd">>; D =:= 3 orelse D =:= 23 -> <<"rd">>; true -> <<"th">> end, date_format(R, <>, Date); date_format(<<"w", R/binary>>, Result, {_, {D, _}, _} = Date) -> Day = calendar:day_of_the_week(D), DayBin = ephp_data:to_bin(if Day =:= 7 -> 0; true -> Day end), date_format(R, <>, Date); date_format(<<"z", R/binary>>, Result, {_, {{Y, M, D}, _}, _} = Date) -> Days1 = calendar:date_to_gregorian_days(Y, 1, 1), Days2 = calendar:date_to_gregorian_days(Y, M, D), Days = ephp_data:to_bin(Days2 - Days1), date_format(R, <>, Date); date_format(<<"W", R/binary>>, Result, {_, {D, _}, _} = Date) -> {_Y, WY} = calendar:iso_week_number(D), WeekOfYear = ephp_data:pad_to_bin(WY, 2), date_format(R, <>, Date); date_format(<<"F", R/binary>>, Result, {_, {{_, M, _}, _}, _} = Date) -> Month = ephp_datetime:get_month(M), date_format(R, <>, Date); date_format(<<"m", R/binary>>, Result, {_, {{_, M, _}, _}, _} = Date) -> Month = ephp_data:pad_to_bin(M, 2), date_format(R, <>, Date); date_format(<<"M", R/binary>>, Result, {_, {{_, M, _}, _}, _} = Date) -> Month = ephp_datetime:get_abbr_month(M), date_format(R, <>, Date); date_format(<<"n", R/binary>>, Result, {_, {{_, M, _}, _}, _} = Date) -> Month = ephp_data:to_bin(M), date_format(R, <>, Date); date_format(<<"t", R/binary>>, Result, {_, {{Y, M, _}, _}, _} = Date) -> Month = ephp_data:to_bin( calendar:last_day_of_the_month(Y, M)), date_format(R, <>, Date); date_format(<<"L", R/binary>>, Result, {_, {{Y, _, _}, _}, _} = Date) -> Leap = case calendar:is_leap_year(Y) of true -> <<"1">>; false -> <<"0">> end, date_format(R, <>, Date); date_format(<<"o", R/binary>>, Result, {_, {D, _}, _} = Date) -> {Y, _WY} = calendar:iso_week_number(D), YearOfWeek = ephp_data:to_bin(Y), date_format(R, <>, Date); date_format(<<"Y", R/binary>>, Result, {_, {{Y, _, _}, _}, _} = Date) -> Year = ephp_data:to_bin(Y), date_format(R, <>, Date); date_format(<<"y", R/binary>>, Result, {_, {{Y, _, _}, _}, _} = Date) -> Year = ephp_data:pad_to_bin(Y rem 100, 2), date_format(R, <>, Date); date_format(<<"a", R/binary>>, Result, {_, {_, {H, _, _}}, _} = Date) -> Type = if H >= 0 andalso H =< 11 -> <<"am">>; true -> <<"pm">> end, date_format(R, <>, Date); date_format(<<"A", R/binary>>, Result, {_, {_, {H, _, _}}, _} = Date) -> Type = if H >= 0 andalso H =< 11 -> <<"AM">>; true -> <<"PM">> end, date_format(R, <>, Date); date_format(<<"B", R/binary>>, Result, {Timestamp, _, _} = Date) -> DateTime = ephp_datetime:get_datetime(Timestamp), BeatsTime = ephp_datetime:to_bmt(DateTime), Beats = ephp_data:to_bin(BeatsTime), date_format(R, <>, Date); date_format(<<"g", R/binary>>, Result, {_, {_, {H, _, _}}, _} = Date) -> Hour = ephp_data:to_bin((H + 11) rem 12 + 1), date_format(R, <>, Date); date_format(<<"G", R/binary>>, Result, {_, {_, {H, _, _}}, _} = Date) -> Hour = ephp_data:to_bin(H), date_format(R, <>, Date); date_format(<<"h", R/binary>>, Result, {_, {_, {H, _, _}}, _} = Date) -> Hour = ephp_data:pad_to_bin((H + 11) rem 12 + 1, 2), date_format(R, <>, Date); date_format(<<"H", R/binary>>, Result, {_, {_, {H, _, _}}, _} = Date) -> Hour = ephp_data:pad_to_bin(H, 2), date_format(R, <>, Date); date_format(<<"i", R/binary>>, Result, {_, {_, {_, M, _}}, _} = Date) -> Minute = ephp_data:pad_to_bin(M, 2), date_format(R, <>, Date); date_format(<<"s", R/binary>>, Result, {_, {_, {_, _, S}}, _} = Date) -> Second = ephp_data:pad_to_bin(S, 2), date_format(R, <>, Date); date_format(<<"u", R/binary>>, Result, {Timestamp, _, _} = Date) -> US = Timestamp * 1000000 rem 1000000, MSecond = ephp_data:pad_to_bin(US, 6), date_format(R, <>, Date); date_format(<<"e", R/binary>>, Result, {_, _, TZ} = Date) -> TimeZone = ephp_data:to_bin(TZ), date_format(R, <>, Date); date_format(<<"I", R/binary>>, Result, {_, DT, TZ} = Date) -> Daylight = case ephp_datetime:is_dst(DT, TZ) of true -> <<"1">>; false -> <<"0">> end, date_format(R, <>, Date); date_format(<<"O", R/binary>>, Result, {_, DT, TZ} = Date) -> Timezone = ephp_datetime:get_tz_time(DT, TZ, <<>>), date_format(R, <>, Date); date_format(<<"P", R/binary>>, Result, {_, DT, TZ} = Date) -> Timezone = ephp_datetime:get_tz_time(DT, TZ, <<":">>), date_format(R, <>, Date); date_format(<<"T", R/binary>>, Result, {_, DT, TZ} = Date) -> IsDst = ephp_datetime:is_dst(DT, TZ), Timezone = ephp_timezone:timezone_to_abbreviation(IsDst, ephp_data:to_bin(TZ)), date_format(R, <>, Date); date_format(<<"Z", R/binary>>, Result, {_, DT, TZ} = Date) -> <> = ephp_datetime:get_tz_time(DT, TZ, <<>>), Secs = integer_to_binary((binary_to_integer(H) * 60 + binary_to_integer(M)) * 60), Timezone = case S of <<"+">> -> Secs; <<"-">> -> <> end, date_format(R, <>, Date); date_format(<<"c", R/binary>>, Result, {_, {{Y, M, D}, {H, I, S}} = DT, TZ} = Date) -> YB = ephp_data:to_bin(Y), MB = ephp_data:pad_to_bin(M, 2), DB = ephp_data:pad_to_bin(D, 2), HB = ephp_data:pad_to_bin(H, 2), IB = ephp_data:pad_to_bin(I, 2), SB = ephp_data:pad_to_bin(S, 2), TZtime = ephp_datetime:get_tz_time(DT, TZ, <<":">>), DateTime = <>, date_format(R, <>, Date); date_format(<<"r", R/binary>>, Result, {_, {{Y, M, D} = Dt, {H, I, S}} = DT, TZ} = Date) -> WB = ephp_datetime:get_abbr_weekday( calendar:day_of_the_week(Dt)), DB = ephp_data:pad_to_bin(D, 2), MB = ephp_datetime:get_abbr_month(M), YB = ephp_data:to_bin(Y), HB = ephp_data:pad_to_bin(H, 2), IB = ephp_data:pad_to_bin(I, 2), SB = ephp_data:pad_to_bin(S, 2), TZtime = ephp_datetime:get_tz_time(DT, TZ, <<>>), DateTime = <>, date_format(R, <>, Date); date_format(<<"U", R/binary>>, Result, {Timestamp, _, _} = Date) -> DateTime = ephp_data:to_bin(trunc(Timestamp)), date_format(R, <>, Date); date_format(<>, Result, Date) -> date_format(R, <>, Date).