%% @author huangyongxing@yeah.net
%% @doc
%% 时间计算相关接口
%%
%% 1. 废弃mod_time时间（100毫秒精度），直接采用ErlangVM系统system_time计算时间
%% 2. 采用夏令时制友好的设计，逻辑设计及接口使用上需要考虑夏令时影响
%% 特别注意：
%%   不要用午夜时间直接加减N个ONE_DAY_SECONDS作为另一个午夜时间，
%%   在夏令时地区，特定的逻辑有可能引发死循环BUG或其他逻辑问题
%% @end
%% ---------------------------------------------------------------------------
-module(utime).
-include("common.hrl").
-export([
        unixtime/0, unixtime/1,                                 %% 获取时间戳(s/ms/us)
        longunixtime/0,                                         %% 获取时间戳(ms)
        unixdate/0, unixdate/1,                                 %% 获取凌晨时间戳(s)
        tomorrow/0, tomorrow/1,                                 %% 获取次日0点间戳
        yesterday/0, yesterday/1,                               %% 获取前一天0点时间戳
        that_day_before/1, that_day_before/2,                   %% 获取N天前0点时间戳
        that_day_after/1, that_day_after/2,                     %% 获取N天后0点时间戳
        unixtime_to_now/1,
        now_to_unixtime/1,
        microsec_to_sec/1,                                      %% 微秒转换成秒
        unixtime_to_localtime/1, unixtime_to_gmttime/1,         %% 时间戳转本地时间/GMT时间
        localtime/0,                                            %% 获取本地时间戳
        get_month_unixtime_range/0, get_month_unixtime_range/1, %% 获取这个月的时间戳范围
        get_date/0,
        day_of_week/0, day_of_week/1,                           %% 获取星期几
        day_of_month/0, day_of_month/1,                         %% 获取今天是当月第几天
        day_span/1, day_span/2,                                 %% 两个时间戳日期跨度
        diff_day/1, diff_day/2,                                 %% 两个时间戳相差多少天（按自然日计算）
        is_today/1,                                             %% 是否今日
        is_today_four/1,
        is_same_day/2,                                          %% 判断是否同一天
        is_same_date/2,                                         %% 判断是否同一天
        is_same_week/2,                                         %% 是否同一周
        is_logic_same_day/1, is_logic_same_day/2,               %% 是否同一刷新日
        is_logic_same_day/3,                                    %% 是否同一刷新日
        is_same_month/2,                                        %% 是否同一月
        get_logic_day_time_info/0,                              %% 逻辑天数信息
        get_logic_day_time_info/1,
        get_logic_day_start_time/0,
        get_logic_day_start_time/1,
        logic_week_start/0,
        logic_week_start/1,
        next_logic_week_start/0,
        next_logic_week_start/1,
        get_logic_time/0,
        get_logic_time/1,                                       %% 返回本地逻辑时间：  {{年，月，日}，{时，分，秒}}
        logic_day_of_week/0,                                    %% 逻辑星期
        logic_day_of_week/1,                                    %% 逻辑星期
        get_before_day/3,                                       %% 获取某一天的前天或者后天
        get_seconds_from_midnight/0,                            %% 获取当天0点到现在的秒数
        get_seconds_from_midnight/1,                            %% 获取当天0点到现在的秒数
        time_to_hms/1,
        get_month/0,
        get_month/1
        ,get_logic_midnight/2
        ,get_next_logic_midnight/2
        ,get_opened_month/0                                     %% 获取当前是开服的第几月
        ,is_continuous_week/1
        ,get_min_unixtime/0
        ,unixtime_to_timestr/1                                  %% unixtime转换为本地时间字符串
        ,time_string_re_mp/0
        ,time_string_to_localtime/1
        ,time_string_to_localtime/2
    ]).

%% ---------------------------------------------------------------------------
%% 时间处理相关接口
%% ---------------------------------------------------------------------------

%% 取得当前的unix时间戳（秒）
% unixtime() ->
%     {M, S, _} = mod_time:now(),
%     M * 1000000 + S.
unixtime() ->       % 秒
    erlang:system_time(seconds).

