dpm_model/calendar/working/
arithmetic.rs1use super::{BeyondCalendar, WorkingTime, hours, millis};
8
9impl WorkingTime {
10 fn at(&self, t: f64) -> Result<i64, BeyondCalendar> {
11 let t = millis(t).ok_or(BeyondCalendar)?;
12 if t < self.window.0 || t > self.window.1 {
13 return Err(BeyondCalendar);
14 }
15 Ok(t)
16 }
17
18 fn span(&self, index: usize) -> Result<((i64, i64), i64), BeyondCalendar> {
19 match (self.spans.get(index), self.before.get(index)) {
20 (Some(span), Some(before)) => Ok((*span, *before)),
21 _ => Err(BeyondCalendar),
22 }
23 }
24
25 fn locate(&self, t: i64) -> Result<(i64, Option<(i64, i64)>), BeyondCalendar> {
28 let Some(index) = self.starts.through(t).checked_sub(1) else {
29 return Ok((0, None));
30 };
31 let ((start, end), before) = self.span(index)?;
32 Ok((before + t.min(end) - start, Some((start, end))))
33 }
34
35 fn worked(&self, t: i64) -> Result<i64, BeyondCalendar> {
37 Ok(self.locate(t)?.0)
38 }
39
40 fn instant(&self, index: usize, target: i64) -> Result<i64, BeyondCalendar> {
42 let ((start, _), before) = self.span(index)?;
43 let at = start + (target - before);
44 if at > self.window.1 {
45 return Err(BeyondCalendar);
46 }
47 Ok(at)
48 }
49
50 pub fn add(&self, t: f64, hours_of_work: f64) -> Result<f64, BeyondCalendar> {
52 if self.always {
53 return Ok(t + hours_of_work.max(0.0));
54 }
55 let from = self.at(t)?;
56 let amount = millis(hours_of_work).ok_or(BeyondCalendar)?;
57 if amount <= 0 {
58 return Ok(t);
59 }
60 let (worked, span) = self.locate(from)?;
61 if let Some((start, end)) = span
63 && from >= start
64 && from < end
65 && from + amount <= end
66 {
67 return Ok(hours(from + amount));
68 }
69 let target = worked + amount;
70 let end = self.instant(self.through.below(target), target)?;
71 Ok(hours(end.max(from)))
72 }
73
74 pub fn add_late(&self, t: f64, hours_of_work: f64) -> Result<f64, BeyondCalendar> {
77 if self.always {
78 return Ok(t + hours_of_work.max(0.0));
79 }
80 let from = self.at(t)?;
81 let amount = millis(hours_of_work).ok_or(BeyondCalendar)?.max(0);
82 let target = self.worked(from)? + amount;
83 let end = self.instant(self.through.through(target), target)?;
84 Ok(hours(end.max(from)))
85 }
86
87 pub fn sub(&self, t: f64, hours_of_work: f64) -> Result<f64, BeyondCalendar> {
89 if self.always {
90 return Ok(t - hours_of_work.max(0.0));
91 }
92 let to = self.at(t)?;
93 let amount = millis(hours_of_work).ok_or(BeyondCalendar)?;
94 if amount <= 0 {
95 return Ok(t);
96 }
97 let (worked, span) = self.locate(to)?;
98 if let Some((start, end)) = span
100 && to > start
101 && to <= end
102 && to - amount > start
103 {
104 return Ok(hours(to - amount));
105 }
106 let target = worked - amount;
107 if target < 0 {
108 return Err(BeyondCalendar);
109 }
110 let start = self.instant(self.through.through(target), target)?;
111 Ok(hours(start.min(to)))
112 }
113
114 pub fn shift(&self, t: f64, hours_of_work: f64) -> Result<f64, BeyondCalendar> {
116 if hours_of_work >= 0.0 {
117 self.add(t, hours_of_work)
118 } else {
119 self.sub(t, -hours_of_work)
120 }
121 }
122
123 pub fn unshift(&self, t: f64, hours_of_work: f64) -> Result<f64, BeyondCalendar> {
125 if hours_of_work >= 0.0 {
126 self.sub(t, hours_of_work)
127 } else {
128 self.add_late(t, -hours_of_work)
129 }
130 }
131
132 pub fn align(&self, t: f64) -> Result<f64, BeyondCalendar> {
134 if self.always {
135 return Ok(t);
136 }
137 let at = self.at(t)?;
138 let ((start, _), _) = self.span(self.ends.below(at))?;
139 Ok(if start > at { hours(start) } else { t })
140 }
141
142 pub fn next_working(&self, t: f64) -> Result<f64, BeyondCalendar> {
145 if self.always {
146 return Ok(t);
147 }
148 let at = self.at(t)?;
149 let ((start, _), _) = self.span(self.ends.through(at))?;
150 Ok(if start > at { hours(start) } else { t })
151 }
152
153 pub fn place(&self, t: f64, hours_of_work: f64) -> Result<(f64, f64), BeyondCalendar> {
157 if self.always {
158 return Ok((t, t + hours_of_work.max(0.0)));
159 }
160 let amount = millis(hours_of_work).ok_or(BeyondCalendar)?;
161 if amount <= 0 {
162 return Ok((t, t));
163 }
164 let at = self.at(t)?;
165 let index = self.ends.through(at);
166 let ((start, end), before) = self.span(index)?;
167 let begin = start.max(at);
168 let starts = if begin > at { hours(begin) } else { t };
169 if begin + amount <= end {
170 return Ok((starts, hours(begin + amount)));
171 }
172 let target = before + (begin - start) + amount;
173 let finish = self.instant(self.through.below(target), target)?;
174 Ok((starts, hours(finish.max(begin))))
175 }
176
177 pub fn retreat(&self, t: f64, hours_of_work: f64) -> Result<f64, BeyondCalendar> {
180 if self.always {
181 return Ok(t - hours_of_work.max(0.0));
182 }
183 let at = self.at(t)?;
184 let index = self
185 .starts
186 .through(at)
187 .checked_sub(1)
188 .ok_or(BeyondCalendar)?;
189 let ((start, end), before) = self.span(index)?;
190 let to = at.min(end);
191 let amount = millis(hours_of_work).ok_or(BeyondCalendar)?;
192 if amount <= 0 {
193 return Ok(if end < at { hours(end) } else { t });
194 }
195 if to - amount > start {
196 return Ok(hours(to - amount));
197 }
198 let target = before + (to - start) - amount;
199 if target < 0 {
200 return Err(BeyondCalendar);
201 }
202 let begin = self.instant(self.through.through(target), target)?;
203 Ok(hours(begin.min(to)))
204 }
205
206 pub fn align_back(&self, t: f64) -> Result<f64, BeyondCalendar> {
208 if self.always {
209 return Ok(t);
210 }
211 let at = self.at(t)?;
212 let index = self
213 .starts
214 .through(at)
215 .checked_sub(1)
216 .ok_or(BeyondCalendar)?;
217 let ((_, end), _) = self.span(index)?;
218 Ok(if end < at { hours(end) } else { t })
219 }
220
221 pub fn between(&self, a: f64, b: f64) -> Result<f64, BeyondCalendar> {
223 if self.always {
224 return Ok((b - a).max(0.0));
225 }
226 let worked = self.worked(self.at(b)?)? - self.worked(self.at(a)?)?;
227 Ok(hours(worked.max(0)))
228 }
229}