From 2b2841e8d3491e1b1cb64d5645b0746d3fee7ac7 Mon Sep 17 00:00:00 2001 From: Simon Vandel Sillesen Date: Wed, 9 Feb 2022 20:50:46 +0100 Subject: [PATCH] feat: Add support for Date <-> chrono::DateTime --- crates/backend/src/typegen.rs | 1 + crates/napi/Cargo.toml | 17 ++++-- crates/napi/src/bindgen_runtime/js_values.rs | 2 + .../src/bindgen_runtime/js_values/date.rs | 52 ++++++++++++++++++ examples/napi/Cargo.toml | 16 +++++- examples/napi/__test__/typegen.spec.ts.md | 2 + examples/napi/__test__/typegen.spec.ts.snap | Bin 2634 -> 2656 bytes examples/napi/__test__/values.spec.ts | 11 ++++ examples/napi/index.d.ts | 2 + examples/napi/src/date.rs | 11 ++++ 10 files changed, 107 insertions(+), 7 deletions(-) create mode 100644 crates/napi/src/bindgen_runtime/js_values/date.rs diff --git a/crates/backend/src/typegen.rs b/crates/backend/src/typegen.rs index 8a3a1383..94e6c899 100644 --- a/crates/backend/src/typegen.rs +++ b/crates/backend/src/typegen.rs @@ -168,6 +168,7 @@ static KNOWN_TYPES: Lazy> = Lazy::new(|| { ("BigInt64Array", "BigInt64Array"), ("BigUint64Array", "BigUint64Array"), ("DataView", "DataView"), + ("DateTime", "Date"), ("Date", "Date"), ("JsDate", "Date"), ("JsBuffer", "Buffer"), diff --git a/crates/napi/Cargo.toml b/crates/napi/Cargo.toml index 1c9f344b..1fe332a1 100644 --- a/crates/napi/Cargo.toml +++ b/crates/napi/Cargo.toml @@ -18,9 +18,10 @@ independent = true [features] async = ["tokio_rt"] compat-mode = [] -default = ["napi3", "compat-mode"] # for most Node.js users +default = ["napi3", "compat-mode"] # for most Node.js users experimental = ["napi-sys/experimental"] -full = ["latin1", "napi8", "async", "serde-json", "experimental"] +chrono_date = ["chrono"] +full = ["latin1", "napi8", "async", "serde-json", "experimental", "chrono_date"] latin1 = ["encoding_rs"] napi1 = [] napi2 = ["napi1"] @@ -48,12 +49,16 @@ tokio_time = ["tokio/time"] [dependencies] ctor = "0.1" lazy_static = "1" -napi-sys = {version = "2.1.0", path = "../sys"} +napi-sys = { version = "2.1.0", path = "../sys" } [dependencies.encoding_rs] optional = true version = "0.8" +[dependencies.chrono] +optional = true +version = "0.4" + [dependencies.tokio] features = ["rt", "rt-multi-thread", "sync"] optional = true @@ -68,4 +73,8 @@ optional = true version = "1" [target.'cfg(windows)'.dependencies] -windows = {version = "0.30", features = ["Win32_System_WindowsProgramming", "Win32_System_LibraryLoader", "Win32_Foundation"]} +windows = { version = "0.30", features = [ + "Win32_System_WindowsProgramming", + "Win32_System_LibraryLoader", + "Win32_Foundation", +] } diff --git a/crates/napi/src/bindgen_runtime/js_values.rs b/crates/napi/src/bindgen_runtime/js_values.rs index 2469f1c8..a835621b 100644 --- a/crates/napi/src/bindgen_runtime/js_values.rs +++ b/crates/napi/src/bindgen_runtime/js_values.rs @@ -8,6 +8,8 @@ mod arraybuffer; mod bigint; mod boolean; mod buffer; +#[cfg(all(feature = "chrono_date", feature = "napi5"))] +mod date; mod either; mod external; mod function; diff --git a/crates/napi/src/bindgen_runtime/js_values/date.rs b/crates/napi/src/bindgen_runtime/js_values/date.rs new file mode 100644 index 00000000..8188fcb6 --- /dev/null +++ b/crates/napi/src/bindgen_runtime/js_values/date.rs @@ -0,0 +1,52 @@ +use crate::{bindgen_prelude::*, check_status, sys, ValueType}; +use chrono::{DateTime, NaiveDateTime, Utc}; + +impl TypeName for DateTime { + fn type_name() -> &'static str { + "DateTime" + } + + fn value_type() -> ValueType { + ValueType::Object + } +} + +impl ValidateNapiValue for DateTime { + fn type_of() -> Vec { + vec![ValueType::Object] + } +} + +impl ToNapiValue for DateTime { + unsafe fn to_napi_value(env: sys::napi_env, val: DateTime) -> Result { + let mut ptr = std::ptr::null_mut(); + let millis_since_epoch_utc = val.timestamp_millis() as f64; + + check_status!( + unsafe { sys::napi_create_date(env, millis_since_epoch_utc, &mut ptr) }, + "Failed to convert rust type `DateTime` into napi value", + )?; + + Ok(ptr) + } +} + +impl FromNapiValue for DateTime { + unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result { + let mut milliseconds_since_epoch_utc = 0.0; + + check_status!( + unsafe { sys::napi_get_date_value(env, napi_val, &mut milliseconds_since_epoch_utc) }, + "Failed to convert napi value into rust type `DateTime`", + )?; + + let milliseconds_since_epoch_utc = milliseconds_since_epoch_utc as i64; + let timestamp_seconds = milliseconds_since_epoch_utc / 1_000; + let naive = NaiveDateTime::from_timestamp_opt( + timestamp_seconds, + (milliseconds_since_epoch_utc % 1_000 * 1_000_000) as u32, + ) + .ok_or_else(|| Error::new(Status::DateExpected, "Found invalid date".to_owned()))?; + Ok(DateTime::::from_utc(naive, Utc)) + } +} diff --git a/examples/napi/Cargo.toml b/examples/napi/Cargo.toml index 9ccaf160..242958e8 100644 --- a/examples/napi/Cargo.toml +++ b/examples/napi/Cargo.toml @@ -9,12 +9,22 @@ version = "0.1.0" crate-type = ["cdylib"] [dependencies] +chrono = "0.4" futures = "0.3" -napi = {path = "../../crates/napi", default-features = false, features = ["tokio_fs", "napi8", "tokio_rt", "serde-json", "async", "experimental", "latin1"]} -napi-derive = {path = "../../crates/macro", features = ["type-def"]} +napi = { path = "../../crates/napi", default-features = false, features = [ + "tokio_fs", + "napi8", + "tokio_rt", + "serde-json", + "async", + "experimental", + "latin1", + "chrono_date", +] } +napi-derive = { path = "../../crates/macro", features = ["type-def"] } serde = "1" serde_derive = "1" serde_json = "1" [build-dependencies] -napi-build = {path = "../../crates/build"} +napi-build = { path = "../../crates/build" } diff --git a/examples/napi/__test__/typegen.spec.ts.md b/examples/napi/__test__/typegen.spec.ts.md index 58ce38af..6f3cce0f 100644 --- a/examples/napi/__test__/typegen.spec.ts.md +++ b/examples/napi/__test__/typegen.spec.ts.md @@ -41,6 +41,8 @@ Generated by [AVA](https://avajs.dev). export function readFile(callback: (arg0: Error | undefined, arg1?: string | undefined | null) => void): void␊ export function returnJsFunction(): (...args: any[]) => any␊ export function dateToNumber(input: Date): number␊ + export function chronoDateToMillis(input: Date): number␊ + export function chronoDateAdd1Minute(input: Date): Date␊ export function eitherStringOrNumber(input: string | number): number␊ export function returnEither(input: number): string | number␊ export function either3(input: string | number | boolean): number␊ diff --git a/examples/napi/__test__/typegen.spec.ts.snap b/examples/napi/__test__/typegen.spec.ts.snap index c8cab00c5dfe14031ad9ea18fa708384ca4e16de..d07cb6c3f46a76a2e4964753de91fa4deae95afc 100644 GIT binary patch literal 2656 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 Oaq0h%j0%N_A^-pt{rtoL literal 2634 zcmV-Q3bpk?RzVOEe%|MK9sfG;Lg8P_ zUw&hXtB~FOMUF%o24u)0mQclrFOr1$il|ADTi?#B|;KL0}+K2^6j^--k8}+BmOC6L{7$@ z2=(jBw{Et)a!DVy9+nGa5P=Z_lz24hD=v=((|I|G>@4F;Q#~w>^ zh(Q|pii-&Br@j|SAf1*=I!Qt&ucS(NG~CuK-Y_MJ6l0d5ON)t3qL+>>3I=w&0w~k5 z288!=2Da5Gpb|$COo-HlwqykY?RNl&-I0^XcVenWU@)x&;G8C6%q4rZn+^tbGEqc2 zPD91}x5A;xFc-waZPLf%LvWE!5IEG$7H#6?kFETng6D(8itX~@VWgbLfgNsb&Ve-B za99^xn;m(sb$2{&Nn-3#cXZv`!0~Aqdepym30h#aFduonO>P7afB^ohpoJNc6Q4E_zV$s>rJpTW>!_(&e-iYBUwOAT4W_%jvJOy+2_kdI!ie+6|$1@|xU}8wgya{hF+|$pD-xO_+q$; zwk%)auoDKdMMv@=+d!XpR#sMkFk}oGO)jrA4S1^HLV9O?(bKW&@F-4|OZH%|IkhmZ zMl3Pma*~uW3muAV&_Kk9yst^+$OX;}l?Dkm8koX^Cq&3-RACQM&5{B28G+oW>&Q)& z+d~7t&4v8n3FyvK+GqTRb+OPDmNUfNDX}A|0S-iB%(IZ*ajL$UBzin}8--L(80?W7 z8m8dpG{N2=Qanlpl*^5EC}cVW$2y24GgZe-J6~!v@BK2)ibr3y8&H@a%vLAgM0P3Nx~nPCc9TdngeVA~gm8|w~p%fiel?4v~7>LlVcr2|s|^G3>%{O`;P zu9*g_sY;iLWW^Ef$|7Yo3NzI5E~utr7V@y{p-Gfx-N&i26Pv+qnh{7kLuG0?0yXrZ4!5CIF9xztm_jh|Y`-K}N#C5z404CM+X0BkGhp zCO==z7`qq_8qH>-Inr9VC5m%g0l*HR+-4*iV*x}uwjUW; z)2?LiEUbXV3_>FEIg>k;+zk!+c73o0>}KzaQ0v^029|DUacfz3Jl5+T*2Q?bF5xy$ z$cjmIj#JW^00{vmcJLla402I@ z3B;>;gKeC-X6Rbyi}B8rVA?qt=fX>ynl{MxjPkk6;3Lh?BcR=&3x!izU1330f1%n9 z=`~yTzM~R!f@#65cgEID6)n(DIzR(>39pon7-`w@ZK(-|6w_BkiY&3RzPt-qnSKOz<`? zLU5uq{8NbUfBseq*NEMo(RhSPT7-?e9aDMT6`=`(f&q@<$pG9}7(fsdFP3-lTr_VFWY}n@s`XDQ!HGXeJ zb+1wv-3kd)LhB$El($&{X1@{CcvFFacslP6-}H|6dwnYv)Lfr# z{Is>%ba!f^9Dvr=Urv#9Sl3Vjp!tN}qnt)jn}*qWpDR%fAA_C7_?gULl5ADsr+ZI) sAMha*AIBEqp}}_Rw(V?%*74$F!L4Y$P2VrPmVQ|JKMAbMiWnjQ0G-Gm0{{R3 diff --git a/examples/napi/__test__/values.spec.ts b/examples/napi/__test__/values.spec.ts index a5c30f41..d6ba3956 100644 --- a/examples/napi/__test__/values.spec.ts +++ b/examples/napi/__test__/values.spec.ts @@ -83,7 +83,9 @@ import { testSerdeRoundtrip, createObjWithProperty, dateToNumber, + chronoDateToMillis, derefUint8Array, + chronoDateAdd1Minute, } from '../' test('export const', (t) => { @@ -564,3 +566,12 @@ Napi5Test('Date test', (t) => { const fixture = new Date('2016-12-24') t.is(dateToNumber(fixture), fixture.valueOf()) }) + +Napi5Test('Date to chrono test', (t) => { + const fixture = new Date('2022-02-09T19:31:55.396Z') + t.is(chronoDateToMillis(fixture), fixture.getTime()) + t.deepEqual( + chronoDateAdd1Minute(fixture), + new Date(fixture.getTime() + 60 * 1000), + ) +}) diff --git a/examples/napi/index.d.ts b/examples/napi/index.d.ts index ef8eb4e3..9f9da35d 100644 --- a/examples/napi/index.d.ts +++ b/examples/napi/index.d.ts @@ -31,6 +31,8 @@ export function optionOnly(callback: (arg0?: string | undefined | null) => void) export function readFile(callback: (arg0: Error | undefined, arg1?: string | undefined | null) => void): void export function returnJsFunction(): (...args: any[]) => any export function dateToNumber(input: Date): number +export function chronoDateToMillis(input: Date): number +export function chronoDateAdd1Minute(input: Date): Date export function eitherStringOrNumber(input: string | number): number export function returnEither(input: number): string | number export function either3(input: string | number | boolean): number diff --git a/examples/napi/src/date.rs b/examples/napi/src/date.rs index 6987cd53..d1d00ca5 100644 --- a/examples/napi/src/date.rs +++ b/examples/napi/src/date.rs @@ -1,6 +1,17 @@ +use chrono::{Duration, Utc}; use napi::bindgen_prelude::*; #[napi] fn date_to_number(input: Date) -> Result { input.value_of() } + +#[napi] +fn chrono_date_to_millis(input: chrono::DateTime) -> i64 { + input.timestamp_millis() +} + +#[napi] +fn chrono_date_add_1_minute(input: chrono::DateTime) -> chrono::DateTime { + input + Duration::minutes(1) +}