unixtime(ms) ->     % 毫秒
    erlang:system_time(milli_seconds);
unixtime(us) ->     % 微秒
    erlang:system_time(micro_seconds);
unixtime(ns) ->     % 纳秒
    erlang:system_time(nano_seconds);

%% 特别注意：
%%   不要用午夜时间直接加减N个ONE_DAY_SECONDS作为另一个午夜时间，
%%   在夏令时地区，特定的逻辑有可能引发死循环BUG或其他逻辑问题

%% 某个时间点当天0点的unix时间戳
unixtime({today, Unixtime}) ->
    % 这种计算方法存在夏令时切换日0点计算问题
    % {_, Time} = unixtime_to_localtime(Unixtime),
    % Unixtime - calendar:time_to_seconds(Time);
    {Date, _Time} = unixtime_to_localtime(Unixtime),
    MidnightLocaltime = {Date, {0, 0, 0}},
    unixtime(MidnightLocaltime);

%% 某个时间点第二天0点的unix时间戳
unixtime({tomorrow, Unixtime}) ->
    {Date, _Time} = unixtime_to_localtime(Unixtime),
    EndLocalTime = {Date, {23, 59, 59}},
    unixtime(EndLocalTime) + 1;

%% 某个时间点前一天0点的unix时间戳
unixtime({yesterday, Unixtime}) ->
    {Date, _Time} = unixtime_to_localtime(Unixtime),
    YesterdayDate = get_before_day(Date, 1, 0),
    unixtime({YesterdayDate, {0, 0, 0}});

unixtime(week) ->   % 周一0点
    Unixtime = erlang:system_time(seconds),
    unixtime({week, Unixtime});

%% 指定时间戳对应的周一0点
unixtime({week, Unixtime}) ->
    {Date, _Time} = unixtime_to_localtime(Unixtime),
    DayOfWeek = calendar:day_of_the_week(Date),
    MondayDate = get_before_day(Date, (DayOfWeek - 1), 0),
    MondayLocaltime = {MondayDate, {0, 0, 0}},
    unixtime(MondayLocaltime);

%% 指定时间戳下周一0点
unixtime({next_week, Unixtime}) ->
    {Date, _Time} = unixtime_to_localtime(Unixtime),
    DayOfWeek = calendar:day_of_the_week(Date),
    NextMondayDate = get_before_day(Date, (7 + 1 - DayOfWeek), 1),
    NextMondayLocaltime = {NextMondayDate, {0, 0, 0}},
    unixtime(NextMondayLocaltime);
%% 指定时间戳上周周一0点
unixtime({pre_week, Unixtime}) ->
    {Date, _Time} = unixtime_to_localtime(Unixtime),
    DayOfWeek = calendar:day_of_the_week(Date),
    PreMondayDate = get_before_day(Date, (7 + (DayOfWeek - 1)), 0),
    PreMondayLocaltime = {PreMondayDate, {0, 0, 0}},
    unixtime(PreMondayLocaltime);
%% 今天某个时刻的时间戳
unixtime({_H,_I,_S}) ->
    unixtime({date(), {_H,_I,_S}});
