Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

English ordinal suffix format option added as {S} #479

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions lib/date/date.ex
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,16 @@ defimpl Timex.Protocol, for: Date do
def day(%Date{} = date),
do: 1 + Timex.diff(date, %Date{:year => date.year, :month => 1, :day => 1}, :days)

def ordinal_suffix(%Date{} = date) do
value = Integer.mod(date.day, 10)
cond do
value == 1 && date.day != 11 -> "st"
value == 2 && date.day != 12 -> "nd"
value == 3 && date.day != 13 -> "rd"
true -> "th"
end
end

def is_valid?(%Date{:year => y, :month => m, :day => d}) do
:calendar.valid_date({y,m,d})
end
Expand Down
10 changes: 10 additions & 0 deletions lib/datetime/datetime.ex
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,16 @@ defimpl Timex.Protocol, for: DateTime do
1 + Timex.diff(date, ref, :days)
end

def ordinal_suffix(%DateTime{} = date) do
value = Integer.mod(date.day, 10)
cond do
value == 1 && date.day != 11 -> "st"
value == 2 && date.day != 12 -> "nd"
value == 3 && date.day != 13 -> "rd"
true -> "th"
end
end

def is_valid?(%DateTime{:year => y, :month => m, :day => d,
:hour => h, :minute => min, :second => sec}) do
:calendar.valid_date({y,m,d}) and Timex.is_valid_time?({h,min,sec})
Expand Down
20 changes: 20 additions & 0 deletions lib/datetime/erlang.ex
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,26 @@ defimpl Timex.Protocol, for: Tuple do
do: 1 + Timex.diff(date, {y,1,1}, :days)
def day(_), do: {:error, :invalid_date}

def ordinal_suffix({y,m,d} = date) when is_date(y,m,d) do
value = Integer.mod(date.day, 10)
cond do
value == 1 && date.day != 11 -> "st"
value == 2 && date.day != 12 -> "nd"
value == 3 && date.day != 13 -> "rd"
true -> "th"
end
end
def ordinal_suffix({{y,m,d} = date,_}) when is_date(y,m,d) do
value = Integer.mod(date.day, 10)
cond do
value == 1 && date.day != 11 -> "st"
value == 2 && date.day != 12 -> "nd"
value == 3 && date.day != 13 -> "rd"
true -> "th"
end
end
def ordinal_suffix(_), do: {:error, :invalid_date}

def is_valid?({y,m,d}) when is_date(y,m,d), do: true
def is_valid?({{y,m,d},{h,mm,s}}) when is_datetime(y,m,d,h,mm,s), do: true
def is_valid?({{y,m,d},{h,mm,s,_us}}) when is_datetime(y,m,d,h,mm,s), do: true
Expand Down
1 change: 1 addition & 0 deletions lib/datetime/map.ex
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ defimpl Timex.Protocol, for: Map do
def week_of_month(map), do: convert!(map, :week_of_month)
def weekday(map), do: convert!(map, :weekday)
def day(map), do: convert!(map, :day)
def ordinal_suffix(map), do: convert!(map, :ordinal_suffix)
def is_valid?(map), do: convert!(map, :is_valid?)
def iso_week(map), do: convert!(map, :iso_week)
def from_iso_day(map, day), do: convert(map, :from_iso_day, [day])
Expand Down
10 changes: 10 additions & 0 deletions lib/datetime/naivedatetime.ex
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,16 @@ defimpl Timex.Protocol, for: NaiveDateTime do
1 + Timex.diff(date, nd, :days)
end

def ordinal_suffix(%NaiveDateTime{} = date) do
value = Integer.mod(date.day, 10)
cond do
value == 1 && date.day != 11 -> "st"
value == 2 && date.day != 12 -> "nd"
value == 3 && date.day != 13 -> "rd"
true -> "th"
end
end

