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