From 6d67452a517f827e120889f42c23babd234f238f Mon Sep 17 00:00:00 2001 From: Philemon_Benner Date: Sun, 18 Feb 2024 14:05:14 +0100 Subject: [PATCH 1/2] added timezone support added simple tests updated README.md --- CMakeLists.txt | 9 +++++++- README.md | 28 ++++++++++++++++++++++++ libcron/CMakeLists.txt | 7 ++++++ libcron/include/libcron/CronClock.h | 22 +++++++++++++++++++ libcron/src/CronClock.cpp | 31 +++++++++++++++++++++++++++ test/CMakeLists.txt | 7 ++++++ test/CronTest.cpp | 33 +++++++++++++++++++++++++++++ 7 files changed, 136 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9a994d5..2470911 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,14 @@ cmake_minimum_required(VERSION 3.6) project(top) + +option(LIBCRON_BUILD_TZ_CLOCK "Build timezone clock" OFF) + +if(LIBCRON_BUILD_TZ_CLOCK) + set(BUILD_TZ_LIB ON CACHE BOOL "Build the tz library" FORCE) + add_subdirectory(libcron/externals/date) +endif() + add_subdirectory(libcron) add_subdirectory(test) @@ -9,4 +17,3 @@ add_dependencies(cron_test libcron) install(TARGETS libcron DESTINATION lib) install(DIRECTORY libcron/include/libcron DESTINATION include) install(DIRECTORY libcron/externals/date/include/date DESTINATION include) - diff --git a/README.md b/README.md index 0995db0..29dca74 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,34 @@ This library uses `std::chrono::system_clock::timepoint` as its time unit. While uses a `LocalClock` by default which offsets `system_clock::now()` by the current UTC-offset. If you wish to work in UTC, then construct the Cron instance, passing it a `libcron::UTCClock`. +## TzClock + +This library also offers a `libcron::TzClock` as its time unit. Which makes use of the timezone support of date's library. +With `libcron::TzClock` you can set one of available regions from the iana Timezone database: + +``` +Cron cron; +if(cron.get_clock().set_time_zone("Africa/Maputo")) + std::cout << "Successfully set timezone to: Africa/Maputo \n"; +else + std::cout << "Failed to set timezone to: Africa/Maputo \n"; +``` + +`libcron::TzClock` behaves like `libcron::UTCClock` if no timezone is set. + +If you want to use TzClock you have to set -DLIBCRON_BUILD_TZ_CLOCK=ON when building libcron. TzClock is a fully optional feature +if you don't enable it, it won't be build at all. + +Using TzClock has the following side effects: + 1. Date requires linkage to `libcurl` + 2. First time TzClocks timezone lookup will occur it will download the most up to date timezone: "~/Downloads/tzdata" ("%homedrive%\%homepath%\downloads\tzdata" on Windows). + 3. TzClock uses a `std::mutex` to protect the time zone in multithreaded envoirements. + 4. This implementation might decrease performance a lot based on point 2 and 3. + +[More Info about date-tz](https://howardhinnant.github.io/date/tz.html) + +[Available Regions](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) + # Supported formatting This implementation supports cron format, as specified below. diff --git a/libcron/CMakeLists.txt b/libcron/CMakeLists.txt index 618cc68..4aba49c 100644 --- a/libcron/CMakeLists.txt +++ b/libcron/CMakeLists.txt @@ -35,6 +35,13 @@ target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_LIST_DIR}/externals/date/include PUBLIC include) +if(LIBCRON_BUILD_TZ_CLOCK) + target_link_libraries(${PROJECT_NAME} PUBLIC + date-tz + ) + target_compile_definitions(${PROJECT_NAME} PUBLIC -DBUILD_TZ_CLOCK) +endif() + if(NOT MSVC) # Assume a modern compiler (gcc 9.3) target_compile_definitions (${PROJECT_NAME} PRIVATE -DHAS_UNCAUGHT_EXCEPTIONS) diff --git a/libcron/include/libcron/CronClock.h b/libcron/include/libcron/CronClock.h index 48e667d..d6c72f7 100644 --- a/libcron/include/libcron/CronClock.h +++ b/libcron/include/libcron/CronClock.h @@ -1,6 +1,10 @@ #pragma once #include +#ifdef BUILD_TZ_CLOCK +#include +#include +#endif namespace libcron { @@ -39,4 +43,22 @@ namespace libcron std::chrono::seconds utc_offset(std::chrono::system_clock::time_point now) const override; }; + +#ifdef BUILD_TZ_CLOCK + class TzClock : public ICronClock + { + public: + std::chrono::system_clock::time_point now() const override + { + auto now = std::chrono::system_clock::now(); + return now + utc_offset(now); + } + + bool set_time_zone(std::string_view tz_name); + std::chrono::seconds utc_offset(std::chrono::system_clock::time_point now) const override; + private: + mutable std::mutex time_zone_mtx{}; + const date::time_zone* time_zone{nullptr}; + }; +#endif } diff --git a/libcron/src/CronClock.cpp b/libcron/src/CronClock.cpp index 9aeb00f..6c91f6a 100644 --- a/libcron/src/CronClock.cpp +++ b/libcron/src/CronClock.cpp @@ -36,4 +36,35 @@ namespace libcron #endif return offset; } + +#ifdef BUILD_TZ_CLOCK + bool TzClock::set_time_zone(std::string_view tz_name) + { + const date::time_zone *new_zone{nullptr}; + + try + { + new_zone = date::locate_zone(tz_name); + } + catch (std::runtime_error &err) + { + return false; + } + + std::lock_guard lock(time_zone_mtx); + time_zone = new_zone; + return true; + } + + std::chrono::seconds TzClock::utc_offset(std::chrono::system_clock::time_point now) const + { + using namespace std::chrono; + // If we don't have a timezone we use utc + std::lock_guard lock(time_zone_mtx); + if (time_zone) + return time_zone->get_info(now).offset; + else + return 0s; + } +#endif } \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 5755431..564bc0f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -38,6 +38,13 @@ else() target_link_libraries(${PROJECT_NAME} libcron) endif() +if(LIBCRON_BUILD_TZ_CLOCK) + target_link_libraries(${PROJECT_NAME} + date-tz + ) + target_compile_definitions(${PROJECT_NAME} PUBLIC -DBUILD_TZ_CLOCK) +endif() + set_target_properties(${PROJECT_NAME} PROPERTIES ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/out" LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/out" diff --git a/test/CronTest.cpp b/test/CronTest.cpp index 253206f..297dc57 100644 --- a/test/CronTest.cpp +++ b/test/CronTest.cpp @@ -448,3 +448,36 @@ SCENARIO("Tasks can be added and removed from the scheduler") } } } + +#ifdef BUILD_TZ_CLOCK + +SCENARIO("TzClock: Timezone is not set fallback to utc") +{ + GIVEN("No timezone") + { + TzClock tz_clock{}; + auto now = std::chrono::system_clock::now(); + REQUIRE(tz_clock.utc_offset(now) == 0s); + } + GIVEN("A wrong timezone") + { + TzClock tz_clock{}; + auto now = std::chrono::system_clock::now(); + tz_clock.set_time_zone("404Not/Found"); + REQUIRE(tz_clock.utc_offset(now) == 0s); + } +} + +SCENARIO("TzClock: Setting time zone") +{ + TzClock tz_clock; + GIVEN("Valid time zone") + { + REQUIRE(tz_clock.set_time_zone("Europe/Berlin")); + } + GIVEN("Invalid time zone") + { + REQUIRE_FALSE(tz_clock.set_time_zone("404Not/Found")); + } +} +#endif \ No newline at end of file From dc4529d9fd2c192ad75280a561ccf35874f35a64 Mon Sep 17 00:00:00 2001 From: Philemon_Benner Date: Thu, 29 Feb 2024 02:50:12 +0100 Subject: [PATCH 2/2] added LockType to TzClock to optionally use libcron::NullLock moved date-tz build to libcron added LIBCRON_MANUAL_TZ_DB option updated README.md added documentation on how to set install path --- CMakeLists.txt | 29 +++++++++++++++++++++++++-- README.md | 12 +++++------ libcron/include/libcron/CronClock.h | 31 ++++++++++++++++++++++++++--- libcron/src/CronClock.cpp | 31 ----------------------------- 4 files changed, 61 insertions(+), 42 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2470911..6bc2c71 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,10 +3,35 @@ cmake_minimum_required(VERSION 3.6) project(top) option(LIBCRON_BUILD_TZ_CLOCK "Build timezone clock" OFF) +option(LIBCRON_MANUAL_TZ_DB "User will set TZ DB manually by invoking date::set_install in their code" OFF) if(LIBCRON_BUILD_TZ_CLOCK) - set(BUILD_TZ_LIB ON CACHE BOOL "Build the tz library" FORCE) - add_subdirectory(libcron/externals/date) + add_library(date-tz) + target_sources(date-tz + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/libcron/externals/date/include/date/tz.h + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/libcron/externals/date/include/date/tz_private.h + ${CMAKE_CURRENT_LIST_DIR}/libcron/externals/date/src/tz.cpp + ) + target_include_directories(date-tz PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/libcron/externals/date/include) + + if (LIBCRON_MANUAL_TZ_DB) + target_compile_definitions(date-tz PRIVATE AUTO_DOWNLOAD=0 HAS_REMOTE_API=0) + else() + find_package(CURL REQUIRED) + target_link_libraries(date-tz PRIVATE CURL::libcurl) + target_compile_definitions(date-tz PRIVATE AUTO_DOWNLOAD=1 HAS_REMOTE_API=1) + endif() + + if (WIN32 AND BUILD_SHARED_LIBS) + target_compile_definitions(date-tz PUBLIC DATE_BUILD_DLL=1) + endif() + + if(NOT MSVC) + find_package(Threads) + target_link_libraries(date-tz PUBLIC Threads::Threads) + endif() endif() add_subdirectory(libcron) diff --git a/README.md b/README.md index 29dca74..36b41db 100644 --- a/README.md +++ b/README.md @@ -149,14 +149,14 @@ else If you want to use TzClock you have to set -DLIBCRON_BUILD_TZ_CLOCK=ON when building libcron. TzClock is a fully optional feature if you don't enable it, it won't be build at all. -Using TzClock has the following side effects: - 1. Date requires linkage to `libcurl` - 2. First time TzClocks timezone lookup will occur it will download the most up to date timezone: "~/Downloads/tzdata" ("%homedrive%\%homepath%\downloads\tzdata" on Windows). - 3. TzClock uses a `std::mutex` to protect the time zone in multithreaded envoirements. - 4. This implementation might decrease performance a lot based on point 2 and 3. +By default when setting the time zone with `TzClock::set_time_zone`, date-tz will automatically download the neweset database version from [Time Zones](https://www.iana.org/time-zones). +This can be disabled with `-DLIBCRON_MANUAL_TZ_DB=ON`. If this is set user will be responsible of downloading timezone database. -[More Info about date-tz](https://howardhinnant.github.io/date/tz.html) +Setting database path / install path can be done with `date::set_install`. If not set path will default to: "~/Downloads/tzdata" ("%homedrive%\%homepath%\downloads\tzdata" on Windows). +Using TzClock without LIBCRON_MANUAL_TZ_DB requires liburl installed in your system. On Windows it will also require 7-Zip installed into the default location. +TzClock by default protects it's time_zone with a `std::mutex` this can be disabled by setting LockType to libcron::NullLock. +[More Info about date-tz](https://howardhinnant.github.io/date/tz.html) [Available Regions](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) # Supported formatting diff --git a/libcron/include/libcron/CronClock.h b/libcron/include/libcron/CronClock.h index d6c72f7..e40a2fa 100644 --- a/libcron/include/libcron/CronClock.h +++ b/libcron/include/libcron/CronClock.h @@ -45,6 +45,7 @@ namespace libcron }; #ifdef BUILD_TZ_CLOCK + template class TzClock : public ICronClock { public: @@ -54,10 +55,34 @@ namespace libcron return now + utc_offset(now); } - bool set_time_zone(std::string_view tz_name); - std::chrono::seconds utc_offset(std::chrono::system_clock::time_point now) const override; + std::chrono::seconds utc_offset(std::chrono::system_clock::time_point now) const override + { + // If we don't have a timezone we use utc + using namespace std::chrono; + std::lock_guard lock(time_zone_mtx); + if (time_zone) + return time_zone->get_info(now).offset; + else + return 0s; + } + + bool set_time_zone(std::string_view tz_name) + { + const date::time_zone *new_zone{nullptr}; + try + { + new_zone = date::locate_zone(tz_name); + } + catch (std::runtime_error &err) + { + return false; + } + std::lock_guard lock(time_zone_mtx); + time_zone = new_zone; + return true; + } private: - mutable std::mutex time_zone_mtx{}; + mutable LockType time_zone_mtx{}; const date::time_zone* time_zone{nullptr}; }; #endif diff --git a/libcron/src/CronClock.cpp b/libcron/src/CronClock.cpp index 6c91f6a..9aeb00f 100644 --- a/libcron/src/CronClock.cpp +++ b/libcron/src/CronClock.cpp @@ -36,35 +36,4 @@ namespace libcron #endif return offset; } - -#ifdef BUILD_TZ_CLOCK - bool TzClock::set_time_zone(std::string_view tz_name) - { - const date::time_zone *new_zone{nullptr}; - - try - { - new_zone = date::locate_zone(tz_name); - } - catch (std::runtime_error &err) - { - return false; - } - - std::lock_guard lock(time_zone_mtx); - time_zone = new_zone; - return true; - } - - std::chrono::seconds TzClock::utc_offset(std::chrono::system_clock::time_point now) const - { - using namespace std::chrono; - // If we don't have a timezone we use utc - std::lock_guard lock(time_zone_mtx); - if (time_zone) - return time_zone->get_info(now).offset; - else - return 0s; - } -#endif } \ No newline at end of file