%%
%% %CopyrightBegin%
%% 
  Copyright Ericsson AB 1996 - 2020 . All Rights Reserved .
%% 
  Licensed under the Apache License , Version 2.0 ( the " License " ) ;
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     -2.0
%%
%% Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an " AS IS " BASIS ,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%% 
%% %CopyrightEnd%
%%
-module(calendar).

%% local and universal time, time conversions

-export([date_to_gregorian_days/1, 
	 date_to_gregorian_days/3,
	 datetime_to_gregorian_seconds/1,
	 day_of_the_week/1,
	 day_of_the_week/3,
	 gregorian_days_to_date/1,
	 gregorian_seconds_to_datetime/1,
	 is_leap_year/1,
	 iso_week_number/0,
	 iso_week_number/1,
	 last_day_of_the_month/2,
	 local_time/0, 
	 local_time_to_universal_time/1, 
	 local_time_to_universal_time/2, 
	 local_time_to_universal_time_dst/1, 
	 now_to_datetime/1,			% = now_to_universal_time/1
	 now_to_local_time/1,
	 now_to_universal_time/1,
         rfc3339_to_system_time/1,
         rfc3339_to_system_time/2,
	 seconds_to_daystime/1,
	 seconds_to_time/1,
         system_time_to_local_time/2,
         system_time_to_universal_time/2,
         system_time_to_rfc3339/1,
         system_time_to_rfc3339/2,
	 time_difference/2,
	 time_to_seconds/1,
	 universal_time/0,
	 universal_time_to_local_time/1,
	 valid_date/1,
	 valid_date/3]).

-deprecated([{local_time_to_universal_time,1,
              "use calendar:local_time_to_universal_time_dst/1 instead"}]).

-define(SECONDS_PER_MINUTE, 60).
-define(SECONDS_PER_HOUR, 3600).
-define(SECONDS_PER_DAY, 86400).
-define(DAYS_PER_YEAR, 365).
-define(DAYS_PER_LEAP_YEAR, 366).
  -define(DAYS_PER_4YEARS , 1461 ) .
  -define(DAYS_PER_100YEARS , 36524 ) .
%% -define(DAYS_PER_400YEARS, 146097).
-define(DAYS_FROM_0_TO_1970, 719528).
-define(DAYS_FROM_0_TO_10000, 2932897).
-define(SECONDS_FROM_0_TO_1970, (?DAYS_FROM_0_TO_1970*?SECONDS_PER_DAY)).
-define(SECONDS_FROM_0_TO_10000, (?DAYS_FROM_0_TO_10000*?SECONDS_PER_DAY)).

%%----------------------------------------------------------------------
%% Types
%%----------------------------------------------------------------------

-export_type([date/0, time/0, datetime/0, datetime1970/0]).

-type year()     :: non_neg_integer().
  should probably be 1970 ..
-type month()    :: 1..12.
-type day()      :: 1..31.
-type hour()     :: 0..23.
-type minute()   :: 0..59.
-type second()   :: 0..59.
-type daynum()   :: 1..7.
  last day of month
-type weeknum()  :: 1..53.

-type date()           :: {year(),month(),day()}.
-type time()           :: {hour(),minute(),second()}.
-type datetime()       :: {date(),time()}.
-type datetime1970()   :: {{year1970(),month(),day()},time()}.
-type yearweeknum()    :: {year(),weeknum()}.

-type rfc3339_string() :: [byte(), ...].
%% By design 'native' is not supported:
-type rfc3339_time_unit() :: 'microsecond'
                           | 'millisecond'
                           | 'nanosecond'
                           | 'second'.

%%----------------------------------------------------------------------

%% All dates are according the the Gregorian calendar. In this module
%% the Gregorian calendar is extended back to year 0 for convenience.
%%
  A year Y is a leap year if and only if either
%%
         ( 1 )     Y is divisible by 4 , but not by 100 , or
         ( 2 )     Y is divisible by 400 .
%%
  Hence , e.g. 1996 is a leap year , 1900 is not , but 2000 is .
%%

%%
%% EXPORTS
%%

  date_to_gregorian_days(Year , Month , Day ) = Integer
  date_to_gregorian_days({Year , Month , Day } ) = Integer
%%
  Computes the total number of days starting from year 0 ,
  January 1st .
%%
  df/2 catches the case Year<0
-spec date_to_gregorian_days(Year, Month, Day) -> Days when
      Year :: year(),
      Month :: month(),
      Day :: day(),
      Days :: non_neg_integer().
date_to_gregorian_days(Year, Month, Day) when is_integer(Day), Day > 0 ->
    Last = last_day_of_the_month(Year, Month),
    if
	Day =< Last ->
	    dy(Year) + dm(Month) + df(Year, Month) + Day - 1
    end.

-spec date_to_gregorian_days(Date) -> Days when
      Date :: date(),
      Days :: non_neg_integer().
date_to_gregorian_days({Year, Month, Day}) ->
    date_to_gregorian_days(Year, Month, Day).


%% datetime_to_gregorian_seconds(DateTime) = Integer
%%
  Computes the total number of seconds starting from year 0 ,
  January 1st .
%%
-spec datetime_to_gregorian_seconds(DateTime) -> Seconds when
      DateTime :: datetime(),
      Seconds :: non_neg_integer().
datetime_to_gregorian_seconds({Date, Time}) ->
    ?SECONDS_PER_DAY*date_to_gregorian_days(Date) +
	time_to_seconds(Time).


  day_of_the_week(Year , Month , Day )
  day_of_the_week({Year , Month , Day } )
%%
  Returns : 1 | .. | 7 . Monday = 1 , Tuesday = 2 , ... , Sunday = 7 .
%%
-spec day_of_the_week(Year, Month, Day) -> daynum() when
      Year :: year(),
      Month :: month(),
      Day :: day().
day_of_the_week(Year, Month, Day) ->
    (date_to_gregorian_days(Year, Month, Day) + 5) rem 7 + 1.

-spec day_of_the_week(Date) -> daynum() when
      Date:: date().
day_of_the_week({Year, Month, Day}) ->
    day_of_the_week(Year, Month, Day).


  gregorian_days_to_date(Days ) = { Year , Month , Day }
%%
-spec gregorian_days_to_date(Days) -> date() when
      Days :: non_neg_integer().
gregorian_days_to_date(Days) ->
    {Year, DayOfYear} = day_to_year(Days),
    {Month, DayOfMonth} = year_day_to_date(Year, DayOfYear),
    {Year, Month, DayOfMonth}.


  gregorian_seconds_to_datetime(Secs )
%%
-spec gregorian_seconds_to_datetime(Seconds) -> datetime() when
      Seconds :: non_neg_integer().
gregorian_seconds_to_datetime(Secs) when Secs >= 0 ->
    Days = Secs div ?SECONDS_PER_DAY,
    Rest = Secs rem ?SECONDS_PER_DAY,
    {gregorian_days_to_date(Days), seconds_to_time(Rest)}.


%% is_leap_year(Year) = true | false
%%
-spec is_leap_year(Year) -> boolean() when
      Year :: year().
is_leap_year(Y) when is_integer(Y), Y >= 0 ->
    is_leap_year1(Y).

-spec is_leap_year1(year()) -> boolean().
is_leap_year1(Year) when Year rem 4 =:= 0, Year rem 100 > 0 ->
    true;
is_leap_year1(Year) when Year rem 400 =:= 0 ->
    true;