def is_valid?(%NaiveDateTime{:year => y, :month => m, :day => d,
:hour => h, :minute => min, :second => sec}) do
:calendar.valid_date({y,m,d}) and Timex.is_valid_time?({h,min,sec})
Expand Down
2 changes: 2 additions & 0 deletions lib/format/datetime/formatter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,8 @@ defmodule Timex.Format.DateTime.Formatter do
do: pad_numeric(date.day, flags, width)
def format_token(_locale, :oday, date, _modifiers, flags, width),
do: pad_numeric(Timex.day(date), flags, width)
def format_token(_locale, :suffix, date, _modifiers, flags, width),
do: pad_numeric(Timex.ordinal_suffix(date), flags, width)
# Weeks
def format_token(_locale, :iso_weeknum, date, _modifiers, flags, width) do
{_, week} = Timex.iso_week(date)
Expand Down
3 changes: 2 additions & 1 deletion lib/parse/datetime/tokenizers/default.ex
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ defmodule Timex.Parse.DateTime.Tokenizers.Default do
# Months
"Mshort", "Mfull", "M",
# Days
"Dord", "D",
"Dord", "D", "S",
# Weeks
"Wiso", "Wmon", "Wsun", "WDmon", "WDsun", "WDshort", "WDfull",
# Time
Expand Down Expand Up @@ -98,6 +98,7 @@ defmodule Timex.Parse.DateTime.Tokenizers.Default do
# Days
"D" -> set_width(1, 2, :day, directive, opts)
"Dord" -> set_width(1, 3, :oday, directive, opts)
"S" -> force_width(2, :suffix, directive, opts)
# Weeks
"Wiso" -> force_width(2, :iso_weeknum, directive, opts)
"Wmon" -> set_width(1, 2, :week_mon, directive, opts)
Expand Down
2 changes: 1 addition & 1 deletion lib/parse/datetime/tokenizers/directive.ex
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ defmodule Timex.Parse.DateTime.Tokenizers.Directive do
]
@mapped_types [iso_year4: :year4, iso_year2: :year2,
month: :month2, mshort: :month_short, mfull: :month_full,
day: :day_of_month, oday: :day_of_year,
day: :day_of_month, oday: :day_of_year, suffix: :day_of_month,
iso_weeknum: :week_of_year, week_mon: :week_of_year, week_sun: :week_of_year_sun,
wday_mon: :weekday, wday_sun: :weekday, wdshort: :weekday_short, wdfull: :weekday_full,
min: :minute, sec: :second, sec_fractional: :second_fractional, sec_epoch: :seconds_epoch,
Expand Down
9 changes: 9 additions & 0 deletions lib/protocol.ex
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,12 @@ defprotocol Timex.Protocol do
@spec day(Types.valid_datetime) :: Types.daynum | {:error, term}
def day(datetime)

@doc """
Get the English ordinal suffix (st, nd, rd, th) of the given date/time value
"""
@spec ordinal_suffix(Types.valid_datetime) :: Types.daynum | {:error, term}
def ordinal_suffix(datetime)

@doc """
Determine if the provided date/time value is valid.
"""
Expand Down Expand Up @@ -280,6 +286,9 @@ defimpl Timex.Protocol, for: Any do
def day(%{__struct__: _} = d), do: Timex.day(Map.from_struct(d))
def day(_datetime), do: {:error, :invalid_date}

def ordinal_suffix(%{__struct__: _} = d), do: Timex.ordinal_suffix(Map.from_struct(d))
def ordinal_suffix(_datetime), do: {:error, :invalid_date}

def is_valid?(%{__struct__: _} = d), do: Timex.is_valid?(Map.from_struct(d))
def is_valid?(_datetime), do: false

Expand Down
11 changes: 11 additions & 0 deletions lib/timex.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1210,6 +1210,17 @@ defmodule Timex do
@spec day(Types.valid_datetime()) :: Types.daynum() | {:error, term}
defdelegate day(datetime), to: Timex.Protocol

@doc """
Returns the English ordinal suffix (st, nd, rd, th) for the day.

## Examples

iex> Timex.ordinal_suffix(~D[2015-06-26])
th
"""
@spec ordinal_suffix(Types.valid_datetime()) :: Types.daynum() | {:error, term}
defdelegate ordinal_suffix(datetime), to: Timex.Protocol

@doc """
Return the number of days in the month which the date falls on.

Expand Down