1use vstd::arithmetic::div_mod::*;
3use vstd::calc;
4use vstd::prelude::*;
5
6macro_rules! is_ly {
7 ($year:expr) => {
8 $year % 4 == 0 && ($year % 100 != 0 || $year % 400 == 0)
9 };
10}
11
12macro_rules! dim {
13 ($year:expr, $month:expr) => {
14 if $month == 2 {
15 if is_ly!($year) {
16 29u8
17 } else {
18 28u8
19 }
20 } else if $month == 4 || $month == 6 || $month == 9 || $month == 11 {
21 30u8
22 } else if $month >= 1 && $month <= 12 {
23 31u8
24 } else {
25 0u8
26 }
27 };
28}
29
30macro_rules! utc_yr {
31 ($short_year:expr) => {
32 if $short_year >= 50u8 {
33 (1900u16 + $short_year as u16) as u16
34 } else {
35 (2000u16 + $short_year as u16) as u16
36 }
37 };
38}
39
40macro_rules! dt_wf {
41 ($value:expr) => {
42 1 <= $value.month
43 && $value.month <= 12
44 && 1 <= $value.day
45 && $value.day <= days_in_month($value.year, $value.month)
46 && $value.hour <= 23
47 && $value.minute <= 59
48 && $value.second <= 59
49 };
50}
51
52macro_rules! nxt_dy {
53 ($value:expr) => {
54 if $value.day < days_in_month($value.year, $value.month) {
55 Some(DateTime {
56 day: ($value.day + 1) as u8,
57 ..$value
58 })
59 } else if $value.month < 12 {
60 Some(DateTime {
61 month: ($value.month + 1) as u8,
62 day: 1,
63 ..$value
64 })
65 } else if $value.year < u16::MAX {
66 Some(DateTime {
67 year: ($value.year + 1) as u16,
68 month: 1,
69 day: 1,
70 ..$value
71 })
72 } else {
73 None
74 }
75 };
76}
77
78macro_rules! prev_dy {
79 ($value:expr) => {
80 if $value.day > 1 {
81 Some(DateTime {
82 day: ($value.day - 1) as u8,
83 ..$value
84 })
85 } else if $value.month > 1 {
86 Some(DateTime {
87 month: ($value.month - 1) as u8,
88 day: days_in_month($value.year, ($value.month - 1) as u8),
89 ..$value
90 })
91 } else if $value.year > 0 {
92 Some(DateTime {
93 year: ($value.year - 1) as u16,
94 month: 12,
95 day: 31,
96 ..$value
97 })
98 } else {
99 None
100 }
101 };
102}
103
104verus! {
105
106pub const ASCII_0: u8 = 0x30;
108
109pub const ASCII_9: u8 = 0x39;
111
112#[derive(Debug, Clone, Copy, PartialEq, Eq, StructuralEq)]
114pub struct DateTime {
115 pub year: u16,
116 pub month: u8,
117 pub day: u8,
118 pub hour: u8,
119 pub minute: u8,
120 pub second: u8,
121}
122
123impl DeepView for DateTime {
124 type V = DateTime;
125
126 closed spec fn deep_view(&self) -> Self::V {
127 *self
128 }
129}
130
131#[derive(Debug, Clone, Copy, PartialEq, Eq, StructuralEq)]
133pub enum TimePrecision {
134 Hour,
136 Minute,
138 Second,
140}
141
142#[derive(Debug, Clone, Copy, PartialEq, Eq, StructuralEq)]
144pub enum TimeZone {
145 Local,
147 Utc,
149}
150
151#[verifier::allow_in_spec]
153pub fn is_leap_year(year: u16) -> bool
154 returns
155 is_ly!(year),
156{
157 is_ly!(year)
158}
159
160#[verifier::allow_in_spec]
163pub fn days_in_month(year: u16, month: u8) -> u8
164 returns
165 dim!(year, month),
166{
167 dim!(year, month)
168}
169
170#[verifier::allow_in_spec]
175pub fn utc_year(short_year: u8) -> u16
176 returns
177 utc_yr!(short_year),
178{
179 utc_yr!(short_year)
180}
181
182pub open spec fn decimal2(bytes: Seq<u8>, pos: usize) -> u8 {
183 ((bytes[pos as int] - ASCII_0) * 10 + (bytes[pos as int + 1] - ASCII_0)) as u8
184}
185
186pub fn decimal_2(bytes: &[u8], pos: usize) -> u8
187 requires
188 pos < usize::MAX,
189 pos + 1 < bytes.len(),
190 ascii_digit(bytes@[pos as int]),
191 ascii_digit(bytes@[pos as int + 1]),
192 returns
193 decimal2(bytes@, pos),
194{
195 ((bytes[pos] - ASCII_0) * 10 + (bytes[pos + 1] - ASCII_0)) as u8
196}
197
198#[verusfmt::skip]
199pub open spec fn decimal4(bytes: Seq<u8>, pos: usize) -> u16
200{
201 ((
202 (bytes[pos as int] - ASCII_0) as u16 * 1000u16)
203 + ((bytes[pos + 1] - ASCII_0) as u16 * 100u16)
204 + ((bytes[pos + 2] - ASCII_0) as u16 * 10u16)
205 + (bytes[pos + 3] - ASCII_0) as u16) as u16
206}
207
208pub fn decimal_4(bytes: &[u8], pos: usize) -> u16
209 requires
210 pos <= usize::MAX - 3,
211 pos + 3 < bytes.len(),
212 ascii_digit(bytes@[pos as int]),
213 ascii_digit(bytes@[pos as int + 1]),
214 ascii_digit(bytes@[pos as int + 2]),
215 ascii_digit(bytes@[pos as int + 3]),
216 returns
217 decimal4(bytes@, pos),
218{
219 (((bytes[pos] - ASCII_0) as u16 * 1000u16) + ((bytes[pos + 1] - ASCII_0) as u16 * 100u16) + ((
220 bytes[pos + 2] - ASCII_0) as u16 * 10u16) + (bytes[pos + 3] - ASCII_0) as u16) as u16
221}
222
223#[verifier::allow_in_spec]
224pub fn datetime_wf(value: DateTime) -> bool
225 returns
226 dt_wf!(value),
227{
228 dt_wf!(value)
229}
230
231pub open spec fn ascii_digit(byte: u8) -> bool {
232 ASCII_0 <= byte <= ASCII_9
233}
234
235#[verifier::allow_in_spec]
236pub fn decimal2_bytes(value: u8) -> [u8; 2]
237 requires
238 value <= 99,
239 returns
240 [(ASCII_0 + value / 10) as u8, (ASCII_0 + value % 10) as u8],
241{
242 [ASCII_0 + value / 10, ASCII_0 + value % 10]
243}
244
245#[verifier::allow_in_spec]
246pub fn decimal4_bytes(value: u16) -> [u8; 4]
247 requires
248 value <= 9999,
249 returns
250 [
251 (ASCII_0 + value as int / 1000) as u8,
252 (ASCII_0 + value as int / 100 % 10) as u8,
253 (ASCII_0 + value as int / 10 % 10) as u8,
254 (ASCII_0 + value as int % 10) as u8,
255 ],
256{
257 [
258 ASCII_0 + (value / 1000) as u8,
259 ASCII_0 + ((value / 100) % 10) as u8,
260 ASCII_0 + ((value / 10) % 10) as u8,
261 ASCII_0 + (value % 10) as u8,
262 ]
263}
264
265pub broadcast proof fn lemma_decimal2_roundtrip(value: u8)
266 requires
267 value <= 99,
268 ensures
269 digits(#[trigger] decimal2_bytes(value)@, 0, 2),
270 decimal2(decimal2_bytes(value)@, 0) == value,
271{
272}
273
274pub broadcast proof fn lemma_decimal2_canonical(bytes: Seq<u8>, pos: usize)
275 requires
276 pos + 2 <= bytes.len(),
277 digits(bytes, pos as int, pos as int + 2),
278 ensures
279 #[trigger] decimal2_bytes(decimal2(bytes, pos))@ == bytes.subrange(
280 pos as int,
281 pos as int + 2,
282 ),
283{
284}
285
286#[verifier::rlimit(50)]
287pub broadcast proof fn lemma_decimal4_roundtrip(value: u16)
288 requires
289 value <= 9999,
290 ensures
291 digits(#[trigger] decimal4_bytes(value)@, 0, 4),
292 decimal4(decimal4_bytes(value)@, 0) == value,
293{
294 }
296
297#[verifier::rlimit(20)]
298pub proof fn lemma_decimal4_canonical(bytes: Seq<u8>, pos: usize)
299 requires
300 pos <= usize::MAX - 2,
301 pos + 4 <= bytes.len(),
302 digits(bytes, pos as int, pos as int + 4),
303 ensures
304 decimal4_bytes(decimal4(bytes, pos))@ == bytes.subrange(pos as int, pos as int + 4),
305{
306 lemma_decimal2_canonical(bytes, pos);
307 lemma_decimal2_canonical(bytes, (pos as int + 2) as usize);
308}
309
310pub open spec fn digits(bytes: Seq<u8>, start: int, end: int) -> bool {
311 &&& 0 <= start <= end <= bytes.len()
312 &&& forall|i: int| start <= i < end ==> ascii_digit(#[trigger] bytes[i])
313}
314
315pub fn is_digits(bytes: &[u8], start: usize, end: usize) -> bool
316 requires
317 start <= end <= bytes.len(),
318 returns
319 digits(bytes@, start as int, end as int),
320{
321 for i in start..end
322 invariant
323 start <= i <= end <= bytes.len(),
324 forall|j: int| start <= j < i ==> ascii_digit(#[trigger] bytes@[j]),
325 {
326 if bytes[i] < ASCII_0 || bytes[i] > ASCII_9 {
327 return false;
328 }
329 }
330 true
331}
332
333#[verifier::allow_in_spec]
334pub fn next_day(value: DateTime) -> (res: Option<DateTime>)
335 requires
336 datetime_wf(value),
337 ensures
338 res matches Some(next) ==> datetime_wf(next),
339 returns
340 nxt_dy!(value),
341{
342 nxt_dy!(value)
343}
344
345#[verifier::allow_in_spec]
346pub fn previous_day(value: DateTime) -> (res: Option<DateTime>)
347 requires
348 datetime_wf(value),
349 ensures
350 res matches Some(previous) ==> datetime_wf(previous),
351 returns
352 prev_dy!(value),
353{
354 prev_dy!(value)
355}
356
357#[verifier::allow_in_spec]
366pub fn normalize_offset(
367 local: DateTime,
368 local_ahead_of_utc: bool,
369 offset_hour: u8,
370 offset_minute: u8,
371) -> (res: Option<DateTime>)
372 requires
373 datetime_wf(local),
374 offset_hour <= 23,
375 offset_minute <= 59,
376 ensures
377 res matches Some(utc) ==> datetime_wf(utc),
378 returns
379 ({
380 let local_minutes = local.hour as i32 * 60 + local.minute as i32;
381 let offset = offset_hour as i32 * 60 + offset_minute as i32;
382 let utc_minutes = if local_ahead_of_utc {
383 local_minutes - offset
384 } else {
385 local_minutes + offset
386 };
387 if utc_minutes < 0 {
388 previous_day(
389 DateTime {
390 hour: ((utc_minutes + 1440) / 60) as u8,
391 minute: ((utc_minutes + 1440) % 60) as u8,
392 ..local
393 },
394 )
395 } else if utc_minutes >= 1440 {
396 next_day(
397 DateTime {
398 hour: ((utc_minutes - 1440) / 60) as u8,
399 minute: ((utc_minutes - 1440) % 60) as u8,
400 ..local
401 },
402 )
403 } else {
404 Some(
405 DateTime {
406 hour: (utc_minutes / 60) as u8,
407 minute: (utc_minutes % 60) as u8,
408 ..local
409 },
410 )
411 }
412 }),
413{
414 let local_minutes = local.hour as i32 * 60 + local.minute as i32;
415 let offset = offset_hour as i32 * 60 + offset_minute as i32;
416 let utc_minutes = if local_ahead_of_utc {
417 local_minutes - offset
418 } else {
419 local_minutes + offset
420 };
421 if utc_minutes < 0 {
422 previous_day(
423 DateTime {
424 hour: ((utc_minutes + 1440) / 60) as u8,
425 minute: ((utc_minutes + 1440) % 60) as u8,
426 ..local
427 },
428 )
429 } else if utc_minutes >= 1440 {
430 next_day(
431 DateTime {
432 hour: ((utc_minutes - 1440) / 60) as u8,
433 minute: ((utc_minutes - 1440) % 60) as u8,
434 ..local
435 },
436 )
437 } else {
438 Some(DateTime { hour: (utc_minutes / 60) as u8, minute: (utc_minutes % 60) as u8, ..local })
439 }
440}
441
442}