is_leap_year1(_) -> false.


%%
  Calculates the iso week number for the current date .
%%
-spec iso_week_number() -> yearweeknum().
iso_week_number() ->
    {Date, _} = local_time(),
    iso_week_number(Date).


%%
  Calculates the iso week number for the given date .
%%
-spec iso_week_number(Date) -> yearweeknum() when
      Date :: date().
iso_week_number({Year, Month, Day}) ->
    D = date_to_gregorian_days({Year, Month, Day}),
    W01_1_Year = gregorian_days_of_iso_w01_1(Year),
    W01_1_NextYear = gregorian_days_of_iso_w01_1(Year + 1),
    if W01_1_Year =< D andalso D < W01_1_NextYear ->
  Current Year Week 01 .. 52(,53 )
	    {Year, (D - W01_1_Year) div 7 + 1};
	D < W01_1_Year ->
  Previous Year 52 or 53
	    PWN = case day_of_the_week(Year - 1, 1, 1) of
		4 -> 53;
		_ -> case day_of_the_week(Year - 1, 12, 31) of
			4 -> 53;
			_ -> 52
		     end
		end,
	    {Year - 1, PWN};
	W01_1_NextYear =< D ->
  Next Year , Week 01
	    {Year + 1, 1}
    end.


%% last_day_of_the_month(Year, Month)
%%
  Returns the number of days in a month .
%%
-spec last_day_of_the_month(Year, Month) -> LastDay when
      Year :: year(),
      Month :: month(),
      LastDay :: ldom().
last_day_of_the_month(Y, M) when is_integer(Y), Y >= 0 ->
    last_day_of_the_month1(Y, M).

-spec last_day_of_the_month1(year(),month()) -> ldom().
last_day_of_the_month1(_, 4) -> 30;
last_day_of_the_month1(_, 6) -> 30;
last_day_of_the_month1(_, 9) -> 30;
last_day_of_the_month1(_,11) -> 30;
last_day_of_the_month1(Y, 2) ->
   case is_leap_year(Y) of
      true -> 29;
      _    -> 28
   end;
last_day_of_the_month1(_, M) when is_integer(M), M > 0, M < 13 ->
    31.


%% local_time()
%%
%% Returns: {date(), time()}, date() = {Y, M, D}, time() = {H, M, S}.
-spec local_time() -> datetime().
local_time() ->
    erlang:localtime().


%% local_time_to_universal_time(DateTime)
%%
-spec local_time_to_universal_time(DateTime1) -> DateTime2 when
      DateTime1 :: datetime1970(),
      DateTime2 :: datetime1970().
local_time_to_universal_time(DateTime) ->
    erlang:localtime_to_universaltime(DateTime).

-spec local_time_to_universal_time(datetime1970(),
				   'true' | 'false' | 'undefined') ->
                                          datetime1970().
local_time_to_universal_time(DateTime, IsDst) ->
    erlang:localtime_to_universaltime(DateTime, IsDst).

-spec local_time_to_universal_time_dst(DateTime1) -> [DateTime] when
      DateTime1 :: datetime1970(),
      DateTime :: datetime1970().
local_time_to_universal_time_dst(DateTime) ->
    UtDst = erlang:localtime_to_universaltime(DateTime, true),
    Ut    = erlang:localtime_to_universaltime(DateTime, false),
    %% Reverse check the universal times
    LtDst = erlang:universaltime_to_localtime(UtDst),
    Lt    = erlang:universaltime_to_localtime(Ut),
    %% Return the valid universal times
    case {LtDst,Lt} of
	{DateTime,DateTime} when UtDst =/= Ut ->
	    [UtDst,Ut];
	{DateTime,_} ->
	    [UtDst];
	{_,DateTime} ->
	    [Ut];
	{_,_} ->
	    []
    end.

%% now_to_universal_time(Now)
%% now_to_datetime(Now)
%%
  Convert from erlang : timestamp ( ) to UTC .
%%
   : Now = now ( ) ; now ( ) = { MegaSec , Sec , MilliSec } , MegaSec = Sec
  = MilliSec = integer ( )
%% Returns: {date(), time()}, date() = {Y, M, D}, time() = {H, M, S}.
%% 
-spec now_to_datetime(Now) -> datetime1970() when
      Now :: erlang:timestamp().
now_to_datetime({MSec, Sec, _uSec}) ->
    system_time_to_datetime(MSec*1000000 + Sec).

-spec now_to_universal_time(Now) -> datetime1970() when
      Now :: erlang:timestamp().
now_to_universal_time(Now) ->
    now_to_datetime(Now).


%% now_to_local_time(Now)
%%
   : Now = now ( )
%%
-spec now_to_local_time(Now) -> datetime1970() when
      Now :: erlang:timestamp().
now_to_local_time({MSec, Sec, _uSec}) ->
    erlang:universaltime_to_localtime(
      now_to_universal_time({MSec, Sec, _uSec})).

-spec rfc3339_to_system_time(DateTimeString) -> integer() when
      DateTimeString :: rfc3339_string().

rfc3339_to_system_time(DateTimeString) ->
    rfc3339_to_system_time(DateTimeString, []).

-spec rfc3339_to_system_time(DateTimeString, Options) -> integer() when
      DateTimeString :: rfc3339_string(),
      Options :: [Option],
      Option :: {'unit', rfc3339_time_unit()}.

rfc3339_to_system_time(DateTimeString, Options) ->
    Unit = proplists:get_value(unit, Options, second),
    %% _T is the character separating the date and the time:
    [Y1, Y2, Y3, Y4, $-, Mon1, Mon2, $-, D1, D2, _T,
     H1, H2, $:, Min1, Min2, $:, S1, S2 | TimeStr] = DateTimeString,
    Hour = list_to_integer([H1, H2]),
    Min = list_to_integer([Min1, Min2]),
    Sec = list_to_integer([S1, S2]),
    Year = list_to_integer([Y1, Y2, Y3, Y4]),
    Month = list_to_integer([Mon1, Mon2]),
    Day = list_to_integer([D1, D2]),
    DateTime = {{Year, Month, Day}, {Hour, Min, Sec}},
    IsFractionChar = fun(C) -> C >= $0 andalso C =< $9 orelse C =:= $. end,
    {FractionStr, UtcOffset} = lists:splitwith(IsFractionChar, TimeStr),
    Time = datetime_to_system_time(DateTime),
    Secs = Time - offset_string_adjustment(Time, second, UtcOffset),
    check(DateTimeString, Options, Secs),
    ScaledEpoch = erlang:convert_time_unit(Secs, second, Unit),
    ScaledEpoch + copy_sign(fraction(Unit, FractionStr), ScaledEpoch).



  seconds_to_daystime(Secs ) = { Days , { Hour , Minute , Second } }
%%
-spec seconds_to_daystime(Seconds) -> {Days, Time} when
      Seconds :: integer(),
      Days :: integer(),
      Time :: time().
seconds_to_daystime(Secs) ->
    Days0 = Secs div ?SECONDS_PER_DAY,
    Secs0 = Secs rem ?SECONDS_PER_DAY,
    if 
	Secs0 < 0 ->
	    {Days0 - 1, seconds_to_time(Secs0 + ?SECONDS_PER_DAY)};
	true ->
	    {Days0, seconds_to_time(Secs0)}
    end.


%%
  seconds_to_time(Secs )
