1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
use std::{ptr, str::FromStr};

use chrono::{DateTime, NaiveDateTime, Utc};

use crate::{bindgen_prelude::*, check_status, sys, ValueType};

impl TypeName for DateTime<Utc> {
  fn type_name() -> &'static str {
    "DateTime"
  }

  fn value_type() -> ValueType {
    ValueType::Object
  }
}

impl ValidateNapiValue for DateTime<Utc> {
  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) })?;
    if !is_date {
      return Err(Error::new(
        Status::InvalidArg,
        "Expected a Date object".to_owned(),
      ));
    }

    Ok(ptr::null_mut())
  }
}

impl ToNapiValue for NaiveDateTime {
  unsafe fn to_napi_value(env: sys::napi_env, val: NaiveDateTime) -> 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 `NaiveDateTime` into napi value",
    )?;

    Ok(ptr)
  }
}

impl FromNapiValue for NaiveDateTime {
  unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
    let mut to_iso_string = ptr::null_mut();
    check_status!(
      unsafe {
        napi_sys::napi_create_string_utf8(
          env,
          "toISOString\0".as_ptr().cast(),
          11,
          &mut to_iso_string,
        )
      },
      "create toISOString JavaScript string failed"
    )?;
    let mut to_iso_string_method = ptr::null_mut();
    check_status!(
      unsafe { sys::napi_get_property(env, napi_val, to_iso_string, &mut to_iso_string_method) },
      "get toISOString method failed"
    )?;
    let mut iso_string_value = ptr::null_mut();
    check_status!(
      unsafe {
        sys::napi_call_function(
          env,
          napi_val,
          to_iso_string_method,
          0,
          ptr::null(),
          &mut iso_string_value,
        )
      },
      "Call toISOString on Date Object failed"
    )?;

    let mut iso_string_length = 0;
    check_status!(
      unsafe {
        sys::napi_get_value_string_utf8(
          env,
          iso_string_value,
          ptr::null_mut(),
          0,
          &mut iso_string_length,
        )
      },
      "Get ISOString length failed"
    )?;
    let mut iso_string = String::with_capacity(iso_string_length + 1);
    check_status!(
      unsafe {
        sys::napi_get_value_string_utf8(
          env,
          iso_string_value,
          iso_string.as_mut_ptr().cast(),
          iso_string_length,
          &mut iso_string_length,
        )
      },
      "Get ISOString length failed"
    )?;

    unsafe { iso_string.as_mut_vec().set_len(iso_string_length) };

    let naive = NaiveDateTime::from_str(iso_string.as_str()).map_err(|err| {
      Error::new(
        Status::InvalidArg,
        format!(
          "Failed to convert napi value into rust type `NaiveDateTime` {} {}",
          err, iso_string
        ),
      )
    })?;

    Ok(naive)
  }
}

impl ToNapiValue for DateTime<Utc> {
  unsafe fn to_napi_value(env: sys::napi_env, val: DateTime<Utc>) -> 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",
    )?;

    Ok(ptr)
  }
}

impl FromNapiValue for DateTime<Utc> {
  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>`",
    )?;

    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::<Utc>::from_naive_utc_and_offset(naive, Utc))
  }
}