@@ -152,6 +152,86 @@ defmodule Shared.Zeitraum do
152152 end
153153 end
154154
155+ @ doc """
156+ Bildet die Vereinigung zweier Zeiträume.
157+
158+ Sollten diese nicht überlappen werden beide als tuple zurückgegeben.
159+
160+ ## Beispiel
161+
162+ iex> vereinigung(~v[2025-05], ~m[2025-01])
163+ %Timex.Interval{from: ~N[2025-01-01 00:00:00], until: ~N[2025-02-03 00:00:00], step: [seconds: 1]}
164+
165+ iex> vereinigung(~v[2025-05], ~v[2025-04])
166+ %Timex.Interval{from: ~N[2025-01-20 00:00:00], until: ~N[2025-02-03 00:00:00], step: [seconds: 1]}
167+
168+ iex> vereinigung(~v[2025-05], ~v[2025-03])
169+ {%Timex.Interval{from: ~N[2025-01-27 00:00:00], until: ~N[2025-02-03 00:00:00], step: [seconds: 1]},
170+ %Timex.Interval{from: ~N[2025-01-13 00:00:00], until: ~N[2025-01-20 00:00:00], step: [seconds: 1]}}
171+ """
172+ @ spec vereinigung ( t ( ) , t ( ) ) :: t ( ) | { t ( ) , t ( ) }
173+ def vereinigung ( a , b ) do
174+ intervall_a = als_intervall ( a )
175+ intervall_b = als_intervall ( b )
176+
177+ if ueberschneidung? ( intervall_a , intervall_b ) or grenzen_aneinander? ( intervall_a , intervall_b ) do
178+ from = Enum . min ( [ intervall_a . from , intervall_b . from ] , NaiveDateTime )
179+ until = Enum . max ( [ intervall_a . until , intervall_b . until ] , NaiveDateTime )
180+
181+ Timex.Interval . new ( from: from , until: until , step: [ seconds: 1 ] )
182+ else
183+ { intervall_a , intervall_b }
184+ end
185+ end
186+
187+ @ doc """
188+ Bildet die Vereinigung aller Zeiträume.
189+
190+ Gibt eine Liste nicht überlappender Zeiträume zurück.
191+
192+ ## Beispiel
193+
194+ iex> vereinigung([~v[2025-05], ~m[2025-01]])
195+ [%Timex.Interval{from: ~N[2025-01-01 00:00:00], until: ~N[2025-02-03 00:00:00], step: [seconds: 1]}]
196+
197+ iex> vereinigung([~v[2025-05], ~v[2025-04]])
198+ [%Timex.Interval{from: ~N[2025-01-20 00:00:00], until: ~N[2025-02-03 00:00:00], step: [seconds: 1]}]
199+
200+ iex> vereinigung([~v[2025-05], ~v[2025-03]])
201+ [%Timex.Interval{from: ~N[2025-01-13 00:00:00], until: ~N[2025-01-20 00:00:00], step: [seconds: 1]},
202+ %Timex.Interval{from: ~N[2025-01-27 00:00:00], until: ~N[2025-02-03 00:00:00], step: [seconds: 1]}]
203+
204+ iex> vereinigung([~D[2025-01-01], ~D[2025-01-02], ~D[2025-01-07], ~D[2025-01-02], ~D[2025-01-05], ~v[2025-01]])
205+ [%Timex.Interval{from: ~N[2024-12-30 00:00:00], until: ~N[2025-01-06 00:00:00], step: [seconds: 1]},
206+ %Timex.Interval{from: ~N[2025-01-07 00:00:00], until: ~N[2025-01-08 00:00:00], step: [seconds: 1]}]
207+
208+ """
209+ @ spec vereinigung ( [ t ( ) ] ) :: [ t ( ) ]
210+ def vereinigung ( intervalle ) do
211+ intervalle
212+ |> Enum . map ( & als_intervall / 1 )
213+ |> Enum . sort ( __MODULE__ )
214+ |> vereinige_sortierte_intervalle ( )
215+ end
216+
217+ defp vereinige_sortierte_intervalle ( [ ] ) , do: [ ]
218+ defp vereinige_sortierte_intervalle ( [ einzelnes ] ) , do: [ einzelnes ]
219+
220+ defp vereinige_sortierte_intervalle ( [ erstes , zweites | rest ] ) do
221+ case vereinigung ( erstes , zweites ) do
222+ % Timex.Interval { } = vereinigtes ->
223+ vereinige_sortierte_intervalle ( [ vereinigtes | rest ] )
224+
225+ { _erstes , _zweites } ->
226+ [ erstes | vereinige_sortierte_intervalle ( [ zweites | rest ] ) ]
227+ end
228+ end
229+
230+ defp grenzen_aneinander? ( intervall_a , intervall_b ) do
231+ NaiveDateTime . compare ( intervall_a . until , intervall_b . from ) == :eq or
232+ NaiveDateTime . compare ( intervall_b . until , intervall_a . from ) == :eq
233+ end
234+
155235 @ doc """
156236 Testet ob der beginn des ersten Zeitraums vor dem des zweiten liegt.
157237
0 commit comments