diff --git a/crates/napi/src/bindgen_runtime/js_values/date.rs b/crates/napi/src/bindgen_runtime/js_values/date.rs index 13fd4a73..20f8f06a 100644 --- a/crates/napi/src/bindgen_runtime/js_values/date.rs +++ b/crates/napi/src/bindgen_runtime/js_values/date.rs @@ -1,10 +1,10 @@ use std::{ptr, str::FromStr}; -use chrono::{DateTime, NaiveDateTime, Utc}; +use chrono::{DateTime, Local, LocalResult, NaiveDateTime, TimeZone}; use crate::{bindgen_prelude::*, check_status, sys, ValueType}; -impl TypeName for DateTime { +impl TypeName for DateTime { fn type_name() -> &'static str { "DateTime" } @@ -14,7 +14,7 @@ impl TypeName for DateTime { } } -impl ValidateNapiValue for DateTime { +impl ValidateNapiValue for DateTime { unsafe fn validate(env: sys::napi_env, napi_val: sys::napi_value) -> Result { let mut is_date = false; check_status!(unsafe { sys::napi_is_date(env, napi_val, &mut is_date) })?; @@ -120,39 +120,38 @@ impl FromNapiValue for NaiveDateTime { } } -impl ToNapiValue for DateTime { - unsafe fn to_napi_value(env: sys::napi_env, val: DateTime) -> Result { +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", + "Failed to convert rust type `DateTime` into napi value", )?; Ok(ptr) } } -impl FromNapiValue for DateTime { +impl FromNapiValue for DateTime +where + DateTime: From>, +{ 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`", + "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 = DateTime::from_timestamp( - 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_naive_utc_and_offset( - naive.naive_utc(), - Utc, - )) + match Local.timestamp_millis_opt(milliseconds_since_epoch_utc as i64) { + LocalResult::Single(dt) => Ok(dt.into()), + _ => Err(Error::new( + Status::DateExpected, + "Found invalid date".to_owned(), + )), + } } } diff --git a/examples/napi/__tests__/__snapshots__/typegen.spec.ts.md b/examples/napi/__tests__/__snapshots__/typegen.spec.ts.md index cc1c64f4..b4138546 100644 --- a/examples/napi/__tests__/__snapshots__/typegen.spec.ts.md +++ b/examples/napi/__tests__/__snapshots__/typegen.spec.ts.md @@ -343,12 +343,26 @@ Generated by [AVA](https://avajs.dev). ␊ export function chronoDateAdd1Minute(input: Date): Date␊ ␊ - export function chronoDateToMillis(input: Date): number␊ + export function chronoDateFixtureReturn1(): Date␊ + ␊ + export function chronoDateFixtureReturn2(): Date␊ + ␊ + export function chronoDateWithTimezoneReturn(): Date | null␊ + ␊ + export function chronoDateWithTimezoneToMillis(input: Date): number␊ + ␊ + export function chronoLocalDateReturn(): Date | null␊ + ␊ + export function chronoLocalDateToMillis(input: Date): number␊ ␊ export function chronoNativeDateTime(date: Date): number␊ ␊ export function chronoNativeDateTimeReturn(): Date | null␊ ␊ + export function chronoUtcDateReturn(): Date | null␊ + ␊ + export function chronoUtcDateToMillis(input: Date): number␊ + ␊ export function concatLatin1(s: string): string␊ ␊ export function concatStr(s: string): string␊ @@ -397,7 +411,7 @@ Generated by [AVA](https://avajs.dev). ␊ export function customStatusCode(): void␊ ␊ - export interface Dates {␊ + export interface DatesWithTimeZone {␊ start: Date␊ end?: Date␊ }␊ @@ -505,6 +519,11 @@ Generated by [AVA](https://avajs.dev). ␊ export function listObjKeys(obj: object): Array␊ ␊ + export interface LocalDates {␊ + start: Date␊ + end?: Date␊ + }␊ + ␊ export function mapOption(val?: number | undefined | null): number | null␊ ␊ export function mutateExternal(external: ExternalObject, newVal: number): void␊ @@ -698,6 +717,11 @@ Generated by [AVA](https://avajs.dev). nullableStringField: string | null␊ }␊ ␊ + export interface UtcDates {␊ + start: Date␊ + end?: Date␊ + }␊ + ␊ export function validateArray(arr: Array): number␊ ␊ export function validateBigint(input: bigint): bigint␊ diff --git a/examples/napi/__tests__/__snapshots__/typegen.spec.ts.snap b/examples/napi/__tests__/__snapshots__/typegen.spec.ts.snap index 9dc5d938..24648c6f 100644 Binary files a/examples/napi/__tests__/__snapshots__/typegen.spec.ts.snap and b/examples/napi/__tests__/__snapshots__/typegen.spec.ts.snap differ diff --git a/examples/napi/__tests__/values.spec.ts b/examples/napi/__tests__/values.spec.ts index c3c805b1..47a710a6 100644 --- a/examples/napi/__tests__/values.spec.ts +++ b/examples/napi/__tests__/values.spec.ts @@ -126,7 +126,11 @@ import { createObjWithProperty, receiveObjectOnlyFromJs, dateToNumber, - chronoDateToMillis, + chronoUtcDateToMillis, + chronoLocalDateToMillis, + chronoDateWithTimezoneToMillis, + chronoDateFixtureReturn1, + chronoDateFixtureReturn2, derefUint8Array, chronoDateAdd1Minute, bufferPassThrough, @@ -1235,13 +1239,22 @@ Napi5Test('Date test', (t) => { Napi5Test('Date to chrono test', (t) => { const fixture = new Date('2022-02-09T19:31:55.396Z') - t.is(chronoDateToMillis(fixture), fixture.getTime()) + t.is(chronoUtcDateToMillis(fixture), fixture.getTime()) + t.is(chronoLocalDateToMillis(fixture), fixture.getTime()) + t.is(chronoDateWithTimezoneToMillis(fixture), fixture.getTime()) t.deepEqual( chronoDateAdd1Minute(fixture), new Date(fixture.getTime() + 60 * 1000), ) }) +Napi5Test('Get date', (t) => { + const fixture1 = new Date('2024-02-07T18:28:18-0800') + t.deepEqual(chronoDateFixtureReturn1(), fixture1) + const fixture2 = new Date('2024-02-07T18:28:18+0530') + t.deepEqual(chronoDateFixtureReturn2(), fixture2) +}) + Napi5Test('Class with getter setter closures', (t) => { const instance = new GetterSetterWithClosures() // @ts-expect-error diff --git a/examples/napi/index.cjs b/examples/napi/index.cjs index e4fd00d3..def2f34e 100644 --- a/examples/napi/index.cjs +++ b/examples/napi/index.cjs @@ -438,9 +438,16 @@ module.exports.callLongThreadsafeFunction = nativeBinding.callLongThreadsafeFunc module.exports.callThreadsafeFunction = nativeBinding.callThreadsafeFunction module.exports.captureErrorInCallback = nativeBinding.captureErrorInCallback module.exports.chronoDateAdd1Minute = nativeBinding.chronoDateAdd1Minute -module.exports.chronoDateToMillis = nativeBinding.chronoDateToMillis +module.exports.chronoDateFixtureReturn1 = nativeBinding.chronoDateFixtureReturn1 +module.exports.chronoDateFixtureReturn2 = nativeBinding.chronoDateFixtureReturn2 +module.exports.chronoDateWithTimezoneReturn = nativeBinding.chronoDateWithTimezoneReturn +module.exports.chronoDateWithTimezoneToMillis = nativeBinding.chronoDateWithTimezoneToMillis +module.exports.chronoLocalDateReturn = nativeBinding.chronoLocalDateReturn +module.exports.chronoLocalDateToMillis = nativeBinding.chronoLocalDateToMillis module.exports.chronoNativeDateTime = nativeBinding.chronoNativeDateTime module.exports.chronoNativeDateTimeReturn = nativeBinding.chronoNativeDateTimeReturn +module.exports.chronoUtcDateReturn = nativeBinding.chronoUtcDateReturn +module.exports.chronoUtcDateToMillis = nativeBinding.chronoUtcDateToMillis module.exports.concatLatin1 = nativeBinding.concatLatin1 module.exports.concatStr = nativeBinding.concatStr module.exports.concatUtf16 = nativeBinding.concatUtf16 diff --git a/examples/napi/index.d.cts b/examples/napi/index.d.cts index e3b775c4..b62656eb 100644 --- a/examples/napi/index.d.cts +++ b/examples/napi/index.d.cts @@ -333,12 +333,26 @@ export function captureErrorInCallback(cb1: () => void, cb2: (arg0: Error) => vo export function chronoDateAdd1Minute(input: Date): Date -export function chronoDateToMillis(input: Date): number +export function chronoDateFixtureReturn1(): Date + +export function chronoDateFixtureReturn2(): Date + +export function chronoDateWithTimezoneReturn(): Date | null + +export function chronoDateWithTimezoneToMillis(input: Date): number + +export function chronoLocalDateReturn(): Date | null + +export function chronoLocalDateToMillis(input: Date): number export function chronoNativeDateTime(date: Date): number export function chronoNativeDateTimeReturn(): Date | null +export function chronoUtcDateReturn(): Date | null + +export function chronoUtcDateToMillis(input: Date): number + export function concatLatin1(s: string): string export function concatStr(s: string): string @@ -387,7 +401,7 @@ export const enum CustomNumEnum { export function customStatusCode(): void -export interface Dates { +export interface DatesWithTimeZone { start: Date end?: Date } @@ -495,6 +509,11 @@ export const enum Kind { export function listObjKeys(obj: object): Array +export interface LocalDates { + start: Date + end?: Date +} + export function mapOption(val?: number | undefined | null): number | null export function mutateExternal(external: ExternalObject, newVal: number): void @@ -688,6 +707,11 @@ export interface UseNullableStruct { nullableStringField: string | null } +export interface UtcDates { + start: Date + end?: Date +} + export function validateArray(arr: Array): number export function validateBigint(input: bigint): bigint diff --git a/examples/napi/src/date.rs b/examples/napi/src/date.rs index e0566d2e..ada4c3d0 100644 --- a/examples/napi/src/date.rs +++ b/examples/napi/src/date.rs @@ -1,6 +1,6 @@ use std::str::FromStr; -use chrono::{Duration, Utc}; +use chrono::{Duration, FixedOffset, Local, TimeZone, Utc}; use napi::bindgen_prelude::*; #[napi] @@ -9,7 +9,17 @@ fn date_to_number(input: Date) -> Result { } #[napi] -fn chrono_date_to_millis(input: chrono::DateTime) -> i64 { +fn chrono_utc_date_to_millis(input: chrono::DateTime) -> i64 { + input.timestamp_millis() +} + +#[napi] +fn chrono_local_date_to_millis(input: chrono::DateTime) -> i64 { + input.timestamp_millis() +} + +#[napi] +fn chrono_date_with_timezone_to_millis(input: chrono::DateTime) -> i64 { input.timestamp_millis() } @@ -19,11 +29,23 @@ fn chrono_date_add_1_minute(input: chrono::DateTime) -> chrono::DateTime, pub end: Option>, } +#[napi(object)] +pub struct LocalDates { + pub start: chrono::DateTime, + pub end: Option>, +} + +#[napi(object)] +pub struct DatesWithTimeZone { + pub start: chrono::DateTime, + pub end: Option>, +} + #[napi] pub fn chrono_native_date_time(date: chrono::NaiveDateTime) -> i64 { date.and_utc().timestamp_millis() @@ -33,3 +55,38 @@ pub fn chrono_native_date_time(date: chrono::NaiveDateTime) -> i64 { pub fn chrono_native_date_time_return() -> Option { chrono::NaiveDateTime::from_str("2016-12-23T15:25:59.325").ok() } + +#[napi] +pub fn chrono_utc_date_return() -> Option> { + chrono::DateTime::::from_str("2016-12-23T15:25:59.325").ok() +} + +#[napi] +pub fn chrono_local_date_return() -> Option> { + chrono::DateTime::::from_str("2016-12-23T15:25:59.325").ok() +} + +#[napi] +pub fn chrono_date_with_timezone_return() -> Option> { + chrono::DateTime::::from_str("2016-12-23T15:25:59.325").ok() +} + +#[napi] +pub fn chrono_date_fixture_return1() -> chrono::DateTime { + // Pacific Standard Time: UTC-08:00 + let pst = FixedOffset::west_opt(8 * 3600).unwrap(); + pst + .with_ymd_and_hms(2024, 2, 7, 18, 28, 18) + .single() + .unwrap() +} + +#[napi] +pub fn chrono_date_fixture_return2() -> chrono::DateTime { + // Indian Standard Time: UTC+05:30 + let ist = FixedOffset::east_opt(5 * 3600 + 30 * 60).unwrap(); + ist + .with_ymd_and_hms(2024, 2, 7, 18, 28, 18) + .single() + .unwrap() +}