1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 1996-2019. All Rights Reserved. 5%% 6%% Licensed under the Apache License, Version 2.0 (the "License"); 7%% you may not use this file except in compliance with the License. 8%% You may obtain a copy of the License at 9%% 10%% http://www.apache.org/licenses/LICENSE-2.0 11%% 12%% Unless required by applicable law or agreed to in writing, software 13%% distributed under the License is distributed on an "AS IS" BASIS, 14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15%% See the License for the specific language governing permissions and 16%% limitations under the License. 17%% 18%% %CopyrightEnd% 19%% 20-module(calendar). 21 22%% local and universal time, time conversions 23 24-export([date_to_gregorian_days/1, 25 date_to_gregorian_days/3, 26 datetime_to_gregorian_seconds/1, 27 day_of_the_week/1, 28 day_of_the_week/3, 29 gregorian_days_to_date/1, 30 gregorian_seconds_to_datetime/1, 31 is_leap_year/1, 32 iso_week_number/0, 33 iso_week_number/1, 34 last_day_of_the_month/2, 35 local_time/0, 36 local_time_to_universal_time/1, 37 local_time_to_universal_time/2, 38 local_time_to_universal_time_dst/1, 39 now_to_datetime/1, % = now_to_universal_time/1 40 now_to_local_time/1, 41 now_to_universal_time/1, 42 rfc3339_to_system_time/1, 43 rfc3339_to_system_time/2, 44 seconds_to_daystime/1, 45 seconds_to_time/1, 46 system_time_to_local_time/2, 47 system_time_to_universal_time/2, 48 system_time_to_rfc3339/1, 49 system_time_to_rfc3339/2, 50 time_difference/2, 51 time_to_seconds/1, 52 universal_time/0, 53 universal_time_to_local_time/1, 54 valid_date/1, 55 valid_date/3]). 56 57-deprecated([{local_time_to_universal_time,1}]). 58 59-define(SECONDS_PER_MINUTE, 60). 60-define(SECONDS_PER_HOUR, 3600). 61-define(SECONDS_PER_DAY, 86400). 62-define(DAYS_PER_YEAR, 365). 63-define(DAYS_PER_LEAP_YEAR, 366). 64%% -define(DAYS_PER_4YEARS, 1461). 65%% -define(DAYS_PER_100YEARS, 36524). 66%% -define(DAYS_PER_400YEARS, 146097). 67-define(DAYS_FROM_0_TO_1970, 719528). 68-define(DAYS_FROM_0_TO_10000, 2932897). 69-define(SECONDS_FROM_0_TO_1970, (?DAYS_FROM_0_TO_1970*?SECONDS_PER_DAY)). 70-define(SECONDS_FROM_0_TO_10000, (?DAYS_FROM_0_TO_10000*?SECONDS_PER_DAY)). 71 72%%---------------------------------------------------------------------- 73%% Types 74%%---------------------------------------------------------------------- 75 76-export_type([date/0, time/0, datetime/0, datetime1970/0]). 77 78-type year() :: non_neg_integer(). 79-type year1970() :: 1970..10000. % should probably be 1970.. 80-type month() :: 1..12. 81-type day() :: 1..31. 82-type hour() :: 0..23. 83-type minute() :: 0..59. 84-type second() :: 0..59. 85-type daynum() :: 1..7. 86-type ldom() :: 28 | 29 | 30 | 31. % last day of month 87-type weeknum() :: 1..53. 88 89-type date() :: {year(),month(),day()}. 90-type time() :: {hour(),minute(),second()}. 91-type datetime() :: {date(),time()}. 92-type datetime1970() :: {{year1970(),month(),day()},time()}. 93-type yearweeknum() :: {year(),weeknum()}. 94 95-type rfc3339_string() :: [byte(), ...]. 96%% By design 'native' is not supported: 97-type rfc3339_time_unit() :: 'microsecond' 98 | 'millisecond' 99 | 'nanosecond' 100 | 'second'. 101 102%%---------------------------------------------------------------------- 103 104%% All dates are according the the Gregorian calendar. In this module 105%% the Gregorian calendar is extended back to year 0 for convenience. 106%% 107%% A year Y is a leap year if and only if either 108%% 109%% (1) Y is divisible by 4, but not by 100, or 110%% (2) Y is divisible by 400. 111%% 112%% Hence, e.g. 1996 is a leap year, 1900 is not, but 2000 is. 113%% 114 115%% 116%% EXPORTS 117%% 118 119%% date_to_gregorian_days(Year, Month, Day) = Integer 120%% date_to_gregorian_days({Year, Month, Day}) = Integer 121%% 122%% Computes the total number of days starting from year 0, 123%% January 1st. 124%% 125%% df/2 catches the case Year<0 126-spec date_to_gregorian_days(Year, Month, Day) -> Days when 127 Year :: year(), 128 Month :: month(), 129 Day :: day(), 130 Days :: non_neg_integer(). 131date_to_gregorian_days(Year, Month, Day) when is_integer(Day), Day > 0 -> 132 Last = last_day_of_the_month(Year, Month), 133 if 134 Day =< Last -> 135 dy(Year) + dm(Month) + df(Year, Month) + Day - 1 136 end. 137 138-spec date_to_gregorian_days(Date) -> Days when 139 Date :: date(), 140 Days :: non_neg_integer(). 141date_to_gregorian_days({Year, Month, Day}) -> 142 date_to_gregorian_days(Year, Month, Day). 143 144 145%% datetime_to_gregorian_seconds(DateTime) = Integer 146%% 147%% Computes the total number of seconds starting from year 0, 148%% January 1st. 149%% 150-spec datetime_to_gregorian_seconds(DateTime) -> Seconds when 151 DateTime :: datetime(), 152 Seconds :: non_neg_integer(). 153datetime_to_gregorian_seconds({Date, Time}) -> 154 ?SECONDS_PER_DAY*date_to_gregorian_days(Date) + 155 time_to_seconds(Time). 156 157 158%% day_of_the_week(Year, Month, Day) 159%% day_of_the_week({Year, Month, Day}) 160%% 161%% Returns: 1 | .. | 7. Monday = 1, Tuesday = 2, ..., Sunday = 7. 162%% 163-spec day_of_the_week(Year, Month, Day) -> daynum() when 164 Year :: year(), 165 Month :: month(), 166 Day :: day(). 167day_of_the_week(Year, Month, Day) -> 168 (date_to_gregorian_days(Year, Month, Day) + 5) rem 7 + 1. 169 170-spec day_of_the_week(Date) -> daynum() when 171 Date:: date(). 172day_of_the_week({Year, Month, Day}) -> 173 day_of_the_week(Year, Month, Day). 174 175 176%% gregorian_days_to_date(Days) = {Year, Month, Day} 177%% 178-spec gregorian_days_to_date(Days) -> date() when 179 Days :: non_neg_integer(). 180gregorian_days_to_date(Days) -> 181 {Year, DayOfYear} = day_to_year(Days), 182 {Month, DayOfMonth} = year_day_to_date(Year, DayOfYear), 183 {Year, Month, DayOfMonth}. 184 185 186%% gregorian_seconds_to_datetime(Secs) 187%% 188-spec gregorian_seconds_to_datetime(Seconds) -> datetime() when 189 Seconds :: non_neg_integer(). 190gregorian_seconds_to_datetime(Secs) when Secs >= 0 -> 191 Days = Secs div ?SECONDS_PER_DAY, 192 Rest = Secs rem ?SECONDS_PER_DAY, 193 {gregorian_days_to_date(Days), seconds_to_time(Rest)}. 194 195 196%% is_leap_year(Year) = true | false 197%% 198-spec is_leap_year(Year) -> boolean() when 199 Year :: year(). 200is_leap_year(Y) when is_integer(Y), Y >= 0 -> 201 is_leap_year1(Y). 202 203-spec is_leap_year1(year()) -> boolean(). 204is_leap_year1(Year) when Year rem 4 =:= 0, Year rem 100 > 0 -> 205 true; 206is_leap_year1(Year) when Year rem 400 =:= 0 -> 207 true; 208is_leap_year1(_) -> false. 209 210 211%% 212%% Calculates the iso week number for the current date. 213%% 214-spec iso_week_number() -> yearweeknum(). 215iso_week_number() -> 216 {Date, _} = local_time(), 217 iso_week_number(Date). 218 219 220%% 221%% Calculates the iso week number for the given date. 222%% 223-spec iso_week_number(Date) -> yearweeknum() when 224 Date :: date(). 225iso_week_number({Year, Month, Day}) -> 226 D = date_to_gregorian_days({Year, Month, Day}), 227 W01_1_Year = gregorian_days_of_iso_w01_1(Year), 228 W01_1_NextYear = gregorian_days_of_iso_w01_1(Year + 1), 229 if W01_1_Year =< D andalso D < W01_1_NextYear -> 230 % Current Year Week 01..52(,53) 231 {Year, (D - W01_1_Year) div 7 + 1}; 232 D < W01_1_Year -> 233 % Previous Year 52 or 53 234 PWN = case day_of_the_week(Year - 1, 1, 1) of 235 4 -> 53; 236 _ -> case day_of_the_week(Year - 1, 12, 31) of 237 4 -> 53; 238 _ -> 52 239 end 240 end, 241 {Year - 1, PWN}; 242 W01_1_NextYear =< D -> 243 % Next Year, Week 01 244 {Year + 1, 1} 245 end. 246 247 248%% last_day_of_the_month(Year, Month) 249%% 250%% Returns the number of days in a month. 251%% 252-spec last_day_of_the_month(Year, Month) -> LastDay when 253 Year :: year(), 254 Month :: month(), 255 LastDay :: ldom(). 256last_day_of_the_month(Y, M) when is_integer(Y), Y >= 0 -> 257 last_day_of_the_month1(Y, M). 258 259-spec last_day_of_the_month1(year(),month()) -> ldom(). 260last_day_of_the_month1(_, 4) -> 30; 261last_day_of_the_month1(_, 6) -> 30; 262last_day_of_the_month1(_, 9) -> 30; 263last_day_of_the_month1(_,11) -> 30; 264last_day_of_the_month1(Y, 2) -> 265 case is_leap_year(Y) of 266 true -> 29; 267 _ -> 28 268 end; 269last_day_of_the_month1(_, M) when is_integer(M), M > 0, M < 13 -> 270 31. 271 272 273%% local_time() 274%% 275%% Returns: {date(), time()}, date() = {Y, M, D}, time() = {H, M, S}. 276-spec local_time() -> datetime(). 277local_time() -> 278 erlang:localtime(). 279 280 281%% local_time_to_universal_time(DateTime) 282%% 283-spec local_time_to_universal_time(DateTime1) -> DateTime2 when 284 DateTime1 :: datetime1970(), 285 DateTime2 :: datetime1970(). 286local_time_to_universal_time(DateTime) -> 287 erlang:localtime_to_universaltime(DateTime). 288 289-spec local_time_to_universal_time(datetime1970(), 290 'true' | 'false' | 'undefined') -> 291 datetime1970(). 292local_time_to_universal_time(DateTime, IsDst) -> 293 erlang:localtime_to_universaltime(DateTime, IsDst). 294 295-spec local_time_to_universal_time_dst(DateTime1) -> [DateTime] when 296 DateTime1 :: datetime1970(), 297 DateTime :: datetime1970(). 298local_time_to_universal_time_dst(DateTime) -> 299 %% Reverse check the universal times 300 {UtDst, LtDst} = 301 try 302 UtDst0 = erlang:localtime_to_universaltime(DateTime, true), 303 {UtDst0, erlang:universaltime_to_localtime(UtDst0)} 304 catch error:badarg -> {error, error} 305 end, 306 {Ut, Lt} = 307 try 308 Ut0 = erlang:localtime_to_universaltime(DateTime, false), 309 {Ut0, erlang:universaltime_to_localtime(Ut0)} 310 catch error:badarg -> {error, error} 311 end, 312 %% Return the valid universal times 313 case {LtDst,Lt} of 314 {DateTime,DateTime} when UtDst =/= Ut -> 315 [UtDst,Ut]; 316 {DateTime,_} -> 317 [UtDst]; 318 {_,DateTime} -> 319 [Ut]; 320 {_,_} -> 321 [] 322 end. 323 324%% now_to_universal_time(Now) 325%% now_to_datetime(Now) 326%% 327%% Convert from erlang:timestamp() to UTC. 328%% 329%% Args: Now = now(); now() = {MegaSec, Sec, MilliSec}, MegaSec = Sec 330%% = MilliSec = integer() 331%% Returns: {date(), time()}, date() = {Y, M, D}, time() = {H, M, S}. 332%% 333-spec now_to_datetime(Now) -> datetime1970() when 334 Now :: erlang:timestamp(). 335now_to_datetime({MSec, Sec, _uSec}) -> 336 system_time_to_datetime(MSec*1000000 + Sec). 337 338-spec now_to_universal_time(Now) -> datetime1970() when 339 Now :: erlang:timestamp(). 340now_to_universal_time(Now) -> 341 now_to_datetime(Now). 342 343 344%% now_to_local_time(Now) 345%% 346%% Args: Now = now() 347%% 348-spec now_to_local_time(Now) -> datetime1970() when 349 Now :: erlang:timestamp(). 350now_to_local_time({MSec, Sec, _uSec}) -> 351 erlang:universaltime_to_localtime( 352 now_to_universal_time({MSec, Sec, _uSec})). 353 354-spec rfc3339_to_system_time(DateTimeString) -> integer() when 355 DateTimeString :: rfc3339_string(). 356 357rfc3339_to_system_time(DateTimeString) -> 358 rfc3339_to_system_time(DateTimeString, []). 359 360-spec rfc3339_to_system_time(DateTimeString, Options) -> integer() when 361 DateTimeString :: rfc3339_string(), 362 Options :: [Option], 363 Option :: {'unit', rfc3339_time_unit()}. 364 365rfc3339_to_system_time(DateTimeString, Options) -> 366 Unit = proplists:get_value(unit, Options, second), 367 %% _T is the character separating the date and the time: 368 [Y1, Y2, Y3, Y4, $-, Mon1, Mon2, $-, D1, D2, _T, 369 H1, H2, $:, Min1, Min2, $:, S1, S2 | TimeStr] = DateTimeString, 370 Hour = list_to_integer([H1, H2]), 371 Min = list_to_integer([Min1, Min2]), 372 Sec = list_to_integer([S1, S2]), 373 Year = list_to_integer([Y1, Y2, Y3, Y4]), 374 Month = list_to_integer([Mon1, Mon2]), 375 Day = list_to_integer([D1, D2]), 376 DateTime = {{Year, Month, Day}, {Hour, Min, Sec}}, 377 IsFractionChar = fun(C) -> C >= $0 andalso C =< $9 orelse C =:= $. end, 378 {FractionStr, UtcOffset} = lists:splitwith(IsFractionChar, TimeStr), 379 Time = datetime_to_system_time(DateTime), 380 Secs = Time - offset_adjustment(Time, second, UtcOffset), 381 check(DateTimeString, Options, Secs), 382 ScaledEpoch = erlang:convert_time_unit(Secs, second, Unit), 383 ScaledEpoch + copy_sign(fraction(Unit, FractionStr), ScaledEpoch). 384 385 386 387%% seconds_to_daystime(Secs) = {Days, {Hour, Minute, Second}} 388%% 389-spec seconds_to_daystime(Seconds) -> {Days, Time} when 390 Seconds :: integer(), 391 Days :: integer(), 392 Time :: time(). 393seconds_to_daystime(Secs) -> 394 Days0 = Secs div ?SECONDS_PER_DAY, 395 Secs0 = Secs rem ?SECONDS_PER_DAY, 396 if 397 Secs0 < 0 -> 398 {Days0 - 1, seconds_to_time(Secs0 + ?SECONDS_PER_DAY)}; 399 true -> 400 {Days0, seconds_to_time(Secs0)} 401 end. 402 403 404%% 405%% seconds_to_time(Secs) 406%% 407%% Wraps. 408%% 409-type secs_per_day() :: 0..?SECONDS_PER_DAY. 410-spec seconds_to_time(Seconds) -> time() when 411 Seconds :: secs_per_day(). 412seconds_to_time(Secs) when Secs >= 0, Secs < ?SECONDS_PER_DAY -> 413 Secs0 = Secs rem ?SECONDS_PER_DAY, 414 Hour = Secs0 div ?SECONDS_PER_HOUR, 415 Secs1 = Secs0 rem ?SECONDS_PER_HOUR, 416 Minute = Secs1 div ?SECONDS_PER_MINUTE, 417 Second = Secs1 rem ?SECONDS_PER_MINUTE, 418 {Hour, Minute, Second}. 419 420-spec system_time_to_local_time(Time, TimeUnit) -> datetime() when 421 Time :: integer(), 422 TimeUnit :: erlang:time_unit(). 423 424system_time_to_local_time(Time, TimeUnit) -> 425 UniversalDate = system_time_to_universal_time(Time, TimeUnit), 426 erlang:universaltime_to_localtime(UniversalDate). 427 428-spec system_time_to_universal_time(Time, TimeUnit) -> datetime() when 429 Time :: integer(), 430 TimeUnit :: erlang:time_unit(). 431 432system_time_to_universal_time(Time, TimeUnit) -> 433 Secs = erlang:convert_time_unit(Time, TimeUnit, second), 434 system_time_to_datetime(Secs). 435 436-spec system_time_to_rfc3339(Time) -> DateTimeString when 437 Time :: integer(), 438 DateTimeString :: rfc3339_string(). 439 440system_time_to_rfc3339(Time) -> 441 system_time_to_rfc3339(Time, []). 442 443-type offset() :: [byte()] | (Time :: integer()). 444-spec system_time_to_rfc3339(Time, Options) -> DateTimeString when 445 Time :: integer(), % Since Epoch 446 Options :: [Option], 447 Option :: {'offset', offset()} 448 | {'time_designator', byte()} 449 | {'unit', rfc3339_time_unit()}, 450 DateTimeString :: rfc3339_string(). 451 452system_time_to_rfc3339(Time, Options) -> 453 Unit = proplists:get_value(unit, Options, second), 454 OffsetOption = proplists:get_value(offset, Options, ""), 455 T = proplists:get_value(time_designator, Options, $T), 456 AdjustmentSecs = offset_adjustment(Time, Unit, OffsetOption), 457 Offset = offset(OffsetOption, AdjustmentSecs), 458 Adjustment = erlang:convert_time_unit(AdjustmentSecs, second, Unit), 459 AdjustedTime = Time + Adjustment, 460 Factor = factor(Unit), 461 Secs = AdjustedTime div Factor, 462 check(Time, Options, Secs), 463 DateTime = system_time_to_datetime(Secs), 464 {{Year, Month, Day}, {Hour, Min, Sec}} = DateTime, 465 FractionStr = fraction_str(Factor, AdjustedTime), 466 L = [pad4(Year), "-", pad2(Month), "-", pad2(Day), [T], 467 pad2(Hour), ":", pad2(Min), ":", pad2(Sec), FractionStr, Offset], 468 lists:append(L). 469 470%% time_difference(T1, T2) = Tdiff 471%% 472%% Returns the difference between two {Date, Time} structures. 473%% 474%% T1 = T2 = {Date, Time}, Tdiff = {Day, {Hour, Min, Sec}}, 475%% Date = {Year, Month, Day}, Time = {Hour, Minute, Sec}, 476%% Year = Month = Day = Hour = Minute = Sec = integer() 477%% 478-spec time_difference(T1, T2) -> {Days, Time} when 479 T1 :: datetime(), 480 T2 :: datetime(), 481 Days :: integer(), 482 Time :: time(). 483time_difference({{Y1, Mo1, D1}, {H1, Mi1, S1}}, 484 {{Y2, Mo2, D2}, {H2, Mi2, S2}}) -> 485 Secs = datetime_to_gregorian_seconds({{Y2, Mo2, D2}, {H2, Mi2, S2}}) - 486 datetime_to_gregorian_seconds({{Y1, Mo1, D1}, {H1, Mi1, S1}}), 487 seconds_to_daystime(Secs). 488 489 490%% 491%% time_to_seconds(Time) 492%% 493-spec time_to_seconds(Time) -> secs_per_day() when 494 Time :: time(). 495time_to_seconds({H, M, S}) when is_integer(H), is_integer(M), is_integer(S) -> 496 H * ?SECONDS_PER_HOUR + 497 M * ?SECONDS_PER_MINUTE + S. 498 499 500%% universal_time() 501%% 502%% Returns: {date(), time()}, date() = {Y, M, D}, time() = {H, M, S}. 503-spec universal_time() -> datetime(). 504universal_time() -> 505 erlang:universaltime(). 506 507 508%% universal_time_to_local_time(DateTime) 509%% 510-spec universal_time_to_local_time(DateTime) -> datetime() when 511 DateTime :: datetime1970(). 512universal_time_to_local_time(DateTime) -> 513 erlang:universaltime_to_localtime(DateTime). 514 515 516%% valid_date(Year, Month, Day) = true | false 517%% valid_date({Year, Month, Day}) = true | false 518%% 519-spec valid_date(Year, Month, Day) -> boolean() when 520 Year :: integer(), 521 Month :: integer(), 522 Day :: integer(). 523valid_date(Y, M, D) when is_integer(Y), is_integer(M), is_integer(D) -> 524 valid_date1(Y, M, D). 525 526-spec valid_date1(integer(), integer(), integer()) -> boolean(). 527valid_date1(Y, M, D) when Y >= 0, M > 0, M < 13, D > 0 -> 528 D =< last_day_of_the_month(Y, M); 529valid_date1(_, _, _) -> 530 false. 531 532-spec valid_date(Date) -> boolean() when 533 Date :: date(). 534valid_date({Y, M, D}) -> 535 valid_date(Y, M, D). 536 537 538%% 539%% LOCAL FUNCTIONS 540%% 541-type day_of_year() :: 0..365. 542 543%% day_to_year(DayOfEpoch) = {Year, DayOfYear} 544%% 545%% The idea here is to first set the upper and lower bounds for a year, 546%% and then adjust a range by interpolation search. Although complexity 547%% of the algorithm is log(log(n)), at most 1 or 2 recursive steps 548%% are taken. 549%% 550-spec day_to_year(non_neg_integer()) -> {year(), day_of_year()}. 551day_to_year(DayOfEpoch) when DayOfEpoch >= 0 -> 552 YMax = DayOfEpoch div ?DAYS_PER_YEAR, 553 YMin = DayOfEpoch div ?DAYS_PER_LEAP_YEAR, 554 {Y1, D1} = dty(YMin, YMax, DayOfEpoch, dy(YMin), dy(YMax)), 555 {Y1, DayOfEpoch - D1}. 556 557-spec dty(year(), year(), non_neg_integer(), non_neg_integer(), 558 non_neg_integer()) -> 559 {year(), non_neg_integer()}. 560dty(Min, Max, _D1, DMin, _DMax) when Min == Max -> 561 {Min, DMin}; 562dty(Min, Max, D1, DMin, DMax) -> 563 Diff = Max - Min, 564 Mid = Min + (Diff * (D1 - DMin)) div (DMax - DMin), 565 MidLength = 566 case is_leap_year(Mid) of 567 true -> ?DAYS_PER_LEAP_YEAR; 568 false -> ?DAYS_PER_YEAR 569 end, 570 case dy(Mid) of 571 D2 when D1 < D2 -> 572 NewMax = Mid - 1, 573 dty(Min, NewMax, D1, DMin, dy(NewMax)); 574 D2 when D1 - D2 >= MidLength -> 575 NewMin = Mid + 1, 576 dty(NewMin, Max, D1, dy(NewMin), DMax); 577 D2 -> 578 {Mid, D2} 579 end. 580 581%% 582%% The Gregorian days of the iso week 01 day 1 for a given year. 583%% 584-spec gregorian_days_of_iso_w01_1(year()) -> non_neg_integer(). 585gregorian_days_of_iso_w01_1(Year) -> 586 D0101 = date_to_gregorian_days(Year, 1, 1), 587 DOW = day_of_the_week(Year, 1, 1), 588 if DOW =< 4 -> 589 D0101 - DOW + 1; 590 true -> 591 D0101 + 7 - DOW + 1 592 end. 593 594%% year_day_to_date(Year, DayOfYear) = {Month, DayOfMonth} 595%% 596%% Note: 1 is the first day of the month. 597%% 598-spec year_day_to_date(year(), day_of_year()) -> {month(), day()}. 599year_day_to_date(Year, DayOfYear) -> 600 ExtraDay = case is_leap_year(Year) of 601 true -> 602 1; 603 false -> 604 0 605 end, 606 {Month, Day} = year_day_to_date2(ExtraDay, DayOfYear), 607 {Month, Day + 1}. 608 609 610%% Note: 0 is the first day of the month 611%% 612-spec year_day_to_date2(0 | 1, day_of_year()) -> {month(), 0..30}. 613year_day_to_date2(_, Day) when Day < 31 -> 614 {1, Day}; 615year_day_to_date2(E, Day) when 31 =< Day, Day < 59 + E -> 616 {2, Day - 31}; 617year_day_to_date2(E, Day) when 59 + E =< Day, Day < 90 + E -> 618 {3, Day - (59 + E)}; 619year_day_to_date2(E, Day) when 90 + E =< Day, Day < 120 + E -> 620 {4, Day - (90 + E)}; 621year_day_to_date2(E, Day) when 120 + E =< Day, Day < 151 + E -> 622 {5, Day - (120 + E)}; 623year_day_to_date2(E, Day) when 151 + E =< Day, Day < 181 + E -> 624 {6, Day - (151 + E)}; 625year_day_to_date2(E, Day) when 181 + E =< Day, Day < 212 + E -> 626 {7, Day - (181 + E)}; 627year_day_to_date2(E, Day) when 212 + E =< Day, Day < 243 + E -> 628 {8, Day - (212 + E)}; 629year_day_to_date2(E, Day) when 243 + E =< Day, Day < 273 + E -> 630 {9, Day - (243 + E)}; 631year_day_to_date2(E, Day) when 273 + E =< Day, Day < 304 + E -> 632 {10, Day - (273 + E)}; 633year_day_to_date2(E, Day) when 304 + E =< Day, Day < 334 + E -> 634 {11, Day - (304 + E)}; 635year_day_to_date2(E, Day) when 334 + E =< Day -> 636 {12, Day - (334 + E)}. 637 638%% dy(Year) 639%% 640%% Days in previous years. 641%% 642-spec dy(integer()) -> non_neg_integer(). 643dy(Y) when Y =< 0 -> 644 0; 645dy(Y) -> 646 X = Y - 1, 647 (X div 4) - (X div 100) + (X div 400) + 648 X*?DAYS_PER_YEAR + ?DAYS_PER_LEAP_YEAR. 649 650%% dm(Month) 651%% 652%% Returns the total number of days in all months 653%% preceeding Month, for an ordinary year. 654%% 655-spec dm(month()) -> 656 0 | 31 | 59 | 90 | 120 | 151 | 181 | 212 | 243 | 273 | 304 | 334. 657dm(1) -> 0; dm(2) -> 31; dm(3) -> 59; dm(4) -> 90; 658dm(5) -> 120; dm(6) -> 151; dm(7) -> 181; dm(8) -> 212; 659dm(9) -> 243; dm(10) -> 273; dm(11) -> 304; dm(12) -> 334. 660 661%% df(Year, Month) 662%% 663%% Accounts for an extra day in February if Year is 664%% a leap year, and if Month > 2. 665%% 666-spec df(year(), month()) -> 0 | 1. 667df(_, Month) when Month < 3 -> 668 0; 669df(Year, _) -> 670 case is_leap_year(Year) of 671 true -> 1; 672 false -> 0 673 end. 674 675check(_Arg, _Options, Secs) when Secs >= - ?SECONDS_FROM_0_TO_1970, 676 Secs < ?SECONDS_FROM_0_TO_10000 -> 677 ok; 678check(Arg, Options, _Secs) -> 679 erlang:error({badarg, [Arg, Options]}). 680 681datetime_to_system_time(DateTime) -> 682 datetime_to_gregorian_seconds(DateTime) - ?SECONDS_FROM_0_TO_1970. 683 684system_time_to_datetime(Seconds) -> 685 gregorian_seconds_to_datetime(Seconds + ?SECONDS_FROM_0_TO_1970). 686 687offset(OffsetOption, Secs0) when OffsetOption =:= ""; 688 is_integer(OffsetOption) -> 689 Sign = case Secs0 < 0 of 690 true -> $-; 691 false -> $+ 692 end, 693 Secs = abs(Secs0), 694 Hour = Secs div 3600, 695 Min = (Secs rem 3600) div 60, 696 [Sign | lists:append([pad2(Hour), ":", pad2(Min)])]; 697offset(OffsetOption, _Secs) -> 698 OffsetOption. 699 700offset_adjustment(Time, Unit, OffsetString) when is_list(OffsetString) -> 701 offset_string_adjustment(Time, Unit, OffsetString); 702offset_adjustment(_Time, Unit, Offset) when is_integer(Offset) -> 703 erlang:convert_time_unit(Offset, Unit, second). 704 705offset_string_adjustment(Time, Unit, "") -> 706 local_offset(Time, Unit); 707offset_string_adjustment(_Time, _Unit, "Z") -> 708 0; 709offset_string_adjustment(_Time, _Unit, "z") -> 710 0; 711offset_string_adjustment(_Time, _Unit, Tz) -> 712 [Sign, H1, H2, $:, M1, M2] = Tz, 713 Hour = list_to_integer([H1, H2]), 714 Min = list_to_integer([M1, M2]), 715 Adjustment = 3600 * Hour + 60 * Min, 716 case Sign of 717 $- -> -Adjustment; 718 $+ -> Adjustment 719 end. 720 721local_offset(SystemTime, Unit) -> 722 %% Not optimized for special cases. 723 UniversalTime = system_time_to_universal_time(SystemTime, Unit), 724 LocalTime = erlang:universaltime_to_localtime(UniversalTime), 725 LocalSecs = datetime_to_gregorian_seconds(LocalTime), 726 UniversalSecs = datetime_to_gregorian_seconds(UniversalTime), 727 LocalSecs - UniversalSecs. 728 729fraction_str(1, _Time) -> 730 ""; 731fraction_str(Factor, Time) -> 732 Fraction = Time rem Factor, 733 S = integer_to_list(abs(Fraction)), 734 [$. | pad(log10(Factor) - length(S), S)]. 735 736fraction(second, _) -> 737 0; 738fraction(_, "") -> 739 0; 740fraction(Unit, FractionStr) -> 741 round(factor(Unit) * list_to_float([$0|FractionStr])). 742 743copy_sign(N1, N2) when N2 < 0 -> -N1; 744copy_sign(N1, _N2) -> N1. 745 746factor(second) -> 1; 747factor(millisecond) -> 1000; 748factor(microsecond) -> 1000000; 749factor(nanosecond) -> 1000000000. 750 751log10(1000) -> 3; 752log10(1000000) -> 6; 753log10(1000000000) -> 9. 754 755pad(0, S) -> 756 S; 757pad(I, S) -> 758 [$0 | pad(I - 1, S)]. 759 760pad2(N) when N < 10 -> 761 [$0 | integer_to_list(N)]; 762pad2(N) -> 763 integer_to_list(N). 764 765pad4(N) when N < 10 -> 766 [$0, $0, $0 | integer_to_list(N)]; 767pad4(N) when N < 100 -> 768 [$0, $0 | integer_to_list(N)]; 769pad4(N) when N < 1000 -> 770 [$0 | integer_to_list(N)]; 771pad4(N) -> 772 integer_to_list(N). 773