mirror of
https://github.com/napi-rs/napi-rs.git
synced 2025-12-08 19:56:07 +00:00
feat(napi): support chrono datetime with timezones (#2074)
* feat(napi): implement From/ToNapiValue for chrono::DateTime<Local> and chrono::DateTime<FixedOffset> (#1902) * chore(napi): update tests Co-authored-by: sup39 <dev@sup39.dev> --------- Co-authored-by: sup39 <dev@sup39.dev>
This commit is contained in:
parent
a54b759bb1
commit
68b5f2c587
@ -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<Utc> {
|
||||
impl<Tz: TimeZone> TypeName for DateTime<Tz> {
|
||||
fn type_name() -> &'static str {
|
||||
"DateTime"
|
||||
}
|
||||
@ -14,7 +14,7 @@ impl TypeName for DateTime<Utc> {
|
||||
}
|
||||
}
|
||||
|
||||
impl ValidateNapiValue for DateTime<Utc> {
|
||||
impl<Tz: TimeZone> ValidateNapiValue for DateTime<Tz> {
|
||||
unsafe fn validate(env: sys::napi_env, napi_val: sys::napi_value) -> Result<sys::napi_value> {
|
||||
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<Utc> {
|
||||
unsafe fn to_napi_value(env: sys::napi_env, val: DateTime<Utc>) -> Result<sys::napi_value> {
|
||||
impl<Tz: TimeZone> ToNapiValue for DateTime<Tz> {
|
||||
unsafe fn to_napi_value(env: sys::napi_env, val: DateTime<Tz>) -> Result<sys::napi_value> {
|
||||
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<Utc>` into napi value",
|
||||
"Failed to convert rust type `DateTime` into napi value",
|
||||
)?;
|
||||
|
||||
Ok(ptr)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromNapiValue for DateTime<Utc> {
|
||||
impl<Tz: TimeZone> FromNapiValue for DateTime<Tz>
|
||||
where
|
||||
DateTime<Tz>: From<DateTime<Local>>,
|
||||
{
|
||||
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
|
||||
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<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 = 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::<Utc>::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(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<string>␊
|
||||
␊
|
||||
export interface LocalDates {␊
|
||||
start: Date␊
|
||||
end?: Date␊
|
||||
}␊
|
||||
␊
|
||||
export function mapOption(val?: number | undefined | null): number | null␊
|
||||
␊
|
||||
export function mutateExternal(external: ExternalObject<number>, 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>): number␊
|
||||
␊
|
||||
export function validateBigint(input: bigint): bigint␊
|
||||
|
||||
Binary file not shown.
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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<string>
|
||||
|
||||
export interface LocalDates {
|
||||
start: Date
|
||||
end?: Date
|
||||
}
|
||||
|
||||
export function mapOption(val?: number | undefined | null): number | null
|
||||
|
||||
export function mutateExternal(external: ExternalObject<number>, 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>): number
|
||||
|
||||
export function validateBigint(input: bigint): bigint
|
||||
|
||||
@ -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<f64> {
|
||||
}
|
||||
|
||||
#[napi]
|
||||
fn chrono_date_to_millis(input: chrono::DateTime<Utc>) -> i64 {
|
||||
fn chrono_utc_date_to_millis(input: chrono::DateTime<Utc>) -> i64 {
|
||||
input.timestamp_millis()
|
||||
}
|
||||
|
||||
#[napi]
|
||||
fn chrono_local_date_to_millis(input: chrono::DateTime<Local>) -> i64 {
|
||||
input.timestamp_millis()
|
||||
}
|
||||
|
||||
#[napi]
|
||||
fn chrono_date_with_timezone_to_millis(input: chrono::DateTime<FixedOffset>) -> i64 {
|
||||
input.timestamp_millis()
|
||||
}
|
||||
|
||||
@ -19,11 +29,23 @@ fn chrono_date_add_1_minute(input: chrono::DateTime<Utc>) -> chrono::DateTime<Ut
|
||||
}
|
||||
|
||||
#[napi(object)]
|
||||
pub struct Dates {
|
||||
pub struct UtcDates {
|
||||
pub start: chrono::DateTime<Utc>,
|
||||
pub end: Option<chrono::DateTime<Utc>>,
|
||||
}
|
||||
|
||||
#[napi(object)]
|
||||
pub struct LocalDates {
|
||||
pub start: chrono::DateTime<Local>,
|
||||
pub end: Option<chrono::DateTime<Local>>,
|
||||
}
|
||||
|
||||
#[napi(object)]
|
||||
pub struct DatesWithTimeZone {
|
||||
pub start: chrono::DateTime<FixedOffset>,
|
||||
pub end: Option<chrono::DateTime<FixedOffset>>,
|
||||
}
|
||||
|
||||
#[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> {
|
||||
chrono::NaiveDateTime::from_str("2016-12-23T15:25:59.325").ok()
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn chrono_utc_date_return() -> Option<chrono::DateTime<Utc>> {
|
||||
chrono::DateTime::<Utc>::from_str("2016-12-23T15:25:59.325").ok()
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn chrono_local_date_return() -> Option<chrono::DateTime<Local>> {
|
||||
chrono::DateTime::<Local>::from_str("2016-12-23T15:25:59.325").ok()
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn chrono_date_with_timezone_return() -> Option<chrono::DateTime<FixedOffset>> {
|
||||
chrono::DateTime::<FixedOffset>::from_str("2016-12-23T15:25:59.325").ok()
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn chrono_date_fixture_return1() -> chrono::DateTime<FixedOffset> {
|
||||
// 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<FixedOffset> {
|
||||
// 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()
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user