add date
All checks were successful
test / test (push) Successful in 1m38s

This commit is contained in:
Hamcha 2024-12-01 00:39:49 +01:00
parent a61dc3d878
commit 0a1f06dbe5
Signed by: hamcha
GPG key ID: 1669C533B8CF6D89
4 changed files with 88 additions and 9 deletions

View file

@ -9,6 +9,7 @@ gleam = ">= 1.6.2"
[dependencies]
gleam_stdlib = ">= 0.34.0 and < 2.0.0"
ieee_float = ">= 1.0.3 and < 2.0.0"
birl = ">= 1.7.1 and < 2.0.0"
[dev-dependencies]
gleeunit = ">= 1.0.0 and < 2.0.0"

View file

@ -2,13 +2,16 @@
# You typically do not need to edit this file
packages = [
{ name = "birl", version = "1.7.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "ranger"], otp_app = "birl", source = "hex", outer_checksum = "5C66647D62BCB11FE327E7A6024907C4A17954EF22865FE0940B54A852446D01" },
{ name = "gleam_erlang", version = "0.32.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "B18643083A0117AC5CFD0C1AEEBE5469071895ECFA426DCC26517A07F6AD9948" },
{ name = "gleam_stdlib", version = "0.45.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "206FCE1A76974AECFC55AEBCD0217D59EDE4E408C016E2CFCCC8FF51278F186E" },
{ name = "gleeunit", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "F7A7228925D3EE7D0813C922E062BFD6D7E9310F0BEE585D3A42F3307E3CFD13" },
{ name = "ieee_float", version = "1.0.3", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "ieee_float", source = "hex", outer_checksum = "0E9B6233343BD8EDBF813B50D5480139A3E318D257EA206D5C500984A829C515" },
{ name = "ranger", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "ranger", source = "hex", outer_checksum = "B8F3AFF23A3A5B5D9526B8D18E7C43A7DFD3902B151B97EC65397FE29192B695" },
]
[requirements]
birl = { version = ">= 1.7.1 and < 2.0.0" }
gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" }
gleeunit = { version = ">= 1.0.0 and < 2.0.0" }
ieee_float = { version = ">= 1.0.3 and < 2.0.0" }

View file

@ -1,8 +1,10 @@
import birl.{type Time}
import gleam/bit_array
import gleam/list.{reverse}
import gleam/result.{try}
import ieee_float.{
type IEEEFloat, from_bytes_16_be, from_bytes_32_be, from_bytes_64_be,
type IEEEFloat, finite, from_bytes_16_be, from_bytes_32_be, from_bytes_64_be,
multiply, power, round,
}
pub type DecodedValue {
@ -13,6 +15,7 @@ pub type DecodedValue {
String(value: String)
Array(List(DecodedValue))
Map(List(#(DecodedValue, DecodedValue)))
DateTime(Time)
Null
Undefined
}
@ -24,9 +27,6 @@ pub type DecodeResult =
DecoderResult(DecodedValue)
pub opaque type DecodeError {
// More data was expected but file/stream ended prematurely
UnexpectedEnd
// Malformed sequence found
Malformed
}
@ -51,6 +51,7 @@ pub fn decode(bits: BitArray) -> DecodeResult {
<<5:3, rest:bits>> ->
decode_map(rest)
|> map_decoded(Map)
<<6:3, rest:bits>> -> decode_tag(rest)
<<7:3, x:5, rest:bits>> if x < 25 -> decode_simple(<<x:5, rest:bits>>)
<<7:3, x:5, rest:bits>> if x >= 25 ->
decode_float(<<x:5, rest:bits>>)
@ -68,7 +69,7 @@ fn decode_int(bits: BitArray) -> DecoderResult(Int) {
<<27:5, val:int-unsigned-size(64), rest:bits>> -> Ok(#(val, rest))
<<x:5, rest:bits>> if x < 24 -> Ok(#(x, rest))
<<x:5, _:bits>> if x >= 24 -> Error(Malformed)
_ -> Error(UnexpectedEnd)
_ -> Error(Malformed)
}
}
@ -106,6 +107,57 @@ fn decode_map(
Ok(#(pairs, remainder))
}
fn decode_tag(bits: BitArray) -> DecodeResult {
use #(tag, rest) <- try(decode_int(bits))
case tag {
0 -> decode_datestring(rest) |> map_decoded(DateTime)
1 -> decode_unixtime(rest) |> map_decoded(DateTime)
// CBOR magic number (no-op for us)
55_799 -> decode(rest)
_ -> Error(Malformed)
}
}
fn decode_datestring(bits: BitArray) -> DecoderResult(Time) {
use string_content <- try(case bits {
<<3:3, remainder:bits>> -> Ok(remainder)
_ -> Error(Malformed)
})
use #(datestring, rest) <- try(decode_string(string_content))
case birl.parse(datestring) {
Ok(datetime) -> Ok(#(datetime, rest))
_ -> Error(Malformed)
}
}
fn decode_unixtime(bits: BitArray) -> DecoderResult(Time) {
case bits {
<<0:3, rest:bits>> ->
decode_int(rest)
|> map_decoded(birl.from_unix)
// This is probably undefined behavior, as I doubt birl supports negative unixtimes
<<1:3, rest:bits>> ->
decode_int(rest)
|> map_decoded(birl.from_unix)
// Float unixtimes is also very iffy and the spec says we should be able to ignore it
// However, lets give it a shot and turn it into a millisecond value
<<7:3, rest:bits>> ->
decode_float(rest)
|> try(fn(x) {
let #(time, remainder) = x
let milli_time = multiply(time, power(finite(10.0), finite(3.0)))
case round(milli_time) {
Ok(milli_int) -> Ok(#(birl.from_unix_milli(milli_int), remainder))
_ -> Error(Malformed)
}
})
_ -> Error(Malformed)
}
}
fn decode_float(bits: BitArray) -> DecoderResult(IEEEFloat) {
case bits {
<<25:5, float16:bytes-size(2), rest:bits>> ->
@ -157,8 +209,8 @@ fn decode_next_n(
fn map_decoded(
result: DecoderResult(t),
map_fn: fn(t) -> DecodedValue,
) -> DecodeResult {
map_fn: fn(t) -> out,
) -> DecoderResult(out) {
use #(value, rest) <- try(result)
Ok(#(map_fn(value), rest))
}

View file

@ -1,10 +1,11 @@
import birl
import gleam/list
import gleeunit
import gleeunit/should
import ieee_float.{finite, nan, positive_infinity}
import kmer.{
type DecodeResult, type DecodedValue, Array, Boolean, Bytes, Float, Integer,
Map, Null, String, Undefined,
type DecodeResult, type DecodedValue, Array, Boolean, Bytes, DateTime, Float,
Integer, Map, Null, String, Undefined,
}
pub fn main() {
@ -155,6 +156,28 @@ pub fn decode_map_test() {
|> table_fn(table_test_decode)
}
pub fn decode_time_test() {
let assert Ok(textdate_example) = birl.parse("2024-11-30T21:56:27Z")
[
// ISO8601 date
#(
<<6:3, 0:5, 3:3, 20:5, "2024-11-30T21:56:27Z">>,
DateTime(textdate_example),
),
// Unixtime
#(
<<6:3, 1:5, 1:3, 26:5, 1_733_005_876:32>>,
DateTime(birl.from_unix(1_733_005_876)),
),
#(
<<6:3, 1:5, 7:3, 27:5, 0x41D9D2E48D200000:64>>,
DateTime(birl.from_unix_milli(1_733_005_876_500)),
),
]
|> table_fn(table_test_decode)
}
fn table_fn(tests: List(#(a, b)), test_fn: fn(a, b) -> Nil) {
list.map(tests, fn(t) {
let #(test_case, expected) = t