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

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

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.

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).
Copy link
Owner

Choose a reason for hiding this comment

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

I'm thinking this won't work when the library is used as a Windows service as there's no home folder for the LOCALSYSTEM account. Can the download location be passed in to TzClock somehow so that the application can determine where it is downloaded? Also, what about an embedded version of the TZ database?

Copy link
Author

Choose a reason for hiding this comment

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

I will have a look on Sunday if anything of the following will be possible. Setting database install path will be possible out of the box from date-tz but only at build time trough -DINSTALL=/some/path. I'm sure embedded is possible but would require a good amount of work, like different platform support (Arduino, ESP-IDF, Micro-python, Rust), either embedding db into flash at compile time or using Eeprom at runtime to store latest db, if you are targeting microcontrollers. I don't think I will have time doing all of this.

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.
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
22 changes: 22 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,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
}
31 changes: 31 additions & 0 deletions libcron/src/CronClock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::mutex> 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<std::mutex> lock(time_zone_mtx);
if (time_zone)
return time_zone->get_info(now).offset;
else
return 0s;
}
#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
Loading