Skip to content

Commit 288e8ea

Browse files
josevalimJosé Valim
authored andcommitted
Move away from rata die reference and use iso_days instead (#6328)
We chose to use the year zero in the astronomical year numbering as a reference point, as it is also the reference used by Calendar.ISO, the default calendar used by Elixir, It is also the same epoch used by Erlang's :calendar module. Note we chose to not call it gregorian epoch due to the confusion between January 1 AC 1 and 0000-01-01. The latter actually maps to January 1 BC 1. We also moved away from Calendar.Julian, which could have been based on Calendrical Calculations, and chose Calendar.Holocene, which adds exactly 10,000 years to Calendar.ISO and as such has a very straigh-forward implementation. More information: + https://en.wikipedia.org/wiki/Year_zero + https://en.wikipedia.org/wiki/Holocene_calendar
1 parent 36069cb commit 288e8ea

File tree

12 files changed

+238
-270
lines changed

12 files changed

+238
-270
lines changed

lib/elixir/lib/calendar.ex

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,16 @@ defmodule Calendar do
3535
@typedoc """
3636
The internal date format that is used when converting between calendars.
3737
38-
This is the amount of days including the fractional part that has passed of the last day,
39-
since midnight 1 January AD 1 of the Proleptic Gregorian Calendar
40-
(0000-01-01+00:00T00:00.00000 in ISO 8601 notation).
38+
This is the amount of days including the fractional part that has passed of
39+
the last day since 0000-01-01+00:00T00:00.00000 in ISO 8601 notation (also
40+
known as midnight 1 January BC 1 of the Proleptic Gregorian Calendar).
4141
4242
The `parts_per_day` represent how many subparts the current day is subdivided in
4343
(for different calendars, picking a different `parts_per_day` might make sense).
44-
The `parts_in_day` represents how many of these `parts_per_day` have passed in the last day.
45-
46-
Thus, a Rata Die like `{1234, {1, 2}}` should be read as `1234½`.
44+
The `parts_in_day` represents how many of these `parts_per_day` have passed in the
45+
last day.
4746
"""
48-
@type rata_die :: {days :: integer, day_fraction}
47+
@type iso_days :: {days :: integer, day_fraction}
4948

5049
@typedoc """
5150
Microseconds with stored precision.
@@ -127,14 +126,14 @@ defmodule Calendar do
127126
@callback time_to_string(hour, minute, second, microsecond) :: String.t
128127

129128
@doc """
130-
Converts the given datetime (with time zone) into the `t:rata_die` format.
129+
Converts the given datetime (with time zone) into the `t:iso_days` format.
131130
"""
132-
@callback naive_datetime_to_rata_die(year, month, day, hour, minute, second, microsecond) :: rata_die
131+
@callback naive_datetime_to_iso_days(year, month, day, hour, minute, second, microsecond) :: iso_days
133132

134133
@doc """
135-
Converts `t:rata_die` to the Calendar's datetime format.
134+
Converts `t:iso_days` to the Calendar's datetime format.
136135
"""
137-
@callback naive_datetime_from_rata_die(rata_die) :: {year, month, day, hour, minute, second, microsecond}
136+
@callback naive_datetime_from_iso_days(iso_days) :: {year, month, day, hour, minute, second, microsecond}
138137

139138
@doc """
140139
Converts the given time to the `t:day_fraction` format.

lib/elixir/lib/calendar/date.ex

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -82,20 +82,20 @@ defmodule Date do
8282
366
8383
iex> Enum.member?(range, ~D[2001-02-01])
8484
true
85-
iex> Enum.reduce(range, 0, fn(_date, acc) -> acc - 1 end)
85+
iex> Enum.reduce(range, 0, fn _date, acc -> acc - 1 end)
8686
-366
8787
"""
8888

8989
@spec range(Calendar.date, Calendar.date) :: Date.Range.t
9090
def range(%{calendar: calendar} = first, %{calendar: calendar} = last) do
91-
{first_days, _} = to_rata_die(first)
92-
{last_days, _} = to_rata_die(last)
91+
{first_days, _} = to_iso_days(first)
92+
{last_days, _} = to_iso_days(last)
9393

9494
%Date.Range{
9595
first: first,
9696
last: last,
97-
first_rata_die: first_days,
98-
last_rata_die: last_days,
97+
first_in_iso_days: first_days,
98+
last_in_iso_days: last_days,
9999
}
100100
end
101101

@@ -412,7 +412,7 @@ defmodule Date do
412412
end
413413
def compare(date1, date2) do
414414
if Calendar.compatible_calendars?(date1.calendar, date2.calendar) do
415-
case {to_rata_die(date1), to_rata_die(date2)} do
415+
case {to_iso_days(date1), to_iso_days(date2)} do
416416
{first, second} when first > second -> :gt
417417
{first, second} when first < second -> :lt
418418
_ -> :eq
@@ -437,10 +437,12 @@ defmodule Date do
437437
438438
## Examples
439439
440-
Imagine someone implements `Calendar.Julian`:
440+
Imagine someone implements `Calendar.Holocene`, a calendar based on the
441+
Gregorian calendar that adds exactly 10,000 years to the current Gregorian
442+
year:
441443
442-
iex> Date.convert(~D[2000-01-01], Calendar.Julian)
443-
{:ok, %Date{calendar: Calendar.Julian, year: 1999, month: 12, day: 19}}
444+
iex> Date.convert(~D[2000-01-01], Calendar.Holocene)
445+
{:ok, %Date{calendar: Calendar.Holocene, year: 12000, month: 1, day: 1}}
444446
445447
"""
446448
@spec convert(Calendar.date, Calendar.calendar) :: {:ok, t} | {:error, :incompatible_calendars}
@@ -451,8 +453,8 @@ defmodule Date do
451453
if Calendar.compatible_calendars?(calendar, target_calendar) do
452454
result_date =
453455
date
454-
|> to_rata_die()
455-
|> from_rata_die(target_calendar)
456+
|> to_iso_days()
457+
|> from_iso_days(target_calendar)
456458
{:ok, result_date}
457459
else
458460
{:error, :incompatible_calendars}
@@ -465,10 +467,12 @@ defmodule Date do
465467
466468
## Examples
467469
468-
Imagine someone implements `Calendar.Julian`:
470+
Imagine someone implements `Calendar.Holocene`, a calendar based on the
471+
Gregorian calendar that adds exactly 10,000 years to the current Gregorian
472+
year:
469473
470-
iex> Date.convert!(~D[2000-01-01], Calendar.Julian)
471-
%Date{calendar: Calendar.Julian, year: 1999, month: 12, day: 19}
474+
iex> Date.convert!(~D[2000-01-01], Calendar.Holocene)
475+
%Date{calendar: Calendar.Holocene, year: 12000, month: 1, day: 1}
472476
473477
"""
474478
@spec convert!(Calendar.date, Calendar.calendar) :: t
@@ -500,8 +504,8 @@ defmodule Date do
500504
"""
501505
@spec add(Calendar.date, integer()) :: t
502506
def add(%{calendar: calendar} = date, days) do
503-
{rata_days, fraction} = to_rata_die(date)
504-
from_rata_die({rata_days + days, fraction}, calendar)
507+
{iso_days_days, fraction} = to_iso_days(date)
508+
from_iso_days({iso_days_days + days, fraction}, calendar)
505509
end
506510

507511
@doc """
@@ -525,33 +529,33 @@ defmodule Date do
525529
@spec diff(Calendar.date, Calendar.date) :: integer
526530
def diff(%{calendar: Calendar.ISO, year: year1, month: month1, day: day1},
527531
%{calendar: Calendar.ISO, year: year2, month: month2, day: day2}) do
528-
Calendar.ISO.date_to_rata_die_days(year1, month1, day1) -
529-
Calendar.ISO.date_to_rata_die_days(year2, month2, day2)
532+
Calendar.ISO.date_to_iso_days_days(year1, month1, day1) -
533+
Calendar.ISO.date_to_iso_days_days(year2, month2, day2)
530534
end
531535

532536
def diff(%{calendar: calendar1} = date1, %{calendar: calendar2} = date2) do
533537
if Calendar.compatible_calendars?(calendar1, calendar2) do
534-
{days1, _} = to_rata_die(date1)
535-
{days2, _} = to_rata_die(date2)
538+
{days1, _} = to_iso_days(date1)
539+
{days2, _} = to_iso_days(date2)
536540
days1 - days2
537541
else
538542
raise ArgumentError, "cannot calculate the difference between #{inspect date1} and #{inspect date2} because their calendars are not compatible and thus the result would be ambiguous"
539543
end
540544
end
541545

542-
defp to_rata_die(%{calendar: Calendar.ISO, year: year, month: month, day: day}) do
543-
{Calendar.ISO.date_to_rata_die_days(year, month, day), {0, 86400000000}}
546+
defp to_iso_days(%{calendar: Calendar.ISO, year: year, month: month, day: day}) do
547+
{Calendar.ISO.date_to_iso_days_days(year, month, day), {0, 86400000000}}
544548
end
545-
defp to_rata_die(%{calendar: calendar, year: year, month: month, day: day}) do
546-
calendar.naive_datetime_to_rata_die(year, month, day, 0, 0, 0, {0, 0})
549+
defp to_iso_days(%{calendar: calendar, year: year, month: month, day: day}) do
550+
calendar.naive_datetime_to_iso_days(year, month, day, 0, 0, 0, {0, 0})
547551
end
548552

549-
defp from_rata_die({days, _}, Calendar.ISO) do
550-
{year, month, day} = Calendar.ISO.date_from_rata_die_days(days)
553+
defp from_iso_days({days, _}, Calendar.ISO) do
554+
{year, month, day} = Calendar.ISO.date_from_iso_days_days(days)
551555
%Date{year: year, month: month, day: day, calendar: Calendar.ISO}
552556
end
553-
defp from_rata_die(rata_die, target_calendar) do
554-
{year, month, day, _, _, _, _} = target_calendar.naive_datetime_from_rata_die(rata_die)
557+
defp from_iso_days(iso_days, target_calendar) do
558+
{year, month, day, _, _, _, _} = target_calendar.naive_datetime_from_iso_days(iso_days)
555559
%Date{year: year, month: month, day: day, calendar: target_calendar}
556560
end
557561

lib/elixir/lib/calendar/date_range.ex

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,21 @@ defmodule Date.Range do
1313
"""
1414

1515
@type t :: %__MODULE__{first: Date.t, last: Date.t,
16-
first_rata_die: rata_die_days, last_rata_die: rata_die_days}
16+
first_in_iso_days: Calendar.days,
17+
last_in_iso_days: Calendar.days}
1718

