~aleteoryx/muditaos

39a760bad6d6dd28300bad334c7902d7e46fa6e2 — Maciej Gibowicz 4 years ago 09950fa
[EGD-6702] Add time zone library

The library is used to manually set the time zone.
It is based on the IANA time zone database.
M .gitmodules => .gitmodules +3 -0
@@ 86,3 86,6 @@
	path = third-party/re2/src
	url = ../re2.git
	branch = rt1051
[submodule "third-party/utz/src/utz"]
	path = third-party/utz/src/utz
	url = https://github.com/mudita/utz.git

M module-utils/time/CMakeLists.txt => module-utils/time/CMakeLists.txt +2 -0
@@ 8,6 8,7 @@ target_sources(time
        time/time_conversion.cpp
        time/time_date_validation.cpp
        time/TimeRangeParser.cpp
        time/TimeZone.cpp
)

target_include_directories(time PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)


@@ 17,6 18,7 @@ target_link_libraries(time
        log
        module-utils
        utf8
        utz::utz
)

if (${ENABLE_TESTS})

M module-utils/time/test/CMakeLists.txt => module-utils/time/test/CMakeLists.txt +9 -0
@@ 7,3 7,12 @@ add_catch2_executable(
        module-utils
        time
)

add_catch2_executable(
    NAME
        utils-TimeZone
    SRCS
        unittest_TimeZone.cpp
    LIBS
        time
)
\ No newline at end of file

A module-utils/time/test/unittest_TimeZone.cpp => module-utils/time/test/unittest_TimeZone.cpp +69 -0
@@ 0,0 1,69 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#define CATCH_CONFIG_MAIN
#include <catch2/catch.hpp>
#include <time/TimeZone.hpp>

TEST_CASE("TimeZone parser")
{
    SECTION("Checking the time zone rules for Warsaw")
    {
        std::string zone{"Warsaw"};
        auto rules  = utils::time::getTimeZoneRules(zone);
        auto offset = utils::time::getTimeZoneOffset(zone);

        REQUIRE(offset == 4);
        REQUIRE(rules.size() != 0);
    }

    SECTION("Checking the time zone rules for London")
    {
        std::string zone{"London"};
        auto rules  = utils::time::getTimeZoneRules(zone);
        auto offset = utils::time::getTimeZoneOffset(zone);

        REQUIRE(offset == 0);
        REQUIRE(rules.size() != 0);
    }

    SECTION("Checking the time zone rules for New York")
    {
        std::string zone{"New York"};
        auto rules  = utils::time::getTimeZoneRules(zone);
        auto offset = utils::time::getTimeZoneOffset(zone);

        REQUIRE(offset == -20);
        REQUIRE(rules.size() != 0);
    }

    SECTION("Checking the time zone rules for Tehran")
    {
        std::string zone{"Tehran"};
        auto rules  = utils::time::getTimeZoneRules(zone);
        auto offset = utils::time::getTimeZoneOffset(zone);

        REQUIRE(offset == 14);
        REQUIRE(rules.size() != 0);
    }

    SECTION("Checking the time zone rules for Auckland")
    {
        std::string zone{"Auckland"};
        auto rules  = utils::time::getTimeZoneRules(zone);
        auto offset = utils::time::getTimeZoneOffset(zone);

        REQUIRE(offset == 48);
        REQUIRE(rules.size() != 0);
    }

    SECTION("Checking the time zone rules for unknown zone")
    {
        std::string zone{"unknown"};
        auto rules  = utils::time::getTimeZoneRules(zone);
        auto offset = utils::time::getTimeZoneOffset(zone);

        REQUIRE(offset == 0);
        REQUIRE(rules.size() == 0);
    }
}

A module-utils/time/time/TimeZone.cpp => module-utils/time/time/TimeZone.cpp +97 -0
@@ 0,0 1,97 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "TimeZone.hpp"
#include <utz/utz.h>
#include <utz/zones.h>
#include <log/log.hpp>
#include <ctime>

namespace utils::time
{
    constexpr uint16_t maxZoneRuleLength{50};
    constexpr uint8_t localTimeYearOffset{100};

    [[nodiscard]] auto getAvailableTimeZonesToDisplay() -> std::vector<std::string>
    {
        std::vector<std::string> timeZonesNames;
        auto zonePointer = reinterpret_cast<const char *>(zone_names);
        uzone_t zoneOut;

        for (uint16_t zone = 0; zone < NUM_ZONE_NAMES; zone++) {
            unpack_zone(&zone_defns[get_next(&zonePointer)], zonePointer, &zoneOut);
            timeZonesNames.push_back(zoneOut.name);
            zonePointer++;
        }

        return timeZonesNames;
    }