%% erlang中的calendar::localtime()转换为unix时间戳（1970年以来秒数）
%% LocalTime 格式 {{Y,M,D},{H,I,S}}
%%
%% 夏令时开始时，时间会往前拔，会形成人为概念上时间的空缺。
%% 夏令时结束时，时间会回拔，也就是会形成人为概念上的时间重叠。
%%
%% 以欧洲某些地区为例，
%%
%% 2020-03-29 进入夏令时，1点时会拔到2点，产生概念上的无效时间
%% 2020-03-29 01:00:00 --- 2020-03-29 01:59:59
%% 下面的接口调用，如果使用这段时间，实际上会得到unix时间戳值，
%% 例如 2020-03-29 01:30:00 对应 2020-03-29 02:30:00的unix时间戳
%%
%% 2020-10-25 结束夏令时，2点时会拔回1点，产生概念上的重叠时间
%% 2020-10-25 01:00:00 --- 2020-10-25 01:59:59
%% 下面的接口调用，如果使用这段时间，实际上无法预估会返回两个
%% 重叠时间中的哪个unix时间戳（系统底层猜测你需要哪个）。
%% 实测是两种都有可能，具体查看erlang:localtime_to_universaltime/1文档
%%
%% 由于夏令时调整都处于深夜，也无特别游戏功能触发，应可不理会。
%% 如果功能中有涉及此类用法，需要留意这一点。必要时增加一个非夏令时的接口。
unixtime(LocalTime = {{_Y,_M,_D},{_H,_I,_S}}) ->
    UniversalTime = erlang:localtime_to_universaltime(LocalTime),
    Unixtime = erlang:universaltime_to_posixtime(UniversalTime),
    Unixtime.

%% ---------------------------------------------------------------------------
%% @doc unix time转换成local time
-spec unixtime_to_localtime(UnixTime) -> LocalTime when
    UnixTime    :: integer(),
    LocalTime   :: {
        {Y :: integer(), Mon :: integer(), D :: integer()}
        ,{H :: integer(), M :: integer(), S :: integer()}
    }.
%% ---------------------------------------------------------------------------
unixtime_to_localtime(UnixTime) ->
    UniversalTime = erlang:posixtime_to_universaltime(UnixTime),
    erlang:universaltime_to_localtime(UniversalTime).

% 同 unixtime(ms)
% longunixtime() ->
%     {M, S, Ms} = mod_time:now(),
%     (M * 1000000000000 + S*1000000 + Ms) div 1000.
longunixtime() ->
    erlang:system_time(milli_seconds).

%% 取得当天零点的unix时间戳
% unixdate() ->
%     Now = mod_time:now(),
%     {_, Time} = calendar:now_to_local_time(Now),
%     Ds = calendar:time_to_seconds(Time),
%     {M, S, _} = Now,
%     M * 1000000 + S - Ds.
unixdate() ->
    Unixtime = erlang:system_time(seconds),
    unixtime({today, Unixtime}).

unixdate(Unixtime) ->
    unixtime({today, Unixtime}).

%% 获取明日零点的unix时间戳
tomorrow() ->
    Unixtime = erlang:system_time(seconds),
    unixtime({tomorrow, Unixtime}).

tomorrow(Unixtime) ->
    unixtime({tomorrow, Unixtime}).

%% 获取昨日零点的unix时间戳
yesterday() ->
    Unixtime = erlang:system_time(seconds),
    unixtime({yesterday, Unixtime}).

yesterday(Unixtime) ->
    unixtime({yesterday, Unixtime}).

%% 获取N天之前零点的unix时间戳
that_day_before(N) ->
    Date = erlang:date(),
    ThatDate = get_before_day(Date, N, 0),
    unixtime({ThatDate, {0 ,0, 0}}).

that_day_before(Unixtime, N) ->
    {Date, _Time} = unixtime_to_localtime(Unixtime),
    ThatDate = get_before_day(Date, N, 0),
    unixtime({ThatDate, {0 ,0, 0}}).

that_day_after(N) ->
    Date = erlang:date(),
    ThatDate = get_before_day(Date, N, 1),
    unixtime({ThatDate, {0, 0, 0}}).

that_day_after(Unixtime, N) ->
    {Date, _Time} = unixtime_to_localtime(Unixtime),
    ThatDate = get_before_day(Date, N, 1),
    unixtime({ThatDate, {0 ,0, 0}}).

%% [时间戳::unixtime()] 转换为[erlang:timestamp()]
unixtime_to_now(Time) ->
    M = Time div 1000000,
    S = Time rem 1000000,
    {M, S, 0}.

now_to_unixtime({MegaSecs, Secs, _MicroSecs}) ->
    MegaSecs * 1000000 + Secs.

%% 微秒转换成秒
microsec_to_sec(Timestamp) -> Timestamp div 1000000.

