Packages
ex_cldr_calendars
1.17.0-rc.1
2.4.4
2.4.3
2.4.2
2.4.1
2.4.0
2.3.1
2.3.0
2.2.0
2.1.1
2.1.0
2.0.0
1.26.4
1.26.3
1.26.2
1.26.1
1.26.0
1.25.2
1.25.1
1.25.0
1.24.2
1.24.1
1.24.0
retired
1.23.1
1.23.0
1.22.1
1.22.0
1.21.0
1.20.0
1.19.0
1.18.1
1.18.0
1.17.3
1.17.2
1.17.1
1.17.0
1.17.0-rc.3
1.17.0-rc.2
1.17.0-rc.1
1.17.0-rc.0
1.16.0
1.15.3
1.15.2
1.15.1
1.15.0
1.14.1
1.14.0
1.13.0
1.13.0-rc.1
1.13.0-rc.0
1.12.1
1.12.0
1.11.0
1.11.0-rc.0
1.10.1
1.10.0
1.9.0
1.8.1
1.8.0
1.8.0-rc.0
1.7.1
1.7.0
1.6.0
1.5.1
1.5.0
retired
1.4.0
retired
1.3.0
retired
1.2.0
retired
1.1.0
retired
1.0.0
retired
0.9.0
retired
0.8.0
retired
0.7.0
retired
0.6.0
retired
0.5.0
retired
0.4.1
0.4.0
retired
0.3.0
retired
0.2.0
retired
0.1.0
retired
Localized month- and week-based calendars and calendar functions based upon CLDR data.
Current section
Files
Jump to
Current section
Files
lib/cldr/calendar/interval.ex
defmodule Cldr.Calendar.Interval do
@moduledoc """
Implements functions to return intervals and compare
date intervals.
In particular it provides functions which return an
interval (as a `Date.Range.t`) for years, quarters,
months, weeks and days.
In general, the intervals created with the packaage
[calendar_interval](https://hex.pm/packages/calendar_interval)
are to be preferred since they can used over different
time precisions whereas the functions in this module are
all intervals of a day. In order to be used with `ex_cldr_calendars`,
version "~> 0.2" of [calendar_interval](https://hex.pm/packages/calendar_interval)
is required.
Note however that as of release `0.2`, [calendar_interval](https://hex.pm/packages/calendar_interval) does
not support intervals of `quarters` or `weeks`.
"""
@doc """
Returns a `Date.Range.t` that represents
the `year`.
The range is enumerable.
## Arguments
* `year` is any `year` for `calendar`
* `calendar` is any module that implements
the `Calendar` and `Cldr.Calendar`
behaviours. The default is `Cldr.Calendar.Gregorian`.
## Returns
* A `Date.Range.t()` representing the
the enumerable days in the `year`
## Examples
iex> Cldr.Calendar.Interval.year 2019, Cldr.Calendar.Fiscal.UK
#DateRange<~D[2019-01-01 Cldr.Calendar.Fiscal.UK], ~D[2019-12-31 Cldr.Calendar.Fiscal.UK]>
iex> Cldr.Calendar.Interval.year 2019, Cldr.Calendar.NRF
#DateRange<~D[2019-W01-1 Cldr.Calendar.NRF], ~D[2019-W52-7 Cldr.Calendar.NRF]>
"""
@spec year(Calendar.year(), Cldr.Calendar.calendar()) :: Date.Range.t()
@spec year(Date.t()) :: Date.Range.t()
def year(%{calendar: Calendar.ISO} = date) do
%{date | calendar: Cldr.Calendar.Gregorian}
|> year
|> coerce_iso_calendar
end
def year(%{year: _, month: _, day: _} = date) do
year(date.year, date.calendar)
end
def year(year, calendar \\ Cldr.Calendar.Gregorian) do
calendar.year(year)
end
@doc """
Returns a `Date.Range.t` that represents
the `quarter`.
The range is enumerable.
## Arguments
* `year` is any `year` for `calendar`
* `quarter` is any `quarter` in the
` year` for `calendar`
* `calendar` is any module that implements
the `Calendar` and `Cldr.Calendar`
behaviours. The default is `Cldr.Calendar.Gregorian`.
## Returns
* A `Date.Range.t()` representing the
the enumerable days in the `quarter`
## Examples
iex> Cldr.Calendar.Interval.quarter 2019, 2, Cldr.Calendar.Fiscal.UK
#DateRange<~D[2019-04-01 Cldr.Calendar.Fiscal.UK], ~D[2019-06-30 Cldr.Calendar.Fiscal.UK]>
iex> Cldr.Calendar.Interval.quarter 2019, 2, Cldr.Calendar.ISOWeek
#DateRange<~D[2019-W14-1 Cldr.Calendar.ISOWeek], ~D[2019-W26-7 Cldr.Calendar.ISOWeek]>
"""
@spec quarter(Calendar.year(), Cldr.Calendar.quarter(), Cldr.Calendar.calendar()) ::
Date.Range.t()
@spec quarter(Date.t()) :: Date.Range.t()
def quarter(%{calendar: Calendar.ISO} = date) do
%{date | calendar: Cldr.Calendar.Gregorian}
|> quarter
|> coerce_iso_calendar
end
def quarter(date) do
quarter = Cldr.Calendar.quarter_of_year(date)
quarter(date.year, quarter, date.calendar)
end
def quarter(year, quarter, calendar \\ Cldr.Calendar.Gregorian) do
calendar.quarter(year, quarter)
end
@doc """
Returns a `Date.Range.t` that represents
the `year`.
The range is enumerable.
## Arguments
* `year` is any `year` for `calendar`
* `month` is any `month` in the `year`
for `calendar`
* `calendar` is any module that implements
the `Calendar` and `Cldr.Calendar`
behaviours. The default is `Cldr.Calendar.Gregorian`.
## Returns
* A `Date.Range.t()` representing the
the enumerable days in the `month`
## Examples
iex> Cldr.Calendar.Interval.month 2019, 3, Cldr.Calendar.Fiscal.UK
#DateRange<~D[2019-03-01 Cldr.Calendar.Fiscal.UK], ~D[2019-03-30 Cldr.Calendar.Fiscal.UK]>
iex> Cldr.Calendar.Interval.month 2019, 3, Cldr.Calendar.Fiscal.US
#DateRange<~D[2019-03-01 Cldr.Calendar.Fiscal.US], ~D[2019-03-31 Cldr.Calendar.Fiscal.US]>
"""
@spec month(Calendar.year(), Calendar.month(), Cldr.Calendar.calendar()) :: Date.Range.t()
@spec month(Date.t()) :: Date.Range.t()
def month(%{calendar: Calendar.ISO} = date) do
%{date | calendar: Cldr.Calendar.Gregorian}
|> month
|> coerce_iso_calendar
end
def month(date) do
month = Cldr.Calendar.month_of_year(date)
month(date.year, month, date.calendar)
end
def month(year, month, calendar \\ Cldr.Calendar.Gregorian) do
calendar.month(year, month)
end
@doc """
Returns a `Date.Range.t` that represents
the `year`.
The range is enumerable.
## Arguments
* `year` is any `year` for `calendar`
* `week` is any `week` in the `year`
for `calendar`
* `calendar` is any module that implements
the `Calendar` and `Cldr.Calendar`
behaviours. The default is `Cldr.Calendar.Gregorian`.
## Returns
* A `Date.Range.t()` representing the
the enumerable days in the `week` or
* `{:error, :not_defined}` if the calendar
does not support the concept of weeks
## Examples
iex> Cldr.Calendar.Interval.week 2019, 52, Cldr.Calendar.Fiscal.US
#DateRange<~D[2019-12-22 Cldr.Calendar.Fiscal.US], ~D[2019-12-28 Cldr.Calendar.Fiscal.US]>
iex> Cldr.Calendar.Interval.week 2019, 52, Cldr.Calendar.NRF
#DateRange<~D[2019-W52-1 Cldr.Calendar.NRF], ~D[2019-W52-7 Cldr.Calendar.NRF]>
iex> Cldr.Calendar.Interval.week 2019, 52, Cldr.Calendar.ISOWeek
#DateRange<~D[2019-W52-1 Cldr.Calendar.ISOWeek], ~D[2019-W52-7 Cldr.Calendar.ISOWeek]>
iex> Cldr.Calendar.Interval.week 2019, 52, Cldr.Calendar.Julian
{:error, :not_defined}
"""
@spec week(Calendar.year(), Cldr.Calendar.week(), Cldr.Calendar.calendar()) :: Date.Range.t()
@spec week(Date.t()) :: Date.Range.t()
def week(%{calendar: Calendar.ISO} = date) do
%{date | calendar: Cldr.Calendar.Gregorian}
|> week
|> coerce_iso_calendar
end
def week(date) do
{year, week} = Cldr.Calendar.week_of_year(date)
week(year, week, date.calendar)
end
def week(year, week, calendar \\ Cldr.Calendar.Gregorian) do
calendar.week(year, week)
end
@doc """
Returns a `Date.Range.t` that represents
the `day`.
The range is enumerable.
## Arguments
* `year` is any `year` for `calendar`
* `day` is any `day` in the `year`
for `calendar`
* `calendar` is any module that implements
the `Calendar` and `Cldr.Calendar`
behaviours. The default is `Cldr.Calendar.Gregorian`.
## Returns
* A `Date.Range.t()` representing the
the enumerable days in the `week`
## Examples
iex> Cldr.Calendar.Interval.day 2019, 52, Cldr.Calendar.Fiscal.US
#DateRange<~D[2019-02-21 Cldr.Calendar.Fiscal.US], ~D[2019-02-21 Cldr.Calendar.Fiscal.US]>
iex> Cldr.Calendar.Interval.day 2019, 92, Cldr.Calendar.NRF
#DateRange<~D[2019-W14-1 Cldr.Calendar.NRF], ~D[2019-W14-1 Cldr.Calendar.NRF]>
Cldr.Calendar.Interval.day 2019, 8, Cldr.Calendar.ISOWeek
#DateRange<%Date{calendar: Cldr.Calendar.ISOWeek, day: 1, month: 2, year: 2019}, %Date{calendar: Cldr.Calendar.ISOWeek, day: 1, month: 2, year: 2019}>
"""
@spec day(Calendar.year(), Calendar.day(), Cldr.Calendar.calendar()) :: Date.Range.t()
@spec day(Date.t()) :: Date.Range.t()
def day(%{calendar: Calendar.ISO} = date) do
%{date | calendar: Cldr.Calendar.Gregorian}
|> day
|> coerce_iso_calendar
end
def day(date) do
Date.range(date, date)
end
def day(year, day, calendar \\ Cldr.Calendar.Gregorian) do
if day <= calendar.days_in_year(year) do
iso_days = calendar.first_gregorian_day_of_year(year) + day - 1
with {year, month, day} = calendar.date_from_iso_days(iso_days),
{:ok, date} <- Date.new(year, month, day, calendar) do
day(date)
end
else
{:error, :invalid_date}
end
end
@doc """
Compare two date ranges.
Uses [Allen's Interval Algebra](https://en.wikipedia.org/wiki/Allen%27s_interval_algebra)
to return one of 13 different relationships:
Relation | Converse
---------- | --------------
:precedes | :preceded_by
:meets | :met_by
:overlaps | :overlapped_by
:finished_by | :finishes
:contains | :during
:starts | :started_by
:equals | :equals
## Arguments
* `range_1` is a `Date.Range.t`
* `range_2` is a `Date.Range.t`
## Returns
An atom representing the relationship between the two ranges.
## Examples
iex> Cldr.Calendar.Interval.compare Cldr.Calendar.Interval.day(~D[2019-01-01]),
...> Cldr.Calendar.Interval.day(~D[2019-01-02])
:meets
iex> Cldr.Calendar.Interval.compare Cldr.Calendar.Interval.day(~D[2019-01-01]),
...> Cldr.Calendar.Interval.day(~D[2019-01-03])
:precedes
iex> Cldr.Calendar.Interval.compare Cldr.Calendar.Interval.day(~D[2019-01-03]),
...> Cldr.Calendar.Interval.day(~D[2019-01-01])
:preceded_by
iex> Cldr.Calendar.Interval.compare Cldr.Calendar.Interval.day(~D[2019-01-02]),
...> Cldr.Calendar.Interval.day(~D[2019-01-01])
:met_by
iex> Cldr.Calendar.Interval.compare Cldr.Calendar.Interval.day(~D[2019-01-02]),
...> Cldr.Calendar.Interval.day(~D[2019-01-02])
:equals
"""
@spec compare(range_1 :: Date.Range.t(), range_2 :: Date.Range.t()) ::
Cldr.Calendar.interval_relation()
def compare(
%Date.Range{first_in_iso_days: first, last_in_iso_days: last},
%Date.Range{first_in_iso_days: first, last_in_iso_days: last}
) do
:equals
end
def compare(%Date.Range{} = r1, %Date.Range{} = r2) do
cond do
r1.last_in_iso_days - r2.first_in_iso_days < -1 ->
:precedes
r1.last_in_iso_days - r2.first_in_iso_days == -1 ->
:meets
r1.first_in_iso_days < r2.first_in_iso_days && r1.last_in_iso_days > r2.last_in_iso_days ->
:contains
r1.last_in_iso_days == r2.last_in_iso_days && r1.first_in_iso_days < r2.first_in_iso_days ->
:finished_by
r1.first_in_iso_days < r2.first_in_iso_days && r1.last_in_iso_days > r2.first_in_iso_days ->
:overlaps
r1.first_in_iso_days == r2.first_in_iso_days && r1.last_in_iso_days < r2.last_in_iso_days ->
:starts
r2.last_in_iso_days - r1.first_in_iso_days < -1 ->
:preceded_by
r2.last_in_iso_days - r1.first_in_iso_days == -1 ->
:met_by
r2.last_in_iso_days == r1.last_in_iso_days && r2.first_in_iso_days < r1.first_in_iso_days ->
:finishes
r1.first_in_iso_days > r2.first_in_iso_days && r1.last_in_iso_days < r2.last_in_iso_days ->
:during
r2.first_in_iso_days == r1.first_in_iso_days && r1.last_in_iso_days > r2.last_in_iso_days ->
:started_by
r2.last_in_iso_days > r1.first_in_iso_days && r2.last_in_iso_days < r1.last_in_iso_days ->
:overlapped_by
end
end
@doc false
def to_iso_calendar(%Date.Range{first: first, last: last}) do
Date.range(Date.convert!(first, Calendar.ISO), Date.convert!(last, Calendar.ISO))
end
@doc false
def coerce_iso_calendar(%Date.Range{first: first, last: last}) do
first = %{first | calendar: Calendar.ISO}
last = %{last | calendar: Calendar.ISO}
Date.range(first, last)
end
end