%%
%% Wraps.
%%
-type secs_per_day() :: 0..?SECONDS_PER_DAY.
-spec seconds_to_time(Seconds) -> time() when
      Seconds :: secs_per_day().
seconds_to_time(Secs) when Secs >= 0, Secs < ?SECONDS_PER_DAY ->
    Secs0 = Secs rem ?SECONDS_PER_DAY,
    Hour = Secs0 div ?SECONDS_PER_HOUR,
    Secs1 = Secs0 rem ?SECONDS_PER_HOUR,
    Minute =  Secs1 div ?SECONDS_PER_MINUTE,
    Second =  Secs1 rem ?SECONDS_PER_MINUTE,
    {Hour, Minute, Second}.

-spec system_time_to_local_time(Time, TimeUnit) -> datetime() when
      Time :: integer(),
      TimeUnit :: erlang:time_unit().

system_time_to_local_time(Time, TimeUnit) ->
    UniversalDate = system_time_to_universal_time(Time, TimeUnit),
    erlang:universaltime_to_localtime(UniversalDate).

-spec system_time_to_universal_time(Time, TimeUnit) -> datetime() when
      Time :: integer(),
      TimeUnit :: erlang:time_unit().

system_time_to_universal_time(Time, TimeUnit) ->
    Secs = erlang:convert_time_unit(Time, TimeUnit, second),
    system_time_to_datetime(Secs).

-spec system_time_to_rfc3339(Time) -> DateTimeString when
      Time :: integer(),
      DateTimeString :: rfc3339_string().

system_time_to_rfc3339(Time) ->
    system_time_to_rfc3339(Time, []).

-type offset() :: [byte()] | (Time :: integer()).
-spec system_time_to_rfc3339(Time, Options) -> DateTimeString when
  Since Epoch
      Options :: [Option],
      Option :: {'offset', offset()}
              | {'time_designator', byte()}
              | {'unit', rfc3339_time_unit()},
      DateTimeString :: rfc3339_string().

system_time_to_rfc3339(Time, Options) ->
    Unit = proplists:get_value(unit, Options, second),
    OffsetOption = proplists:get_value(offset, Options, ""),
    T = proplists:get_value(time_designator, Options, $T),
    AdjustmentSecs = offset_adjustment(Time, Unit, OffsetOption),
    Offset = offset(OffsetOption, AdjustmentSecs),
    Adjustment = erlang:convert_time_unit(AdjustmentSecs, second, Unit),
    AdjustedTime = Time + Adjustment,
    Factor = factor(Unit),
    Secs = AdjustedTime div Factor,
    check(Time, Options, Secs),
    DateTime = system_time_to_datetime(Secs),
    {{Year, Month, Day}, {Hour, Min, Sec}} = DateTime,
    FractionStr = fraction_str(Factor, AdjustedTime),
    L = [pad4(Year), "-", pad2(Month), "-", pad2(Day), [T],
         pad2(Hour), ":", pad2(Min), ":", pad2(Sec), FractionStr, Offset],
    lists:append(L).

%% time_difference(T1, T2) = Tdiff
%%
%% Returns the difference between two {Date, Time} structures.
%%
  T1 = T2 = { Date , Time } ,  = { Day , { Hour ,  , Sec } } ,
  Date = { Year , Month , Day } , Time = { Hour , Minute , Sec } ,
  Year = Month = Day = Hour = Minute = Sec = integer ( )
%%
-spec time_difference(T1, T2) -> {Days, Time} when
      T1 :: datetime(),
      T2 :: datetime(),
      Days :: integer(),
      Time :: time().
time_difference({{Y1, Mo1, D1}, {H1, Mi1, S1}}, 
		{{Y2, Mo2, D2}, {H2, Mi2, S2}}) ->
    Secs = datetime_to_gregorian_seconds({{Y2, Mo2, D2}, {H2, Mi2, S2}}) -
	datetime_to_gregorian_seconds({{Y1, Mo1, D1}, {H1, Mi1, S1}}),
    seconds_to_daystime(Secs).


%%
%% time_to_seconds(Time)
%%
-spec time_to_seconds(Time) -> secs_per_day() when
      Time :: time().
time_to_seconds({H, M, S}) when is_integer(H), is_integer(M), is_integer(S) ->
    H * ?SECONDS_PER_HOUR +
	M * ?SECONDS_PER_MINUTE + S.
      

   ( )
%%
%% Returns: {date(), time()}, date() = {Y, M, D}, time() = {H, M, S}.
-spec universal_time() -> datetime().
universal_time() ->
    erlang:universaltime().
 

%% universal_time_to_local_time(DateTime)
%%
-spec universal_time_to_local_time(DateTime) -> datetime() when
      DateTime :: datetime1970().
universal_time_to_local_time(DateTime) ->
    erlang:universaltime_to_localtime(DateTime).


  valid_date(Year , Month , Day ) = true | false
  valid_date({Year , Month , Day } ) = true | false
%%
-spec valid_date(Year, Month, Day) -> boolean() when
      Year :: integer(),
      Month :: integer(),
      Day :: integer().
valid_date(Y, M, D) when is_integer(Y), is_integer(M), is_integer(D) ->
    valid_date1(Y, M, D).

-spec valid_date1(integer(), integer(), integer()) -> boolean().
valid_date1(Y, M, D) when Y >= 0, M > 0, M < 13, D > 0 ->
    D =< last_day_of_the_month(Y, M);
valid_date1(_, _, _) ->
    false.

-spec valid_date(Date) -> boolean() when
      Date :: date().
valid_date({Y, M, D}) ->
    valid_date(Y, M, D).


%%
%%  LOCAL FUNCTIONS
%%
-type day_of_year() :: 0..365.

  day_to_year(DayOfEpoch ) = { Year , DayOfYear }
%%
  The idea here is to first set the upper and lower bounds for a year ,
%% and then adjust a range by interpolation search. Although complexity
  of the algorithm is log(log(n ) ) , at most 1 or 2 recursive steps
%% are taken.
%%
-spec day_to_year(non_neg_integer()) -> {year(), day_of_year()}.
day_to_year(DayOfEpoch) when DayOfEpoch >= 0 ->
    YMax = DayOfEpoch div ?DAYS_PER_YEAR,
    YMin = DayOfEpoch div ?DAYS_PER_LEAP_YEAR,
    {Y1, D1} = dty(YMin, YMax, DayOfEpoch, dy(YMin), dy(YMax)),
    {Y1, DayOfEpoch - D1}.

-spec dty(year(), year(), non_neg_integer(), non_neg_integer(),
    non_neg_integer()) ->
		{year(), non_neg_integer()}.
dty(Min, Max, _D1, DMin, _DMax) when Min == Max ->
    {Min, DMin};
dty(Min, Max, D1, DMin, DMax) ->
    Diff = Max - Min,
    Mid = Min + (Diff * (D1 - DMin)) div (DMax - DMin),
    MidLength =
        case is_leap_year(Mid) of
            true -> ?DAYS_PER_LEAP_YEAR;
            false -> ?DAYS_PER_YEAR
        end,
    case dy(Mid) of
        D2 when D1 < D2 ->
            NewMax = Mid - 1,
            dty(Min, NewMax, D1, DMin, dy(NewMax));
        D2 when D1 - D2 >= MidLength ->
            NewMin = Mid + 1,
            dty(NewMin, Max, D1, dy(NewMin), DMax);
        D2 ->
            {Mid, D2}
    end.

