Skip to content

Commit

Permalink
added timezone support
Browse files Browse the repository at this point in the history
added simple tests

updated README.md
  • Loading branch information
Philemon_Benner committed Feb 18, 2024
1 parent 41f238c commit 6d67452
Show file tree
Hide file tree
Showing 7 changed files with 136 additions and 1 deletion.
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).
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

0 comments on commit 6d67452

Please sign in to comment.