diff --git a/CMakeLists.txt b/CMakeLists.txt index 9a994d5..6bc2c71 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,39 @@ 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) + 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) add_subdirectory(test) @@ -9,4 +42,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..36b41db 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. + +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. + +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 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..e40a2fa 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,47 @@ namespace libcron std::chrono::seconds utc_offset(std::chrono::system_clock::time_point now) const override; }; + +#ifdef BUILD_TZ_CLOCK + template + 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); + } + + 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 LockType time_zone_mtx{}; + const date::time_zone* time_zone{nullptr}; + }; +#endif } 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