%%
  The Gregorian days of the iso week 01 day 1 for a given year .
%%
-spec gregorian_days_of_iso_w01_1(year()) -> non_neg_integer().
gregorian_days_of_iso_w01_1(Year) ->
    D0101 = date_to_gregorian_days(Year, 1, 1),
    DOW = day_of_the_week(Year, 1, 1),
    if DOW =< 4 ->
	D0101 - DOW + 1;
    true ->
	D0101 + 7 - DOW + 1
    end.

  year_day_to_date(Year , DayOfYear )   = { Month ,   DayOfMonth }
%%
  Note : 1 is the first day of the month .
%%
-spec year_day_to_date(year(), day_of_year()) -> {month(), day()}.
year_day_to_date(Year, DayOfYear) ->
    ExtraDay = case is_leap_year(Year) of
		   true ->
		       1;
		   false ->
		       0
	       end,
    {Month, Day} = year_day_to_date2(ExtraDay, DayOfYear),
    {Month, Day + 1}.
  

  Note : 0 is the first day of the month
%% 
-spec year_day_to_date2(0 | 1, day_of_year()) -> {month(), 0..30}.
year_day_to_date2(_, Day) when Day < 31 ->
    {1, Day}; 
year_day_to_date2(E, Day) when 31 =< Day, Day < 59 + E ->
    {2, Day - 31}; 
year_day_to_date2(E, Day) when 59 + E =< Day, Day < 90 + E -> 
    {3, Day - (59 + E)}; 
year_day_to_date2(E, Day) when 90 + E =< Day, Day < 120 + E -> 
    {4, Day - (90 + E)};
year_day_to_date2(E, Day) when 120 + E =< Day, Day < 151 + E -> 
    {5, Day - (120 + E)}; 
year_day_to_date2(E, Day) when 151 + E =< Day, Day < 181 + E ->
    {6, Day - (151 + E)};
year_day_to_date2(E, Day) when 181 + E =< Day, Day < 212 + E -> 
    {7, Day - (181 + E)}; 
year_day_to_date2(E, Day) when 212 + E =< Day, Day < 243 + E ->
    {8, Day - (212 + E)};
year_day_to_date2(E, Day) when 243 + E =< Day, Day < 273 + E ->
    {9, Day - (243 + E)};
year_day_to_date2(E, Day) when 273 + E =< Day, Day < 304 + E ->
    {10, Day - (273 + E)};
year_day_to_date2(E, Day) when 304 + E =< Day, Day < 334 + E ->
    {11, Day - (304 + E)};
year_day_to_date2(E, Day) when 334 + E =< Day -> 
    {12, Day - (334 + E)}.

%% dy(Year)
%%
  Days in previous years .
%%
-spec dy(integer()) -> non_neg_integer().
dy(Y) when Y =< 0 -> 
    0; 
dy(Y) -> 
    X = Y - 1, 
    (X div 4) - (X div 100) + (X div 400) + 
	X*?DAYS_PER_YEAR + ?DAYS_PER_LEAP_YEAR.

%%  dm(Month)
%%
   Returns the total number of days in all months
   preceeding Month , for an ordinary year .
%%
-spec dm(month()) ->
	     0 | 31 | 59 | 90 | 120 | 151 | 181 | 212 | 243 | 273 | 304 | 334.
dm(1) -> 0;    dm(2) ->  31;   dm(3) ->   59;   dm(4) ->  90;
dm(5) -> 120;  dm(6) ->  151;  dm(7) ->  181;   dm(8) -> 212;
dm(9) -> 243;  dm(10) -> 273;  dm(11) -> 304;  dm(12) -> 334.
 
   df(Year , Month )
%%
   Accounts for an extra day in February if Year is
   a leap year , and if Month > 2 .
%%
-spec df(year(), month()) -> 0 | 1.
df(_, Month) when Month < 3 ->
    0;
df(Year, _) ->
    case is_leap_year(Year) of
	true -> 1;
	false  -> 0
    end.

check(_Arg, _Options, Secs) when Secs >= - ?SECONDS_FROM_0_TO_1970,
                                 Secs < ?SECONDS_FROM_0_TO_10000 ->
    ok;
check(Arg, Options, _Secs) ->
    erlang:error({badarg, [Arg, Options]}).

datetime_to_system_time(DateTime) ->
    datetime_to_gregorian_seconds(DateTime) - ?SECONDS_FROM_0_TO_1970.

system_time_to_datetime(Seconds) ->
    gregorian_seconds_to_datetime(Seconds + ?SECONDS_FROM_0_TO_1970).

offset(OffsetOption, Secs0) when OffsetOption =:= "";
                                 is_integer(OffsetOption) ->
    Sign = case Secs0 < 0 of
               true -> $-;
               false -> $+
           end,
    Secs = abs(Secs0),
    Hour = Secs div 3600,
    Min = (Secs rem 3600) div 60,
    [Sign | lists:append([pad2(Hour), ":", pad2(Min)])];
offset(OffsetOption, _Secs) ->
    OffsetOption.

offset_adjustment(Time, Unit, "") ->
    local_offset(Time, Unit);
offset_adjustment(Time, Unit, OffsetString) when is_list(OffsetString) ->
    offset_string_adjustment(Time, Unit, OffsetString);
offset_adjustment(_Time, Unit, Offset) when is_integer(Offset) ->
    erlang:convert_time_unit(Offset, Unit, second).

offset_string_adjustment(_Time, _Unit, "Z") ->
    0;
offset_string_adjustment(_Time, _Unit, "z") ->
    0;
offset_string_adjustment(_Time, _Unit, Tz) ->
    [Sign, H1, H2, $:, M1, M2] = Tz,
    Hour = list_to_integer([H1, H2]),
    Min = list_to_integer([M1, M2]),
    Adjustment = 3600 * Hour + 60 * Min,
    case Sign of
        $- -> -Adjustment;
        $+ -> Adjustment
    end.

local_offset(SystemTime, Unit) ->
    %% Not optimized for special cases.
    UniversalTime = system_time_to_universal_time(SystemTime, Unit),
    LocalTime = erlang:universaltime_to_localtime(UniversalTime),
    LocalSecs = datetime_to_gregorian_seconds(LocalTime),
    UniversalSecs = datetime_to_gregorian_seconds(UniversalTime),
    LocalSecs - UniversalSecs.

fraction_str(1, _Time) ->
    "";
fraction_str(Factor, Time) ->
    Fraction = Time rem Factor,
    S = integer_to_list(abs(Fraction)),
    [$. | pad(log10(Factor) - length(S), S)].

fraction(second, _) ->
    0;
fraction(_, "") ->
    0;
fraction(Unit, FractionStr) ->
    round(factor(Unit) * list_to_float([$0|FractionStr])).

copy_sign(N1, N2) when N2 < 0 -> -N1;
copy_sign(N1, _N2) -> N1.

factor(second)      -> 1;
factor(millisecond) -> 1000;
factor(microsecond) -> 1000000;
factor(nanosecond)  -> 1000000000.

log10(1000) -> 3;
log10(1000000) -> 6;
log10(1000000000) -> 9.

pad(0, S) ->
    S;
pad(I, S) ->
    [$0 | pad(I - 1, S)].

pad2(N) when N < 10 ->
    [$0 | integer_to_list(N)];
pad2(N) ->
    integer_to_list(N).

pad4(N) when N < 10 ->
    [$0, $0, $0 | integer_to_list(N)];