18-
@opaque rata_die_days :: Calendar.days
19-
20-
defstruct [:first, :last, :first_rata_die, :last_rata_die]
19+
defstruct [:first, :last, :first_in_iso_days, :last_in_iso_days]
2120

2221
defimpl Enumerable do
2322
def member?(%{first: %{calendar: calendar, year: first_year, month: first_month, day: first_day},
2423
last: %{calendar: calendar, year: last_year, month: last_month, day: last_day},
25-
first_rata_die: first_rata_die, last_rata_die: last_rata_die},
24+
first_in_iso_days: first_in_iso_days, last_in_iso_days: last_in_iso_days},
2625
%Date{calendar: calendar, year: year, month: month, day: day}) do
2726
first = {first_year, first_month, first_day}
2827
last = {last_year, last_month, last_day}
2928
date = {year, month, day}
3029

31-
if first_rata_die <= last_rata_die do
30+
if first_in_iso_days <= last_in_iso_days do
3231
{:ok, date >= first and date <= last}
3332
else
3433
{:ok, date >= last and date <= first}
@@ -39,13 +38,13 @@ defmodule Date.Range do
3938
{:ok, false}
4039
end
4140

42-
def count(%Date.Range{first_rata_die: first_rata_die, last_rata_die: last_rata_die}) do
43-
{:ok, abs(first_rata_die - last_rata_die) + 1}
41+
def count(%Date.Range{first_in_iso_days: first_in_iso_days, last_in_iso_days: last_in_iso_days}) do
42+
{:ok, abs(first_in_iso_days - last_in_iso_days) + 1}
4443
end
4544