%% [时间戳::unixtime()] 转换为[GMT时间::datetime()]
unixtime_to_gmttime(Unixtime) ->
    Now = unixtime_to_now(Unixtime),
    calendar:now_to_universal_time(Now).

%% 获取本地时间戳 LocalTime = {{Y,M,D},{H,M,S}}
localtime() ->
    % Now = mod_time:now(),
    % calendar:now_to_local_time(Now).
    unixtime_to_localtime(erlang:system_time(seconds)).

%% ---------------------------------------------------------------------------
%% @doc 获取精准到分钟的时间戳(向下取证)
-spec get_min_unixtime() -> Unixtime when
    Unixtime      :: integer().
%% ---------------------------------------------------------------------------
get_min_unixtime() ->
    Unixtime = unixtime(),
    {{_Y,_M,_D},{_H,_Min,S}} = unixtime_to_localtime(Unixtime),
    Unixtime - S.

%% Unix时间戳转换为本地时区的时间字符串格式
%% 注意：这个接口产生的数据，存在与其他外部系统交互的时间字符串,
%% 所以月、日、时、分、秒必须是两位数字，不足需要补0，否则不能被识别为时间字符串
unixtime_to_timestr(Unixtime) ->
    {{Y, Mo, D}, {H, Mi, S}} = unixtime_to_localtime(Unixtime),
    % iolist_to_binary(lists:concat([Y, "-", Mo, "-", D, " ", H, ":", Mi, ":", S])).
    iolist_to_binary(
        io_lib:format(
            "~4..0w-~2..0w-~2..0w ~2..0w:~2..0w:~2..0w",
            [Y, Mo, D, H, Mi, S]
        )).

%% 获得本月的时间范围 -> {BeginUnixTime, EndUnixTime}
get_month_unixtime_range() ->
    NowTime = utime:unixdate(),
    get_month_unixtime_range(NowTime).
get_month_unixtime_range(Unixtime) ->
    {{Y, M, _D}, _} = unixtime_to_localtime(Unixtime),
    DayNum = calendar:last_day_of_the_month(Y, M),
    StartDate = {{Y, M, 1}, {0, 0, 0}},
    StartTime = unixtime(StartDate),
    EndDate = {{Y, M, DayNum}, {23, 59, 59}},
    EndTime = unixtime(EndDate),
    {StartTime, EndTime}.

%% 获取月份
get_month() ->
    Unixtime = unixtime(),
    {{_, M, _}, _} = unixtime_to_localtime(Unixtime),
    M.
get_month(Unixtime) ->
    {{_, M, _}, _} = unixtime_to_localtime(Unixtime),
    M.

is_same_month(Unixtime1, Unixtime2) ->
    {{Y1, M1, _}, _} = unixtime_to_localtime(Unixtime1),
    {{Y2, M2, _}, _} = unixtime_to_localtime(Unixtime2),
    M1 == M2 andalso Y1 == Y2.

%% ---------------------------------------------------------------------------
%% @doc 计算今天的日期 返回一个YYYYMMDD时间样板数值(如20140708)
-spec get_date() -> Date when
    Date      :: integer().
%% ---------------------------------------------------------------------------
get_date() ->
    {Y, M, D} = erlang:date(),
    Y * 10000 + M * 100 + D.

%% 今天是星期几
day_of_week() ->
    UnixTime = unixtime(),
    day_of_week(UnixTime).
day_of_week(UnixTime) ->
    {{Year, Month, Day}, _Time} = unixtime_to_localtime(UnixTime),
    calendar:day_of_the_week(Year, Month, Day).

%% 今天是当月第几天
day_of_month() ->
    UnixTime = unixtime(),
    day_of_month(UnixTime).
day_of_month(UnixTime) ->
    {{_Year, _Month, Day}, _Time} = unixtime_to_localtime(UnixTime),
    Day.

%% 日期跨度（指定Unix时间戳与当前时间）
%% （计算天数时，同时计开始日、结束日，等于两个日期相差天数+1）
day_span(Unixtime) ->
   diff_day(Unixtime) + 1.