pad4(N) when N < 100 ->
    [$0, $0 | integer_to_list(N)];
pad4(N) when N < 1000 ->
    [$0 | integer_to_list(N)];
pad4(N) ->
    integer_to_list(N).


 %CopyrightBegin%
 
 
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

     -2.0

 Unless required by applicable law or agreed to in writing, software
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 
 %CopyrightEnd%

 local and universal time, time conversions
 = now_to_universal_time/1
 -define(DAYS_PER_400YEARS, 146097).
----------------------------------------------------------------------
 Types
----------------------------------------------------------------------
 By design 'native' is not supported:
----------------------------------------------------------------------
 All dates are according the the Gregorian calendar. In this module
 the Gregorian calendar is extended back to year 0 for convenience.





 EXPORTS



 datetime_to_gregorian_seconds(DateTime) = Integer






 is_leap_year(Year) = true | false





 last_day_of_the_month(Year, Month)


 local_time()

 Returns: {date(), time()}, date() = {Y, M, D}, time() = {H, M, S}.
 local_time_to_universal_time(DateTime)

 Reverse check the universal times
 Return the valid universal times
 now_to_universal_time(Now)
 now_to_datetime(Now)


 Returns: {date(), time()}, date() = {Y, M, D}, time() = {H, M, S}.
 
 now_to_local_time(Now)


 _T is the character separating the date and the time:



 Wraps.

 time_difference(T1, T2) = Tdiff

 Returns the difference between two {Date, Time} structures.



 time_to_seconds(Time)


 Returns: {date(), time()}, date() = {Y, M, D}, time() = {H, M, S}.
 universal_time_to_local_time(DateTime)



  LOCAL FUNCTIONS


 and then adjust a range by interpolation search. Although complexity
 are taken.





 
 dy(Year)


  dm(Month)




 Not optimized for special cases.
  Copyright Ericsson AB 1996 - 2020 . All Rights Reserved .
  Licensed under the Apache License , Version 2.0 ( the " License " ) ;
  distributed under the License is distributed on an " AS IS " BASIS ,
-module(calendar).


-export([date_to_gregorian_days/1, 
	 date_to_gregorian_days/3,
	 datetime_to_gregorian_seconds/1,
	 day_of_the_week/1,
	 day_of_the_week/3,
	 gregorian_days_to_date/1,
	 gregorian_seconds_to_datetime/1,
	 is_leap_year/1,
	 iso_week_number/0,
	 iso_week_number/1,
	 last_day_of_the_month/2,
	 local_time/0, 
	 local_time_to_universal_time/1, 
	 local_time_to_universal_time/2, 
	 local_time_to_universal_time_dst/1, 
	 now_to_local_time/1,
	 now_to_universal_time/1,
         rfc3339_to_system_time/1,
         rfc3339_to_system_time/2,
	 seconds_to_daystime/1,
	 seconds_to_time/1,
         system_time_to_local_time/2,
         system_time_to_universal_time/2,
         system_time_to_rfc3339/1,
         system_time_to_rfc3339/2,
	 time_difference/2,
	 time_to_seconds/1,
	 universal_time/0,
	 universal_time_to_local_time/1,
	 valid_date/1,
	 valid_date/3]).

-deprecated([{local_time_to_universal_time,1,
              "use calendar:local_time_to_universal_time_dst/1 instead"}]).

-define(SECONDS_PER_MINUTE, 60).
-define(SECONDS_PER_HOUR, 3600).
-define(SECONDS_PER_DAY, 86400).
-define(DAYS_PER_YEAR, 365).
-define(DAYS_PER_LEAP_YEAR, 366).
  -define(DAYS_PER_4YEARS , 1461 ) .
  -define(DAYS_PER_100YEARS , 36524 ) .
-define(DAYS_FROM_0_TO_1970, 719528).
-define(DAYS_FROM_0_TO_10000, 2932897).
-define(SECONDS_FROM_0_TO_1970, (?DAYS_FROM_0_TO_1970*?SECONDS_PER_DAY)).
-define(SECONDS_FROM_0_TO_10000, (?DAYS_FROM_0_TO_10000*?SECONDS_PER_DAY)).


-export_type([date/0, time/0, datetime/0, datetime1970/0]).

-type year()     :: non_neg_integer().
  should probably be 1970 ..
-type month()    :: 1..12.
-type day()      :: 1..31.
-type hour()     :: 0..23.
-type minute()   :: 0..59.
-type second()   :: 0..59.
-type daynum()   :: 1..7.
  last day of month
-type weeknum()  :: 1..53.

-type date()           :: {year(),month(),day()}.
-type time()           :: {hour(),minute(),second()}.
-type datetime()       :: {date(),time()}.
-type datetime1970()   :: {{year1970(),month(),day()},time()}.
-type yearweeknum()    :: {year(),weeknum()}.

-type rfc3339_string() :: [byte(), ...].
-type rfc3339_time_unit() :: 'microsecond'
                           | 'millisecond'
                           | 'nanosecond'
                           | 'second'.


  A year Y is a leap year if and only if either
         ( 1 )     Y is divisible by 4 , but not by 100 , or
         ( 2 )     Y is divisible by 400 .
  Hence , e.g. 1996 is a leap year , 1900 is not , but 2000 is .


  date_to_gregorian_days(Year , Month , Day ) = Integer
  date_to_gregorian_days({Year , Month , Day } ) = Integer
  Computes the total number of days starting from year 0 ,
  January 1st .
  df/2 catches the case Year<0
-spec date_to_gregorian_days(Year, Month, Day) -> Days when
      Year :: year(),
      Month :: month(),
      Day :: day(),
      Days :: non_neg_integer().
date_to_gregorian_days(Year, Month, Day) when is_integer(Day), Day > 0 ->
    Last = last_day_of_the_month(Year, Month),
    if
	Day =< Last ->
	    dy(Year) + dm(Month) + df(Year, Month) + Day - 1
    end.

-spec date_to_gregorian_days(Date) -> Days when
      Date :: date(),
      Days :: non_neg_integer().
date_to_gregorian_days({Year, Month, Day}) ->
    date_to_gregorian_days(Year, Month, Day).


  Computes the total number of seconds starting from year 0 ,
  January 1st .
-spec datetime_to_gregorian_seconds(DateTime) -> Seconds when
      DateTime :: datetime(),
      Seconds :: non_neg_integer().
datetime_to_gregorian_seconds({Date, Time}) ->
    ?SECONDS_PER_DAY*date_to_gregorian_days(Date) +
	time_to_seconds(Time).


  day_of_the_week(Year , Month , Day )
  day_of_the_week({Year , Month , Day } )
  Returns : 1 | .. | 7 . Monday = 1 , Tuesday = 2 , ... , Sunday = 7 .
-spec day_of_the_week(Year, Month, Day) -> daynum() when
      Year :: year(),
      Month :: month(),
      Day :: day().
day_of_the_week(Year, Month, Day) ->
    (date_to_gregorian_days(Year, Month, Day) + 5) rem 7 + 1.

-spec day_of_the_week(Date) -> daynum() when
      Date:: date().
day_of_the_week({Year, Month, Day}) ->
    day_of_the_week(Year, Month, Day).


  gregorian_days_to_date(Days ) = { Year , Month , Day }
-spec gregorian_days_to_date(Days) -> date() when
      Days :: non_neg_integer().
