format.bend
bend-time-lib@0.4.0.1 · 539 lines · 65 definitions
1import Base
2import bend-scanner@0.1.0.0/parse.bend as P
3import bend-time-lib@0.4.0.1/date.bend as D
4import bend-time-lib@0.4.0.1/datetime.bend as DT
5import bend-time-lib@0.4.0.1/instant.bend as I
6
7type Format.Error is Data:
8 Unexpected{offset: Nat, char: Char}
9 Eof{offset: Nat}
10 Overflow{offset: Nat}
11 Extra{offset: Nat, char: Char}
12 InvalidDate{year: U32, month: U32, day: U32}
13 InvalidTime{hour: U32, minute: U32, second: U32}
14 InvalidOffset{hour: U32, minute: U32}
15 NegativeZeroOffset{}
16 BadFraction{offset: Nat}
17
18type Format.FracP is Data:
19 FracP{frac: DT.TimeFrac, cur: P.Parse.Cur}
20
21type Format.OffP is Data:
22 OffP{offset: DT.UtcOffset, cur: P.Parse.Cur}
23
24type Format.DateP is Data:
25 DateP{date: D.Date, cur: P.Parse.Cur}
26
27def Format.from_parse(e: P.Parse.Error) -> Format.Error:
28 match e:
29 case P.Unexpected{offset, char}:
30 Unexpected{offset, char}
31 case P.Eof{offset}:
32 Eof{offset}
33 case P.Overflow{offset}:
34 Overflow{offset}
35 case P.Extra{offset, char}:
36 Extra{offset, char}
37
38def Format.digit(n: U32) -> Char:
39 Char.from_u32((Char.to_u32('0') + n : U32))
40
41def Format.pad2(+n: U32) -> String:
42 SCon{
43 Format.digit(U32.div(n, 10)),
44 SCon{Format.digit(U32.mod(n, 10)), SNil{}}
45 }
46
47def Format.pad4(+n: U32) -> String:
48 SCon{
49 Format.digit(U32.div(n, 1000)),
50 SCon{
51 Format.digit(U32.mod(U32.div(n, 100), 10)),
52 SCon{
53 Format.digit(U32.mod(U32.div(n, 10), 10)),
54 SCon{Format.digit(U32.mod(n, 10)), SNil{}}
55 }
56 }
57 }
58
59def Format.pad_n.go(k: Nat, +n: U32, acc: String) -> String:
60 match k:
61 case 0n:
62 acc
63 case 1n++p:
64 Format.pad_n.go(
65 p, U32.div(n, 10), SCon{Format.digit(U32.mod(n, 10)), acc}
66 )
67
68def Format.pad_n(n: U32, k: Nat) -> String:
69 Format.pad_n.go(k, n, SNil{})
70
71def Format.show_date(date: D.Date) -> String:
72 match date:
73 case D.Date{y, m, d}:
74 String.append(
75 Format.pad4(y),
76 String.append(
77 "-", String.append(Format.pad2(m), String.append("-", Format.pad2(d)))
78 )
79 )
80
81def Format.show_frac(frac: DT.TimeFrac) -> String:
82 match frac:
83 case DT.FracNone{}:
84 SNil{}
85 case DT.Frac{digits, value}:
86 SCon{'.', Format.pad_n(value, digits)}
87
88def Format.show_off(offset: DT.UtcOffset) -> String:
89 match offset:
90 case DT.OffsetZero{}:
91 "Z"
92 case DT.OffsetEast{h, mi}:
93 String.append(
94 "+", String.append(Format.pad2(h), String.append(":", Format.pad2(mi)))
95 )
96 case DT.OffsetWest{h, mi}:
97 String.append(
98 "-", String.append(Format.pad2(h), String.append(":", Format.pad2(mi)))
99 )
100
101def Format.show_time(time: DT.TimeOfDay) -> String:
102 match time:
103 case DT.TimeOfDay{h, mi, s, frac}:
104 String.append(
105 Format.pad2(h),
106 String.append(
107 ":",
108 String.append(
109 Format.pad2(mi),
110 String.append(
111 ":", String.append(Format.pad2(s), Format.show_frac(frac))
112 )
113 )
114 )
115 )
116
117def Format.show.join(ds: String, ts: String, os: String) -> String:
118 String.append(ds, String.append("T", String.append(ts, os)))
119
120def Format.show(odt: DT.OffsetDateTime) -> String:
121 match odt:
122 case DT.OffsetDateTime{date, time, offset}:
123 Format.show.join(
124 Format.show_date(date), Format.show_time(time), Format.show_off(offset)
125 )
126
127def Format.cur_off(cur: P.Parse.Cur) -> Nat:
128 match cur:
129 case P.Cur{_, off}:
130 off
131
132def Format.nacc(+acc: U32, +h: Char) -> U32:
133 U32.add(U32.mul(acc, 10), (Char.to_u32(h) - Char.to_u32('0') : U32))
134
135def Format.frac.end_ok2(
136 s: String, acc: U32, off: Nat, got: Nat, z: Bool
137) -> Result<&2, &2, Format.Error, Format.FracP>:
138 match z:
139 case True{}:
140 Fail{BadFraction{off}}
141 case False{}:
142 Done{FracP{DT.Frac{got, acc}, P.Cur{s, off}}}
143
144def Format.frac.end_ok(
145 s: String, acc: U32, off: Nat, +got: Nat
146) -> Result<&2, &2, Format.Error, Format.FracP>:
147 Format.frac.end_ok2(s, acc, off, got, Nat.is_eq(got, 0n))
148
149def Format.frac.go(
150 s: String, n: Nat, dig: Bool, +h: Char, +acc: U32, off: Nat, got: Nat
151) -> Result<&2, &2, Format.Error, Format.FracP>:
152 match s n dig:
153 case _ 0n True{}:
154 Fail{BadFraction{off}}
155 case _ 0n False{}:
156 Format.frac.end_ok(SCon{h, s}, acc, off, got)
157 case _ 1n++p False{}:
158 Format.frac.end_ok(SCon{h, s}, acc, off, got)
159 case SNil{} 1n++p True{}:
160 Format.frac.end_ok(SNil{}, Format.nacc(acc, h), 1n+off, 1n+got)
161 case SCon{+h2, t} 1n++p True{}:
162 Format.frac.go(
163 t, p, Char.is_digit(h2), h2, Format.nacc(acc, h), 1n+off, 1n+got
164 )
165
166def Format.frac.start(
167 s: String, off: Nat
168) -> Result<&2, &2, Format.Error, Format.FracP>:
169 match s:
170 case SNil{}:
171 Fail{BadFraction{off}}
172 case SCon{+h, t}:
173 Format.frac.go(t, 9n, Char.is_digit(h), h, 0, off, 0n)
174
175def Format.frac.digits2(
176 r: Result<&2, &2, P.Parse.Error, P.Parse.Cur>
177) -> Result<&2, &2, Format.Error, Format.FracP>:
178 match r:
179 case Fail{e}:
180 Fail{Format.from_parse(e)}
181 case Done{P.Cur{rest, off}}:
182 Format.frac.start(rest, off)
183
184def Format.frac.dot(
185 cur: P.Parse.Cur, is_dot: Bool
186) -> Result<&2, &2, Format.Error, Format.FracP>:
187 match is_dot:
188 case False{}:
189 Done{FracP{DT.FracNone{}, cur}}
190 case True{}:
191 Format.frac.digits2(P.Parse.expect_char(cur, '.'))
192
193def Format.frac.after2(
194 cur: P.Parse.Cur, m: Maybe<&2, Char>
195) -> Result<&2, &2, Format.Error, Format.FracP>:
196 match m:
197 case None{}:
198 Done{FracP{DT.FracNone{}, cur}}
199 case Some{c}:
200 Format.frac.dot(cur, Char.is_eq(c, '.'))
201
202def Format.frac.after(
203 +cur: P.Parse.Cur
204) -> Result<&2, &2, Format.Error, Format.FracP>:
205 Format.frac.after2(cur, P.Parse.peek(cur))
206
207def Format.off.mk(
208 west: Bool, h: U32, mi: U32, cur: P.Parse.Cur,
209 r: Result<&2, &2, DT.UtcOffset.Error, DT.UtcOffset>
210) -> Result<&2, &2, Format.Error, Format.OffP>:
211 match r:
212 case Fail{DT.NegativeZero{}}:
213 Fail{NegativeZeroOffset{}}
214 case Fail{DT.InvalidOffset{hh, mm}}:
215 Fail{InvalidOffset{hh, mm}}
216 case Done{off}:
217 Done{OffP{off, cur}}
218
219def Format.off.min2(
220 +west: Bool, +h: U32, r: Result<&2, &2, P.Parse.Error, P.Parse.Dec>
221) -> Result<&2, &2, Format.Error, Format.OffP>:
222 match r:
223 case Fail{e}:
224 Fail{Format.from_parse(e)}
225 case Done{P.Dec{+mi, cur}}:
226 Format.off.mk(west, h, mi, cur, DT.UtcOffset.from(west, h, mi))
227
228def Format.off.colon(
229 west: Bool, h: U32, r: Result<&2, &2, P.Parse.Error, P.Parse.Cur>
230) -> Result<&2, &2, Format.Error, Format.OffP>:
231 match r:
232 case Fail{e}:
233 Fail{Format.from_parse(e)}
234 case Done{cur}:
235 Format.off.min2(west, h, P.Parse.read_fixed_decimal(cur, 2n, 99))
236
237def Format.off.hour2(
238 west: Bool, r: Result<&2, &2, P.Parse.Error, P.Parse.Dec>
239) -> Result<&2, &2, Format.Error, Format.OffP>:
240 match r:
241 case Fail{e}:
242 Fail{Format.from_parse(e)}
243 case Done{P.Dec{h, cur}}:
244 Format.off.colon(west, h, P.Parse.expect_char(cur, ':'))
245
246def Format.off.sign2(
247 west: Bool, r: Result<&2, &2, P.Parse.Error, P.Parse.Cur>
248) -> Result<&2, &2, Format.Error, Format.OffP>:
249 match r:
250 case Fail{e}:
251 Fail{Format.from_parse(e)}
252 case Done{cur}:
253 Format.off.hour2(west, P.Parse.read_fixed_decimal(cur, 2n, 99))
254
255def Format.off.z2(
256 r: Result<&2, &2, P.Parse.Error, P.Parse.Cur>
257) -> Result<&2, &2, Format.Error, Format.OffP>:
258 match r:
259 case Fail{e}:
260 Fail{Format.from_parse(e)}
261 case Done{cur}:
262 Done{OffP{DT.OffsetZero{}, cur}}
263
264def Format.off.minus(
265 minus: Bool, cur: P.Parse.Cur, +c: Char
266) -> Result<&2, &2, Format.Error, Format.OffP>:
267 match minus:
268 case True{}:
269 Format.off.sign2(True{}, P.Parse.expect_char(cur, '-'))
270 case False{}:
271 Fail{Unexpected{Format.cur_off(cur), c}}
272
273def Format.off.pm(
274 plus: Bool, minus: Bool, cur: P.Parse.Cur, +c: Char
275) -> Result<&2, &2, Format.Error, Format.OffP>:
276 match plus:
277 case True{}:
278 Format.off.sign2(False{}, P.Parse.expect_char(cur, '+'))
279 case False{}:
280 Format.off.minus(minus, cur, c)
281
282def Format.off.ch2(
283 z: Bool, plus: Bool, minus: Bool, cur: P.Parse.Cur, +c: Char
284) -> Result<&2, &2, Format.Error, Format.OffP>:
285 match z:
286 case True{}:
287 Format.off.z2(P.Parse.expect_char(cur, 'Z'))
288 case False{}:
289 Format.off.pm(plus, minus, cur, c)
290
291def Format.off.ch(
292 cur: P.Parse.Cur, +c: Char
293) -> Result<&2, &2, Format.Error, Format.OffP>:
294 Format.off.ch2(
295 Char.is_eq(c, 'Z'), Char.is_eq(c, '+'), Char.is_eq(c, '-'), cur, c
296 )
297
298def Format.off.after2(
299 cur: P.Parse.Cur, m: Maybe<&2, Char>
300) -> Result<&2, &2, Format.Error, Format.OffP>:
301 match m:
302 case None{}:
303 Fail{Eof{Format.cur_off(cur)}}
304 case Some{c}:
305 Format.off.ch(cur, c)
306
307def Format.off.after(
308 +cur: P.Parse.Cur
309) -> Result<&2, &2, Format.Error, Format.OffP>:
310 Format.off.after2(cur, P.Parse.peek(cur))
311
312def Format.civil.mk(
313 +y: U32, +m: U32, +d: U32, cur: P.Parse.Cur,
314 r: Result<&2, &2, D.Date.Error, D.Date>
315) -> Result<&2, &2, Format.Error, Format.DateP>:
316 match r:
317 case Fail{D.InvalidDate{yy, mm, dd}}:
318 Fail{InvalidDate{yy, mm, dd}}
319 case Fail{D.OutOfRange{}}:
320 Fail{InvalidDate{y, m, d}}
321 case Done{date}:
322 Done{DateP{date, cur}}
323
324def Format.civil.d2(
325 +y: U32, +m: U32, r: Result<&2, &2, P.Parse.Error, P.Parse.Dec>
326) -> Result<&2, &2, Format.Error, Format.DateP>:
327 match r:
328 case Fail{e}:
329 Fail{Format.from_parse(e)}
330 case Done{P.Dec{+d, cur}}:
331 Format.civil.mk(y, m, d, cur, D.Date.from(y, m, d))
332
333def Format.civil.dash2(
334 y: U32, m: U32, r: Result<&2, &2, P.Parse.Error, P.Parse.Cur>
335) -> Result<&2, &2, Format.Error, Format.DateP>:
336 match r:
337 case Fail{e}:
338 Fail{Format.from_parse(e)}
339 case Done{cur}:
340 Format.civil.d2(y, m, P.Parse.read_fixed_decimal(cur, 2n, 99))
341
342def Format.civil.m2(
343 y: U32, r: Result<&2, &2, P.Parse.Error, P.Parse.Dec>
344) -> Result<&2, &2, Format.Error, Format.DateP>:
345 match r:
346 case Fail{e}:
347 Fail{Format.from_parse(e)}
348 case Done{P.Dec{m, cur}}:
349 Format.civil.dash2(y, m, P.Parse.expect_char(cur, '-'))
350
351def Format.civil.dash1(
352 y: U32, r: Result<&2, &2, P.Parse.Error, P.Parse.Cur>
353) -> Result<&2, &2, Format.Error, Format.DateP>:
354 match r:
355 case Fail{e}:
356 Fail{Format.from_parse(e)}
357 case Done{cur}:
358 Format.civil.m2(y, P.Parse.read_fixed_decimal(cur, 2n, 99))
359
360def Format.civil.y(
361 r: Result<&2, &2, P.Parse.Error, P.Parse.Dec>
362) -> Result<&2, &2, Format.Error, Format.DateP>:
363 match r:
364 case Fail{e}:
365 Fail{Format.from_parse(e)}
366 case Done{P.Dec{y, cur}}:
367 Format.civil.dash1(y, P.Parse.expect_char(cur, '-'))
368
369def Format.civil(text: String) -> Result<&2, &2, Format.Error, Format.DateP>:
370 Format.civil.y(P.Parse.read_fixed_decimal(P.Parse.start(text), 4n, 9999))
371
372def Format.read_date.fin2(
373 date: D.Date, r: Result<&2, &2, P.Parse.Error, Unit>
374) -> Result<&2, &2, Format.Error, D.Date>:
375 match r:
376 case Fail{e}:
377 Fail{Format.from_parse(e)}
378 case Done{_}:
379 Done{date}
380
381def Format.read_date.fin(
382 r: Result<&2, &2, Format.Error, Format.DateP>
383) -> Result<&2, &2, Format.Error, D.Date>:
384 match r:
385 case Fail{e}:
386 Fail{e}
387 case Done{DateP{date, cur}}:
388 Format.read_date.fin2(date, P.Parse.finish(cur))
389
390def Format.read_date(text: String) -> Result<&2, &2, Format.Error, D.Date>:
391 Format.read_date.fin(Format.civil(text))
392
393def Format.read.fin2(
394 odt: DT.OffsetDateTime, r: Result<&2, &2, P.Parse.Error, Unit>
395) -> Result<&2, &2, Format.Error, DT.OffsetDateTime>:
396 match r:
397 case Fail{e}:
398 Fail{Format.from_parse(e)}
399 case Done{_}:
400 Done{odt}
401
402def Format.read.off2(
403 date: D.Date, time: DT.TimeOfDay,
404 r: Result<&2, &2, Format.Error, Format.OffP>
405) -> Result<&2, &2, Format.Error, DT.OffsetDateTime>:
406 match r:
407 case Fail{e}:
408 Fail{e}
409 case Done{OffP{offset, cur}}:
410 Format.read.fin2(DT.OffsetDateTime.from(date, time, offset), P.Parse.finish(cur))
411
412def Format.read.time2(
413 date: D.Date, +h: U32, +mi: U32, +s: U32, frac: DT.TimeFrac, cur: P.Parse.Cur,
414 r: Result<&2, &2, DT.TimeOfDay.Error, DT.TimeOfDay>
415) -> Result<&2, &2, Format.Error, DT.OffsetDateTime>:
416 match r:
417 case Fail{DT.InvalidTime{hh, mm, ss}}:
418 Fail{InvalidTime{hh, mm, ss}}
419 case Done{time}:
420 Format.read.off2(date, time, Format.off.after(cur))
421
422def Format.read.frac2(
423 date: D.Date, +h: U32, +mi: U32, +s: U32,
424 r: Result<&2, &2, Format.Error, Format.FracP>
425) -> Result<&2, &2, Format.Error, DT.OffsetDateTime>:
426 match r:
427 case Fail{e}:
428 Fail{e}
429 case Done{FracP{+frac, cur}}:
430 Format.read.time2(
431 date, h, mi, s, frac, cur, DT.TimeOfDay.from(h, mi, s, frac)
432 )
433
434def Format.read.sec2(
435 date: D.Date, h: U32, mi: U32,
436 r: Result<&2, &2, P.Parse.Error, P.Parse.Dec>
437) -> Result<&2, &2, Format.Error, DT.OffsetDateTime>:
438 match r:
439 case Fail{e}:
440 Fail{Format.from_parse(e)}
441 case Done{P.Dec{s, cur}}:
442 Format.read.frac2(date, h, mi, s, Format.frac.after(cur))
443
444def Format.read.colon2(
445 date: D.Date, h: U32, mi: U32,
446 r: Result<&2, &2, P.Parse.Error, P.Parse.Cur>
447) -> Result<&2, &2, Format.Error, DT.OffsetDateTime>:
448 match r:
449 case Fail{e}:
450 Fail{Format.from_parse(e)}
451 case Done{cur}:
452 Format.read.sec2(date, h, mi, P.Parse.read_fixed_decimal(cur, 2n, 99))
453
454def Format.read.min2(
455 date: D.Date, h: U32, r: Result<&2, &2, P.Parse.Error, P.Parse.Dec>
456) -> Result<&2, &2, Format.Error, DT.OffsetDateTime>:
457 match r:
458 case Fail{e}:
459 Fail{Format.from_parse(e)}
460 case Done{P.Dec{mi, cur}}:
461 Format.read.colon2(date, h, mi, P.Parse.expect_char(cur, ':'))
462
463def Format.read.colon1(
464 date: D.Date, h: U32, r: Result<&2, &2, P.Parse.Error, P.Parse.Cur>
465) -> Result<&2, &2, Format.Error, DT.OffsetDateTime>:
466 match r:
467 case Fail{e}:
468 Fail{Format.from_parse(e)}
469 case Done{cur}:
470 Format.read.min2(date, h, P.Parse.read_fixed_decimal(cur, 2n, 99))
471
472def Format.read.hour2(
473 date: D.Date, r: Result<&2, &2, P.Parse.Error, P.Parse.Dec>
474) -> Result<&2, &2, Format.Error, DT.OffsetDateTime>:
475 match r:
476 case Fail{e}:
477 Fail{Format.from_parse(e)}
478 case Done{P.Dec{h, cur}}:
479 Format.read.colon1(date, h, P.Parse.expect_char(cur, ':'))
480
481def Format.read.t2(
482 date: D.Date, r: Result<&2, &2, P.Parse.Error, P.Parse.Cur>
483) -> Result<&2, &2, Format.Error, DT.OffsetDateTime>:
484 match r:
485 case Fail{e}:
486 Fail{Format.from_parse(e)}
487 case Done{cur}:
488 Format.read.hour2(date, P.Parse.read_fixed_decimal(cur, 2n, 99))
489
490def Format.read.after_date(
491 r: Result<&2, &2, Format.Error, Format.DateP>
492) -> Result<&2, &2, Format.Error, DT.OffsetDateTime>:
493 match r:
494 case Fail{e}:
495 Fail{e}
496 case Done{DateP{date, cur}}:
497 Format.read.t2(date, P.Parse.expect_char(cur, 'T'))
498
499def Format.read(
500 text: String
501) -> Result<&2, &2, Format.Error, DT.OffsetDateTime>:
502 Format.read.after_date(Format.civil(text))
503
504def Format.show_read(
505 r: Result<&2, &2, Format.Error, DT.OffsetDateTime>
506) -> Result<&2, &2, Format.Error, String>:
507 match r:
508 case Fail{e}:
509 Fail{e}
510 case Done{odt}:
511 Done{Format.show(odt)}
512
513def Format.show_instant.map(
514 r: Result<&2, &2, D.Date.Error, DT.OffsetDateTime>
515) -> Result<&2, &2, D.Date.Error, String>:
516 match r:
517 case Fail{e}:
518 Fail{e}
519 case Done{odt}:
520 Done{Format.show(odt)}
521
522def Format.show_instant(
523 i: I.Instant
524) -> Result<&2, &2, D.Date.Error, String>:
525 Format.show_instant.map(DT.OffsetDateTime.from_instant_utc(i))
526
527def Format.read_instant.map(
528 r: Result<&2, &2, Format.Error, DT.OffsetDateTime>
529) -> Result<&2, &2, Format.Error, I.Instant>:
530 match r:
531 case Fail{e}:
532 Fail{e}
533 case Done{odt}:
534 Done{DT.OffsetDateTime.to_instant(odt)}
535
536def Format.read_instant(
537 text: String
538) -> Result<&2, &2, Format.Error, I.Instant>:
539 Format.read_instant.map(Format.read(text))