%% 日期跨度（两个指定Unix时间戳）
day_span(Unixtime1, Unixtime2) ->
    diff_day(Unixtime1, Unixtime2) + 1.

%% 相差的天数（指定Unix时间戳与当前时间）
diff_day(Unixtime) ->
    NowTime = unixtime(),
    diff_day(Unixtime, NowTime).

%% 相差的天数（两个指定Unix时间戳）
diff_day(Unixtime1, Unixtime2) ->
    {Date1, _} = utime:unixtime_to_localtime(Unixtime1),
    {Date2, _} = utime:unixtime_to_localtime(Unixtime2),
    abs(calendar:date_to_gregorian_days(Date1) - calendar:date_to_gregorian_days(Date2)).

%% @spec is_today(Time) -> boolean()
%% @doc 判断是否为今天时间
is_today(Time) ->
    Now = utime:unixtime(),
    is_same_day(Time, Now).

%%是否今天 四点
is_today_four(Time) when Time=<0->
    false;
is_today_four(Time)->
    is_logic_same_day(Time).

%% -----------------------------------------------------------------
%% 判断是否同一天
%% -----------------------------------------------------------------
is_same_day(Seconds1, Seconds2) ->
    diff_day(Seconds1, Seconds2) =:= 0.

%% seconds_to_localtime(UnixTime) ->
%%    UniversalTime = erlang:posixtime_to_universaltime(UnixTime),
%%    erlang:universaltime_to_localtime(UniversalTime).

%% -----------------------------------------------------------------
%% 判断是否同一星期
%% -----------------------------------------------------------------
is_same_week(Seconds1, Seconds2) ->
    Monday = unixtime({week, Seconds1}),
    Sunday = unixtime({next_week, Seconds1}),
    if ((Seconds2 >= Monday) and (Seconds2 < Sunday)) -> true;
        true -> false
    end.

%% ---------------------------------------------------------------------------
%% @doc 判断是否同一天。同一天返回true，否则返回false
-spec is_logic_same_day(Unixtime1, Unixtime2, ZeroHour) -> true | false when
    Unixtime1    :: integer(),
    Unixtime2    :: integer(),
    ZeroHour     :: integer().
%% ---------------------------------------------------------------------------
is_logic_same_day(UnixTime) ->
    NowTime = utime:unixtime(),
    is_logic_same_day(NowTime, UnixTime, ?DAY_LOGIC_OFFSET_HOUR).

is_logic_same_day(UnixTime1, Unixtime2) ->
    is_logic_same_day(UnixTime1, Unixtime2, ?DAY_LOGIC_OFFSET_HOUR).

is_logic_same_day(Unixtime1, Unixtime2, ZeroHour) ->
    LogicMidnight = get_logic_midnight(Unixtime1, ZeroHour),
    LogicMidnight2 = get_logic_midnight(Unixtime2, ZeroHour),
    %% 基于夏令时间的原因，不能加减一天?ONE_DAY_SECONDS来判断
    LogicMidnight =:= LogicMidnight2.

%% 以ZeroHour点为零点，nowtime为当天的时间戳
get_logic_midnight(NowTime, ZeroHour) ->
    {Date, _Time} = unixtime_to_localtime(NowTime),
    MidnightLocaltime = {Date, {ZeroHour, 0, 0}},
    LogicMidnight = unixtime(MidnightLocaltime),
    case NowTime >= LogicMidnight of
        true ->
            LogicMidnight;
        false ->
            YesterdayDate = get_before_day(Date, 1, 0),
            unixtime({YesterdayDate, {ZeroHour, 0, 0}})
    end.

get_next_logic_midnight(NowTime, ZeroHour) ->
    {Date, _Time} = unixtime_to_localtime(NowTime),
    MidnightLocaltime = {Date, {ZeroHour, 0, 0}},
    LogicMidnight = unixtime(MidnightLocaltime),
    case NowTime >= LogicMidnight of
        true  ->
            TomorrowDate = get_before_day(Date, 1, 1),
            unixtime({TomorrowDate, {ZeroHour, 0, 0}});
        false ->
            LogicMidnight
    end.