gregorian_days_to_date(Days) ->
    {Year, DayOfYear} = day_to_year(Days),
    {Month, DayOfMonth} = year_day_to_date(Year, DayOfYear),
    {Year, Month, DayOfMonth}.


  gregorian_seconds_to_datetime(Secs )
-spec gregorian_seconds_to_datetime(Seconds) -> datetime() when
      Seconds :: non_neg_integer().
gregorian_seconds_to_datetime(Secs) when Secs >= 0 ->
    Days = Secs div ?SECONDS_PER_DAY,
    Rest = Secs rem ?SECONDS_PER_DAY,
    {gregorian_days_to_date(Days), seconds_to_time(Rest)}.


-spec is_leap_year(Year) -> boolean() when
      Year :: year().
is_leap_year(Y) when is_integer(Y), Y >= 0 ->
    is_leap_year1(Y).

-spec is_leap_year1(year()) -> boolean().
is_leap_year1(Year) when Year rem 4 =:= 0, Year rem 100 > 0 ->
    true;
is_leap_year1(Year) when Year rem 400 =:= 0 ->
    true;
is_leap_year1(_) -> false.


  Calculates the iso week number for the current date .
-spec iso_week_number() -> yearweeknum().
iso_week_number() ->
    {Date, _} = local_time(),
    iso_week_number(Date).


  Calculates the iso week number for the given date .
-spec iso_week_number(Date) -> yearweeknum() when
      Date :: date().
iso_week_number({Year, Month, Day}) ->
    D = date_to_gregorian_days({Year, Month, Day}),
    W01_1_Year = gregorian_days_of_iso_w01_1(Year),
    W01_1_NextYear = gregorian_days_of_iso_w01_1(Year + 1),
    if W01_1_Year =< D andalso D < W01_1_NextYear ->
  Current Year Week 01 .. 52(,53 )
	    {Year, (D - W01_1_Year) div 7 + 1};
	D < W01_1_Year ->
  Previous Year 52 or 53
	    PWN = case day_of_the_week(Year - 1, 1, 1) of
		4 -> 53;
		_ -> case day_of_the_week(Year - 1, 12, 31) of
			4 -> 53;
			_ -> 52
		     end
		end,
	    {Year - 1, PWN};
	W01_1_NextYear =< D ->
  Next Year , Week 01
	    {Year + 1, 1}
    end.


  Returns the number of days in a month .
-spec last_day_of_the_month(Year, Month) -> LastDay when
      Year :: year(),
      Month :: month(),
      LastDay :: ldom().
last_day_of_the_month(Y, M) when is_integer(Y), Y >= 0 ->
    last_day_of_the_month1(Y, M).

-spec last_day_of_the_month1(year(),month()) -> ldom().
last_day_of_the_month1(_, 4) -> 30;
last_day_of_the_month1(_, 6) -> 30;
last_day_of_the_month1(_, 9) -> 30;
last_day_of_the_month1(_,11) -> 30;
last_day_of_the_month1(Y, 2) ->
   case is_leap_year(Y) of
      true -> 29;
      _    -> 28
   end;
last_day_of_the_month1(_, M) when is_integer(M), M > 0, M < 13 ->
    31.


-spec local_time() -> datetime().
local_time() ->
    erlang:localtime().


-spec local_time_to_universal_time(DateTime1) -> DateTime2 when
      DateTime1 :: datetime1970(),
      DateTime2 :: datetime1970().
local_time_to_universal_time(DateTime) ->
    erlang:localtime_to_universaltime(DateTime).

-spec local_time_to_universal_time(datetime1970(),
				   'true' | 'false' | 'undefined') ->
                                          datetime1970().
local_time_to_universal_time(DateTime, IsDst) ->
    erlang:localtime_to_universaltime(DateTime, IsDst).

-spec local_time_to_universal_time_dst(DateTime1) -> [DateTime] when
      DateTime1 :: datetime1970(),
      DateTime :: datetime1970().
local_time_to_universal_time_dst(DateTime) ->
    UtDst = erlang:localtime_to_universaltime(DateTime, true),
    Ut    = erlang:localtime_to_universaltime(DateTime, false),
    LtDst = erlang:universaltime_to_localtime(UtDst),
    Lt    = erlang:universaltime_to_localtime(Ut),
    case {LtDst,Lt} of
	{DateTime,DateTime} when UtDst =/= Ut ->
	    [UtDst,Ut];
	{DateTime,_} ->
	    [UtDst];
	{_,DateTime} ->
	    [Ut];
	{_,_} ->
	    []
    end.

  Convert from erlang : timestamp ( ) to UTC .
   : Now = now ( ) ; now ( ) = { MegaSec , Sec , MilliSec } , MegaSec = Sec
  = MilliSec = integer ( )
-spec now_to_datetime(Now) -> datetime1970() when
      Now :: erlang:timestamp().
now_to_datetime({MSec, Sec, _uSec}) ->
    system_time_to_datetime(MSec*1000000 + Sec).

-spec now_to_universal_time(Now) -> datetime1970() when
      Now :: erlang:timestamp().
now_to_universal_time(Now) ->
    now_to_datetime(Now).


   : Now = now ( )
-spec now_to_local_time(Now) -> datetime1970() when
      Now :: erlang:timestamp().
now_to_local_time({MSec, Sec, _uSec}) ->
    erlang:universaltime_to_localtime(
      now_to_universal_time({MSec, Sec, _uSec})).

-spec rfc3339_to_system_time(DateTimeString) -> integer() when
      DateTimeString :: rfc3339_string().

rfc3339_to_system_time(DateTimeString) ->
    rfc3339_to_system_time(DateTimeString, []).

-spec rfc3339_to_system_time(DateTimeString, Options) -> integer() when
      DateTimeString :: rfc3339_string(),
      Options :: [Option],
      Option :: {'unit', rfc3339_time_unit()}.

rfc3339_to_system_time(DateTimeString, Options) ->
    Unit = proplists:get_value(unit, Options, second),
    [Y1, Y2, Y3, Y4, $-, Mon1, Mon2, $-, D1, D2, _T,
     H1, H2, $:, Min1, Min2, $:, S1, S2 | TimeStr] = DateTimeString,
    Hour = list_to_integer([H1, H2]),
    Min = list_to_integer([Min1, Min2]),
    Sec = list_to_integer([S1, S2]),
    Year = list_to_integer([Y1, Y2, Y3, Y4]),
    Month = list_to_integer([Mon1, Mon2]),
    Day = list_to_integer([D1, D2]),
    DateTime = {{Year, Month, Day}, {Hour, Min, Sec}},
    IsFractionChar = fun(C) -> C >= $0 andalso C =< $9 orelse C =:= $. end,
    {FractionStr, UtcOffset} = lists:splitwith(IsFractionChar, TimeStr),
    Time = datetime_to_system_time(DateTime),
    Secs = Time - offset_string_adjustment(Time, second, UtcOffset),
    check(DateTimeString, Options, Secs),
    ScaledEpoch = erlang:convert_time_unit(Secs, second, Unit),
    ScaledEpoch + copy_sign(fraction(Unit, FractionStr), ScaledEpoch).



  seconds_to_daystime(Secs ) = { Days , { Hour , Minute , Second } }
-spec seconds_to_daystime(Seconds) -> {Days, Time} when
      Seconds :: integer(),
      Days :: integer(),
      Time :: time().
