From 85a3b8e13661062ca2d080445df58cfb555f14f2 Mon Sep 17 00:00:00 2001 From: DavisVaughan Date: Thu, 8 Apr 2021 20:01:47 -0400 Subject: [PATCH 1/8] Implement limited callable API for vroom --- inst/include/clock/clock.h | 48 ++++++++++++++++++++++++ inst/include/clock/decl.h | 20 ++++++++++ src/cpp11.cpp | 3 ++ src/exported.cpp | 76 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 147 insertions(+) create mode 100644 inst/include/clock/clock.h create mode 100644 inst/include/clock/decl.h create mode 100644 src/exported.cpp diff --git a/inst/include/clock/clock.h b/inst/include/clock/clock.h new file mode 100644 index 00000000..4632b20a --- /dev/null +++ b/inst/include/clock/clock.h @@ -0,0 +1,48 @@ +#ifndef CLOCK_CLOCK_H +#define CLOCK_CLOCK_H + +#include + +#include +#include + +#include "decl.h" + +namespace rclock { + +static +inline +std::chrono::seconds +build(short year, + unsigned month, + unsigned day, + unsigned hour, + unsigned minute, + unsigned second) { + typedef std::chrono::seconds fn_t(short, unsigned, unsigned, unsigned, unsigned, unsigned); + static fn_t *fn = (fn_t*) R_GetCCallable("clock", "clock_build"); + return fn(year, month, day, hour, minute, second); +} + +static +inline +struct time_zone +zone_name_load(const std::string& zone_name) { + typedef struct time_zone fn_t(const std::string&); + static fn_t *fn = (fn_t*) R_GetCCallable("clock", "clock_zone_name_load"); + return fn(zone_name); +} + +static +inline +struct sys_result +naive_to_sys(const std::chrono::seconds& naive, + const struct time_zone& zone) { + typedef struct sys_result fn_t(const std::chrono::seconds&, const struct time_zone&); + static fn_t *fn = (fn_t*) R_GetCCallable("clock", "clock_naive_to_sys"); + return fn(naive, zone); +} + +} // namespace rclock + +#endif diff --git a/inst/include/clock/decl.h b/inst/include/clock/decl.h new file mode 100644 index 00000000..2184209e --- /dev/null +++ b/inst/include/clock/decl.h @@ -0,0 +1,20 @@ +#ifndef CLOCK_DECL_H +#define CLOCK_DECL_H + +#include +#include + +namespace rclock { + +struct time_zone { + const void* p_time_zone; +}; + +struct sys_result { + std::chrono::seconds sys_time; + bool ok; +}; + +} // namespace rclock + +#endif diff --git a/src/cpp11.cpp b/src/cpp11.cpp index 5c6b9dd3..4bccc2c9 100644 --- a/src/cpp11.cpp +++ b/src/cpp11.cpp @@ -1139,8 +1139,11 @@ static const R_CallMethodDef CallEntries[] = { }; } +void export_clock_callables(DllInfo* dll); + extern "C" void R_init_clock(DllInfo* dll){ R_registerRoutines(dll, NULL, CallEntries, NULL, NULL); R_useDynamicSymbols(dll, FALSE); + export_clock_callables(dll); R_forceSymbols(dll, TRUE); } diff --git a/src/exported.cpp b/src/exported.cpp new file mode 100644 index 00000000..a3da7a5e --- /dev/null +++ b/src/exported.cpp @@ -0,0 +1,76 @@ +#include "clock.h" +#include "zone.h" +#include "clock/decl.h" +#include // For DllInfo on R 3.3 + +// ----------------------------------------------------------------------------- + +std::chrono::seconds +clock_build(short year, + unsigned month, + unsigned day, + unsigned hour, + unsigned minute, + unsigned second) { + const date::local_days ld{date::year{year} / month / day}; + + return std::chrono::seconds{second} + + std::chrono::minutes{minute} + + std::chrono::hours{hour} + + ld.time_since_epoch(); +} + +// ----------------------------------------------------------------------------- + +struct rclock::time_zone +clock_zone_name_load(const std::string& zone_name) { + const date::time_zone* p_time_zone = zone_name_load(zone_name); + + const struct rclock::time_zone zone { + static_cast(p_time_zone) + }; + + return zone; +} + +// ----------------------------------------------------------------------------- + +struct rclock::sys_result +clock_naive_to_sys(const std::chrono::seconds& naive, + const struct rclock::time_zone& zone) { + const date::local_seconds lt{naive}; + const date::time_zone* p_time_zone = static_cast(zone.p_time_zone); + + const date::local_info info = p_time_zone->get_info(lt); + + struct rclock::sys_result out; + out.ok = true; + + switch (info.result) { + case date::local_info::unique: { + out.sys_time = lt.time_since_epoch() - info.first.offset; + break; + } + case date::local_info::ambiguous: { + // Choose `earliest` of the two ambiguous times + out.sys_time = lt.time_since_epoch() - info.first.offset; + break; + } + case date::local_info::nonexistent: { + // Client should return `NA` + out.ok = false; + break; + } + } + + return out; +} + +// ----------------------------------------------------------------------------- + +[[cpp11::init]] +void export_clock_callables(DllInfo* dll){ + R_RegisterCCallable("clock", "clock_build", (DL_FUNC)clock_build); + R_RegisterCCallable("clock", "clock_zone_name_load", (DL_FUNC)clock_zone_name_load); + R_RegisterCCallable("clock", "clock_naive_to_sys", (DL_FUNC)clock_naive_to_sys); +} From 7c2f93b2f46e5709673e720274b6f1fe90aa75a4 Mon Sep 17 00:00:00 2001 From: DavisVaughan Date: Fri, 9 Apr 2021 08:44:30 -0400 Subject: [PATCH 2/8] Remove `clock_build()` in favor of using `date.h` tools directly --- inst/include/clock/clock.h | 24 ++++++------------------ inst/include/clock/decl.h | 5 +++-- src/exported.cpp | 30 ++++++------------------------ 3 files changed, 15 insertions(+), 44 deletions(-) diff --git a/inst/include/clock/clock.h b/inst/include/clock/clock.h index 4632b20a..3f1e2eca 100644 --- a/inst/include/clock/clock.h +++ b/inst/include/clock/clock.h @@ -6,24 +6,12 @@ #include #include +#include + #include "decl.h" namespace rclock { -static -inline -std::chrono::seconds -build(short year, - unsigned month, - unsigned day, - unsigned hour, - unsigned minute, - unsigned second) { - typedef std::chrono::seconds fn_t(short, unsigned, unsigned, unsigned, unsigned, unsigned); - static fn_t *fn = (fn_t*) R_GetCCallable("clock", "clock_build"); - return fn(year, month, day, hour, minute, second); -} - static inline struct time_zone @@ -36,11 +24,11 @@ zone_name_load(const std::string& zone_name) { static inline struct sys_result -naive_to_sys(const std::chrono::seconds& naive, +local_to_sys(const date::local_seconds& lt, const struct time_zone& zone) { - typedef struct sys_result fn_t(const std::chrono::seconds&, const struct time_zone&); - static fn_t *fn = (fn_t*) R_GetCCallable("clock", "clock_naive_to_sys"); - return fn(naive, zone); + typedef struct sys_result fn_t(const date::local_seconds&, const struct time_zone&); + static fn_t *fn = (fn_t*) R_GetCCallable("clock", "clock_local_to_sys"); + return fn(lt, zone); } } // namespace rclock diff --git a/inst/include/clock/decl.h b/inst/include/clock/decl.h index 2184209e..905359ac 100644 --- a/inst/include/clock/decl.h +++ b/inst/include/clock/decl.h @@ -1,9 +1,10 @@ #ifndef CLOCK_DECL_H #define CLOCK_DECL_H -#include #include +#include + namespace rclock { struct time_zone { @@ -11,7 +12,7 @@ struct time_zone { }; struct sys_result { - std::chrono::seconds sys_time; + date::sys_seconds st; bool ok; }; diff --git a/src/exported.cpp b/src/exported.cpp index a3da7a5e..ba558a82 100644 --- a/src/exported.cpp +++ b/src/exported.cpp @@ -5,23 +5,6 @@ // ----------------------------------------------------------------------------- -std::chrono::seconds -clock_build(short year, - unsigned month, - unsigned day, - unsigned hour, - unsigned minute, - unsigned second) { - const date::local_days ld{date::year{year} / month / day}; - - return std::chrono::seconds{second} + - std::chrono::minutes{minute} + - std::chrono::hours{hour} + - ld.time_since_epoch(); -} - -// ----------------------------------------------------------------------------- - struct rclock::time_zone clock_zone_name_load(const std::string& zone_name) { const date::time_zone* p_time_zone = zone_name_load(zone_name); @@ -36,24 +19,24 @@ clock_zone_name_load(const std::string& zone_name) { // ----------------------------------------------------------------------------- struct rclock::sys_result -clock_naive_to_sys(const std::chrono::seconds& naive, +clock_local_to_sys(const date::local_seconds& lt, const struct rclock::time_zone& zone) { - const date::local_seconds lt{naive}; const date::time_zone* p_time_zone = static_cast(zone.p_time_zone); const date::local_info info = p_time_zone->get_info(lt); struct rclock::sys_result out; - out.ok = true; switch (info.result) { case date::local_info::unique: { - out.sys_time = lt.time_since_epoch() - info.first.offset; + out.st = date::sys_seconds{lt.time_since_epoch() - info.first.offset}; + out.ok = true; break; } case date::local_info::ambiguous: { // Choose `earliest` of the two ambiguous times - out.sys_time = lt.time_since_epoch() - info.first.offset; + out.st = date::sys_seconds{lt.time_since_epoch() - info.first.offset}; + out.ok = true; break; } case date::local_info::nonexistent: { @@ -70,7 +53,6 @@ clock_naive_to_sys(const std::chrono::seconds& naive, [[cpp11::init]] void export_clock_callables(DllInfo* dll){ - R_RegisterCCallable("clock", "clock_build", (DL_FUNC)clock_build); R_RegisterCCallable("clock", "clock_zone_name_load", (DL_FUNC)clock_zone_name_load); - R_RegisterCCallable("clock", "clock_naive_to_sys", (DL_FUNC)clock_naive_to_sys); + R_RegisterCCallable("clock", "clock_local_to_sys", (DL_FUNC)clock_local_to_sys); } From 3b27339627180ea71fb81ac7ba12a6cf3ffc318d Mon Sep 17 00:00:00 2001 From: DavisVaughan Date: Fri, 9 Apr 2021 08:53:52 -0400 Subject: [PATCH 3/8] Remove indirection around `date::time_zone*` It seems like the forward declaration of `class time_zone` in `date/tz.h` is enough to allow us to use it as a return value and an argument --- inst/include/clock/clock.h | 11 ++++++----- inst/include/clock/decl.h | 4 ---- src/exported.cpp | 14 +++----------- 3 files changed, 9 insertions(+), 20 deletions(-) diff --git a/inst/include/clock/clock.h b/inst/include/clock/clock.h index 3f1e2eca..92c458f7 100644 --- a/inst/include/clock/clock.h +++ b/inst/include/clock/clock.h @@ -7,6 +7,7 @@ #include #include +#include #include "decl.h" @@ -14,9 +15,9 @@ namespace rclock { static inline -struct time_zone +const date::time_zone* zone_name_load(const std::string& zone_name) { - typedef struct time_zone fn_t(const std::string&); + typedef const date::time_zone* fn_t(const std::string&); static fn_t *fn = (fn_t*) R_GetCCallable("clock", "clock_zone_name_load"); return fn(zone_name); } @@ -25,10 +26,10 @@ static inline struct sys_result local_to_sys(const date::local_seconds& lt, - const struct time_zone& zone) { - typedef struct sys_result fn_t(const date::local_seconds&, const struct time_zone&); + const date::time_zone* p_time_zone) { + typedef struct sys_result fn_t(const date::local_seconds&, const date::time_zone*); static fn_t *fn = (fn_t*) R_GetCCallable("clock", "clock_local_to_sys"); - return fn(lt, zone); + return fn(lt, p_time_zone); } } // namespace rclock diff --git a/inst/include/clock/decl.h b/inst/include/clock/decl.h index 905359ac..aa5796c5 100644 --- a/inst/include/clock/decl.h +++ b/inst/include/clock/decl.h @@ -7,10 +7,6 @@ namespace rclock { -struct time_zone { - const void* p_time_zone; -}; - struct sys_result { date::sys_seconds st; bool ok; diff --git a/src/exported.cpp b/src/exported.cpp index ba558a82..6d4f27f5 100644 --- a/src/exported.cpp +++ b/src/exported.cpp @@ -5,24 +5,16 @@ // ----------------------------------------------------------------------------- -struct rclock::time_zone +const date::time_zone* clock_zone_name_load(const std::string& zone_name) { - const date::time_zone* p_time_zone = zone_name_load(zone_name); - - const struct rclock::time_zone zone { - static_cast(p_time_zone) - }; - - return zone; + return zone_name_load(zone_name); } // ----------------------------------------------------------------------------- struct rclock::sys_result clock_local_to_sys(const date::local_seconds& lt, - const struct rclock::time_zone& zone) { - const date::time_zone* p_time_zone = static_cast(zone.p_time_zone); - + const date::time_zone* p_time_zone) { const date::local_info info = p_time_zone->get_info(lt); struct rclock::sys_result out; From 14f671dbaf39b5c3f65833b147d47348a5b59f5d Mon Sep 17 00:00:00 2001 From: DavisVaughan Date: Fri, 9 Apr 2021 11:14:50 -0400 Subject: [PATCH 4/8] Bump development version for vroom --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 82a15505..64ecf66b 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: clock Title: Date-Time Types and Tools -Version: 0.1.0.9000 +Version: 0.1.0.9001 Authors@R: c(person(given = "Davis", family = "Vaughan", From 266f3bccc8c18d5f3fd44c797214d071d00578ef Mon Sep 17 00:00:00 2001 From: DavisVaughan Date: Fri, 9 Apr 2021 12:08:43 -0400 Subject: [PATCH 5/8] Allow runtime errors to be thrown from `clock_locate_zone()` vroom will possibly call this in parallel, and will catch any runtime errors. However, you cannot throw a cpp11 error, or allocate any R memory, while in a thread. --- inst/include/clock/clock.h | 4 ++-- src/exported.cpp | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/inst/include/clock/clock.h b/inst/include/clock/clock.h index 92c458f7..51d3a4de 100644 --- a/inst/include/clock/clock.h +++ b/inst/include/clock/clock.h @@ -16,9 +16,9 @@ namespace rclock { static inline const date::time_zone* -zone_name_load(const std::string& zone_name) { +locate_zone(const std::string& zone_name) { typedef const date::time_zone* fn_t(const std::string&); - static fn_t *fn = (fn_t*) R_GetCCallable("clock", "clock_zone_name_load"); + static fn_t *fn = (fn_t*) R_GetCCallable("clock", "clock_locate_zone"); return fn(zone_name); } diff --git a/src/exported.cpp b/src/exported.cpp index 6d4f27f5..2d220392 100644 --- a/src/exported.cpp +++ b/src/exported.cpp @@ -1,13 +1,12 @@ #include "clock.h" -#include "zone.h" #include "clock/decl.h" #include // For DllInfo on R 3.3 // ----------------------------------------------------------------------------- const date::time_zone* -clock_zone_name_load(const std::string& zone_name) { - return zone_name_load(zone_name); +clock_locate_zone(const std::string& zone_name) { + return date::locate_zone(zone_name); } // ----------------------------------------------------------------------------- @@ -45,6 +44,6 @@ clock_local_to_sys(const date::local_seconds& lt, [[cpp11::init]] void export_clock_callables(DllInfo* dll){ - R_RegisterCCallable("clock", "clock_zone_name_load", (DL_FUNC)clock_zone_name_load); - R_RegisterCCallable("clock", "clock_local_to_sys", (DL_FUNC)clock_local_to_sys); + R_RegisterCCallable("clock", "clock_locate_zone", (DL_FUNC)clock_locate_zone); + R_RegisterCCallable("clock", "clock_local_to_sys", (DL_FUNC)clock_local_to_sys); } From 8bc210542f659defbdeb0ea3d530272b7c01c652 Mon Sep 17 00:00:00 2001 From: DavisVaughan Date: Fri, 9 Apr 2021 13:39:23 -0400 Subject: [PATCH 6/8] Document exported helpers --- src/exported.cpp | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/exported.cpp b/src/exported.cpp index 2d220392..db358924 100644 --- a/src/exported.cpp +++ b/src/exported.cpp @@ -4,6 +4,17 @@ // ----------------------------------------------------------------------------- +/* + * Look up a time zone by name + * + * Returns a `time_zone` pointer for use in `clock_local_to_sys()`. + * + * `""` should not be passed through here. If you need to pass through the + * system time zone, materialize its value with `Sys.timezone()` first. + * + * Throws a `std::runtime_error()` with an informative message if the + * `zone_name` does not exist in the database. + */ const date::time_zone* clock_locate_zone(const std::string& zone_name) { return date::locate_zone(zone_name); @@ -11,6 +22,17 @@ clock_locate_zone(const std::string& zone_name) { // ----------------------------------------------------------------------------- +/* + * Pair a local time with a time zone to compute the corresponding sys time + * + * Returns `sys_result`, a struct with two members: + * - `date::sys_seconds st`: The sys time. Undefined if `!ok`. + * - `bool ok`: If `lt` represents a nonexistent time, then `ok = false`. + * + * Clients should check `ok` before accessing `st`. + * + * For ambiguous times, always returns the earliest of the two possible times. + */ struct rclock::sys_result clock_local_to_sys(const date::local_seconds& lt, const date::time_zone* p_time_zone) { From f52749056871029ca3f06e157157a7031937b75f Mon Sep 17 00:00:00 2001 From: DavisVaughan Date: Fri, 9 Apr 2021 13:52:41 -0400 Subject: [PATCH 7/8] Export `clock_get_local_info()` for maximum flexibility This way the client can choose exactly how to handle nonexistent and ambiguous times, and we don't have to return a custom struct for nonexistent times --- inst/include/clock/clock.h | 12 ++++------ inst/include/clock/decl.h | 17 -------------- src/exported.cpp | 48 +++++++------------------------------- 3 files changed, 13 insertions(+), 64 deletions(-) delete mode 100644 inst/include/clock/decl.h diff --git a/inst/include/clock/clock.h b/inst/include/clock/clock.h index 51d3a4de..7efd94f3 100644 --- a/inst/include/clock/clock.h +++ b/inst/include/clock/clock.h @@ -9,8 +9,6 @@ #include #include -#include "decl.h" - namespace rclock { static @@ -24,11 +22,11 @@ locate_zone(const std::string& zone_name) { static inline -struct sys_result -local_to_sys(const date::local_seconds& lt, - const date::time_zone* p_time_zone) { - typedef struct sys_result fn_t(const date::local_seconds&, const date::time_zone*); - static fn_t *fn = (fn_t*) R_GetCCallable("clock", "clock_local_to_sys"); +date::local_info +get_local_info(const date::local_seconds& lt, + const date::time_zone* p_time_zone) { + typedef date::local_info fn_t(const date::local_seconds&, const date::time_zone*); + static fn_t *fn = (fn_t*) R_GetCCallable("clock", "clock_get_local_info"); return fn(lt, p_time_zone); } diff --git a/inst/include/clock/decl.h b/inst/include/clock/decl.h deleted file mode 100644 index aa5796c5..00000000 --- a/inst/include/clock/decl.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef CLOCK_DECL_H -#define CLOCK_DECL_H - -#include - -#include - -namespace rclock { - -struct sys_result { - date::sys_seconds st; - bool ok; -}; - -} // namespace rclock - -#endif diff --git a/src/exported.cpp b/src/exported.cpp index db358924..2ab595c4 100644 --- a/src/exported.cpp +++ b/src/exported.cpp @@ -1,5 +1,4 @@ #include "clock.h" -#include "clock/decl.h" #include // For DllInfo on R 3.3 // ----------------------------------------------------------------------------- @@ -7,7 +6,7 @@ /* * Look up a time zone by name * - * Returns a `time_zone` pointer for use in `clock_local_to_sys()`. + * Returns a `time_zone` pointer for use in `clock_get_local_info()`. * * `""` should not be passed through here. If you need to pass through the * system time zone, materialize its value with `Sys.timezone()` first. @@ -23,49 +22,18 @@ clock_locate_zone(const std::string& zone_name) { // ----------------------------------------------------------------------------- /* - * Pair a local time with a time zone to compute the corresponding sys time - * - * Returns `sys_result`, a struct with two members: - * - `date::sys_seconds st`: The sys time. Undefined if `!ok`. - * - `bool ok`: If `lt` represents a nonexistent time, then `ok = false`. - * - * Clients should check `ok` before accessing `st`. - * - * For ambiguous times, always returns the earliest of the two possible times. + * Pair a local time with a time zone to compute all local time information */ -struct rclock::sys_result -clock_local_to_sys(const date::local_seconds& lt, - const date::time_zone* p_time_zone) { - const date::local_info info = p_time_zone->get_info(lt); - - struct rclock::sys_result out; - - switch (info.result) { - case date::local_info::unique: { - out.st = date::sys_seconds{lt.time_since_epoch() - info.first.offset}; - out.ok = true; - break; - } - case date::local_info::ambiguous: { - // Choose `earliest` of the two ambiguous times - out.st = date::sys_seconds{lt.time_since_epoch() - info.first.offset}; - out.ok = true; - break; - } - case date::local_info::nonexistent: { - // Client should return `NA` - out.ok = false; - break; - } - } - - return out; +date::local_info +clock_get_local_info(const date::local_seconds& lt, + const date::time_zone* p_time_zone) { + return p_time_zone->get_info(lt); } // ----------------------------------------------------------------------------- [[cpp11::init]] void export_clock_callables(DllInfo* dll){ - R_RegisterCCallable("clock", "clock_locate_zone", (DL_FUNC)clock_locate_zone); - R_RegisterCCallable("clock", "clock_local_to_sys", (DL_FUNC)clock_local_to_sys); + R_RegisterCCallable("clock", "clock_locate_zone", (DL_FUNC)clock_locate_zone); + R_RegisterCCallable("clock", "clock_get_local_info", (DL_FUNC)clock_get_local_info); } From 5a73ffb73c51a055824a9c670ad2abb2882b915b Mon Sep 17 00:00:00 2001 From: DavisVaughan Date: Fri, 9 Apr 2021 14:01:07 -0400 Subject: [PATCH 8/8] NEWS --- NEWS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NEWS.md b/NEWS.md index 115c6505..02b499be 100644 --- a/NEWS.md +++ b/NEWS.md @@ -46,6 +46,8 @@ * Linking against cpp11 0.2.7 is now required to fix a rare memory leak issue. +* Exposed an extremely experimental and limited C++ API for vroom (#322). + # clock 0.1.0 * Added a `NEWS.md` file to track changes to the package.