time_range_ext/
working_hours.rs1use std::ops::{Add, Sub};
2use time::{OffsetDateTime, Time, Weekday};
3use time::ext::NumericalDuration;
4use crate::TimeRange;
5
6#[derive(Debug, Clone)]
7pub struct WorkingHours {
8 pub start: Time,
9 pub end: Time,
10 pub active_days: Vec<Weekday>,
11
12 pub lower_bound: Option<OffsetDateTime>,
13 pub upper_bound: Option<OffsetDateTime>
14}
15
16impl WorkingHours {
17 pub fn is_active(&self) -> bool {
18 !self.active_days.is_empty()
19 }
20
21 pub fn active_during_ts(&self, time: OffsetDateTime) -> bool {
22 self.active_days.contains(&time.weekday()) && self.start <= time.time() && time.time() <= self.end
23 }
24
25 pub fn active_during_day(&self, day: Weekday) -> bool {
26 self.active_days.contains(&day)
27 }
28
29 pub fn working_time_in_range(&self, range: TimeRange) -> Vec<TimeRange> {
30 if !self.is_active() {
31 return vec![];
32 }
33
34 let range_start = range.start;
35 let range_end = range.end;
36
37 let mut working_times = vec![];
38 let mut current = range_start;
39
40 if let Some(previous_working_hours) = self.previous_working_hours(current) {
41 if range_start <= previous_working_hours.end {
42 working_times.push(previous_working_hours);
43 }
44 }
45
46 while current < range_end {
47 if !self.active_during_day(current.weekday()) {
48 current = current.add(1.days());
49 continue;
50 }
51
52 let next_shift = self.next_working_hours(current);
53
54 if let Some(next_working_hours) = next_shift {
55 if next_working_hours.start <= range_end && !working_times.contains(&next_working_hours) {
56 working_times.push(next_working_hours);
57 }
58 }
59
60 current = current.add(1.days());
61 }
62
63 if let Some(first) = working_times.first_mut() {
64 if first.start < range_start {
65 first.start = range_start;
66 }
67 }
68
69 if let Some(last) = working_times.last_mut() {
70 if last.end > range_end {
71 last.end = range_end;
72 }
73 }
74
75 working_times
76 }
77
78 fn exceeds_bounds(&self, ts: OffsetDateTime) -> bool {
79 self.lower_bound.map_or(false, |l| ts < l) || self.upper_bound.map_or(false, |u| ts > u)
80 }
81
82 fn previous_working_hours(&self, ts: OffsetDateTime) -> Option<TimeRange> {
83 let mut current = ts;
84
85 if !self.is_active() {
86 return None;
87 }
88
89 if self.exceeds_bounds(current) {
90 return None;
91 }
92
93 if self.start > ts.time() {
96 current = current.sub(1.days());
97 }
98
99 while !self.active_during_day(current.weekday()) {
100 current = current.sub(1.days());
101 }
102
103 if self.start > self.end {
105 let start_date = current.replace_time(self.start);
106 let mut end_date = current.replace_time(self.end);
107
108 if end_date < start_date {
109 end_date = end_date.add(1.days());
110 }
111
112 if self.upper_bound.map_or(false, |r| end_date > r) {
115 end_date = self.upper_bound.unwrap();
116 }
117
118 return Some(TimeRange {
119 start: start_date,
120 end: end_date,
121 });
122 }
123
124 let mut end = current.replace_time(self.end);
125
126 if self.upper_bound.map_or(false, |r| end > r) {
128 end = self.upper_bound.unwrap();
129 }
130
131 let mut start = current.replace_time(self.start);
132
133 if self.lower_bound.map_or(false, |l| start < l) {
134 start = self.lower_bound.unwrap();
135 }
136
137 Some(TimeRange { start, end })
138 }
139
140 fn next_working_hours(&self, ts: OffsetDateTime) -> Option<TimeRange> {
141 let mut current = ts;
142
143 if !self.is_active() {
144 return None;
145 }
146
147 if self.exceeds_bounds(current) {
148 return None;
149 }
150
151 if current.time() > self.start && current.time() > self.end {
152 current = current.add(1.days());
153 }
154
155 while !self.active_during_day(current.weekday()) {
156 current = current.add(time::Duration::days(1));
157 }
158
159 if self.start > self.end {
160 let start_date = current.replace_time(self.start);
161 let mut end_date = current.replace_time(self.end).add(1.days());
162
163 if self.upper_bound.map_or(false, |r| end_date > r) {
166 end_date = self.upper_bound.unwrap();
167 }
168
169 if end_date < start_date {
170 end_date = end_date.add(1.days());
171 }
172
173 return Some(TimeRange {
174 start: start_date,
175 end: end_date,
176 });
177 }
178
179 let mut end = current.replace_time(self.end);
180 if self.upper_bound.map_or(false, |r| end > r) {
181 end = self.upper_bound.unwrap();
182 }
183
184 let mut start = current.replace_time(self.start);
185
186 if self.lower_bound.map_or(false, |l| start < l) {
187 start = self.lower_bound.unwrap();
188 }
189
190 if start > end {
191 return None;
192 }
193
194 Some(TimeRange { start, end })
195 }
196}