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

added timezone support #34

Closed
Closed
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
34 changes: 33 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)

Expand All @@ -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)

28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<TzClock> 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.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait, it requires 7-zip ? Meaning it will execute it to unpack? I'll have to pull the emergency break here. This library should not execute any application, too much of a security issue the user of this library doesn't have control over.

With that in mind, we'll have to settle with using a pre-existing DB and let the library user handle management of that file.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With that in mind, the TzClock can take a path to the DB as a constructor argument and call date:set_install, ensuring it is available before the construction of the clock.

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.
Expand Down
7 changes: 7 additions & 0 deletions libcron/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
47 changes: 47 additions & 0 deletions libcron/include/libcron/CronClock.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
#pragma once

#include <chrono>
#ifdef BUILD_TZ_CLOCK
#include <date/tz.h>
#include <mutex>
#endif

namespace libcron
{
Expand Down Expand Up @@ -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<typename LockType = std::mutex>
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<LockType> 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<LockType> lock(time_zone_mtx);
time_zone = new_zone;
return true;
}
private:
mutable LockType time_zone_mtx{};
const date::time_zone* time_zone{nullptr};
};
#endif
}
7 changes: 7 additions & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
33 changes: 33 additions & 0 deletions test/CronTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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