    [[nodiscard]] auto getTimeZoneOffset(const std::string &zoneName) -> std::int8_t
    {
        uzone_t zoneOut;

        if (get_zone_by_name(const_cast<char *>(zoneName.c_str()), &zoneOut)) {
            LOG_ERROR("Zone %s not found", zoneName.c_str());
            return 0;
        }

        return zoneOut.src->offset_inc_minutes;
    }

    void unpackDstRules(urule_packed_t *packedRules, char *dstRules, int8_t tzOffset, uint8_t currYear, bool isEndRule)
    {
        switch (packedRules->on_dayofmonth) {
        case 0: // the last day of week in month
            [[fallthrough]];
        case 1: // first day of week in month
            snprintf(dstRules,
                     maxZoneRuleLength,
                     ",M%d.%d.%d/%d",
                     packedRules->in_month,
                     packedRules->on_dayofmonth == 0 ? 5 : packedRules->on_dayofmonth,
                     packedRules->on_dayofweek % 7,
                     packedRules->at_hours + (isEndRule ? 1 : 0) + (packedRules->at_is_local_time ? 0 : tzOffset));
            break;
        default: // The Julian day n (1 <= n <= 365)
            snprintf(dstRules,
                     maxZoneRuleLength,
                     ",J%d/%d",
                     get_day_in_year(packedRules, currYear),
                     packedRules->at_hours + (isEndRule ? 1 : 0) + (packedRules->at_is_local_time ? 0 : tzOffset));
        }
    }

    [[nodiscard]] auto getTimeZoneRules(const std::string &zoneName) -> std::string
    {
        std::string zoneRules;
        char offsetRules[maxZoneRuleLength];
        urule_packed_t startRules, endRules;
        uzone_t zoneOut;
        time_t tm           = std::time(nullptr);
        uint8_t currentYear = localtime(&tm)->tm_year - localTimeYearOffset;

        if (get_zone_by_name(const_cast<char *>(zoneName.c_str()), &zoneOut)) {
            LOG_ERROR("Zone %s not found", zoneName.c_str());
            return "";
        }

        snprintf(offsetRules, maxZoneRuleLength, "UTC%d:%02d", -zoneOut.offset.hours, zoneOut.offset.minutes);

        zoneRules = offsetRules;

        if (!get_zone_rules(zoneOut.src, currentYear, &startRules, &endRules)) {
            char dstRules[maxZoneRuleLength];

            zoneRules.append("UTC");
            unpackDstRules(&startRules, dstRules, zoneOut.offset.hours, currentYear, false);
            zoneRules.append(dstRules);

            unpackDstRules(&endRules, dstRules, zoneOut.offset.hours, currentYear, true);
            zoneRules.append(dstRules);
        }

        return zoneRules;
    }

} // namespace utils::time

A module-utils/time/time/TimeZone.hpp => module-utils/time/time/TimeZone.hpp +31 -0
@@ 0,0 1,31 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <string>
#include <vector>

namespace utils::time
{
    /** @brief get all available TimeZones to display
     *
     *  @return vector of string with TimeZones names
     */
    [[nodiscard]] auto getAvailableTimeZonesToDisplay() -> std::vector<std::string>;

    /** @brief get offset of TimeZone
     *
     *  @param zoneName - name of TimeZone
     *  @return offset hours and minutes with a sign in the interval of 15 minutes [6 => 1h 30 min]
     */
    [[nodiscard]] auto getTimeZoneOffset(const std::string &zoneName) -> std::int8_t;

    /** @brief get offset and DST rules of TimeZone
     *
     *  @param zoneName - name of TimeZone
     *  @return string with rules to set TimeZone
     */
    [[nodiscard]] auto getTimeZoneRules(const std::string &zoneName) -> std::string;

} // namespace utils::time

M third-party/CMakeLists.txt => third-party/CMakeLists.txt +1 -0
@@ 13,6 13,7 @@ add_subdirectory(littlefs)
add_subdirectory(json)
add_subdirectory(gsl)
add_subdirectory(re2)
add_subdirectory(utz)
if (${PROJECT_TARGET} STREQUAL "TARGET_RT1051")
    add_subdirectory(CrashDebug)
endif()

A third-party/utz/CMakeLists.txt => third-party/utz/CMakeLists.txt +17 -0
@@ 0,0 1,17 @@

add_library(utz)
add_library(utz::utz ALIAS utz)
target_sources(utz
   PRIVATE
      ${CMAKE_CURRENT_SOURCE_DIR}/src/utz/utz.c
      ${CMAKE_CURRENT_SOURCE_DIR}/src/utz/zones.c
   PUBLIC
      ${CMAKE_CURRENT_SOURCE_DIR}/src/utz/utz.h
      ${CMAKE_CURRENT_SOURCE_DIR}/src/utz/zones.h
)

target_include_directories(utz
   PUBLIC
      $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>
)


A third-party/utz/src/utz => third-party/utz/src/utz +1 -0
@@ 0,0 1,1 @@
Subproject commit d3da89075cc344d5763d2abd294b67c326abeaec