%% 获得逻辑天时间信息
%% @return {逻辑天数开始, 逻辑天数中间的零点, 逻辑天数结束}
get_logic_day_time_info() ->
    NowTime = unixtime(),
    get_logic_day_time_info(NowTime).

%% 计算4点偏差
%% 基于夏令时间的原因，不能加减一天?ONE_DAY_SECONDS来计算
get_logic_day_time_info(Timestamp) ->
    {Date, _Time} = unixtime_to_localtime(Timestamp),
    ZeroHour = ?DAY_LOGIC_OFFSET_HOUR,
    MidnightLocaltime = {Date, {ZeroHour, 0, 0}},
    %% 今天4点
    LogicMidnight = unixtime(MidnightLocaltime),
    %% 昨天4点
    YesterdayDate = get_before_day(Date, 1, 0),
    YesterdayLogicMidnight = unixtime({YesterdayDate, {ZeroHour, 0, 0}}),
    %% 明天4点
    TomorrowDate = get_before_day(Date, 1, 1),
    TomorrowLogicMidnight = unixtime({TomorrowDate, {ZeroHour, 0, 0}}),
    %% 现在的时间大于4点
    case Timestamp >= LogicMidnight of
        true ->
            TomorrowZeroTime = unixdate(TomorrowLogicMidnight),
            {LogicMidnight, TomorrowZeroTime, TomorrowLogicMidnight - 1};
        false ->
            ZeroTime = unixdate(Timestamp),
            {YesterdayLogicMidnight, ZeroTime, LogicMidnight - 1}
    end.

%% 获得逻辑天数的开始时间
%% retrun 逻辑天数开始时间
get_logic_day_start_time() ->
    get_logic_day_start_time(unixtime()).

get_logic_day_start_time(Timestamp) ->
    {FourClock, _ZeroTime, _NextFourClock} = get_logic_day_time_info(Timestamp),
    FourClock.

%% 逻辑上的当周开始时间戳
logic_week_start() ->
    Now = unixtime(),
    logic_week_start(Now).

%% 逻辑上的当周开始时间戳
logic_week_start(Unixtime) ->
    {Date, _Time} = unixtime_to_localtime(Unixtime),
    DayOfWeek = calendar:day_of_the_week(Date),
    ZeroHour = ?DAY_LOGIC_OFFSET_HOUR,
    MondayDate = get_before_day(Date, (DayOfWeek - 1), 0),
    MondayLocaltime = {MondayDate, {ZeroHour, 0, 0}},
    %% 指定时间戳对应的周一逻辑0点
    WeekStart = unixtime(MondayLocaltime),
    case Unixtime >= WeekStart of
        true ->
            WeekStart;
        false ->
            %% 指定时间戳上周周一逻辑0点
            PreMondayDate = get_before_day(Date, (7 + (DayOfWeek - 1)), 0),
            PreMondayLocaltime = {PreMondayDate, {ZeroHour, 0, 0}},
            PreWeekStart = unixtime(PreMondayLocaltime),
            PreWeekStart
    end.

%% 逻辑上的下周开始时间时间
next_logic_week_start() ->
    NowTime = unixtime(),
    next_logic_week_start(NowTime).

%% 逻辑上的下周开始时间时间
next_logic_week_start(Unixtime) ->
    {Date, _Time} = unixtime_to_localtime(Unixtime),
    DayOfWeek = calendar:day_of_the_week(Date),
    ZeroHour = ?DAY_LOGIC_OFFSET_HOUR,
    MondayDate = get_before_day(Date, (DayOfWeek - 1), 0),
    MondayLocaltime = {MondayDate, {ZeroHour, 0, 0}},
    %% 指定时间戳对应的周一逻辑0点
    WeekStart = unixtime(MondayLocaltime),
    case Unixtime >= WeekStart of
        true  ->
            NextMondayDate = get_before_day(Date, 7 - DayOfWeek + 1, 1),
            NextMondayLocaltime = {NextMondayDate, {ZeroHour, 0, 0}},
            %% 指定时间戳对应的下周一逻辑0点
            unixtime(NextMondayLocaltime);
        false ->
            WeekStart
    end.

