Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support passing and return POSIXct types between R and JS #36

Merged
merged 1 commit into from
May 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions R/utils.R
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,7 @@ quickjs_version <- function() {
version_file <- system.file("VERSION", package = "QuickJSR", mustWork = TRUE)
readLines(version_file)
}

get_tz_offset_seconds <- function() {
as.POSIXlt(Sys.time())$gmtoff
}
10 changes: 8 additions & 2 deletions inst/include/quickjsr/JSCommonType.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#ifndef QUICKJSR_JSCOMMONTYPE_HPP
#define QUICKJSR_JSCOMMONTYPE_HPP

#include <quickjsr/JSValue_Date.hpp>
#include <quickjs-libc.h>

namespace quickjsr {
Expand All @@ -10,6 +11,7 @@ enum JSCommonType {
Double,
Logical,
Character,
Date,
NumberArray,
Object,
Unknown
Expand All @@ -30,6 +32,9 @@ JSCommonType JS_GetCommonType(JSContext* ctx, const JSValue& val) {
if (JS_IsString(val)) {
return Character;
}
if (JS_IsDate(ctx, val)) {
return Date;
}
if (JS_IsArray(ctx, val)) {
JSCommonType common_type = JS_ArrayCommonType(ctx, val);
if (common_type == Integer || common_type == Double) {
Expand All @@ -51,8 +56,9 @@ JSCommonType JS_UpdateCommonType(JSCommonType current, JSContext* ctx, const JSV
if (current == new_type) {
return current;
}
// If both types are not NumberArray (checked above), return Object
if (new_type == NumberArray || current == NumberArray || new_type == Object) {
// If one, but not both, types are NumberArray or Date (checked above), return Object
if (new_type == NumberArray || current == NumberArray || new_type == Object
|| new_type == Date || current == Date) {
return Object;
}

Expand Down
61 changes: 61 additions & 0 deletions inst/include/quickjsr/JSValue_Date.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#ifndef QUICKJSR_JSVALUE_DATE_HPP
#define QUICKJSR_JSVALUE_DATE_HPP

#include <cpp11.hpp>
#include <quickjs-libc.h>
#include <cstring>

namespace quickjsr {

inline double get_tz_offset_seconds() {
cpp11::function get_tz_offset_seconds = cpp11::package("QuickJSR")["get_tz_offset_seconds"];

return cpp11::as_cpp<double>(get_tz_offset_seconds());
}

inline bool JS_IsDate(JSContext* ctx, const JSValue& val) {
JSValue ctor = JS_GetPropertyStr(ctx, val, "constructor");
JSValue ctorName = JS_GetPropertyStr(ctx, ctor, "name");

const char* name = JS_ToCString(ctx, ctorName);
bool isDate = strcmp(name, "Date") == 0;

JS_FreeCString(ctx, name);
JS_FreeValue(ctx, ctorName);
JS_FreeValue(ctx, ctor);

return isDate;
}

inline JSValue JS_NewDate(JSContext* ctx, double timestamp, bool posixct = false) {
static constexpr double milliseconds_day = 86400000;
static constexpr double milliseconds_second = 1000;
double tz_offset_seconds = get_tz_offset_seconds();
JSValue global_obj = JS_GetGlobalObject(ctx);
JSValue date_ctor = JS_GetPropertyStr(ctx, global_obj, "Date");
JSValue timestamp_val;
if (posixct) {
timestamp_val = JS_NewFloat64(ctx, (timestamp + tz_offset_seconds) * milliseconds_second);
} else {
timestamp_val = JS_NewFloat64(ctx, timestamp * milliseconds_day);
}
JSValue date = JS_CallConstructor(ctx, date_ctor, 1, &timestamp_val);

JS_FreeValue(ctx, global_obj);
JS_FreeValue(ctx, date_ctor);
JS_FreeValue(ctx, timestamp_val);
return date;
}

inline JSValue JS_GetTime(JSContext* ctx, const JSValue& val) {
JSAtom timeAtom = JS_NewAtom(ctx, "getTime");
JSValue timeVal = JS_Invoke(ctx, val, timeAtom, 0, NULL);

JS_FreeAtom(ctx, timeAtom);

return timeVal;
}

} // namespace quickjsr

#endif
9 changes: 9 additions & 0 deletions inst/include/quickjsr/JSValue_to_Cpp.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#define QUICKJSR_JSVALUE_TO_CPP_HPP

#include <quickjsr/type_traits.hpp>
#include <quickjsr/JSCommonType.hpp>
#include <type_traits>
#include <string>
#include <vector>
Expand All @@ -10,6 +11,14 @@
namespace quickjsr {
template <typename T, enable_if_type_t<double, T>* = nullptr>
T JSValue_to_Cpp(JSContext* ctx, JSValue val) {
if (JS_IsDate(ctx, val)) {
JSValue time = JS_GetTime(ctx, val);
double res;
JS_ToFloat64(ctx, &res, time);
JS_FreeValue(ctx, time);
return res / 1000.0; // Convert milliseconds to seconds for R POSIXct
}

double res;
JS_ToFloat64(ctx, &res, val);
return res;
Expand Down
11 changes: 11 additions & 0 deletions inst/include/quickjsr/JSValue_to_SEXP.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <quickjs-libc.h>
#include <quickjsr/JSValue_to_Cpp.hpp>
#include <quickjsr/JSCommonType.hpp>
#include <quickjsr/JSValue_Date.hpp>

namespace quickjsr {

Expand Down Expand Up @@ -35,6 +36,11 @@ SEXP JSValue_to_SEXP_scalar(JSContext* ctx, const JSValue& val) {
if (JS_IsString(val)) {
return cpp11::as_sexp(JSValue_to_Cpp<std::string>(ctx, val));
}
if (JS_IsDate(ctx, val)) {
cpp11::writable::doubles res = cpp11::as_sexp(JSValue_to_Cpp<double>(ctx, val));
res.attr("class") = "POSIXct";
return res;
}
return cpp11::as_sexp("Unsupported type");
}

Expand All @@ -48,6 +54,11 @@ SEXP JSValue_to_SEXP_vector(JSContext* ctx, const JSValue& val) {
return cpp11::as_sexp(JSValue_to_Cpp<std::vector<bool>>(ctx, val));
case Character:
return cpp11::as_sexp(JSValue_to_Cpp<std::vector<std::string>>(ctx, val));
case Date: {
cpp11::writable::doubles res = cpp11::as_sexp(JSValue_to_Cpp<std::vector<double>>(ctx, val));
res.attr("class") = "POSIXct";
return res;
}
case NumberArray: {
std::vector<std::vector<double>> res = JSValue_to_Cpp<std::vector<std::vector<double>>>(ctx, val);
// Check that the inner vectors are all the same length
Expand Down
17 changes: 4 additions & 13 deletions inst/include/quickjsr/SEXP_to_JSValue.hpp
Original file line number Diff line number Diff line change
@@ -1,22 +1,11 @@
#ifndef QUICKJSR_SEXP_TO_JSVALUE_HPP
#define QUICKJSR_SEXP_TO_JSVALUE_HPP

#include <quickjsr/JSValue_Date.hpp>
#include <cpp11.hpp>
#include <quickjs-libc.h>

namespace quickjsr {
inline JSValue JS_NewDate(JSContext* ctx, double timestamp) {
static constexpr double milliseconds_day = 86400000;
JSValue global_obj = JS_GetGlobalObject(ctx);
JSValue date_ctor = JS_GetPropertyStr(ctx, global_obj, "Date");
JSValue timestamp_val = JS_NewFloat64(ctx, timestamp * milliseconds_day);
JSValue date = JS_CallConstructor(ctx, date_ctor, 1, &timestamp_val);

JS_FreeValue(ctx, global_obj);
JS_FreeValue(ctx, date_ctor);
JS_FreeValue(ctx, timestamp_val);
return date;
}
// Forward declaration to allow for recursive calls
inline JSValue SEXP_to_JSValue(JSContext* ctx, const SEXP& x, bool auto_unbox, bool auto_unbox_curr);
inline JSValue SEXP_to_JSValue(JSContext* ctx, const SEXP& x, bool auto_unbox, bool auto_unbox_curr, int index);
Expand Down Expand Up @@ -121,7 +110,9 @@ namespace quickjsr {
}
}
case REALSXP: {
if (Rf_inherits(x, "Date")) {
if (Rf_inherits(x, "POSIXct")) {
return JS_NewDate(ctx, REAL_ELT(x, index), true);
} else if (Rf_inherits(x, "Date")) {
return JS_NewDate(ctx, REAL_ELT(x, index));
} else {
return JS_NewFloat64(ctx, REAL_ELT(x, index));
Expand Down
3 changes: 3 additions & 0 deletions inst/tinytest/test_to_json_date.R
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ expect_equal(to_json(data.frame(foo=object)),
"[{\"foo\":\"1985-06-18T00:00:00.000Z\"}]");
expect_equal(to_json(list(foo=data.frame(bar=object))),
"{\"foo\":[{\"bar\":\"1985-06-18T00:00:00.000Z\"}]}");

object <- as.POSIXct("1985-06-18 12:34:56");
expect_equal(to_json(object), "[\"1985-06-18T12:34:56.000Z\"]");
Loading