seconds_to_daystime(Secs) ->
    Days0 = Secs div ?SECONDS_PER_DAY,
    Secs0 = Secs rem ?SECONDS_PER_DAY,
    if 
	Secs0 < 0 ->
	    {Days0 - 1, seconds_to_time(Secs0 + ?SECONDS_PER_DAY)};
	true ->
	    {Days0, seconds_to_time(Secs0)}
    end.


  seconds_to_time(Secs )
-type secs_per_day() :: 0..?SECONDS_PER_DAY.
-spec seconds_to_time(Seconds) -> time() when
      Seconds :: secs_per_day().
seconds_to_time(Secs) when Secs >= 0, Secs < ?SECONDS_PER_DAY ->
    Secs0 = Secs rem ?SECONDS_PER_DAY,
    Hour = Secs0 div ?SECONDS_PER_HOUR,
    Secs1 = Secs0 rem ?SECONDS_PER_HOUR,
    Minute =  Secs1 div ?SECONDS_PER_MINUTE,
    Second =  Secs1 rem ?SECONDS_PER_MINUTE,
    {Hour, Minute, Second}.

-spec system_time_to_local_time(Time, TimeUnit) -> datetime() when
      Time :: integer(),
      TimeUnit :: erlang:time_unit().

system_time_to_local_time(Time, TimeUnit) ->
    UniversalDate = system_time_to_universal_time(Time, TimeUnit),
    erlang:universaltime_to_localtime(UniversalDate).

-spec system_time_to_universal_time(Time, TimeUnit) -> datetime() when
      Time :: integer(),
      TimeUnit :: erlang:time_unit().

system_time_to_universal_time(Time, TimeUnit) ->
    Secs = erlang:convert_time_unit(Time, TimeUnit, second),
    system_time_to_datetime(Secs).

-spec system_time_to_rfc3339(Time) -> DateTimeString when
      Time :: integer(),
      DateTimeString :: rfc3339_string().

system_time_to_rfc3339(Time) ->
    system_time_to_rfc3339(Time, []).

-type offset() :: [byte()] | (Time :: integer()).
-spec system_time_to_rfc3339(Time, Options) -> DateTimeString when
  Since Epoch
      Options :: [Option],
      Option :: {'offset', offset()}
              | {'time_designator', byte()}
              | {'unit', rfc3339_time_unit()},
      DateTimeString :: rfc3339_string().

system_time_to_rfc3339(Time, Options) ->
    Unit = proplists:get_value(unit, Options, second),
    OffsetOption = proplists:get_value(offset, Options, ""),
    T = proplists:get_value(time_designator, Options, $T),
    AdjustmentSecs = offset_adjustment(Time, Unit, OffsetOption),
    Offset = offset(OffsetOption, AdjustmentSecs),
    Adjustment = erlang:convert_time_unit(AdjustmentSecs, second, Unit),
    AdjustedTime = Time + Adjustment,
    Factor = factor(Unit),
    Secs = AdjustedTime div Factor,
    check(Time, Options, Secs),
    DateTime = system_time_to_datetime(Secs),
    {{Year, Month, Day}, {Hour, Min, Sec}} = DateTime,
    FractionStr = fraction_str(Factor, AdjustedTime),
    L = [pad4(Year), "-", pad2(Month), "-", pad2(Day), [T],
         pad2(Hour), ":", pad2(Min), ":", pad2(Sec), FractionStr, Offset],
    lists:append(L).

  T1 = T2 = { Date , Time } ,  = { Day , { Hour ,  , Sec } } ,
  Date = { Year , Month , Day } , Time = { Hour , Minute , Sec } ,
  Year = Month = Day = Hour = Minute = Sec = integer ( )
-spec time_difference(T1, T2) -> {Days, Time} when
      T1 :: datetime(),
      T2 :: datetime(),
      Days :: integer(),
      Time :: time().
time_difference({{Y1, Mo1, D1}, {H1, Mi1, S1}}, 
		{{Y2, Mo2, D2}, {H2, Mi2, S2}}) ->
    Secs = datetime_to_gregorian_seconds({{Y2, Mo2, D2}, {H2, Mi2, S2}}) -
	datetime_to_gregorian_seconds({{Y1, Mo1, D1}, {H1, Mi1, S1}}),
    seconds_to_daystime(Secs).


-spec time_to_seconds(Time) -> secs_per_day() when
      Time :: time().
time_to_seconds({H, M, S}) when is_integer(H), is_integer(M), is_integer(S) ->
    H * ?SECONDS_PER_HOUR +
	M * ?SECONDS_PER_MINUTE + S.
      

   ( )
-spec universal_time() -> datetime().
universal_time() ->
    erlang:universaltime().
 

-spec universal_time_to_local_time(DateTime) -> datetime() when
      DateTime :: datetime1970().
universal_time_to_local_time(DateTime) ->
    erlang:universaltime_to_localtime(DateTime).


  valid_date(Year , Month , Day ) = true | false
  valid_date({Year , Month , Day } ) = true | false
-spec valid_date(Year, Month, Day) -> boolean() when
      Year :: integer(),
      Month :: integer(),
      Day :: integer().
valid_date(Y, M, D) when is_integer(Y), is_integer(M), is_integer(D) ->
    valid_date1(Y, M, D).

-spec valid_date1(integer(), integer(), integer()) -> boolean().
valid_date1(Y, M, D) when Y >= 0, M > 0, M < 13, D > 0 ->
    D =< last_day_of_the_month(Y, M);
valid_date1(_, _, _) ->
    false.

-spec valid_date(Date) -> boolean() when
      Date :: date().
valid_date({Y, M, D}) ->
    valid_date(Y, M, D).


-type day_of_year() :: 0..365.

  day_to_year(DayOfEpoch ) = { Year , DayOfYear }
  The idea here is to first set the upper and lower bounds for a year ,
  of the algorithm is log(log(n ) ) , at most 1 or 2 recursive steps
-spec day_to_year(non_neg_integer()) -> {year(), day_of_year()}.
day_to_year(DayOfEpoch) when DayOfEpoch >= 0 ->
    YMax = DayOfEpoch div ?DAYS_PER_YEAR,
    YMin = DayOfEpoch div ?DAYS_PER_LEAP_YEAR,
    {Y1, D1} = dty(YMin, YMax, DayOfEpoch, dy(YMin), dy(YMax)),
    {Y1, DayOfEpoch - D1}.

-spec dty(year(), year(), non_neg_integer(), non_neg_integer(),
    non_neg_integer()) ->
		{year(), non_neg_integer()}.
dty(Min, Max, _D1, DMin, _DMax) when Min == Max ->
    {Min, DMin};
dty(Min, Max, D1, DMin, DMax) ->
    Diff = Max - Min,
    Mid = Min + (Diff * (D1 - DMin)) div (DMax - DMin),
    MidLength =
        case is_leap_year(Mid) of
            true -> ?DAYS_PER_LEAP_YEAR;
            false -> ?DAYS_PER_YEAR
        end,
    case dy(Mid) of
        D2 when D1 < D2 ->
            NewMax = Mid - 1,
            dty(Min, NewMax, D1, DMin, dy(NewMax));
        D2 when D1 - D2 >= MidLength ->
            NewMin = Mid + 1,
            dty(NewMin, Max, D1, dy(NewMin), DMax);
        D2 ->
            {Mid, D2}
    end.

  The Gregorian days of the iso week 01 day 1 for a given year .