%% 返回本地逻辑时间：  {{年，月，日}，{时，分，秒}}
get_logic_time() ->
    {{NowYear,NowMon,NowDay}, {NowHour, NowMin,NowSec}} = calendar:local_time(),
    get_logic_time({{NowYear,NowMon,NowDay}, {NowHour, NowMin,NowSec}}).

get_logic_time({{NowYear,NowMon,NowDay}, {NowHour, NowMin,NowSec}}) ->
    case NowHour < ?LOGIC_NEW_DAY of
        true ->
            case NowDay-1 =< 0 of
                true ->
                    case NowMon-1=< 0 of
                        true ->
                            Year=NowYear-1,
                            Month = 12,
                            Day = 31;
                        _ ->
                            Year=NowYear,
                            Month=NowMon-1,
                            Day=calendar:last_day_of_the_month(Year,Month)
                    end;
                _ ->
                    Year = NowYear,
                    Month = NowMon,
                    Day = NowDay-1
            end,
            {{Year, Month, Day}, {NowHour, NowMin,NowSec}};
        _ ->
            {{NowYear,NowMon,NowDay}, {NowHour, NowMin,NowSec}}
    end.

%% 获得逻辑星期
logic_day_of_week() ->
    day_of_week(get_logic_day_start_time()).
logic_day_of_week(Timestamp) ->
    day_of_week(Timestamp).

%% X年X月X日的前后Dif天是几月几号  Type 0为前，1为后
get_before_day({Year, Month, Day}, Dif, Type) when Dif =< 28 ->
    case Type==0 of
        true ->
            case Day-Dif> 0 of
                true ->
                    {Year, Month, Day-Dif};
                false ->
                    case Month == 1 of
                        true ->
                            {Year-1, 12, 31+Day-Dif};
                        false ->
                            Temp = calendar:last_day_of_the_month(Year, Month-1),
                            {Year, Month-1, Temp+Day-Dif}
                    end
            end;
        false ->
            Now = calendar:last_day_of_the_month(Year, Month),
            case Day+Dif=<Now of
                true ->
                    {Year, Month, Day+Dif};
                false ->
                    case Month == 12 of
                        true ->
                            {Year+1, 1, Day+Dif-31};
                        false ->
                            {Year, Month+1, Day+Dif-Now}
                    end
            end
    end;
get_before_day({Year, Month, Day}, Dif, Type) ->
    DateTmp = get_before_day({Year, Month, Day}, 28, Type),
    get_before_day(DateTmp, Dif - 28, Type).

%% -----------------------------------------------------------------
%% 获取当天0点到现在的秒数:: 注意夏令时影响
%% 以欧洲某些地区为例，
%%
%% 2020-03-29 进入夏令时，1点时会拔到2点
%% 10的时候调用该方法，get_seconds_from_midnight() = 3600*9
%% -----------------------------------------------------------------
get_seconds_from_midnight() ->
    NowTime = unixtime(),
    NowTime - unixdate(NowTime).

get_seconds_from_midnight(Seconds) ->
    Seconds - unixdate(Seconds).

%% -----------------------------------------------------------------
%% 判断是否同一天
%% -----------------------------------------------------------------
is_same_date(UnixTime1, UnixTime2) ->
    utime:diff_day(max(0, UnixTime1), max(0, UnixTime2)) == 0.

%% 时间转时分秒
time_to_hms(Time) ->
    H = Time div 3600,
    M = Time rem 3600 div 60,
    S = Time rem 3600 rem 60,
    {H, M, S}.

get_opened_month() ->
    {{NowYear, Month, _}, _} = localtime(),
    {{OpenYear, OpenMonth, _},_} = unixtime_to_localtime(util:get_open_time()),
    if
        OpenYear == NowYear ->
            Month - OpenMonth + 1;
        true ->
            max(OpenYear - NowYear, 1) * 12 + (Month - OpenMonth)
    end.