46-
def reduce(%Date.Range{first_rata_die: first_rata_die, last_rata_die: last_rata_die,
45+
def reduce(%Date.Range{first_in_iso_days: first_in_iso_days, last_in_iso_days: last_in_iso_days,
4746
first: %{calendar: calendar}}, acc, fun) do
48-
reduce(first_rata_die, last_rata_die, acc, fun, calendar, first_rata_die <= last_rata_die)
47+
reduce(first_in_iso_days, last_in_iso_days, acc, fun, calendar, first_in_iso_days <= last_in_iso_days)
4948
end
5049

5150
defp reduce(_x, _y, {:halt, acc}, _fun, _calendar, _up?) do
@@ -57,24 +56,24 @@ defmodule Date.Range do
5756
end
5857

5958
defp reduce(x, y, {:cont, acc}, fun, calendar, up? = true) when x <= y do
60-
reduce(x + 1, y, fun.(date_from_rata_days(x, calendar), acc), fun, calendar, up?)
59+
reduce(x + 1, y, fun.(date_from_iso_days_days(x, calendar), acc), fun, calendar, up?)
6160
end
6261

6362
defp reduce(x, y, {:cont, acc}, fun, calendar, up? = false) when x >= y do
64-
reduce(x - 1, y, fun.(date_from_rata_days(x, calendar), acc), fun, calendar, up?)
63+
reduce(x - 1, y, fun.(date_from_iso_days_days(x, calendar), acc), fun, calendar, up?)
6564
end
6665

6766
defp reduce(_, _, {:cont, acc}, _fun, _calendar, _up) do
6867
{:done, acc}
6968
end
7069

71-
defp date_from_rata_days(days, Calendar.ISO) do
72-
{year, month, day} = Calendar.ISO.date_from_rata_die_days(days)
70+
defp date_from_iso_days_days(days, Calendar.ISO) do
71+
{year, month, day} = Calendar.ISO.date_from_iso_days_days(days)
7372
%Date{year: year, month: month, day: day, calendar: Calendar.ISO}
7473
end
7574

76-
defp date_from_rata_days(days, calendar) do
77-
{year, month, day, _, _, _, _} = calendar.naive_datetime_from_rata_die({days, {0, 86400000000}})
75+
defp date_from_iso_days_days(days, calendar) do
76+
{year, month, day, _, _, _, _} = calendar.naive_datetime_from_iso_days({days, {0, 86400000000}})
7877
%Date{year: year, month: month, day: day, calendar: calendar}
7978
end
8079
end

0 commit comments

Comments
 (0)