diff options
Diffstat (limited to 'lib/stdlib/src/calendar.erl')
-rw-r--r-- | lib/stdlib/src/calendar.erl | 459 |
1 files changed, 459 insertions, 0 deletions
diff --git a/lib/stdlib/src/calendar.erl b/lib/stdlib/src/calendar.erl new file mode 100644 index 0000000000..ddc0666f77 --- /dev/null +++ b/lib/stdlib/src/calendar.erl @@ -0,0 +1,459 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights 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, + 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, + seconds_to_daystime/1, + seconds_to_time/1, + 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}]). + +-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). + +%%---------------------------------------------------------------------- +%% Types +%%---------------------------------------------------------------------- + +-type year() :: non_neg_integer(). +-type year1970() :: 1970..10000. % 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. +-type ldom() :: 28 | 29 | 30 | 31. % last day of month + +-type t_now() :: {non_neg_integer(),non_neg_integer(),non_neg_integer()}. + +-type t_date() :: {year(),month(),day()}. +-type t_time() :: {hour(),minute(),second()}. +-type t_datetime() :: {t_date(),t_time()}. +-type t_datetime1970() :: {{year1970(),month(),day()},t_time()}. + +%%---------------------------------------------------------------------- + +%% 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()) -> 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(t_date()) -> 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(t_datetime()) -> 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(). +day_of_the_week(Year, Month, Day) -> + (date_to_gregorian_days(Year, Month, Day) + 5) rem 7 + 1. + +-spec day_of_the_week(t_date()) -> daynum(). +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(non_neg_integer()) -> t_date(). +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(non_neg_integer()) -> t_datetime(). +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(). +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. + + +%% last_day_of_the_month(Year, Month) +%% +%% Returns the number of days in a month. +%% +-spec last_day_of_the_month(year(), month()) -> 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() -> t_datetime(). +local_time() -> + erlang:localtime(). + + +%% local_time_to_universal_time(DateTime) +%% +-spec local_time_to_universal_time(t_datetime1970()) -> t_datetime1970(). +local_time_to_universal_time(DateTime) -> + erlang:localtime_to_universaltime(DateTime). + +-spec local_time_to_universal_time(t_datetime1970(), + 'true' | 'false' | 'undefined') -> + t_datetime1970(). +local_time_to_universal_time(DateTime, IsDst) -> + erlang:localtime_to_universaltime(DateTime, IsDst). + +-spec local_time_to_universal_time_dst(t_datetime1970()) -> [t_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 now() to UTC. +%% +%% Args: 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(t_now()) -> t_datetime1970(). +now_to_datetime({MSec, Sec, _uSec}) -> + Sec0 = MSec*1000000 + Sec + ?DAYS_FROM_0_TO_1970*?SECONDS_PER_DAY, + gregorian_seconds_to_datetime(Sec0). + +-spec now_to_universal_time(t_now()) -> t_datetime1970(). +now_to_universal_time(Now) -> + now_to_datetime(Now). + + +%% now_to_local_time(Now) +%% +%% Args: Now = now() +%% +-spec now_to_local_time(t_now()) -> t_datetime1970(). +now_to_local_time({MSec, Sec, _uSec}) -> + erlang:universaltime_to_localtime( + now_to_universal_time({MSec, Sec, _uSec})). + + + +%% seconds_to_daystime(Secs) = {Days, {Hour, Minute, Second}} +%% +-spec seconds_to_daystime(integer()) -> {integer(), t_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(secs_per_day()) -> t_time(). +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}. + +%% time_difference(T1, T2) = Tdiff +%% +%% Returns the difference between two {Date, Time} structures. +%% +%% T1 = T2 = {Date, Time}, Tdiff = {Day, {Hour, Min, Sec}}, +%% Date = {Year, Month, Day}, Time = {Hour, Minute, Sec}, +%% Year = Month = Day = Hour = Minute = Sec = integer() +%% +-type timediff() :: {integer(), t_time()}. +-spec time_difference(t_datetime(), t_datetime()) -> timediff(). +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(t_time()) -> secs_per_day(). +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. + + +%% universal_time() +%% +%% Returns: {date(), time()}, date() = {Y, M, D}, time() = {H, M, S}. +-spec universal_time() -> t_datetime(). +universal_time() -> + erlang:universaltime(). + + +%% universal_time_to_local_time(DateTime) +%% +-spec universal_time_to_local_time(t_datetime()) -> t_datetime(). +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(integer(), integer(), integer()) -> boolean(). +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({integer(),integer(),integer()}) -> boolean(). +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 guess a year, and then adjust. Although +%% the implementation is recursive, at most 1 or 2 recursive steps +%% are taken. +%% If DayOfEpoch is very large, we need far more than 1 or 2 iterations, +%% since we just subtract a yearful of days at a time until we're there. +%% +-spec day_to_year(non_neg_integer()) -> {year(), day_of_year()}. +day_to_year(DayOfEpoch) when DayOfEpoch >= 0 -> + Y0 = DayOfEpoch div ?DAYS_PER_YEAR, + {Y1, D1} = dty(Y0, DayOfEpoch, dy(Y0)), + {Y1, DayOfEpoch - D1}. + +-spec dty(year(), non_neg_integer(), non_neg_integer()) -> + {year(), non_neg_integer()}. +dty(Y, D1, D2) when D1 < D2 -> + dty(Y-1, D1, dy(Y-1)); +dty(Y, _D1, D2) -> + {Y, D2}. + +%% 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. |