%%--------------------------------------------------
%% 是否是连续的周日期
%% @param  WeekL [周几]
%% @return       true|false
%%--------------------------------------------------
% is_continuous_week([]) -> false;
% is_continuous_week(WeekL) ->
%     List = [1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6],
%     string:str(List, WeekL) > 0.
% %     is_continuous_week_helper(WeekL, length(WeekL), List).
% % 
% % is_continuous_week_helper(_WeekL, _Len, []) -> false;
% % is_continuous_week_helper(WeekL, Len, [_H|L] = List) ->
% %     case lists:sublist(List, Len) =:= WeekL of
% %         false ->
% %             is_continuous_week_helper(WeekL, Len, L);
% %         _ -> true
% %     end.

is_continuous_week([DayOfWeek|_] = DayOfWeekList) when DayOfWeek > 0 andalso DayOfWeek =< 7 ->
    check_continuous(DayOfWeek, DayOfWeekList);
is_continuous_week(_) -> false.

check_continuous(DayOfWeek, [DayOfWeek | List]) ->
    NextDayOfWeek =
    case DayOfWeek =:= 7 of
        true  -> 1;
        false -> DayOfWeek + 1
    end,
    check_continuous(NextDayOfWeek, List);
check_continuous(_DayOfWeek, [_|_]) ->
    false;
check_continuous(_, []) ->
    true.

%% ---------------------------------------------------------------------------
%% @doc 时间字符串转化为时间元组使用的正则匹配mp
-spec time_string_re_mp() -> Return when
    Return      :: {re_pattern, term(), term(), term(), term()}.
%% ---------------------------------------------------------------------------
time_string_re_mp() ->
    {ok, MP} = re:compile(<<"[ ]*([\\d\\\-]+)[ ]+([\\d:]+)[ ]*">>, [unicode, caseless]),
    MP.

%% ---------------------------------------------------------------------------
%% @doc 时间字符串转化为时间元组.
%% <<"2019-05-31 00:00:00">> => {{2019,5,31},{0,0,0}}
-spec time_string_to_localtime(TimeString) -> TimeTuple | error when
    TimeString  :: binary(),
    TimeTuple   :: tuple().
%% ---------------------------------------------------------------------------
time_string_to_localtime(<<"">>) ->
    error;
time_string_to_localtime(TimeString) ->
    MP = time_string_re_mp(),
    time_string_to_localtime(TimeString, MP).

time_string_to_localtime(TimeString, MP) ->
    case re:run(TimeString, MP, [global, {capture, all, binary}]) of
        {match, MatchMpList} ->
            try
                case convert_time_info(MatchMpList) of
                    TimeTuple when is_tuple(TimeTuple) -> TimeTuple;
                    _ -> error
                end
                catch _:_ -> error
            end;
        _ ->
            error
    end.

%% 抽取指定时间YYYY-mm-dd HH:MM:DD的时间信息数据
convert_time_info([[_Binary, Match1, Match2] | _Others]) ->
    DateStart   = convert_binary_date(Match1),
    DayTimeStart= convert_binary_day_time(Match2),
    case lists:member(error, [DateStart, DayTimeStart]) of
        true -> error;
        false -> {DateStart, DayTimeStart}
    end;
convert_time_info([]) ->
    error.

convert_binary_day_time(DayTimeString) ->
    case binary:split(DayTimeString, <<":">>, [global, trim]) of
        [Hour, Min, Sec] ->
            {binary_to_integer(Hour), binary_to_integer(Min), binary_to_integer(Sec)};
        [Hour, Min] ->
            {binary_to_integer(Hour), binary_to_integer(Min), 0};
        _ ->
            error
    end.

convert_binary_date(DateString) ->
    case binary:split(DateString, <<"-">>, [global, trim]) of
        [Year, Mon, Day] ->
            {binary_to_integer(Year), binary_to_integer(Mon), binary_to_integer(Day)};
        _ ->
            error
    end.