-spec gregorian_days_of_iso_w01_1(year()) -> non_neg_integer().
gregorian_days_of_iso_w01_1(Year) ->
    D0101 = date_to_gregorian_days(Year, 1, 1),
    DOW = day_of_the_week(Year, 1, 1),
    if DOW =< 4 ->
	D0101 - DOW + 1;
    true ->
	D0101 + 7 - DOW + 1
    end.

  year_day_to_date(Year , DayOfYear )   = { Month ,   DayOfMonth }
  Note : 1 is the first day of the month .
-spec year_day_to_date(year(), day_of_year()) -> {month(), day()}.
year_day_to_date(Year, DayOfYear) ->
    ExtraDay = case is_leap_year(Year) of
		   true ->
		       1;
		   false ->
		       0
	       end,
    {Month, Day} = year_day_to_date2(ExtraDay, DayOfYear),
    {Month, Day + 1}.
  

  Note : 0 is the first day of the month
-spec year_day_to_date2(0 | 1, day_of_year()) -> {month(), 0..30}.
year_day_to_date2(_, Day) when Day < 31 ->
    {1, Day}; 
year_day_to_date2(E, Day) when 31 =< Day, Day < 59 + E ->
    {2, Day - 31}; 
year_day_to_date2(E, Day) when 59 + E =< Day, Day < 90 + E -> 
    {3, Day - (59 + E)}; 
year_day_to_date2(E, Day) when 90 + E =< Day, Day < 120 + E -> 
    {4, Day - (90 + E)};
year_day_to_date2(E, Day) when 120 + E =< Day, Day < 151 + E -> 
    {5, Day - (120 + E)}; 
year_day_to_date2(E, Day) when 151 + E =< Day, Day < 181 + E ->
    {6, Day - (151 + E)};
year_day_to_date2(E, Day) when 181 + E =< Day, Day < 212 + E -> 
    {7, Day - (181 + E)}; 
year_day_to_date2(E, Day) when 212 + E =< Day, Day < 243 + E ->
    {8, Day - (212 + E)};
year_day_to_date2(E, Day) when 243 + E =< Day, Day < 273 + E ->
    {9, Day - (243 + E)};
year_day_to_date2(E, Day) when 273 + E =< Day, Day < 304 + E ->
    {10, Day - (273 + E)};
year_day_to_date2(E, Day) when 304 + E =< Day, Day < 334 + E ->
    {11, Day - (304 + E)};
year_day_to_date2(E, Day) when 334 + E =< Day -> 
    {12, Day - (334 + E)}.

  Days in previous years .
-spec dy(integer()) -> non_neg_integer().
dy(Y) when Y =< 0 -> 
    0; 
dy(Y) -> 
    X = Y - 1, 
    (X div 4) - (X div 100) + (X div 400) + 
	X*?DAYS_PER_YEAR + ?DAYS_PER_LEAP_YEAR.

   Returns the total number of days in all months
   preceeding Month , for an ordinary year .
-spec dm(month()) ->
	     0 | 31 | 59 | 90 | 120 | 151 | 181 | 212 | 243 | 273 | 304 | 334.
dm(1) -> 0;    dm(2) ->  31;   dm(3) ->   59;   dm(4) ->  90;
dm(5) -> 120;  dm(6) ->  151;  dm(7) ->  181;   dm(8) -> 212;
dm(9) -> 243;  dm(10) -> 273;  dm(11) -> 304;  dm(12) -> 334.
 
   df(Year , Month )
   Accounts for an extra day in February if Year is
   a leap year , and if Month > 2 .
-spec df(year(), month()) -> 0 | 1.
df(_, Month) when Month < 3 ->
    0;
df(Year, _) ->
    case is_leap_year(Year) of
	true -> 1;
	false  -> 0
    end.

check(_Arg, _Options, Secs) when Secs >= - ?SECONDS_FROM_0_TO_1970,
                                 Secs < ?SECONDS_FROM_0_TO_10000 ->
    ok;
check(Arg, Options, _Secs) ->
    erlang:error({badarg, [Arg, Options]}).

datetime_to_system_time(DateTime) ->
    datetime_to_gregorian_seconds(DateTime) - ?SECONDS_FROM_0_TO_1970.

system_time_to_datetime(Seconds) ->
    gregorian_seconds_to_datetime(Seconds + ?SECONDS_FROM_0_TO_1970).

offset(OffsetOption, Secs0) when OffsetOption =:= "";
                                 is_integer(OffsetOption) ->
    Sign = case Secs0 < 0 of
               true -> $-;
               false -> $+
           end,
    Secs = abs(Secs0),
    Hour = Secs div 3600,
    Min = (Secs rem 3600) div 60,
    [Sign | lists:append([pad2(Hour), ":", pad2(Min)])];
offset(OffsetOption, _Secs) ->
    OffsetOption.

offset_adjustment(Time, Unit, "") ->
    local_offset(Time, Unit);
offset_adjustment(Time, Unit, OffsetString) when is_list(OffsetString) ->
    offset_string_adjustment(Time, Unit, OffsetString);
offset_adjustment(_Time, Unit, Offset) when is_integer(Offset) ->
    erlang:convert_time_unit(Offset, Unit, second).

offset_string_adjustment(_Time, _Unit, "Z") ->
    0;
offset_string_adjustment(_Time, _Unit, "z") ->
    0;
offset_string_adjustment(_Time, _Unit, Tz) ->
    [Sign, H1, H2, $:, M1, M2] = Tz,
    Hour = list_to_integer([H1, H2]),
    Min = list_to_integer([M1, M2]),
    Adjustment = 3600 * Hour + 60 * Min,
    case Sign of
        $- -> -Adjustment;
        $+ -> Adjustment
    end.

local_offset(SystemTime, Unit) ->
    UniversalTime = system_time_to_universal_time(SystemTime, Unit),
    LocalTime = erlang:universaltime_to_localtime(UniversalTime),
    LocalSecs = datetime_to_gregorian_seconds(LocalTime),
    UniversalSecs = datetime_to_gregorian_seconds(UniversalTime),
    LocalSecs - UniversalSecs.

fraction_str(1, _Time) ->
    "";
fraction_str(Factor, Time) ->
    Fraction = Time rem Factor,
    S = integer_to_list(abs(Fraction)),
    [$. | pad(log10(Factor) - length(S), S)].

fraction(second, _) ->
    0;
fraction(_, "") ->
    0;
fraction(Unit, FractionStr) ->
    round(factor(Unit) * list_to_float([$0|FractionStr])).

copy_sign(N1, N2) when N2 < 0 -> -N1;
copy_sign(N1, _N2) -> N1.

factor(second)      -> 1;
factor(millisecond) -> 1000;
factor(microsecond) -> 1000000;
factor(nanosecond)  -> 1000000000.

log10(1000) -> 3;
log10(1000000) -> 6;
log10(1000000000) -> 9.

pad(0, S) ->
    S;
pad(I, S) ->
    [$0 | pad(I - 1, S)].

pad2(N) when N < 10 ->
    [$0 | integer_to_list(N)];
pad2(N) ->
    integer_to_list(N).

pad4(N) when N < 10 ->
    [$0, $0, $0 | integer_to_list(N)];
pad4(N) when N < 100 ->
    [$0, $0 | integer_to_list(N)];
pad4(N) when N < 1000 ->
    [$0 | integer_to_list(N)];
pad4(N) ->
    integer_to_list(N).
