format.bend

bend-datetime@0.4.0.0 · 539 lines · 0 definitions

1import Base
2import bend-scanner@0.1.0.0/parse.bend as P
3import bend-datetime@0.4.0.0/date.bend as D
4import bend-datetime@0.4.0.0/datetime.bend as DT
5import bend-datetime@0.4.0.0/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))