11defmodule Shared.Month do
2- defmodule InvalidMonthIndex do
2+ defmodule InvalidMonthIndexError do
33 defexception [ :message ]
44 end
55
@@ -48,7 +48,7 @@ defmodule Shared.Month do
4848 %Month{year: 2019, month: 7}
4949
5050 iex> Month.new!(2019, -7)
51- ** (Shared.Month.InvalidMonthIndex ) Month must be an integer between 1 and 12, but was -7
51+ ** (Shared.Month.InvalidMonthIndexError ) Month must be an integer between 1 and 12, but was -7
5252
5353 """
5454 def new! ( year , month ) do
@@ -57,7 +57,7 @@ defmodule Shared.Month do
5757 month
5858
5959 { :error , :invalid_month_index } ->
60- raise InvalidMonthIndex ,
60+ raise InvalidMonthIndexError ,
6161 "Month must be an integer between 1 and 12, but was " <> inspect ( month )
6262 end
6363 end
@@ -107,7 +107,7 @@ defmodule Shared.Month do
107107 %Month{year: 2018, month: 5}
108108
109109 iex> Month.from_day!(%Date{year: 2018, month: 13, day: 17})
110- ** (Shared.Month.InvalidMonthIndex ) Month must be an integer between 1 and 12, but was 13
110+ ** (Shared.Month.InvalidMonthIndexError ) Month must be an integer between 1 and 12, but was 13
111111
112112 """
113113 @ spec from_day! ( Date . t ( ) | DateTime . t ( ) | NaiveDateTime . t ( ) ) :: t ( )
@@ -261,6 +261,32 @@ defmodule Shared.Month do
261261
262262 def parse ( _str ) , do: { :error , :invalid_month_format }
263263
264+ @ doc ~S"""
265+ ## Examples
266+
267+ iex> Month.parse!("2019-10")
268+ %Month{year: 2019, month: 10}
269+
270+ iex> Month.parse!("2019-13")
271+ ** (Shared.Month.InvalidMonthIndexError) Invalid month index: 2019-13
272+
273+ iex> Month.parse!("foo")
274+ ** (Shared.Month.InvalidMonthIndexError) Invalid month format: foo
275+
276+ """
277+ def parse! ( string ) do
278+ case parse ( string ) do
279+ { :ok , month } ->
280+ month
281+
282+ { :error , :invalid_month_index } ->
283+ raise InvalidMonthIndexError , "Invalid month index: #{ string } "
284+
285+ { :error , :invalid_month_format } ->
286+ raise InvalidMonthIndexError , "Invalid month format: #{ string } "
287+ end
288+ end
289+
264290 @ doc ~S"""
265291 ## Examples
266292
@@ -297,6 +323,31 @@ defmodule Shared.Month do
297323 """
298324 def to_range ( % __MODULE__ { } = month ) , do: Date . range ( first_day ( month ) , last_day ( month ) )
299325
326+ @ doc ~S"""
327+ Returns an end-exclusive Datetime Interval spanning the whole month.
328+
329+ ## Examples
330+
331+ iex> Shared.Month.to_datetime_interval(@third_month_of_2018)
332+ %Timex.Interval{
333+ from: ~N[2018-03-01 00:00:00],
334+ left_open: false,
335+ right_open: true,
336+ step: [seconds: 1],
337+ until: ~N[2018-04-01 00:00:00]
338+ }
339+
340+ """
341+ @ spec to_datetime_interval ( t ( ) ) :: Shared.Zeitperiode . t ( )
342+ def to_datetime_interval ( % __MODULE__ { } = month ) do
343+ { first_day , last_day } = to_dates ( month )
344+
345+ { :ok , from } = NaiveDateTime . new ( first_day , ~T[ 00:00:00] )
346+ { :ok , until } = NaiveDateTime . new ( Date . add ( last_day , 1 ) , ~T[ 00:00:00] )
347+
348+ Shared.Zeitperiode . new ( from , until )
349+ end
350+
300351 @ doc ~S"""
301352 ## Examples
302353
@@ -329,36 +380,32 @@ defmodule Shared.Month do
329380 do: Date . new! ( year , month , day )
330381
331382 @ doc ~S"""
332- ## Examples
383+ Returns the month advanced by the provided number of months from the starting month.
333384
334- iex> Month.add(@third_month_of_2018, 9)
335- %Month{year: 2018, month: 12}
336-
337- iex> Month.add(@third_month_of_2018, 10)
338- %Month{year: 2019, month: 1}
385+ ## Examples
339386
340- iex> Month.add(@third_month_of_2018, 22 )
341- %Month{year: 2020, month: 1 }
387+ iex> Month.shift(~m[2020-05], 2 )
388+ %Month{year: 2020, month: 7 }
342389
343- iex> Month.add(@third_month_of_2018 , -2 )
344- %Month{year: 2018 , month: 1 }
390+ iex> Month.shift(~m[2020-05] , -5 )
391+ %Month{year: 2019 , month: 12 }
345392
346- iex> Month.add(@third_month_of_2018, -3 )
347- %Month{year: 2017 , month: 12}
393+ iex> Month.shift(~m[2018-03], 9 )
394+ %Month{year: 2018 , month: 12}
348395
349- iex> Month.add(@third_month_of_2018, -15 )
350- %Month{year: 2016 , month: 12 }
396+ iex> Month.shift(~m[2018-03], 10 )
397+ %Month{year: 2019 , month: 1 }
351398
352- iex> Month.add(@third_month_of_2018 , 0)
399+ iex> Month.shift(~m[2018-03] , 0)
353400 %Month{year: 2018, month: 3}
354401
355402 """
356- @ spec add ( t ( ) , integer ( ) ) :: t ( )
357- def add ( % __MODULE__ { } = month , 0 ) , do: month
403+ @ spec shift ( t ( ) , integer ( ) ) :: t ( )
404+ def shift ( % __MODULE__ { } = month , 0 ) , do: month
358405
359- def add ( % __MODULE__ { year: year , month: month } , months_to_add ) when is_integer ( months_to_add ) do
360- new_year = year + div ( months_to_add , 12 )
361- new_month = month + rem ( months_to_add , 12 )
406+ def shift ( % __MODULE__ { year: year , month: month } , amount_of_months ) when is_integer ( amount_of_months ) do
407+ new_year = year + div ( amount_of_months , 12 )
408+ new_month = month + rem ( amount_of_months , 12 )
362409
363410 cond do
364411 new_month > 12 -> new! ( new_year + 1 , new_month - 12 )
@@ -367,46 +414,91 @@ defmodule Shared.Month do
367414 end
368415 end
369416
370- @ doc """
371- Returns the number of months you need to add to first_month to arrive at second_month .
417+ @ doc ~S """
418+ Deprecated: Use `shift/2` instead .
372419 """
373- @ spec diff ( first_month :: t ( ) , second_month :: t ( ) ) :: integer ( )
374- def diff ( % __MODULE__ { year: first_year , month: first_month } , % __MODULE__ {
375- year: second_year ,
376- month: second_month
420+ @ deprecated "Use shift/2 instead"
421+ @ spec add ( t ( ) , integer ( ) ) :: t ( )
422+ def add ( month , amount ) , do: shift ( month , amount )
423+
424+ @ doc ~S"""
425+ Returns the number of months from `from_month` to `to_month`.
426+
427+ ## Examples
428+
429+ iex> Month.diff(~m[2025-02], ~m[2025-01])
430+ 1
431+
432+ iex> Month.diff(~m[2025-01], ~m[2025-01])
433+ 0
434+
435+ iex> Month.diff(~m[2025-01], ~m[2025-02])
436+ -1
437+
438+ iex> Month.diff(~m[2025-01], ~m[2024-01])
439+ 12
440+
441+ """
442+ @ spec diff ( to_month :: t ( ) , from_month :: t ( ) ) :: integer ( )
443+ def diff ( % __MODULE__ { year: to_year , month: to_month } , % __MODULE__ {
444+ year: from_year ,
445+ month: from_month
377446 } ) do
378- 12 * ( second_year - first_year ) + second_month - first_month
447+ 12 * ( to_year - from_year ) + to_month - from_month
379448 end
380449
381450 @ doc ~S"""
451+ Returns true if the first month is before the second month.
452+
382453 ## Examples:
383454
384- iex> @third_month_of_2018 |> Month.earlier_than?(@third_month_of_2019 )
455+ iex> Month.before?(~m[2018-03], ~m[2019-03] )
385456 true
386457
387- iex> @third_month_of_2018 |> Month.earlier_than?(@third_month_of_2017 )
458+ iex> Month.before?(~m[2018-03], ~m[2017-03] )
388459 false
389460
390- iex> @third_month_of_2018 |> Month.earlier_than?(@fourth_month_of_2018 )
461+ iex> Month.before?(~m[2018-03], ~m[2018-04] )
391462 true
392463
393- iex> @third_month_of_2018 |> Month.earlier_than?(@second_month_of_2019)
394- true
395-
396- iex> @third_month_of_2018 |> Month.earlier_than?(@third_month_of_2018)
397- false
398-
399- iex> @third_month_of_2018 |> Month.earlier_than?(@second_month_of_2018)
464+ iex> Month.before?(~m[2018-03], ~m[2018-03])
400465 false
401466
402467 """
403- def earlier_than? ( % __MODULE__ { year: year , month: month } , % __MODULE__ {
468+ @ spec before? ( t ( ) , t ( ) ) :: boolean ( )
469+ def before? ( % __MODULE__ { year: year , month: month } , % __MODULE__ {
404470 year: other_year ,
405471 month: other_month
406472 } ) do
407473 year < other_year || ( year == other_year && month < other_month )
408474 end
409475
476+ @ doc ~S"""
477+ Deprecated: Use `before?/2` instead.
478+ """
479+ @ deprecated "Use before?/2 instead"
480+ def earlier_than? ( month , other_month ) , do: before? ( month , other_month )
481+
482+ @ doc ~S"""
483+ Returns true if the month `month` is after the month `other_month`.
484+
485+ ## Examples:
486+
487+ iex> ~m[2025-03] |> Month.after?(~m[2025-01])
488+ true
489+
490+ iex> ~m[2024-12] |> Month.after?(~m[2025-01])
491+ false
492+
493+ iex> ~m[2025-01] |> Month.after?(~m[2025-01])
494+ false
495+
496+ """
497+ @ spec after? ( t ( ) , t ( ) ) :: boolean ( )
498+ def after? ( % __MODULE__ { } = month , % __MODULE__ { } = other_month ) do
499+ not equal_or_earlier_than? ( month , other_month )
500+ end
501+
410502 @ doc ~S"""
411503 ## Examples:
412504
@@ -430,7 +522,7 @@ defmodule Shared.Month do
430522
431523 """
432524 def equal_or_earlier_than? ( % __MODULE__ { } = month , % __MODULE__ { } = other_month ) do
433- month == other_month || earlier_than ?( month , other_month )
525+ month == other_month || before ?( month , other_month )
434526 end
435527
436528 @ doc ~S"""
@@ -448,7 +540,7 @@ defmodule Shared.Month do
448540 def compare ( % __MODULE__ { } = month , month ) , do: :eq
449541
450542 def compare ( % __MODULE__ { } = first , % __MODULE__ { } = second ) do
451- if first |> earlier_than? ( second ) do
543+ if before? ( first , second ) do
452544 :lt
453545 else
454546 :gt
0 commit comments