This is an automated email from the ASF dual-hosted git repository.
szaszm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi-minifi-cpp.git
The following commit(s) were added to refs/heads/main by this push:
new ad886ae9e MINIFICPP-1809 custom Cron (quartz syntax) implementation
and cron tests
ad886ae9e is described below
commit ad886ae9ef840d619a6b85351d8466448af23b3a
Author: Martin Zink <[email protected]>
AuthorDate: Wed Jun 8 18:16:16 2022 +0200
MINIFICPP-1809 custom Cron (quartz syntax) implementation and cron tests
Closes #1335
Signed-off-by: Marton Szasz <[email protected]>
Co-authored-by: Ferenc Gerlits <[email protected]>
---
CMakeLists.txt | 4 -
LICENSE | 23 -
cmake/BuildTests.cmake | 1 +
cmake/Date.cmake | 2 +-
extensions/expression-language/CMakeLists.txt | 2 +-
.../standard-processors/tests/CMakeLists.txt | 1 +
.../tests/integration/TailFileTest.cpp | 6 +-
libminifi/CMakeLists.txt | 2 +-
libminifi/include/CronDrivenSchedulingAgent.h | 52 +-
libminifi/include/utils/Cron.h | 56 ++
libminifi/include/utils/TestUtils.h | 11 +
libminifi/include/utils/TimeUtil.h | 37 +-
libminifi/src/CronDrivenSchedulingAgent.cpp | 81 +--
libminifi/src/controllers/SSLContextService.cpp | 2 +-
libminifi/src/utils/Cron.cpp | 511 +++++++++++++++
.../src/utils/TestUtils.cpp | 24 +-
libminifi/test/resources/TestTailFileCron.yml | 6 +-
libminifi/test/unit/CronTests.cpp | 702 +++++++++++++++++++++
libminifi/test/unit/SchedulingAgentTests.cpp | 140 ++++
libminifi/test/unit/TimeUtilTests.cpp | 91 +++
thirdparty/cron/Cron.h | 121 ----
thirdparty/cron/LICENSE.TXT | 21 -
22 files changed, 1615 insertions(+), 281 deletions(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d7492af33..ee6ca4575 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -351,10 +351,6 @@ target_include_directories(concurrentqueue SYSTEM
INTERFACE "${CMAKE_CURRENT_SOU
add_library(RapidJSON INTERFACE)
target_include_directories(RapidJSON SYSTEM INTERFACE
"${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/rapidjson-48fbd8cd202ca54031fe799db2ad44ffa8e77c13/include")
-# Cron
-add_library(cron INTERFACE)
-target_include_directories(cron SYSTEM INTERFACE
"${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cron")
-
# cxxopts
include(CxxOpts)
diff --git a/LICENSE b/LICENSE
index 836615ad9..4dba5c977 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1697,29 +1697,6 @@ These files are available under a 3-Clause BSD license:
implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the License for more information.
-This project bundles the Bosma Scheduler library, which is available under an
MIT Licenses:
-MIT License
-
-Copyright (c) 2017 Bosma
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
-
This product bundles 'libssh2' which is available under a "3-clause BSD"
license.
/* Copyright (c) 2004-2007 Sara Golemon <[email protected]>
diff --git a/cmake/BuildTests.cmake b/cmake/BuildTests.cmake
index 043fc50a3..d5014b679 100644
--- a/cmake/BuildTests.cmake
+++ b/cmake/BuildTests.cmake
@@ -111,6 +111,7 @@ SET(UNIT_TEST_COUNT 0)
FOREACH(testfile ${UNIT_TESTS})
get_filename_component(testfilename "${testfile}" NAME_WE)
add_executable("${testfilename}" "${TEST_DIR}/unit/${testfile}")
+ target_compile_definitions("${testfilename}" PRIVATE
TZ_DATA_DIR="${CMAKE_BINARY_DIR}/tzdata")
createTests("${testfilename}")
target_link_libraries(${testfilename} ${CATCH_MAIN_LIB})
MATH(EXPR UNIT_TEST_COUNT "${UNIT_TEST_COUNT}+1")
diff --git a/cmake/Date.cmake b/cmake/Date.cmake
index 9b6e24948..2e52f6ee5 100644
--- a/cmake/Date.cmake
+++ b/cmake/Date.cmake
@@ -47,7 +47,7 @@ endif()
FetchContent_Declare(date_src
GIT_REPOSITORY https://github.com/HowardHinnant/date.git
- GIT_TAG v3.0.0 # adjust tag/branch/commit as needed
+ GIT_TAG v3.0.1 # adjust tag/branch/commit as needed
)
FetchContent_GetProperties(date_src)
if (NOT date_src_POPULATED)
diff --git a/extensions/expression-language/CMakeLists.txt
b/extensions/expression-language/CMakeLists.txt
index d8f9b5c80..bca4816eb 100644
--- a/extensions/expression-language/CMakeLists.txt
+++ b/extensions/expression-language/CMakeLists.txt
@@ -106,7 +106,7 @@ endif()
add_library(minifi-expression-language-extensions SHARED ${SOURCES}
${BISON_el-parser_OUTPUTS} ${FLEX_el-scanner_OUTPUTS})
target_link_libraries(minifi-expression-language-extensions ${LIBMINIFI})
-target_link_libraries(minifi-expression-language-extensions date::tz RapidJSON
CURL::libcurl)
+target_link_libraries(minifi-expression-language-extensions RapidJSON
CURL::libcurl)
register_extension(minifi-expression-language-extensions "EXPRESSION LANGUAGE
EXTENSIONS" EXPRESSION-LANGUAGE-EXTENSIONS "This enables NiFi expression
language" "extensions/expression-language/tests")
register_extension_linter(minifi-expression-language-extensions-linter)
diff --git a/extensions/standard-processors/tests/CMakeLists.txt
b/extensions/standard-processors/tests/CMakeLists.txt
index 6ae351367..d0ffa6dc3 100644
--- a/extensions/standard-processors/tests/CMakeLists.txt
+++ b/extensions/standard-processors/tests/CMakeLists.txt
@@ -65,6 +65,7 @@ FOREACH(testfile ${PROCESSOR_INTEGRATION_TESTS})
target_link_libraries(${testfilename})
target_link_libraries(${testfilename} minifi-standard-processors)
target_link_libraries(${testfilename} minifi-civet-extensions)
+ target_compile_definitions("${testfilename}" PRIVATE
TZ_DATA_DIR="${CMAKE_BINARY_DIR}/tzdata")
if (NOT DISABLE_ROCKSDB)
target_link_libraries(${testfilename} minifi-rocksdb-repos)
endif()
diff --git a/extensions/standard-processors/tests/integration/TailFileTest.cpp
b/extensions/standard-processors/tests/integration/TailFileTest.cpp
index 42e1a7f4e..0114693ab 100644
--- a/extensions/standard-processors/tests/integration/TailFileTest.cpp
+++ b/extensions/standard-processors/tests/integration/TailFileTest.cpp
@@ -32,12 +32,13 @@
#include "state/ProcessorController.h"
#include "integration/IntegrationBase.h"
#include "utils/IntegrationTestUtils.h"
+#include "utils/TestUtils.h"
using std::literals::chrono_literals::operator""s;
class TailFileTestHarness : public IntegrationBase {
public:
- TailFileTestHarness() : IntegrationBase(1s) {
+ TailFileTestHarness() : IntegrationBase(2s) {
dir = testController.createTempDirectory();
statefile = dir + utils::file::get_separator();
@@ -53,6 +54,9 @@ class TailFileTestHarness : public IntegrationBase {
LogTestController::getInstance().setInfo<minifi::processors::LogAttribute>();
LogTestController::getInstance().setTrace<minifi::processors::TailFile>();
LogTestController::getInstance().setTrace<minifi::FlowController>();
+#ifdef WIN32
+ utils::dateSetInstall(TZ_DATA_DIR);
+#endif
}
void cleanup() override {
diff --git a/libminifi/CMakeLists.txt b/libminifi/CMakeLists.txt
index 367da27a7..0bf8b8824 100644
--- a/libminifi/CMakeLists.txt
+++ b/libminifi/CMakeLists.txt
@@ -89,7 +89,7 @@ else()
endif()
include(RangeV3)
-list(APPEND LIBMINIFI_LIBRARIES yaml-cpp ZLIB::ZLIB concurrentqueue RapidJSON
spdlog cron Threads::Threads gsl-lite libsodium range-v3 expected-lite
date::date)
+list(APPEND LIBMINIFI_LIBRARIES yaml-cpp ZLIB::ZLIB concurrentqueue RapidJSON
spdlog Threads::Threads gsl-lite libsodium range-v3 expected-lite date::date
date::tz)
if(NOT WIN32)
list(APPEND LIBMINIFI_LIBRARIES OSSP::libuuid++)
endif()
diff --git a/libminifi/include/CronDrivenSchedulingAgent.h
b/libminifi/include/CronDrivenSchedulingAgent.h
index 23a2c6c0a..3595f302f 100644
--- a/libminifi/include/CronDrivenSchedulingAgent.h
+++ b/libminifi/include/CronDrivenSchedulingAgent.h
@@ -17,43 +17,41 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-#ifndef LIBMINIFI_INCLUDE_CRONDRIVENSCHEDULINGAGENT_H_
-#define LIBMINIFI_INCLUDE_CRONDRIVENSCHEDULINGAGENT_H_
+#pragma once
#include <chrono>
#include <map>
#include <memory>
#include <string>
+#include <utility>
#include "core/logging/Logger.h"
#include "core/ProcessContext.h"
#include "core/Processor.h"
#include "core/ProcessSessionFactory.h"
-#include "Cron.h"
+#include "utils/Cron.h"
#include "ThreadedSchedulingAgent.h"
-namespace org {
-namespace apache {
-namespace nifi {
-namespace minifi {
+namespace org::apache::nifi::minifi {
-// CronDrivenSchedulingAgent Class
class CronDrivenSchedulingAgent : public ThreadedSchedulingAgent {
public:
- // Constructor
- /*!
- * Create a new event driven scheduling agent.
- */
- CronDrivenSchedulingAgent(const
gsl::not_null<core::controller::ControllerServiceProvider*>
controller_service_provider, std::shared_ptr<core::Repository> repo,
- std::shared_ptr<core::Repository> flow_repo,
std::shared_ptr<core::ContentRepository> content_repo,
std::shared_ptr<Configure> configuration,
- utils::ThreadPool<utils::TaskRescheduleInfo>
&thread_pool)
- : ThreadedSchedulingAgent(controller_service_provider, repo, flow_repo,
content_repo, configuration, thread_pool) {
+ CronDrivenSchedulingAgent(const
gsl::not_null<core::controller::ControllerServiceProvider*>
controller_service_provider,
+ std::shared_ptr<core::Repository> repo,
+ std::shared_ptr<core::Repository> flow_repo,
+ std::shared_ptr<core::ContentRepository>
content_repo,
+ std::shared_ptr<Configure> configuration,
+ utils::ThreadPool<utils::TaskRescheduleInfo>&
thread_pool)
+ : ThreadedSchedulingAgent(controller_service_provider, std::move(repo),
std::move(flow_repo), std::move(content_repo), std::move(configuration),
thread_pool) {
}
- // Destructor
+
+ CronDrivenSchedulingAgent(const CronDrivenSchedulingAgent& parent) = delete;
+ CronDrivenSchedulingAgent& operator=(const CronDrivenSchedulingAgent&
parent) = delete;
~CronDrivenSchedulingAgent() override = default;
- // Run function for the thread
- utils::TaskRescheduleInfo run(core::Processor* processor, const
std::shared_ptr<core::ProcessContext> &processContext,
- const std::shared_ptr<core::ProcessSessionFactory> &sessionFactory)
override;
+
+ utils::TaskRescheduleInfo run(core::Processor *processor,
+ const std::shared_ptr<core::ProcessContext>&
processContext,
+ const
std::shared_ptr<core::ProcessSessionFactory>& sessionFactory) override;
void stop() override {
std::lock_guard<std::mutex> locK(mutex_);
@@ -63,16 +61,8 @@ class CronDrivenSchedulingAgent : public
ThreadedSchedulingAgent {
private:
std::mutex mutex_;
- std::map<utils::Identifier, Bosma::Cron> schedules_;
- std::map<utils::Identifier, std::chrono::system_clock::time_point>
last_exec_;
- // Prevent default copy constructor and assignment operation
- // Only support pass by reference or pointer
- CronDrivenSchedulingAgent(const CronDrivenSchedulingAgent &parent);
- CronDrivenSchedulingAgent &operator=(const CronDrivenSchedulingAgent
&parent);
+ std::map<utils::Identifier, utils::Cron> schedules_;
+ std::map<utils::Identifier, date::local_time<std::chrono::seconds>>
last_exec_;
};
-} // namespace minifi
-} // namespace nifi
-} // namespace apache
-} // namespace org
-#endif // LIBMINIFI_INCLUDE_CRONDRIVENSCHEDULINGAGENT_H_
+} // namespace org::apache::nifi::minifi
diff --git a/libminifi/include/utils/Cron.h b/libminifi/include/utils/Cron.h
new file mode 100644
index 000000000..ad8b6f605
--- /dev/null
+++ b/libminifi/include/utils/Cron.h
@@ -0,0 +1,56 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <exception>
+#include <string>
+#include <chrono>
+#include <optional>
+#include <memory>
+#include <utility>
+#include "date/tz.h"
+#include "Exception.h"
+
+namespace org::apache::nifi::minifi::utils {
+class BadCronExpression : public minifi::Exception {
+ public:
+ explicit BadCronExpression(const std::string& errmsg) :
minifi::Exception(errmsg) {}
+};
+
+class CronField {
+ public:
+ virtual ~CronField() = default;
+
+ [[nodiscard]] virtual bool matches(date::local_seconds time_point) const = 0;
+};
+
+class Cron {
+ public:
+ explicit Cron(const std::string& expression);
+
+ [[nodiscard]] std::optional<date::local_seconds>
calculateNextTrigger(date::local_seconds start) const;
+
+ std::unique_ptr<CronField> second_;
+ std::unique_ptr<CronField> minute_;
+ std::unique_ptr<CronField> hour_;
+ std::unique_ptr<CronField> day_;
+ std::unique_ptr<CronField> month_;
+ std::unique_ptr<CronField> day_of_week_;
+ std::unique_ptr<CronField> year_;
+};
+
+} // namespace org::apache::nifi::minifi::utils
diff --git a/libminifi/include/utils/TestUtils.h
b/libminifi/include/utils/TestUtils.h
index b31a27a0d..c125ad72c 100644
--- a/libminifi/include/utils/TestUtils.h
+++ b/libminifi/include/utils/TestUtils.h
@@ -28,6 +28,10 @@
#include "utils/Id.h"
#include "utils/TimeUtil.h"
+#ifdef WIN32
+#include "date/tz.h"
+#endif
+
namespace org {
namespace apache {
namespace nifi {
@@ -65,6 +69,13 @@ class ManualClock : public timeutils::Clock {
std::chrono::milliseconds time_{0};
};
+
+#ifdef WIN32
+// The tzdata location is set as a global variable in date-tz library
+// We need to set it from from libminifi to effect calls made from libminifi
(on Windows)
+void dateSetInstall(const std::string& install);
+#endif
+
} // namespace utils
} // namespace minifi
} // namespace nifi
diff --git a/libminifi/include/utils/TimeUtil.h
b/libminifi/include/utils/TimeUtil.h
index 68c7c8b7d..5a352947c 100644
--- a/libminifi/include/utils/TimeUtil.h
+++ b/libminifi/include/utils/TimeUtil.h
@@ -14,8 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-#ifndef LIBMINIFI_INCLUDE_UTILS_TIMEUTIL_H_
-#define LIBMINIFI_INCLUDE_UTILS_TIMEUTIL_H_
+#pragma once
#include <cstring>
#include <ctime>
@@ -308,12 +307,32 @@ std::optional<TargetDuration> StringToDuration(const
std::string& input) {
std::chrono::days>(unit, value);
}
-} // namespace org::apache::nifi::minifi::utils::timeutils
+inline date::local_seconds roundToNextYear(date::local_seconds tp) {
+ date::year_month_day date(std::chrono::floor<std::chrono::days>(tp));
+ auto start_of_year = date.year()/1/1;
+ return date::local_days(start_of_year + std::chrono::years(1));
+}
+
+inline date::local_seconds roundToNextMonth(date::local_seconds tp) {
+ date::year_month_day date(std::chrono::floor<std::chrono::days>(tp));
+ auto start_of_month = date.year()/date.month()/1;
+ return date::local_days(start_of_month + std::chrono::months(1));
+}
+
+inline date::local_seconds roundToNextDay(date::local_seconds tp) {
+ return std::chrono::floor<std::chrono::days>(tp) + std::chrono::days(1);
+}
-// for backwards compatibility, to be removed after 0.8
-using org::apache::nifi::minifi::utils::timeutils::getTimeNano;
-using org::apache::nifi::minifi::utils::timeutils::getTimeStr;
-using org::apache::nifi::minifi::utils::timeutils::parseDateTimeStr;
-using org::apache::nifi::minifi::utils::timeutils::getDateTimeStr;
+inline date::local_seconds roundToNextHour(date::local_seconds tp) {
+ return std::chrono::floor<std::chrono::hours>(tp) + std::chrono::hours(1);
+}
+
+inline date::local_seconds roundToNextMinute(date::local_seconds tp) {
+ return std::chrono::floor<std::chrono::minutes>(tp) +
std::chrono::minutes(1);
+}
-#endif // LIBMINIFI_INCLUDE_UTILS_TIMEUTIL_H_
+inline date::local_seconds roundToNextSecond(date::local_seconds tp) {
+ return std::chrono::floor<std::chrono::seconds>(tp) +
std::chrono::seconds(1);
+}
+
+} // namespace org::apache::nifi::minifi::utils::timeutils
diff --git a/libminifi/src/CronDrivenSchedulingAgent.cpp
b/libminifi/src/CronDrivenSchedulingAgent.cpp
index 27fddc552..3ad94f1ea 100644
--- a/libminifi/src/CronDrivenSchedulingAgent.cpp
+++ b/libminifi/src/CronDrivenSchedulingAgent.cpp
@@ -20,66 +20,53 @@
#include "CronDrivenSchedulingAgent.h"
#include <chrono>
#include <memory>
-#include <thread>
-#include <iostream>
#include "core/Processor.h"
#include "core/ProcessContext.h"
#include "core/ProcessSessionFactory.h"
-#include "core/Property.h"
-using namespace std::literals::chrono_literals;
+namespace org::apache::nifi::minifi {
-namespace org {
-namespace apache {
-namespace nifi {
-namespace minifi {
+utils::TaskRescheduleInfo CronDrivenSchedulingAgent::run(core::Processor*
processor,
+ const
std::shared_ptr<core::ProcessContext>& processContext,
+ const
std::shared_ptr<core::ProcessSessionFactory>& sessionFactory) {
+ using namespace std::literals::chrono_literals;
+ using std::chrono::ceil;
+ using std::chrono::seconds;
+ using std::chrono::milliseconds;
+ using std::chrono::time_point_cast;
+ using std::chrono::system_clock;
-utils::TaskRescheduleInfo CronDrivenSchedulingAgent::run(core::Processor*
processor, const std::shared_ptr<core::ProcessContext> &processContext,
- const
std::shared_ptr<core::ProcessSessionFactory> &sessionFactory) {
if (this->running_ && processor->isRunning()) {
auto uuid = processor->getUUID();
- std::chrono::system_clock::time_point result;
- std::chrono::system_clock::time_point from =
std::chrono::system_clock::now();
- {
- std::lock_guard<std::mutex> locK(mutex_);
+ auto current_time = date::make_zoned<seconds>(date::current_zone(),
time_point_cast<seconds>(system_clock::now()));
+ std::lock_guard<std::mutex> lock(mutex_);
- auto sched_f = schedules_.find(uuid);
- if (sched_f != std::end(schedules_)) {
- result = last_exec_[uuid];
- if (from >= result) {
- result = sched_f->second.cron_to_next(from);
- last_exec_[uuid] = result;
- } else {
- // we may be woken up a little early so that we can honor our time.
- // in this case we can return the next time to run with the
expectation
- // that the wakeup mechanism gets more granular.
- return
utils::TaskRescheduleInfo::RetryIn(std::chrono::duration_cast<std::chrono::milliseconds>(result
- from));
- }
- } else {
- Bosma::Cron schedule(processor->getCronPeriod());
- result = schedule.cron_to_next(from);
- last_exec_[uuid] = result;
- schedules_.insert(std::make_pair(uuid, schedule));
- }
- }
+ schedules_.emplace(uuid, utils::Cron(processor->getCronPeriod()));
+ last_exec_.emplace(uuid, current_time.get_local_time());
+
+ auto last_trigger = last_exec_[uuid];
+ auto next_to_last_trigger =
schedules_.at(uuid).calculateNextTrigger(last_trigger);
+ if (!next_to_last_trigger)
+ return utils::TaskRescheduleInfo::Done();
- if (result > from) {
- bool shouldYield = this->onTrigger(processor, processContext,
sessionFactory);
+ if (*next_to_last_trigger > current_time.get_local_time())
+ return
utils::TaskRescheduleInfo::RetryIn(ceil<milliseconds>(*next_to_last_trigger-current_time.get_local_time()));
- if (processor->isYield()) {
- // Honor the yield
- return utils::TaskRescheduleInfo::RetryIn(processor->getYieldTime());
- } else if (shouldYield && this->bored_yield_duration_ > 0ms) {
- // No work to do or need to apply back pressure
- return utils::TaskRescheduleInfo::RetryIn(this->bored_yield_duration_);
- }
+ last_exec_[uuid] = current_time.get_local_time();
+ bool shouldYield = this->onTrigger(processor, processContext,
sessionFactory);
+
+ if (processor->isYield()) {
+ return utils::TaskRescheduleInfo::RetryIn(processor->getYieldTime());
+ } else if (shouldYield && this->bored_yield_duration_ > 0ms) {
+ return utils::TaskRescheduleInfo::RetryIn(this->bored_yield_duration_);
}
- return
utils::TaskRescheduleInfo::RetryIn(std::chrono::duration_cast<std::chrono::milliseconds>(result
- from));
+
+ auto next_trigger =
schedules_.at(uuid).calculateNextTrigger(current_time.get_local_time());
+ if (!next_trigger)
+ return utils::TaskRescheduleInfo::Done();
+ return
utils::TaskRescheduleInfo::RetryIn(ceil<milliseconds>(*next_trigger-current_time.get_local_time()));
}
return utils::TaskRescheduleInfo::Done();
}
-} /* namespace minifi */
-} /* namespace nifi */
-} /* namespace apache */
-} /* namespace org */
+} // namespace org::apache::nifi::minifi
diff --git a/libminifi/src/controllers/SSLContextService.cpp
b/libminifi/src/controllers/SSLContextService.cpp
index 3dd2f8127..f765cbb50 100644
--- a/libminifi/src/controllers/SSLContextService.cpp
+++ b/libminifi/src/controllers/SSLContextService.cpp
@@ -563,7 +563,7 @@ void SSLContextService::initializeProperties() {
void SSLContextService::verifyCertificateExpiration() {
auto verify = [&] (const std::string& cert_file, const
utils::tls::X509_unique_ptr& cert) {
if (auto end_date = utils::tls::getCertificateExpiration(cert)) {
- std::string end_date_str =
getTimeStr(std::chrono::duration_cast<std::chrono::milliseconds>(end_date->time_since_epoch()).count());
+ std::string end_date_str =
utils::timeutils::getTimeStr(std::chrono::duration_cast<std::chrono::milliseconds>(end_date->time_since_epoch()).count());
if (end_date.value() < std::chrono::system_clock::now()) {
core::logging::LOG_ERROR(logger_) << "Certificate in '" << cert_file
<< "' expired at " << end_date_str;
} else if (auto diff = end_date.value() -
std::chrono::system_clock::now(); diff < std::chrono::weeks{2}) {
diff --git a/libminifi/src/utils/Cron.cpp b/libminifi/src/utils/Cron.cpp
new file mode 100644
index 000000000..5409765f9
--- /dev/null
+++ b/libminifi/src/utils/Cron.cpp
@@ -0,0 +1,511 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "utils/Cron.h"
+#include <charconv>
+#include "utils/TimeUtil.h"
+#include "utils/StringUtils.h"
+#include "date/date.h"
+
+using namespace std::literals::chrono_literals;
+
+using std::chrono::seconds;
+using std::chrono::minutes;
+using std::chrono::hours;
+using std::chrono::days;
+
+using date::local_seconds;
+using date::day;
+using date::weekday;
+using date::month;
+using date::year;
+using date::year_month_day;
+using date::last;
+using date::local_days;
+using date::from_stream;
+using date::make_time;
+using date::Friday;
+using date::Saturday;
+using date::Sunday;
+
+namespace org::apache::nifi::minifi::utils {
+namespace {
+
+template<class T>
+std::optional<T> fromChars(const std::string& input) {
+ T t{};
+ const auto result = std::from_chars(input.data(), input.data() +
input.size(), t);
+ if (result.ptr != input.data() + input.size())
+ return std::nullopt;
+ return t;
+}
+
+bool operator<=(const weekday& lhs, const weekday& rhs) {
+ return lhs.c_encoding() <= rhs.c_encoding();
+}
+
+template <typename FieldType>
+FieldType parse(const std::string&);
+
+template <>
+seconds parse<seconds>(const std::string& second_str) {
+ if (auto sec_int = fromChars<uint64_t>(second_str); sec_int && *sec_int <=
59)
+ return seconds(*sec_int);
+ throw BadCronExpression("Invalid second " + second_str);
+}
+
+template <>
+minutes parse<minutes>(const std::string& minute_str) {
+ if (auto min_int = fromChars<uint64_t>(minute_str); min_int && *min_int <=
59)
+ return minutes(*min_int);
+ throw BadCronExpression("Invalid minute " + minute_str);
+}
+
+template <>
+hours parse<hours>(const std::string& hour_str) {
+ if (auto hour_int = fromChars<uint64_t>(hour_str); hour_int && *hour_int <=
23)
+ return hours(*hour_int);
+ throw BadCronExpression("Invalid hour " + hour_str);
+}
+
+template <>
+days parse<days>(const std::string& days_str) {
+ if (auto days_int = fromChars<uint64_t>(days_str))
+ return days(*days_int);
+ throw BadCronExpression("Invalid days " + days_str);
+}
+
+template <>
+day parse<day>(const std::string& day_str) {
+ if (auto day_int = fromChars<uint64_t>(day_str); day_int && day_int >= 1 &&
day_int <= 31)
+ return day(*day_int);
+ throw BadCronExpression("Invalid day " + day_str);
+}
+
+template <>
+month parse<month>(const std::string& month_str) {
+// https://github.com/HowardHinnant/date/issues/550
+// Due to https://gcc.gnu.org/bugzilla/show_bug.cgi?id=78714
+// the month parsing with '%b' is case sensitive in gcc11
+// This has been fixed in gcc12
+#if defined(__GNUC__) && __GNUC__ < 12
+ auto patched_month_str = StringUtils::toLower(month_str);
+ if (!patched_month_str.empty())
+ patched_month_str[0] = static_cast<char>(std::toupper(static_cast<unsigned
char>(patched_month_str[0])));
+ std::stringstream stream(patched_month_str);
+#else
+ std::stringstream stream(month_str);
+#endif
+
+ stream.imbue(std::locale("en_US.UTF-8"));
+ month parsed_month{};
+ if (month_str.size() > 2) {
+ from_stream(stream, "%b", parsed_month);
+ if (!stream.fail() && parsed_month.ok() && stream.peek() == EOF)
+ return parsed_month;
+ } else {
+ from_stream(stream, "%m", parsed_month);
+ if (!stream.fail() && parsed_month.ok() && stream.peek() == EOF)
+ return parsed_month;
+ }
+
+ throw BadCronExpression("Invalid month " + month_str);
+}
+
+template <>
+weekday parse<weekday>(const std::string& weekday_str) {
+// https://github.com/HowardHinnant/date/issues/550
+// Due to https://gcc.gnu.org/bugzilla/show_bug.cgi?id=78714
+// the weekday parsing with '%a' is case sensitive in gcc11
+// This has been fixed in gcc12
+#if defined(__GNUC__) && __GNUC__ < 12
+ auto patched_weekday_str = StringUtils::toLower(weekday_str);
+ if (!patched_weekday_str.empty())
+ patched_weekday_str[0] =
static_cast<char>(std::toupper(static_cast<unsigned
char>(patched_weekday_str[0])));
+ std::stringstream stream(patched_weekday_str);
+#else
+ std::stringstream stream(weekday_str);
+#endif
+ stream.imbue(std::locale("en_US.UTF-8"));
+
+ if (weekday_str.size() > 2) {
+ weekday parsed_weekday{};
+ from_stream(stream, "%a", parsed_weekday);
+ if (!stream.fail() && parsed_weekday.ok() && stream.peek() == EOF)
+ return parsed_weekday;
+ } else {
+ unsigned weekday_num;
+ stream >> weekday_num;
+ if (!stream.fail() && weekday_num < 7 && stream.peek() == EOF)
+ return weekday(weekday_num-1);
+ }
+ throw BadCronExpression("Invalid weekday: " + weekday_str);
+}
+
+template <>
+year parse<year>(const std::string& year_str) {
+ if (auto year_int = fromChars<uint64_t>(year_str); year_int && *year_int >=
1970 && *year_int <= 2999)
+ return year(*year_int);
+ throw BadCronExpression("Invalid year: " + year_str);
+}
+
+template <typename FieldType>
+FieldType getFieldType(local_seconds time_point);
+
+template <>
+year getFieldType(local_seconds time_point) {
+ year_month_day year_month_day(floor<days>(time_point));
+ return year_month_day.year();
+}
+
+template <>
+month getFieldType(local_seconds time_point) {
+ year_month_day year_month_day(floor<days>(time_point));
+ return year_month_day.month();
+}
+
+template <>
+day getFieldType(local_seconds time_point) {
+ year_month_day year_month_day(floor<days>(time_point));
+ return year_month_day.day();
+}
+
+template <>
+hours getFieldType(local_seconds time_point) {
+ auto dp = floor<days>(time_point);
+ auto time = make_time(time_point-dp);
+ return time.hours();
+}
+
+template <>
+minutes getFieldType(local_seconds time_point) {
+ auto dp = floor<days>(time_point);
+ auto time = make_time(time_point-dp);
+ return time.minutes();
+}
+
+template <>
+seconds getFieldType(local_seconds time_point) {
+ auto dp = floor<days>(time_point);
+ auto time = make_time(time_point-dp);
+ return time.seconds();
+}
+
+template <>
+weekday getFieldType(local_seconds time_point) {
+ auto dp = floor<days>(time_point);
+ return weekday(dp);
+}
+
+bool isWeekday(year_month_day date) {
+ weekday date_weekday = weekday(local_days(date));
+ return date_weekday != Saturday && date_weekday != Sunday;
+}
+
+template <typename FieldType>
+class SingleValueField : public CronField {
+ public:
+ explicit SingleValueField(FieldType value) : value_(value) {}
+
+ [[nodiscard]] bool matches(local_seconds time_point) const override {
+ return value_ == getFieldType<FieldType>(time_point);
+ }
+
+ private:
+ FieldType value_;
+};
+
+class NotCheckedField : public CronField {
+ public:
+ NotCheckedField() = default;
+
+ [[nodiscard]] bool matches(local_seconds) const override { return true; }
+};
+
+class AllValuesField : public CronField {
+ public:
+ AllValuesField() = default;
+
+ [[nodiscard]] bool matches(local_seconds) const override { return true; }
+};
+
+template <typename FieldType>
+class RangeField : public CronField {
+ public:
+ explicit RangeField(FieldType lower_bound, FieldType upper_bound)
+ : lower_bound_(std::move(lower_bound)),
+ upper_bound_(std::move(upper_bound)) {
+ if (!(lower_bound_ <= upper_bound_))
+ throw std::out_of_range("lower bound must be smaller or equal to upper
bound");
+ }
+
+ [[nodiscard]] bool matches(local_seconds value) const override {
+ return lower_bound_ <= getFieldType<FieldType>(value) &&
getFieldType<FieldType>(value) <= upper_bound_;
+ }
+
+ private:
+ FieldType lower_bound_;
+ FieldType upper_bound_;
+};
+
+template <typename FieldType>
+class ListField : public CronField {
+ public:
+ explicit ListField(std::vector<FieldType> valid_values) :
valid_values_(std::move(valid_values)) {}
+
+ [[nodiscard]] bool matches(local_seconds value) const override {
+ return std::find(valid_values_.begin(), valid_values_.end(),
getFieldType<FieldType>(value)) != valid_values_.end();
+ }
+
+ private:
+ std::vector<FieldType> valid_values_;
+};
+
+template <typename FieldType>
+class IncrementField : public CronField {
+ public:
+ IncrementField(FieldType start, int increment) : start_(start),
increment_(increment) {}
+
+ [[nodiscard]] bool matches(local_seconds value) const override {
+ return (getFieldType<FieldType>(value) - start_).count() % increment_ == 0;
+ }
+
+ private:
+ FieldType start_;
+ int increment_;
+};
+
+class LastNthDayInMonthField : public CronField {
+ public:
+ explicit LastNthDayInMonthField(days offset) : offset_(offset) {
+ if (!(offset_ <= std::chrono::days(30)))
+ throw BadCronExpression("Offset from last day must be <= 30");
+ }
+
+ [[nodiscard]] bool matches(local_seconds tp) const override {
+ year_month_day date(floor<days>(tp));
+ auto last_day = date.year()/date.month()/last;
+ auto target_date = local_days(last_day)-offset_;
+ return local_days(date) == target_date;
+ }
+
+ private:
+ days offset_;
+};
+
+class NthWeekdayField : public CronField {
+ public:
+ NthWeekdayField(weekday wday, uint8_t n) : weekday_(wday), n_(n) {}
+
+ [[nodiscard]] bool matches(local_seconds tp) const override {
+ year_month_day date(floor<days>(tp));
+ auto target_date = date.year()/date.month()/(weekday_[n_]);
+ return local_days(date) == local_days(target_date);
+ }
+
+ private:
+ weekday weekday_;
+ uint8_t n_;
+};
+
+class LastWeekDayField : public CronField {
+ public:
+ LastWeekDayField() = default;
+
+ [[nodiscard]] bool matches(local_seconds value) const override {
+ year_month_day date(floor<days>(value));
+ year_month_day last_day_of_the_month_date =
year_month_day(local_days(date.year()/date.month()/last));
+ if (isWeekday(last_day_of_the_month_date))
+ return date == last_day_of_the_month_date;
+ year_month_day last_friday_of_the_month_date =
year_month_day(local_days(date.year()/date.month()/Friday[last]));
+ return date == last_friday_of_the_month_date;
+ }
+};
+
+class LastSpecificDayOfTheWeekOfTheMonth : public CronField {
+ public:
+ explicit LastSpecificDayOfTheWeekOfTheMonth(weekday wday) : weekday_(wday) {}
+
+ [[nodiscard]] bool matches(local_seconds value) const override {
+ year_month_day date(floor<days>(value));
+ year_month_day last_weekday_of_month_date =
year_month_day(local_days(date.year()/date.month()/weekday_[last]));
+ return date == last_weekday_of_month_date;
+ }
+ private:
+ weekday weekday_;
+};
+
+class ClosestWeekdayToTheNthDayOfTheMonth : public CronField {
+ public:
+ explicit ClosestWeekdayToTheNthDayOfTheMonth(day day_number) :
day_number_(day_number) {}
+
+ [[nodiscard]] bool matches(local_seconds value) const override {
+ year_month_day date(floor<days>(value));
+ for (auto diff : {0, -1, 1, -2, 2}) {
+ auto target_date = date.year() / date.month() / (day_number_ +
days(diff));
+ if (target_date.ok() && isWeekday(target_date))
+ return target_date == date;
+ }
+
+ return false;
+ }
+
+ private:
+ day day_number_;
+};
+
+template <typename FieldType>
+std::unique_ptr<CronField> parseCronField(const std::string& field_str) {
+ try {
+ if (field_str == "*") {
+ return std::make_unique<AllValuesField>();
+ }
+
+ if (field_str == "?") {
+ return std::make_unique<NotCheckedField>();
+ }
+
+ if (field_str == "L") {
+ if constexpr (std::is_same<day, FieldType>())
+ return std::make_unique<LastNthDayInMonthField>(days(0));
+ if constexpr (std::is_same<weekday, FieldType>())
+ return std::make_unique<SingleValueField<weekday>>(Saturday);
+ throw BadCronExpression("L can only be used in the Day of month/Day of
week fields");
+ }
+
+ if (field_str == "LW") {
+ if constexpr (!std::is_same<day, FieldType>())
+ throw BadCronExpression("LW can only be used in the Day of month
field");
+ return std::make_unique<LastWeekDayField>();
+ }
+
+ if (field_str.find('#') != std::string::npos) {
+ if constexpr (!std::is_same<weekday, FieldType>())
+ throw BadCronExpression("# can only be used in the Day of week field");
+ auto operands = StringUtils::split(field_str, "#");
+ if (operands.size() != 2)
+ throw BadCronExpression("Invalid field " + field_str);
+
+ if (auto second_operand = fromChars<uint64_t>(operands[1]))
+ return std::make_unique<NthWeekdayField>(parse<weekday>(operands[0]),
*second_operand);
+ }
+
+ if (field_str.find('-') != std::string::npos) {
+ auto operands = StringUtils::split(field_str, "-");
+ if (operands.size() != 2)
+ throw BadCronExpression("Invalid field " + field_str);
+ if (operands[0] == "L") {
+ if constexpr (std::is_same<day, FieldType>())
+ return
std::make_unique<LastNthDayInMonthField>(parse<days>(operands[1]));
+ }
+ return
std::make_unique<RangeField<FieldType>>(parse<FieldType>(operands[0]),
parse<FieldType>(operands[1]));
+ }
+
+ if (field_str.ends_with('L')) {
+ if constexpr (!std::is_same<weekday, FieldType>())
+ throw BadCronExpression("<X>L can only be used in the Day of week
field");
+ auto prefix = field_str.substr(0, field_str.size()-1);
+ return
std::make_unique<LastSpecificDayOfTheWeekOfTheMonth>(parse<weekday>(prefix));
+ }
+
+ if (field_str.find('/') != std::string::npos) {
+ auto operands = StringUtils::split(field_str, "/");
+ if (operands.size() != 2)
+ throw BadCronExpression("Invalid field " + field_str);
+ if (operands[0] == "*")
+ operands[0] = "0";
+ if (auto second_operand = fromChars<int>(operands[1]))
+ return
std::make_unique<IncrementField<FieldType>>(parse<FieldType>(operands[0]),
*second_operand);
+ }
+
+ if (field_str.find(',') != std::string::npos) {
+ auto operands_str = StringUtils::split(field_str, ",");
+ std::vector<FieldType> operands;
+ std::transform(operands_str.begin(), operands_str.end(),
std::back_inserter(operands), parse<FieldType>);
+ return std::make_unique<ListField<FieldType>>(std::move(operands));
+ }
+
+ if (field_str.ends_with('W')) {
+ if constexpr (!std::is_same<day, FieldType>())
+ throw BadCronExpression("W can only be used in the Day of month
field");
+ auto operands_str = StringUtils::split(field_str, "W");
+ if (operands_str.size() != 2)
+ throw BadCronExpression("Invalid field " + field_str);
+ return
std::make_unique<ClosestWeekdayToTheNthDayOfTheMonth>(parse<day>(operands_str[0]));
+ }
+
+ return
std::make_unique<SingleValueField<FieldType>>(parse<FieldType>(field_str));
+ } catch (const std::exception& e) {
+ throw BadCronExpression("Couldn't parse cron field: " + field_str + " " +
e.what());
+ }
+}
+} // namespace
+
+Cron::Cron(const std::string& expression) {
+ auto tokens = StringUtils::split(expression, " ");
+
+ if (tokens.size() != 6 && tokens.size() != 7)
+ throw BadCronExpression("malformed cron string (must be 6 or 7 fields): "
+ expression);
+
+ second_ = parseCronField<seconds>(tokens[0]);
+ minute_ = parseCronField<minutes>(tokens[1]);
+ hour_ = parseCronField<hours>(tokens[2]);
+ day_ = parseCronField<day>(tokens[3]);
+ month_ = parseCronField<month>(tokens[4]);
+ day_of_week_ = parseCronField<weekday>(tokens[5]);
+ if (tokens.size() == 7)
+ year_ = parseCronField<year>(tokens[6]);
+}
+
+std::optional<local_seconds> Cron::calculateNextTrigger(const local_seconds
start) const {
+ gsl_Expects(second_ && minute_ && hour_ && day_ && month_ && day_of_week_);
+ auto next = timeutils::roundToNextSecond(start);
+ while (next < local_days((year(2999)/1/1))) {
+ if (year_ && !year_->matches(next)) {
+ next = timeutils::roundToNextYear(next);
+ continue;
+ }
+ if (!month_->matches(next)) {
+ next = timeutils::roundToNextMonth(next);
+ continue;
+ }
+ if (!day_->matches(next)) {
+ next = timeutils::roundToNextDay(next);
+ continue;
+ }
+ if (!day_of_week_->matches(next)) {
+ next = timeutils::roundToNextDay(next);
+ continue;
+ }
+ if (!hour_->matches(next)) {
+ next = timeutils::roundToNextHour(next);
+ continue;
+ }
+ if (!minute_->matches(next)) {
+ next = timeutils::roundToNextMinute(next);
+ continue;
+ }
+ if (!second_->matches(next)) {
+ next = timeutils::roundToNextSecond(next);
+ continue;
+ }
+ return next;
+ }
+ return std::nullopt;
+}
+
+} // namespace org::apache::nifi::minifi::utils
diff --git a/extensions/standard-processors/tests/unit/SchedulingAgentTests.cpp
b/libminifi/src/utils/TestUtils.cpp
similarity index 60%
rename from extensions/standard-processors/tests/unit/SchedulingAgentTests.cpp
rename to libminifi/src/utils/TestUtils.cpp
index a65516e1f..8d46e8efd 100644
--- a/extensions/standard-processors/tests/unit/SchedulingAgentTests.cpp
+++ b/libminifi/src/utils/TestUtils.cpp
@@ -1,5 +1,4 @@
/**
- *
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
@@ -16,21 +15,12 @@
* limitations under the License.
*/
-#include <memory>
-#include <string>
-#include <vector>
-#include "io/CRCStream.h"
-#include "io/BufferStream.h"
-#include "TestBase.h"
-#include "Catch.h"
-#include "GetFile.h"
-#include "LogAttribute.h"
-#include "SchedulingAgent.h"
-#include "TimerDrivenSchedulingAgent.h"
-
+#include "utils/TestUtils.h"
-TEST_CASE("TestTDAgent", "[test1]") {
- std::shared_ptr<core::Processor> procA =
std::make_shared<minifi::processors::GetFile>("getFile");
- std::shared_ptr<core::Processor> procB =
std::make_shared<minifi::processors::LogAttribute>("logAttribute");
- // agent.run()
+namespace org::apache::nifi::minifi::utils {
+#ifdef WIN32
+void dateSetInstall(const std::string& install) {
+ date::set_install(install);
}
+#endif
+} // namespace org::apache::nifi::minifi::utils
diff --git a/libminifi/test/resources/TestTailFileCron.yml
b/libminifi/test/resources/TestTailFileCron.yml
index 5e6d0f248..afd5d41f8 100644
--- a/libminifi/test/resources/TestTailFileCron.yml
+++ b/libminifi/test/resources/TestTailFileCron.yml
@@ -25,7 +25,7 @@ Processors:
class: org.apache.nifi.processors.standard.TailFile
max concurrent tasks: 1
scheduling strategy: CRON_DRIVEN
- scheduling period: "* * * * *"
+ scheduling period: "* * * * * *"
penalization period: 30 sec
yield period: 1 sec
run duration nanos: 0
@@ -58,7 +58,7 @@ Connections:
source relationship name: success
max work queue size: 0
max work queue data size: 1 MB
- flowfile expiration: 60 sec
+ flowfile expiration: 60 sec
Remote Processing Groups:
-
+
diff --git a/libminifi/test/unit/CronTests.cpp
b/libminifi/test/unit/CronTests.cpp
new file mode 100644
index 000000000..4d8fa5669
--- /dev/null
+++ b/libminifi/test/unit/CronTests.cpp
@@ -0,0 +1,702 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <string>
+
+#include "../Catch.h"
+#include "utils/Cron.h"
+#include "date/date.h"
+#include "date/tz.h"
+
+using std::chrono::system_clock;
+using std::chrono::seconds;
+using org::apache::nifi::minifi::utils::Cron;
+
+
+void checkNext(const std::string& expr, const date::zoned_time<seconds>& from,
const date::zoned_time<seconds>& next) {
+ auto cron_expression = Cron(expr);
+ auto next_trigger =
cron_expression.calculateNextTrigger(from.get_local_time());
+ CHECK(next_trigger == next.get_local_time());
+}
+
+TEST_CASE("Cron expression ctor tests", "[cron]") {
+ REQUIRE_THROWS(Cron("1600 ms"));
+ REQUIRE_THROWS(Cron("foo"));
+ REQUIRE_THROWS(Cron("61 0 0 * * *"));
+ REQUIRE_THROWS(Cron("0 61 0 * * *"));
+ REQUIRE_THROWS(Cron("0 0 24 * * *"));
+ REQUIRE_THROWS(Cron("0 0 0 32 * *"));
+
+ REQUIRE_THROWS(Cron("1banana * * * * * *"));
+ REQUIRE_THROWS(Cron("* 1banana * * * * *"));
+ REQUIRE_THROWS(Cron("* * 1banana * * * *"));
+ REQUIRE_THROWS(Cron("* * * 1banana * * *"));
+ REQUIRE_THROWS(Cron("* * * * 1banana * *"));
+ REQUIRE_THROWS(Cron("* * * * DECbanana * *"));
+ REQUIRE_THROWS(Cron("* * * * * WEDbanana *"));
+
+ REQUIRE_THROWS(Cron("* * * * * * 1banana"));
+ REQUIRE_THROWS(Cron("* * * * * * 2000banana"));
+
+ REQUIRE_THROWS(Cron("1G * * * * * *"));
+ REQUIRE_THROWS(Cron("* 1G * * * * *"));
+ REQUIRE_THROWS(Cron("* * 1G * * * *"));
+ REQUIRE_THROWS(Cron("* * * 1G * * *"));
+ REQUIRE_THROWS(Cron("* * * * 1G * *"));
+ REQUIRE_THROWS(Cron("* * * * * 1G *"));
+ REQUIRE_THROWS(Cron("* * * * * * 1G"));
+
+ // Number of fields must be 6 or 7
+ REQUIRE_THROWS(Cron("* * * * *"));
+ REQUIRE_NOTHROW(Cron("* * * * * *"));
+ REQUIRE_NOTHROW(Cron("* * * * * * *"));
+ REQUIRE_THROWS(Cron("* * * * * * * *"));
+
+ // LW can only be used in 4th field
+ REQUIRE_THROWS(Cron("LW * * * * * *"));
+ REQUIRE_THROWS(Cron("* LW * * * * *"));
+ REQUIRE_THROWS(Cron("* * LW * * * *"));
+ REQUIRE_NOTHROW(Cron("* * * LW * * *"));
+ REQUIRE_THROWS(Cron("* * * * LW * *"));
+ REQUIRE_THROWS(Cron("* * * * * LW *"));
+ REQUIRE_THROWS(Cron("* * * * * * LW"));
+
+ // n#m can only be used in 6th field
+ REQUIRE_THROWS(Cron("2#1 * * * * * *"));
+ REQUIRE_THROWS(Cron("* 2#1 * * * * *"));
+ REQUIRE_THROWS(Cron("* * 2#1 * * * *"));
+ REQUIRE_THROWS(Cron("* * * 2#1 * * *"));
+ REQUIRE_THROWS(Cron("* * * * 2#1 * *"));
+ REQUIRE_NOTHROW(Cron("* * * * * 2#1 *"));
+ REQUIRE_THROWS(Cron("* * * * * * 2#1"));
+
+ // L can only be used in 4th, 6th fields
+ REQUIRE_THROWS(Cron("L * * * * * *"));
+ REQUIRE_THROWS(Cron("* L * * * * *"));
+ REQUIRE_THROWS(Cron("* * L * * * *"));
+ REQUIRE_NOTHROW(Cron("* * * L * * *"));
+ REQUIRE_THROWS(Cron("* * * * L * *"));
+ REQUIRE_NOTHROW(Cron("* * * * * L *"));
+ REQUIRE_THROWS(Cron("* * * * * * L"));
+
+ REQUIRE_NOTHROW(Cron("0 0 12 * * ?"));
+ REQUIRE_NOTHROW(Cron("0 15 10 ? * *"));
+ REQUIRE_NOTHROW(Cron("0 15 10 * * ?"));
+ REQUIRE_NOTHROW(Cron("0 15 10 * * ? *"));
+ REQUIRE_NOTHROW(Cron("0 15 10 * * ? 2005"));
+ REQUIRE_NOTHROW(Cron("0 * 14 * * ?"));
+ REQUIRE_NOTHROW(Cron("0 0/5 14 * * ?"));
+ REQUIRE_NOTHROW(Cron("0 0/5 14,18 * * ?"));
+ REQUIRE_NOTHROW(Cron("0 0-5 14 * * ?"));
+ REQUIRE_NOTHROW(Cron("0 10,44 14 ? 3 WED"));
+ REQUIRE_NOTHROW(Cron("0 15 10 ? * MON-FRI"));
+ REQUIRE_NOTHROW(Cron("0 15 10 15 * ?"));
+ REQUIRE_NOTHROW(Cron("0 15 10 L * ?"));
+ REQUIRE_NOTHROW(Cron("0 15 10 L-2 * ?"));
+ REQUIRE_NOTHROW(Cron("0 15 10 ? * 6L"));
+ REQUIRE_NOTHROW(Cron("0 15 10 ? * 6L"));
+ REQUIRE_NOTHROW(Cron("0 15 10 ? * 6L 2002-2005"));
+ REQUIRE_NOTHROW(Cron("0 15 10 ? * 6#3"));
+ REQUIRE_NOTHROW(Cron("0 0 12 1/5 * ?"));
+ REQUIRE_NOTHROW(Cron("0 11 11 11 11 ?"));
+
+ REQUIRE_THROWS(Cron("0 15 10 L-32 * ?"));
+ REQUIRE_THROWS(Cron("15-10 * * * * * *"));
+ REQUIRE_THROWS(Cron("* 4-3 * * * * *"));
+ REQUIRE_THROWS(Cron("* * 4-3 * * * *"));
+ REQUIRE_THROWS(Cron("* * * 31-29 * * *"));
+ REQUIRE_THROWS(Cron("0 0 0 ? * MON-SUN"));
+ REQUIRE_NOTHROW(Cron("0 0 0 ? * SUN-MON"));
+}
+
+TEST_CASE("Cron allowed nonnumerical inputs", "[cron]") {
+ REQUIRE_NOTHROW(Cron("* * * *
Jan,fEb,MAR,Apr,May,jun,Jul,Aug,Sep,Oct,Nov,Dec * *"));
+ REQUIRE_NOTHROW(Cron("* * * * * Mon,tUe,WeD,Thu,Fri,SAT,Sun *"));
+}
+
+TEST_CASE("Cron::calculateNextTrigger", "[cron]") {
+ using date::sys_days;
+ using namespace date::literals;
+ using namespace std::literals::chrono_literals;
+#ifdef WIN32
+ date::set_install(TZ_DATA_DIR);
+#endif
+
+ checkNext("0/15 * 1-4 * * ?",
+ sys_days(2012_y / 07 / 01) + 9h + 53min + 50s,
+ sys_days(2012_y / 07 / 02) + 01h + 00min + 00s);
+ checkNext("0/15 * 1-4 * * ? *",
+ sys_days(2012_y / 07 / 01) + 9h + 53min + 50s,
+ sys_days(2012_y / 07 / 02) + 01h + 00min + 00s);
+ checkNext("0/15 * 1-4 * * ?",
+ sys_days(2012_y / 07 / 01) + 9h + 53min + 00s,
+ sys_days(2012_y / 07 / 02) + 01h + 00min + 00s);
+ checkNext("*/15 * 1-4 * * ?",
+ sys_days(2012_y / 07 / 01) + 9h + 53min + 50s,
+ sys_days(2012_y / 07 / 02) + 01h + 00min + 00s);
+ checkNext("*/15 * 1-4 * * ? *",
+ sys_days(2012_y / 07 / 01) + 9h + 53min + 50s,
+ sys_days(2012_y / 07 / 02) + 01h + 00min + 00s);
+ checkNext("*/15 * 1-4 * * ?",
+ sys_days(2012_y / 07 / 01) + 9h + 53min + 00s,
+ sys_days(2012_y / 07 / 02) + 01h + 00min + 00s);
+ checkNext("0 0/2 1-4 * * ?",
+ sys_days(2012_y / 07 / 01) + 9h + 00min + 00s,
+ sys_days(2012_y / 07 / 02) + 01h + 00min + 00s);
+ checkNext("* * * * * ?",
+ sys_days(2012_y / 07 / 01) + 9h + 00min + 00s,
+ sys_days(2012_y / 07 / 01) + 9h + 00min + 01s);
+ checkNext("* * * * * ?",
+ sys_days(2012_y / 12 / 01) + 9h + 00min + 58s,
+ sys_days(2012_y / 12 / 01) + 9h + 00min + 59s);
+ checkNext("10 * * * * ?",
+ sys_days(2012_y / 12 / 01) + 9h + 42min + 9s,
+ sys_days(2012_y / 12 / 01) + 9h + 42min + 10s);
+ checkNext("11 * * * * ?",
+ sys_days(2012_y / 12 / 01) + 9h + 42min + 10s,
+ sys_days(2012_y / 12 / 01) + 9h + 42min + 11s);
+ checkNext("10 * * * * ?",
+ sys_days(2012_y / 12 / 01) + 9h + 42min + 10s,
+ sys_days(2012_y / 12 / 01) + 9h + 43min + 10s);
+ checkNext("10-15 * * * * ?",
+ sys_days(2012_y / 12 / 01) + 9h + 42min + 9s,
+ sys_days(2012_y / 12 / 01) + 9h + 42min + 10s);
+ checkNext("10-15 * * * * ?",
+ sys_days(2012_y / 12 / 01) + 21h + 42min + 14s,
+ sys_days(2012_y / 12 / 01) + 21h + 42min + 15s);
+ checkNext("0 * * * * ?",
+ sys_days(2012_y / 12 / 01) + 21h + 10min + 42s,
+ sys_days(2012_y / 12 / 01) + 21h + 11min + 00s);
+ checkNext("0 * * * * ?",
+ sys_days(2012_y / 12 / 01) + 21h + 11min + 00s,
+ sys_days(2012_y / 12 / 01) + 21h + 12min + 00s);
+ checkNext("0 11 * * * ?",
+ sys_days(2012_y / 12 / 01) + 21h + 10min + 42s,
+ sys_days(2012_y / 12 / 01) + 21h + 11min + 00s);
+ checkNext("0 10 * * * ?",
+ sys_days(2012_y / 12 / 01) + 21h + 11min + 00s,
+ sys_days(2012_y / 12 / 01) + 22h + 10min + 00s);
+ checkNext("0 0 * * * ?",
+ sys_days(2012_y / 9 / 30) + 11h + 01min + 00s,
+ sys_days(2012_y / 9 / 30) + 12h + 00min + 00s);
+ checkNext("0 0 * * * ?",
+ sys_days(2012_y / 9 / 30) + 12h + 00min + 00s,
+ sys_days(2012_y / 9 / 30) + 13h + 00min + 00s);
+ checkNext("0 0 * * * ?",
+ sys_days(2012_y / 9 / 10) + 23h + 01min + 00s,
+ sys_days(2012_y / 9 / 11) + 00h + 00min + 00s);
+ checkNext("0 0 * * * ?",
+ sys_days(2012_y / 9 / 11) + 00h + 00min + 00s,
+ sys_days(2012_y / 9 / 11) + 01h + 00min + 00s);
+ checkNext("0 0 0 * * ?",
+ sys_days(2012_y / 9 / 01) + 14h + 42min + 43s,
+ sys_days(2012_y / 9 / 02) + 00h + 00min + 00s);
+ checkNext("0 0 0 * * ?",
+ sys_days(2012_y / 9 / 02) + 00h + 00min + 00s,
+ sys_days(2012_y / 9 / 03) + 00h + 00min + 00s);
+ checkNext("* * * 10 * ?",
+ sys_days(2012_y / 10 / 9) + 15h + 12min + 42s,
+ sys_days(2012_y / 10 / 10) + 00h + 00min + 00s);
+ checkNext("* * * 10 * ?",
+ sys_days(2012_y / 10 / 11) + 15h + 12min + 42s,
+ sys_days(2012_y / 11 / 10) + 00h + 00min + 00s);
+ checkNext("0 0 0 * * ? 2020",
+ sys_days(2012_y / 9 / 30) + 15h + 12min + 42s,
+ sys_days(2020_y / 01 / 01) + 00h + 00min + 00s);
+ checkNext("0 0 0 * * ?",
+ sys_days(2012_y / 9 / 30) + 15h + 12min + 42s,
+ sys_days(2012_y / 10 / 01) + 00h + 00min + 00s);
+ checkNext("0 0 0 * * ?",
+ sys_days(2012_y / 10 / 01) + 00h + 00min + 00s,
+ sys_days(2012_y / 10 / 02) + 00h + 00min + 00s);
+ checkNext("0 0 0 * * ?",
+ sys_days(2012_y / 8 / 30) + 15h + 12min + 42s,
+ sys_days(2012_y / 8 / 31) + 00h + 00min + 00s);
+ checkNext("0 0 0 * * ?",
+ sys_days(2012_y / 8 / 31) + 00h + 00min + 00s,
+ sys_days(2012_y / 9 / 01) + 00h + 00min + 00s);
+ checkNext("0 0 0 * * ?",
+ sys_days(2012_y / 10 / 30) + 15h + 12min + 42s,
+ sys_days(2012_y / 10 / 31) + 00h + 00min + 00s);
+ checkNext("0 0 0 * * ?",
+ sys_days(2012_y / 10 / 31) + 00h + 00min + 00s,
+ sys_days(2012_y / 11 / 01) + 00h + 00min + 00s);
+ checkNext("0 0 0 1 * ?",
+ sys_days(2012_y / 10 / 30) + 15h + 12min + 42s,
+ sys_days(2012_y / 11 / 01) + 00h + 00min + 00s);
+ checkNext("0 0 0 1 * ?",
+ sys_days(2012_y / 11 / 01) + 00h + 00min + 00s,
+ sys_days(2012_y / 12 / 01) + 00h + 00min + 00s);
+ checkNext("0 0 0 1 * ?",
+ sys_days(2010_y / 12 / 31) + 15h + 12min + 42s,
+ sys_days(2011_y / 01 / 01) + 00h + 00min + 00s);
+ checkNext("0 0 0 1 * ?",
+ sys_days(2011_y / 01 / 01) + 00h + 00min + 00s,
+ sys_days(2011_y / 02 / 01) + 00h + 00min + 00s);
+ checkNext("0 0 0 31 * ?",
+ sys_days(2011_y / 10 / 30) + 15h + 12min + 42s,
+ sys_days(2011_y / 10 / 31) + 00h + 00min + 00s);
+ checkNext("0 0 0 1 * ?",
+ sys_days(2011_y / 10 / 30) + 15h + 12min + 42s,
+ sys_days(2011_y / 11 / 01) + 00h + 00min + 00s);
+ checkNext("* * * ? * 2",
+ sys_days(2010_y / 10 / 25) + 15h + 12min + 42s,
+ sys_days(2010_y / 10 / 25) + 15h + 12min + 43s);
+ checkNext("* * * ? * 2",
+ sys_days(2010_y / 10 / 20) + 15h + 12min + 42s,
+ sys_days(2010_y / 10 / 25) + 00h + 00min + 00s);
+ checkNext("* * * ? * 2",
+ sys_days(2010_y / 10 / 27) + 15h + 12min + 42s,
+ sys_days(2010_y / 11 / 01) + 00h + 00min + 00s);
+ checkNext("55 5 * * * ?",
+ sys_days(2010_y / 10 / 27) + 15h + 04min + 54s,
+ sys_days(2010_y / 10 / 27) + 15h + 05min + 55s);
+ checkNext("55 5 * * * ?",
+ sys_days(2010_y / 10 / 27) + 15h + 05min + 55s,
+ sys_days(2010_y / 10 / 27) + 16h + 05min + 55s);
+ checkNext("55 * 10 * * ?",
+ sys_days(2010_y / 10 / 27) + 9h + 04min + 54s,
+ sys_days(2010_y / 10 / 27) + 10h + 00min + 55s);
+ checkNext("55 * 10 * * ?",
+ sys_days(2010_y / 10 / 27) + 10h + 00min + 55s,
+ sys_days(2010_y / 10 / 27) + 10h + 01min + 55s);
+ checkNext("* 5 10 * * ?",
+ sys_days(2010_y / 10 / 27) + 9h + 04min + 55s,
+ sys_days(2010_y / 10 / 27) + 10h + 05min + 00s);
+ checkNext("* 5 10 * * ?",
+ sys_days(2010_y / 10 / 27) + 10h + 05min + 00s,
+ sys_days(2010_y / 10 / 27) + 10h + 05min + 01s);
+ checkNext("55 * * 3 * ?",
+ sys_days(2010_y / 10 / 02) + 10h + 05min + 54s,
+ sys_days(2010_y / 10 / 03) + 00h + 00min + 55s);
+ checkNext("55 * * 3 * ?",
+ sys_days(2010_y / 10 / 03) + 00h + 00min + 55s,
+ sys_days(2010_y / 10 / 03) + 00h + 01min + 55s);
+ checkNext("* * * 3 11 ?",
+ sys_days(2010_y / 10 / 02) + 14h + 42min + 55s,
+ sys_days(2010_y / 11 / 03) + 00h + 00min + 00s);
+ checkNext("* * * 3 11 ?",
+ sys_days(2010_y / 11 / 03) + 00h + 00min + 00s,
+ sys_days(2010_y / 11 / 03) + 00h + 00min + 01s);
+ checkNext("0 0 0 29 2 ?",
+ sys_days(2007_y / 02 / 10) + 14h + 42min + 55s,
+ sys_days(2008_y / 02 / 29) + 00h + 00min + 00s);
+ checkNext("0 0 0 29 2 ?",
+ sys_days(2008_y / 02 / 29) + 00h + 00min + 00s,
+ sys_days(2012_y / 02 / 29) + 00h + 00min + 00s);
+ checkNext("0 0 7 ? * Mon-Fri",
+ sys_days(2009_y / 9 / 26) + 00h + 42min + 55s,
+ sys_days(2009_y / 9 / 28) + 07h + 00min + 00s);
+ checkNext("0 0 7 ? * Mon-Fri",
+ sys_days(2009_y / 9 / 26) + 00h + 42min + 55s,
+ sys_days(2009_y / 9 / 28) + 07h + 00min + 00s);
+ checkNext("0 0 7 ? * Mon,Tue,Wed,Thu,Fri",
+ sys_days(2009_y / 9 / 28) + 07h + 00min + 00s,
+ sys_days(2009_y / 9 / 29) + 07h + 00min + 00s);
+ checkNext("0 30 23 30 1/3 ?",
+ sys_days(2010_y / 12 / 30) + 00h + 00min + 00s,
+ sys_days(2011_y / 01 / 30) + 23h + 30min + 00s);
+ checkNext("0 30 23 30 1/3 ?",
+ sys_days(2011_y / 01 / 30) + 23h + 30min + 00s,
+ sys_days(2011_y / 04 / 30) + 23h + 30min + 00s);
+ checkNext("0 30 23 30 1/3 ?",
+ sys_days(2011_y / 04 / 30) + 23h + 30min + 00s,
+ sys_days(2011_y / 07 / 30) + 23h + 30min + 00s);
+
+ checkNext("0 0 0 LW * ? *",
+ sys_days(2022_y / 02 / 27) + 02h + 00min + 00s,
+ sys_days(2022_y / 02 / 28) + 00h + 00min + 00s);
+ checkNext("0 0 0 LW * ? *",
+ sys_days(2024_y / 02 / 27) + 02h + 00min + 00s,
+ sys_days(2024_y / 02 / 29) + 00h + 00min + 00s);
+ checkNext("0 0 0 LW * ? *",
+ sys_days(2027_y / 02 / 27) + 02h + 00min + 00s,
+ sys_days(2027_y / 03 / 31) + 00h + 00min + 00s);
+ checkNext("0 0 0 ? * 3#1 *",
+ sys_days(2022_y / 05 / 04) + 00h + 00min + 00s,
+ sys_days(2022_y / 06 / 07) + 00h + 00min + 00s);
+ checkNext("0 0 0 ? * 3#2 *",
+ sys_days(2022_y / 05 / 04) + 00h + 00min + 00s,
+ sys_days(2022_y / 05 / 10) + 00h + 00min + 00s);
+ checkNext("0 0 0 ? * 3#3 *",
+ sys_days(2022_y / 05 / 04) + 00h + 00min + 00s,
+ sys_days(2022_y / 05 / 17) + 00h + 00min + 00s);
+ checkNext("0 0 0 ? * 3#4 *",
+ sys_days(2022_y / 05 / 04) + 00h + 00min + 00s,
+ sys_days(2022_y / 05 / 24) + 00h + 00min + 00s);
+ checkNext("0 0 0 ? * 3#5 *",
+ sys_days(2022_y / 05 / 04) + 00h + 00min + 00s,
+ sys_days(2022_y / 05 / 31) + 00h + 00min + 00s);
+ checkNext("0 0 0 L * ? *",
+ sys_days(2022_y / 01 / 04) + 00h + 00min + 00s,
+ sys_days(2022_y / 01 / 31) + 00h + 00min + 00s);
+ checkNext("0 0 0 L * ? *",
+ sys_days(2022_y / 02 / 04) + 00h + 00min + 00s,
+ sys_days(2022_y / 02 / 28) + 00h + 00min + 00s);
+ checkNext("0 0 0 L * ? *",
+ sys_days(2024_y / 02 / 04) + 00h + 00min + 00s,
+ sys_days(2024_y / 02 / 29) + 00h + 00min + 00s);
+ checkNext("0 0 0 L * ? *",
+ sys_days(2022_y / 03 / 04) + 00h + 00min + 00s,
+ sys_days(2022_y / 03 / 31) + 00h + 00min + 00s);
+ checkNext("0 0 0 L * ? *",
+ sys_days(2022_y / 04 / 04) + 00h + 00min + 00s,
+ sys_days(2022_y / 04 / 30) + 00h + 00min + 00s);
+ checkNext("0 0 0 L * ? *",
+ sys_days(2022_y / 05 / 31) + 00h + 00min + 00s,
+ sys_days(2022_y / 06 / 30) + 00h + 00min + 00s);
+ checkNext("0 0 0 ? * L *",
+ sys_days(2022_y / 01 / 07) + 00h + 00min + 00s,
+ sys_days(2022_y / 01 / 8) + 00h + 00min + 00s);
+ checkNext("0 0 0 ? * L *",
+ sys_days(2022_y / 02 / 04) + 00h + 00min + 00s,
+ sys_days(2022_y / 02 / 05) + 00h + 00min + 00s);
+ checkNext("0 0 0 ? * L *",
+ sys_days(2024_y / 02 / 04) + 00h + 00min + 00s,
+ sys_days(2024_y / 02 / 10) + 00h + 00min + 00s);
+ checkNext("0 0 0 ? * L *",
+ sys_days(2022_y / 03 / 04) + 00h + 00min + 00s,
+ sys_days(2022_y / 03 / 05) + 00h + 00min + 00s);
+ checkNext("0 0 0 ? * L *",
+ sys_days(2022_y / 04 / 04) + 00h + 00min + 00s,
+ sys_days(2022_y / 04 / 9) + 00h + 00min + 00s);
+ checkNext("0 0 0 ? * L *",
+ sys_days(2022_y / 05 / 28) + 00h + 00min + 00s,
+ sys_days(2022_y / 06 / 04) + 00h + 00min + 00s);
+ checkNext("0 0 0 1W * ? *",
+ sys_days(2022_y / 05 / 01) + 00h + 00min + 00s,
+ sys_days(2022_y / 05 / 02) + 00h + 00min + 00s);
+ checkNext("0 0 0 4W * ? *",
+ sys_days(2022_y / 05 / 01) + 00h + 00min + 00s,
+ sys_days(2022_y / 05 / 04) + 00h + 00min + 00s);
+ checkNext("0 0 0 14W * ? *",
+ sys_days(2022_y / 05 / 01) + 00h + 00min + 00s,
+ sys_days(2022_y / 05 / 13) + 00h + 00min + 00s);
+ checkNext("0 0 0 15W * ? *",
+ sys_days(2022_y / 05 / 01) + 00h + 00min + 00s,
+ sys_days(2022_y / 05 / 16) + 00h + 00min + 00s);
+ checkNext("0 0 0 31W * ? *",
+ sys_days(2022_y / 02 / 01) + 00h + 00min + 00s,
+ sys_days(2022_y / 03 / 31) + 00h + 00min + 00s);
+ checkNext("0 0 0 1W * ? *",
+ sys_days(2021_y / 12 / 15) + 00h + 00min + 00s,
+ sys_days(2022_y / 01 / 03) + 00h + 00min + 00s);
+ checkNext("0 0 0 31W * ? *",
+ sys_days(2022_y / 07 / 15) + 00h + 00min + 00s,
+ sys_days(2022_y / 07 / 29) + 00h + 00min + 00s);
+
+ checkNext("0 15 10 ? * 6L",
+ sys_days(2022_y / 07 / 15) + 00h + 00min + 00s,
+ sys_days(2022_y / 07 / 29) + 10h + 15min + 00s);
+
+ checkNext("0 0 0 L-3 * ?",
+ sys_days(2022_y / 01 / 10) + 00h + 00min + 00s,
+ sys_days(2022_y / 01 / 28) + 00h + 00min + 00s);
+
+ checkNext("0 0 0 L-30 * ?",
+ sys_days(2022_y / 01 / 10) + 00h + 00min + 00s,
+ sys_days(2022_y / 03 / 01) + 00h + 00min + 00s);
+}
+
+TEST_CASE("Cron::calculateNextTrigger with timezones", "[cron]") {
+ using date::local_days;
+ using date::locate_zone;
+ using date::zoned_time;
+ using namespace date::literals;
+ using namespace std::literals::chrono_literals;
+#ifdef WIN32
+ date::set_install(TZ_DATA_DIR);
+#endif
+
+ const std::vector<std::string> time_zones{ "Europe/Berlin", "Asia/Seoul",
"America/Los_Angeles", "Asia/Singapore", "UCT" };
+
+ for (const auto& time_zone: time_zones) {
+ checkNext("0/15 * 1-4 * * ?",
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 07 / 01)
+ 9h + 53min + 50s),
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 07 / 02)
+ 01h + 00min + 00s));
+ checkNext("0/15 * 1-4 * * ? *",
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 07 / 01)
+ 9h + 53min + 50s),
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 07 / 02)
+ 01h + 00min + 00s));
+ checkNext("0/15 * 1-4 * * ?",
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 07 / 01)
+ 9h + 53min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 07 / 02)
+ 01h + 00min + 00s));
+ checkNext("0/15 * 1-4 * * ?",
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 07 / 01)
+ 9h + 53min + 50s),
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 07 / 02)
+ 01h + 00min + 00s));
+ checkNext("0/15 * 1-4 * * ? *",
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 07 / 01)
+ 9h + 53min + 50s),
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 07 / 02)
+ 01h + 00min + 00s));
+ checkNext("0/15 * 1-4 * * ?",
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 07 / 01)
+ 9h + 53min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 07 / 02)
+ 01h + 00min + 00s));
+ checkNext("0 0/2 1-4 * * ?",
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 07 / 01)
+ 9h + 00min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 07 / 02)
+ 01h + 00min + 00s));
+ checkNext("* * * * * ?",
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 07 / 01)
+ 9h + 00min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 07 / 01)
+ 9h + 00min + 01s));
+ checkNext("* * * * * ?",
+ make_zoned(locate_zone(time_zone), local_days(2016_y / 12 / 01)
+ 9h + 00min + 58s),
+ make_zoned(locate_zone(time_zone), local_days(2016_y / 12 / 01)
+ 9h + 00min + 59s));
+ checkNext("10 * * * * ?",
+ make_zoned(locate_zone(time_zone), local_days(2016_y / 12 / 01)
+ 9h + 42min + 9s),
+ make_zoned(locate_zone(time_zone), local_days(2016_y / 12 / 01)
+ 9h + 42min + 10s));
+ checkNext("11 * * * * ?",
+ make_zoned(locate_zone(time_zone), local_days(2016_y / 12 / 01)
+ 9h + 42min + 10s),
+ make_zoned(locate_zone(time_zone), local_days(2016_y / 12 / 01)
+ 9h + 42min + 11s));
+ checkNext("10 * * * * ?",
+ make_zoned(locate_zone(time_zone), local_days(2016_y / 12 / 01)
+ 9h + 42min + 10s),
+ make_zoned(locate_zone(time_zone), local_days(2016_y / 12 / 01)
+ 9h + 43min + 10s));
+ checkNext("10-15 * * * * ?",
+ make_zoned(locate_zone(time_zone), local_days(2016_y / 12 / 01)
+ 9h + 42min + 9s),
+ make_zoned(locate_zone(time_zone), local_days(2016_y / 12 / 01)
+ 9h + 42min + 10s));
+ checkNext("10-15 * * * * ?",
+ make_zoned(locate_zone(time_zone), local_days(2016_y / 12 / 01)
+ 21h + 42min + 14s),
+ make_zoned(locate_zone(time_zone), local_days(2016_y / 12 / 01)
+ 21h + 42min + 15s));
+ checkNext("0 * * * * ?",
+ make_zoned(locate_zone(time_zone), local_days(2016_y / 12 / 01)
+ 21h + 10min + 42s),
+ make_zoned(locate_zone(time_zone), local_days(2016_y / 12 / 01)
+ 21h + 11min + 00s));
+ checkNext("0 * * * * ?",
+ make_zoned(locate_zone(time_zone), local_days(2016_y / 12 / 01)
+ 21h + 11min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2016_y / 12 / 01)
+ 21h + 12min + 00s));
+ checkNext("0 11 * * * ?",
+ make_zoned(locate_zone(time_zone), local_days(2016_y / 12 / 01)
+ 21h + 10min + 42s),
+ make_zoned(locate_zone(time_zone), local_days(2016_y / 12 / 01)
+ 21h + 11min + 00s));
+ checkNext("0 10 * * * ?",
+ make_zoned(locate_zone(time_zone), local_days(2016_y / 12 / 01)
+ 21h + 11min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2016_y / 12 / 01)
+ 22h + 10min + 00s));
+ checkNext("0 0 * * * ?",
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 9 / 30) +
11h + 01min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 9 / 30) +
12h + 00min + 00s));
+ checkNext("0 0 * * * ?",
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 9 / 30) +
12h + 00min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 9 / 30) +
13h + 00min + 00s));
+ checkNext("0 0 * * * ?",
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 9 / 10) +
23h + 01min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 9 / 11) +
00h + 00min + 00s));
+ checkNext("0 0 * * * ?",
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 9 / 11) +
00h + 00min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 9 / 11) +
01h + 00min + 00s));
+ checkNext("0 0 0 * * ?",
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 9 / 01) +
14h + 42min + 43s),
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 9 / 02) +
00h + 00min + 00s));
+ checkNext("0 0 0 * * ?",
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 9 / 02) +
00h + 00min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 9 / 03) +
00h + 00min + 00s));
+ checkNext("* * * 10 * ?",
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 10 / 9) +
15h + 12min + 42s),
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 10 / 10)
+ 00h + 00min + 00s));
+ checkNext("* * * 10 * ?",
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 10 / 11)
+ 15h + 12min + 42s),
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 11 / 10)
+ 00h + 00min + 00s));
+ checkNext("0 0 0 * * ? 2020",
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 9 / 30) +
15h + 12min + 42s),
+ make_zoned(locate_zone(time_zone), local_days(2020_y / 01 / 01)
+ 00h + 00min + 00s));
+ checkNext("0 0 0 * * ?",
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 9 / 30) +
15h + 12min + 42s),
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 10 / 01)
+ 00h + 00min + 00s));
+ checkNext("0 0 0 * * ?",
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 10 / 01)
+ 00h + 00min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 10 / 02)
+ 00h + 00min + 00s));
+ checkNext("0 0 0 * * ?",
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 8 / 30) +
15h + 12min + 42s),
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 8 / 31) +
00h + 00min + 00s));
+ checkNext("0 0 0 * * ?",
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 8 / 31) +
00h + 00min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 9 / 01) +
00h + 00min + 00s));
+ checkNext("0 0 0 * * ?",
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 10 / 30)
+ 15h + 12min + 42s),
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 10 / 31)
+ 00h + 00min + 00s));
+ checkNext("0 0 0 * * ?",
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 10 / 31)
+ 00h + 00min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 11 / 01)
+ 00h + 00min + 00s));
+ checkNext("0 0 0 1 * ?",
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 10 / 30)
+ 15h + 12min + 42s),
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 11 / 01)
+ 00h + 00min + 00s));
+ checkNext("0 0 0 1 * ?",
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 11 / 01)
+ 00h + 00min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 12 / 01)
+ 00h + 00min + 00s));
+ checkNext("0 0 0 1 * ?",
+ make_zoned(locate_zone(time_zone), local_days(2010_y / 12 / 31)
+ 15h + 12min + 42s),
+ make_zoned(locate_zone(time_zone), local_days(2011_y / 01 / 01)
+ 00h + 00min + 00s));
+ checkNext("0 0 0 1 * ?",
+ make_zoned(locate_zone(time_zone), local_days(2011_y / 01 / 01)
+ 00h + 00min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2011_y / 02 / 01)
+ 00h + 00min + 00s));
+ checkNext("0 0 0 31 * ?",
+ make_zoned(locate_zone(time_zone), local_days(2011_y / 10 / 30)
+ 15h + 12min + 42s),
+ make_zoned(locate_zone(time_zone), local_days(2011_y / 10 / 31)
+ 00h + 00min + 00s));
+ checkNext("0 0 0 1 * ?",
+ make_zoned(locate_zone(time_zone), local_days(2011_y / 10 / 30)
+ 15h + 12min + 42s),
+ make_zoned(locate_zone(time_zone), local_days(2011_y / 11 / 01)
+ 00h + 00min + 00s));
+ checkNext("* * * ? * 2",
+ make_zoned(locate_zone(time_zone), local_days(2010_y / 10 / 25)
+ 15h + 12min + 42s),
+ make_zoned(locate_zone(time_zone), local_days(2010_y / 10 / 25)
+ 15h + 12min + 43s));
+ checkNext("* * * ? * 2",
+ make_zoned(locate_zone(time_zone), local_days(2010_y / 10 / 20)
+ 15h + 12min + 42s),
+ make_zoned(locate_zone(time_zone), local_days(2010_y / 10 / 25)
+ 00h + 00min + 00s));
+ checkNext("* * * ? * 2",
+ make_zoned(locate_zone(time_zone), local_days(2010_y / 10 / 27)
+ 15h + 12min + 42s),
+ make_zoned(locate_zone(time_zone), local_days(2010_y / 11 / 01)
+ 00h + 00min + 00s));
+ checkNext("55 5 * * * ?",
+ make_zoned(locate_zone(time_zone), local_days(2010_y / 10 / 27)
+ 15h + 04min + 54s),
+ make_zoned(locate_zone(time_zone), local_days(2010_y / 10 / 27)
+ 15h + 05min + 55s));
+ checkNext("55 5 * * * ?",
+ make_zoned(locate_zone(time_zone), local_days(2010_y / 10 / 27)
+ 15h + 05min + 55s),
+ make_zoned(locate_zone(time_zone), local_days(2010_y / 10 / 27)
+ 16h + 05min + 55s));
+ checkNext("55 * 10 * * ?",
+ make_zoned(locate_zone(time_zone), local_days(2010_y / 10 / 27)
+ 9h + 04min + 54s),
+ make_zoned(locate_zone(time_zone), local_days(2010_y / 10 / 27)
+ 10h + 00min + 55s));
+ checkNext("55 * 10 * * ?",
+ make_zoned(locate_zone(time_zone), local_days(2010_y / 10 / 27)
+ 10h + 00min + 55s),
+ make_zoned(locate_zone(time_zone), local_days(2010_y / 10 / 27)
+ 10h + 01min + 55s));
+ checkNext("* 5 10 * * ?",
+ make_zoned(locate_zone(time_zone), local_days(2010_y / 10 / 27)
+ 9h + 04min + 55s),
+ make_zoned(locate_zone(time_zone), local_days(2010_y / 10 / 27)
+ 10h + 05min + 00s));
+ checkNext("* 5 10 * * ?",
+ make_zoned(locate_zone(time_zone), local_days(2010_y / 10 / 27)
+ 10h + 05min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2010_y / 10 / 27)
+ 10h + 05min + 01s));
+ checkNext("55 * * 3 * ?",
+ make_zoned(locate_zone(time_zone), local_days(2010_y / 10 / 02)
+ 10h + 05min + 54s),
+ make_zoned(locate_zone(time_zone), local_days(2010_y / 10 / 03)
+ 00h + 00min + 55s));
+ checkNext("55 * * 3 * ?",
+ make_zoned(locate_zone(time_zone), local_days(2010_y / 10 / 03)
+ 00h + 00min + 55s),
+ make_zoned(locate_zone(time_zone), local_days(2010_y / 10 / 03)
+ 00h + 01min + 55s));
+ checkNext("* * * 3 11 ?",
+ make_zoned(locate_zone(time_zone), local_days(2010_y / 10 / 02)
+ 14h + 42min + 55s),
+ make_zoned(locate_zone(time_zone), local_days(2010_y / 11 / 03)
+ 00h + 00min + 00s));
+ checkNext("* * * 3 11 ?",
+ make_zoned(locate_zone(time_zone), local_days(2010_y / 11 / 03)
+ 00h + 00min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2010_y / 11 / 03)
+ 00h + 00min + 01s));
+ checkNext("0 0 0 29 2 ?",
+ make_zoned(locate_zone(time_zone), local_days(2007_y / 02 / 10)
+ 14h + 42min + 55s),
+ make_zoned(locate_zone(time_zone), local_days(2008_y / 02 / 29)
+ 00h + 00min + 00s));
+ checkNext("0 0 0 29 2 ?",
+ make_zoned(locate_zone(time_zone), local_days(2008_y / 02 / 29)
+ 00h + 00min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2012_y / 02 / 29)
+ 00h + 00min + 00s));
+ checkNext("0 0 7 ? * Mon-Fri",
+ make_zoned(locate_zone(time_zone), local_days(2009_y / 9 / 26) +
00h + 42min + 55s),
+ make_zoned(locate_zone(time_zone), local_days(2009_y / 9 / 28) +
07h + 00min + 00s));
+ checkNext("0 0 7 ? * Mon-Fri",
+ make_zoned(locate_zone(time_zone), local_days(2009_y / 9 / 26) +
00h + 42min + 55s),
+ make_zoned(locate_zone(time_zone), local_days(2009_y / 9 / 28) +
07h + 00min + 00s));
+ checkNext("0 0 7 ? * Mon,Tue,Wed,Thu,Fri",
+ make_zoned(locate_zone(time_zone), local_days(2009_y / 9 / 28) +
07h + 00min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2009_y / 9 / 29) +
07h + 00min + 00s));
+ checkNext("0 30 23 30 1/3 ?",
+ make_zoned(locate_zone(time_zone), local_days(2010_y / 12 / 30)
+ 00h + 00min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2011_y / 01 / 30)
+ 23h + 30min + 00s));
+ checkNext("0 30 23 30 1/3 ?",
+ make_zoned(locate_zone(time_zone), local_days(2011_y / 01 / 30)
+ 23h + 30min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2011_y / 04 / 30)
+ 23h + 30min + 00s));
+ checkNext("0 30 23 30 1/3 ?",
+ make_zoned(locate_zone(time_zone), local_days(2011_y / 04 / 30)
+ 23h + 30min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2011_y / 07 / 30)
+ 23h + 30min + 00s));
+
+ checkNext("0 0 0 LW * ? *",
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 02 / 27)
+ 02h + 00min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 02 / 28)
+ 00h + 00min + 00s));
+ checkNext("0 0 0 LW * ? *",
+ make_zoned(locate_zone(time_zone), local_days(2024_y / 02 / 27)
+ 02h + 00min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2024_y / 02 / 29)
+ 00h + 00min + 00s));
+ checkNext("0 0 0 LW * ? *",
+ make_zoned(locate_zone(time_zone), local_days(2027_y / 02 / 27)
+ 02h + 00min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2027_y / 03 / 31)
+ 00h + 00min + 00s));
+ checkNext("0 0 0 ? * 3#1 *",
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 05 / 04)
+ 00h + 00min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 06 / 07)
+ 00h + 00min + 00s));
+ checkNext("0 0 0 ? * 3#2 *",
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 05 / 04)
+ 00h + 00min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 05 / 10)
+ 00h + 00min + 00s));
+ checkNext("0 0 0 ? * 3#3 *",
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 05 / 04)
+ 00h + 00min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 05 / 17)
+ 00h + 00min + 00s));
+ checkNext("0 0 0 ? * 3#4 *",
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 05 / 04)
+ 00h + 00min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 05 / 24)
+ 00h + 00min + 00s));
+ checkNext("0 0 0 ? * 3#5 *",
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 05 / 04)
+ 00h + 00min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 05 / 31)
+ 00h + 00min + 00s));
+ checkNext("0 0 0 L * ? *",
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 01 / 04)
+ 00h + 00min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 01 / 31)
+ 00h + 00min + 00s));
+ checkNext("0 0 0 L * ? *",
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 02 / 04)
+ 00h + 00min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 02 / 28)
+ 00h + 00min + 00s));
+ checkNext("0 0 0 L * ? *",
+ make_zoned(locate_zone(time_zone), local_days(2024_y / 02 / 04)
+ 00h + 00min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2024_y / 02 / 29)
+ 00h + 00min + 00s));
+ checkNext("0 0 0 L * ? *",
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 03 / 04)
+ 00h + 00min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 03 / 31)
+ 00h + 00min + 00s));
+ checkNext("0 0 0 L * ? *",
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 04 / 04)
+ 00h + 00min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 04 / 30)
+ 00h + 00min + 00s));
+ checkNext("0 0 0 L * ? *",
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 05 / 31)
+ 00h + 00min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 06 / 30)
+ 00h + 00min + 00s));
+ checkNext("0 0 0 ? * L *",
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 01 / 07)
+ 00h + 00min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 01 / 8) +
00h + 00min + 00s));
+ checkNext("0 0 0 ? * L *",
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 02 / 04)
+ 00h + 00min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 02 / 05)
+ 00h + 00min + 00s));
+ checkNext("0 0 0 ? * L *",
+ make_zoned(locate_zone(time_zone), local_days(2024_y / 02 / 04)
+ 00h + 00min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2024_y / 02 / 10)
+ 00h + 00min + 00s));
+ checkNext("0 0 0 ? * L *",
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 03 / 04)
+ 00h + 00min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 03 / 05)
+ 00h + 00min + 00s));
+ checkNext("0 0 0 ? * L *",
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 04 / 04)
+ 00h + 00min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 04 / 9) +
00h + 00min + 00s));
+ checkNext("0 0 0 ? * L *",
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 05 / 28)
+ 00h + 00min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 06 / 04)
+ 00h + 00min + 00s));
+ checkNext("0 0 0 1W * ? *",
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 05 / 01)
+ 00h + 00min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 05 / 02)
+ 00h + 00min + 00s));
+ checkNext("0 0 0 4W * ? *",
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 05 / 01)
+ 00h + 00min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 05 / 04)
+ 00h + 00min + 00s));
+ checkNext("0 0 0 14W * ? *",
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 05 / 01)
+ 00h + 00min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 05 / 13)
+ 00h + 00min + 00s));
+ checkNext("0 0 0 15W * ? *",
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 05 / 01)
+ 00h + 00min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 05 / 16)
+ 00h + 00min + 00s));
+ checkNext("0 0 0 31W * ? *",
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 02 / 01)
+ 00h + 00min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 03 / 31)
+ 00h + 00min + 00s));
+ checkNext("0 0 0 1W * ? *",
+ make_zoned(locate_zone(time_zone), local_days(2021_y / 12 / 15)
+ 00h + 00min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 01 / 03)
+ 00h + 00min + 00s));
+ checkNext("0 0 0 31W * ? *",
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 07 / 15)
+ 00h + 00min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 07 / 29)
+ 00h + 00min + 00s));
+
+ checkNext("0 15 10 ? * 6L",
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 07 / 15)
+ 00h + 00min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 07 / 29)
+ 10h + 15min + 00s));
+
+ checkNext("0 0 0 L-3 * ?",
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 01 / 10)
+ 00h + 00min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 01 / 28)
+ 00h + 00min + 00s));
+
+ checkNext("0 0 0 L-30 * ?",
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 01 / 10)
+ 00h + 00min + 00s),
+ make_zoned(locate_zone(time_zone), local_days(2022_y / 03 / 01)
+ 00h + 00min + 00s));
+ }
+}
diff --git a/libminifi/test/unit/SchedulingAgentTests.cpp
b/libminifi/test/unit/SchedulingAgentTests.cpp
new file mode 100644
index 000000000..c3e03a356
--- /dev/null
+++ b/libminifi/test/unit/SchedulingAgentTests.cpp
@@ -0,0 +1,140 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "../Catch.h"
+#include "../TestBase.h"
+#include "ProvenanceTestHelper.h"
+#include "utils/TestUtils.h"
+
+using namespace std::literals::chrono_literals;
+
+namespace org::apache::nifi::minifi::testing {
+
+class CountOnTriggersProcessor : public minifi::core::Processor {
+ public:
+ using minifi::core::Processor::Processor;
+
+ void onTrigger(core::ProcessContext*, core::ProcessSession*) override {
+ ++number_of_triggers;
+ }
+
+ size_t getNumberOfTriggers() const { return number_of_triggers; }
+
+ private:
+ std::atomic<size_t> number_of_triggers = 0;
+};
+
+
+TEST_CASE("SchedulingAgentTests", "[SchedulingAgent]") {
+ std::shared_ptr<core::Repository> test_repo =
std::make_shared<TestRepository>();
+ std::shared_ptr<core::ContentRepository> content_repo =
std::make_shared<core::repository::VolatileContentRepository>();
+ std::shared_ptr<TestRepository> repo =
std::static_pointer_cast<TestRepository>(test_repo);
+ std::shared_ptr<minifi::FlowController> controller =
+ std::make_shared<TestFlowController>(test_repo, test_repo, content_repo);
+
+ TestController testController;
+ auto test_plan = testController.createPlan();
+ auto controller_services_ =
std::make_shared<minifi::core::controller::ControllerServiceMap>();
+ auto configuration = std::make_shared<minifi::Configure>();
+ auto controller_services_provider_ =
std::make_shared<minifi::core::controller::StandardControllerServiceProvider>(controller_services_,
nullptr, configuration);
+ utils::ThreadPool<utils::TaskRescheduleInfo> thread_pool;
+ auto count_proc = std::make_shared<CountOnTriggersProcessor>("count_proc");
+ count_proc->incrementActiveTasks();
+ count_proc->setScheduledState(core::RUNNING);
+ auto node = std::make_shared<core::ProcessorNode>(count_proc.get());
+ auto context = std::make_shared<core::ProcessContext>(node, nullptr, repo,
repo, content_repo);
+ std::shared_ptr<core::ProcessSessionFactory> factory =
std::make_shared<core::ProcessSessionFactory>(context);
+ count_proc->setSchedulingPeriodNano(1250ms);
+#ifdef WIN32
+ utils::dateSetInstall(TZ_DATA_DIR);
+#endif
+
+ SECTION("Timer Driven") {
+ auto timer_driven_agent =
std::make_shared<TimerDrivenSchedulingAgent>(gsl::make_not_null(controller_services_provider_.get()),
test_repo, test_repo, content_repo, configuration, thread_pool);
+ timer_driven_agent->start();
+ auto first_task_reschedule_info =
timer_driven_agent->run(count_proc.get(), context, factory);
+ CHECK(!first_task_reschedule_info.finished_);
+ CHECK(first_task_reschedule_info.wait_time_ == 1250ms);
+ CHECK(count_proc->getNumberOfTriggers() == 1);
+
+ auto second_task_reschedule_info =
timer_driven_agent->run(count_proc.get(), context, factory);
+
+ CHECK(!second_task_reschedule_info.finished_);
+ CHECK(second_task_reschedule_info.wait_time_ == 1250ms);
+ CHECK(count_proc->getNumberOfTriggers() == 2);
+ }
+
+ SECTION("Event Driven") {
+ auto event_driven_agent =
std::make_shared<EventDrivenSchedulingAgent>(gsl::make_not_null(controller_services_provider_.get()),
test_repo, test_repo, content_repo, configuration, thread_pool);
+ event_driven_agent->start();
+ auto first_task_reschedule_info =
event_driven_agent->run(count_proc.get(), context, factory);
+ CHECK(!first_task_reschedule_info.finished_);
+ CHECK(first_task_reschedule_info.wait_time_ == 0ms);
+ auto count_num_after_one_schedule = count_proc->getNumberOfTriggers();
+ CHECK(count_num_after_one_schedule > 100);
+
+ auto second_task_reschedule_info =
event_driven_agent->run(count_proc.get(), context, factory);
+ CHECK(!second_task_reschedule_info.finished_);
+ CHECK(second_task_reschedule_info.wait_time_ == 0ms);
+ auto count_num_after_two_schedule = count_proc->getNumberOfTriggers();
+ CHECK(count_num_after_two_schedule > count_num_after_one_schedule+100);
+ }
+
+ SECTION("Cron Driven every year") {
+ count_proc->setCronPeriod("0 0 0 1 1 ?");
+ auto cron_driven_agent =
std::make_shared<CronDrivenSchedulingAgent>(gsl::make_not_null(controller_services_provider_.get()),
test_repo, test_repo, content_repo, configuration, thread_pool);
+ cron_driven_agent->start();
+ auto first_task_reschedule_info = cron_driven_agent->run(count_proc.get(),
context, factory);
+ CHECK(!first_task_reschedule_info.finished_);
+ if (first_task_reschedule_info.wait_time_ > 1min) { // To avoid possibly
failing around dec 31 23:59:59
+ auto next_run_time_point =
std::chrono::round<std::chrono::years>(std::chrono::system_clock::now() +
first_task_reschedule_info.wait_time_);
+ CHECK(next_run_time_point ==
std::chrono::ceil<std::chrono::years>(std::chrono::system_clock::now()));
+ CHECK(count_proc->getNumberOfTriggers() == 0);
+
+ auto second_task_reschedule_info =
cron_driven_agent->run(count_proc.get(), context, factory);
+ CHECK(!second_task_reschedule_info.finished_);
+ next_run_time_point =
std::chrono::round<std::chrono::years>(std::chrono::system_clock::now() +
first_task_reschedule_info.wait_time_);
+ CHECK(next_run_time_point ==
std::chrono::ceil<std::chrono::years>(std::chrono::system_clock::now()));
+ CHECK(count_proc->getNumberOfTriggers() == 0);
+ }
+ }
+
+ SECTION("Cron Driven every sec") {
+ count_proc->setCronPeriod("* * * * * *");
+ auto cron_driven_agent =
std::make_shared<CronDrivenSchedulingAgent>(gsl::make_not_null(controller_services_provider_.get()),
test_repo, test_repo, content_repo, configuration, thread_pool);
+ cron_driven_agent->start();
+ auto first_task_reschedule_info = cron_driven_agent->run(count_proc.get(),
context, factory);
+ CHECK(!first_task_reschedule_info.finished_);
+ CHECK(first_task_reschedule_info.wait_time_ <= 1s);
+ CHECK(count_proc->getNumberOfTriggers() == 0);
+
+ std::this_thread::sleep_for(first_task_reschedule_info.wait_time_ + 1ms);
+ auto second_task_reschedule_info =
cron_driven_agent->run(count_proc.get(), context, factory);
+ CHECK(!second_task_reschedule_info.finished_);
+ CHECK(second_task_reschedule_info.wait_time_ <= 1s);
+ CHECK(count_proc->getNumberOfTriggers() == 1);
+ }
+
+ SECTION("Cron Driven no future triggers") {
+ count_proc->setCronPeriod("* * * * * * 2012");
+ auto cron_driven_agent =
std::make_shared<CronDrivenSchedulingAgent>(gsl::make_not_null(controller_services_provider_.get()),
test_repo, test_repo, content_repo, configuration, thread_pool);
+ cron_driven_agent->start();
+ auto first_task_reschedule_info = cron_driven_agent->run(count_proc.get(),
context, factory);
+ CHECK(first_task_reschedule_info.finished_);
+ }
+}
+} // namespace org::apache::nifi::minifi::testing
diff --git a/libminifi/test/unit/TimeUtilTests.cpp
b/libminifi/test/unit/TimeUtilTests.cpp
index 574cc837c..0a96e5134 100644
--- a/libminifi/test/unit/TimeUtilTests.cpp
+++ b/libminifi/test/unit/TimeUtilTests.cpp
@@ -150,3 +150,94 @@ TEST_CASE("Test string to duration conversion",
"[timedurationtests]") {
REQUIRE_FALSE(StringToDuration<std::chrono::seconds>("5 apples") == 1s);
REQUIRE_FALSE(StringToDuration<std::chrono::seconds>("1 year") == 1s);
}
+
+namespace {
+date::local_time<std::chrono::seconds> parseLocalTimePoint(const std::string&
str) {
+ date::local_time<std::chrono::seconds> tp;
+ std::stringstream stream(str);
+ date::from_stream(stream, "%Y-%m-%d %T", tp);
+ return tp;
+}
+} // namespace
+
+TEST_CASE("Test roundToNextYear", "[roundingTests]") {
+ using org::apache::nifi::minifi::utils::timeutils::roundToNextYear;
+
+ CHECK(parseLocalTimePoint("2022-01-01 00:00:00") ==
roundToNextYear(parseLocalTimePoint("2021-12-21 08:20:53")));
+ CHECK(parseLocalTimePoint("2023-01-01 00:00:00") ==
roundToNextYear(parseLocalTimePoint("2022-01-01 13:59:59")));
+ CHECK(parseLocalTimePoint("1974-01-01 00:00:00") ==
roundToNextYear(parseLocalTimePoint("1973-02-01 00:00:01")));
+ CHECK(parseLocalTimePoint("1971-01-01 00:00:00") ==
roundToNextYear(parseLocalTimePoint("1970-11-03 23:59:59")));
+ CHECK(parseLocalTimePoint("2023-01-01 00:00:00") ==
roundToNextYear(parseLocalTimePoint("2022-12-03 15:22:22")));
+ CHECK(parseLocalTimePoint("2254-01-01 00:00:00") ==
roundToNextYear(parseLocalTimePoint("2253-05-01 23:59:59")));
+ CHECK(parseLocalTimePoint("1951-01-01 00:00:00") ==
roundToNextYear(parseLocalTimePoint("1950-11-03 23:59:59")));
+}
+
+TEST_CASE("Test roundToNextMonth", "[roundingTests]") {
+ using org::apache::nifi::minifi::utils::timeutils::roundToNextMonth;
+
+ CHECK(parseLocalTimePoint("2022-01-01 00:00:00") ==
roundToNextMonth(parseLocalTimePoint("2021-12-21 01:00:00")));
+ CHECK(parseLocalTimePoint("2022-02-01 00:00:00") ==
roundToNextMonth(parseLocalTimePoint("2022-01-31 02:00:00")));
+ CHECK(parseLocalTimePoint("2022-03-01 00:00:00") ==
roundToNextMonth(parseLocalTimePoint("2022-02-01 12:00:00")));
+ CHECK(parseLocalTimePoint("2022-12-01 00:00:00") ==
roundToNextMonth(parseLocalTimePoint("2022-11-30 00:00:00")));
+ CHECK(parseLocalTimePoint("2023-01-01 00:00:00") ==
roundToNextMonth(parseLocalTimePoint("2022-12-31 00:00:00")));
+ CHECK(parseLocalTimePoint("2022-06-01 00:00:00") ==
roundToNextMonth(parseLocalTimePoint("2022-05-12 23:00:58")));
+ CHECK(parseLocalTimePoint("2022-07-01 00:00:00") ==
roundToNextMonth(parseLocalTimePoint("2022-06-21 11:00:00")));
+ CHECK(parseLocalTimePoint("2022-08-01 00:00:00") ==
roundToNextMonth(parseLocalTimePoint("2022-07-21 12:12:00")));
+ CHECK(parseLocalTimePoint("2022-09-01 00:00:00") ==
roundToNextMonth(parseLocalTimePoint("2022-08-31 06:00:00")));
+}
+
+TEST_CASE("Test roundToNextDay", "[roundingTests]") {
+ using org::apache::nifi::minifi::utils::timeutils::roundToNextDay;
+
+ CHECK(parseLocalTimePoint("2021-02-01 00:00:00") ==
roundToNextDay(parseLocalTimePoint("2021-01-31 01:00:00")));
+ CHECK(parseLocalTimePoint("2022-03-01 00:00:00") ==
roundToNextDay(parseLocalTimePoint("2022-02-28 02:00:00")));
+ CHECK(parseLocalTimePoint("2024-02-29 00:00:00") ==
roundToNextDay(parseLocalTimePoint("2024-02-28 02:00:00")));
+ CHECK(parseLocalTimePoint("2023-01-01 00:00:00") ==
roundToNextDay(parseLocalTimePoint("2022-12-31 12:00:00")));
+ CHECK(parseLocalTimePoint("2022-07-11 00:00:00") ==
roundToNextDay(parseLocalTimePoint("2022-07-10 00:00:00")));
+ CHECK(parseLocalTimePoint("2023-01-01 00:00:00") ==
roundToNextDay(parseLocalTimePoint("2022-12-31 00:00:00")));
+ CHECK(parseLocalTimePoint("2022-05-13 00:00:00") ==
roundToNextDay(parseLocalTimePoint("2022-05-12 23:00:58")));
+ CHECK(parseLocalTimePoint("2022-06-22 00:00:00") ==
roundToNextDay(parseLocalTimePoint("2022-06-21 11:00:00")));
+ CHECK(parseLocalTimePoint("2022-07-22 00:00:00") ==
roundToNextDay(parseLocalTimePoint("2022-07-21 12:12:00")));
+}
+
+TEST_CASE("Test roundToNextHour", "[roundingTests]") {
+ using org::apache::nifi::minifi::utils::timeutils::roundToNextHour;
+
+ CHECK(parseLocalTimePoint("2021-01-31 02:00:00") ==
roundToNextHour(parseLocalTimePoint("2021-01-31 01:00:00")));
+ CHECK(parseLocalTimePoint("2022-03-01 00:00:00") ==
roundToNextHour(parseLocalTimePoint("2022-02-28 23:00:00")));
+ CHECK(parseLocalTimePoint("2024-02-29 00:00:00") ==
roundToNextHour(parseLocalTimePoint("2024-02-28 23:00:00")));
+ CHECK(parseLocalTimePoint("2022-12-31 13:00:00") ==
roundToNextHour(parseLocalTimePoint("2022-12-31 12:00:00")));
+ CHECK(parseLocalTimePoint("2022-07-10 01:00:00") ==
roundToNextHour(parseLocalTimePoint("2022-07-10 00:00:00")));
+ CHECK(parseLocalTimePoint("2022-12-31 01:00:00") ==
roundToNextHour(parseLocalTimePoint("2022-12-31 00:00:00")));
+ CHECK(parseLocalTimePoint("2022-05-13 00:00:00") ==
roundToNextHour(parseLocalTimePoint("2022-05-12 23:00:58")));
+ CHECK(parseLocalTimePoint("2022-06-21 12:00:00") ==
roundToNextHour(parseLocalTimePoint("2022-06-21 11:00:00")));
+ CHECK(parseLocalTimePoint("2022-07-21 13:00:00") ==
roundToNextHour(parseLocalTimePoint("2022-07-21 12:12:00")));
+}
+
+TEST_CASE("Test roundToNextMinute", "[roundingTests]") {
+ using org::apache::nifi::minifi::utils::timeutils::roundToNextMinute;
+
+ CHECK(parseLocalTimePoint("2021-01-31 01:24:00") ==
roundToNextMinute(parseLocalTimePoint("2021-01-31 01:23:00")));
+ CHECK(parseLocalTimePoint("2022-03-01 00:00:00") ==
roundToNextMinute(parseLocalTimePoint("2022-02-28 23:59:59")));
+ CHECK(parseLocalTimePoint("2024-02-28 23:01:00") ==
roundToNextMinute(parseLocalTimePoint("2024-02-28 23:00:00")));
+ CHECK(parseLocalTimePoint("2022-12-31 12:01:00") ==
roundToNextMinute(parseLocalTimePoint("2022-12-31 12:00:00")));
+ CHECK(parseLocalTimePoint("2022-07-10 00:01:00") ==
roundToNextMinute(parseLocalTimePoint("2022-07-10 00:00:00")));
+ CHECK(parseLocalTimePoint("2022-12-31 00:01:00") ==
roundToNextMinute(parseLocalTimePoint("2022-12-31 00:00:00")));
+ CHECK(parseLocalTimePoint("2022-05-12 23:01:00") ==
roundToNextMinute(parseLocalTimePoint("2022-05-12 23:00:58")));
+ CHECK(parseLocalTimePoint("2022-06-21 11:01:00") ==
roundToNextMinute(parseLocalTimePoint("2022-06-21 11:00:00")));
+ CHECK(parseLocalTimePoint("2022-07-21 12:13:00") ==
roundToNextMinute(parseLocalTimePoint("2022-07-21 12:12:00")));
+}
+
+TEST_CASE("Test roundToNextSecond", "[roundingTests]") {
+ using org::apache::nifi::minifi::utils::timeutils::roundToNextSecond;
+
+ CHECK(parseLocalTimePoint("2021-01-31 01:23:01") ==
roundToNextSecond(parseLocalTimePoint("2021-01-31 01:23:00")));
+ CHECK(parseLocalTimePoint("2022-03-01 00:00:00") ==
roundToNextSecond(parseLocalTimePoint("2022-02-28 23:59:59")));
+ CHECK(parseLocalTimePoint("2024-02-28 23:00:01") ==
roundToNextSecond(parseLocalTimePoint("2024-02-28 23:00:00")));
+ CHECK(parseLocalTimePoint("2022-12-31 12:00:01") ==
roundToNextSecond(parseLocalTimePoint("2022-12-31 12:00:00")));
+ CHECK(parseLocalTimePoint("2022-07-10 00:00:01") ==
roundToNextSecond(parseLocalTimePoint("2022-07-10 00:00:00")));
+ CHECK(parseLocalTimePoint("2022-12-31 00:00:01") ==
roundToNextSecond(parseLocalTimePoint("2022-12-31 00:00:00")));
+ CHECK(parseLocalTimePoint("2022-05-12 23:00:59") ==
roundToNextSecond(parseLocalTimePoint("2022-05-12 23:00:58")));
+ CHECK(parseLocalTimePoint("2022-06-21 11:00:01") ==
roundToNextSecond(parseLocalTimePoint("2022-06-21 11:00:00")));
+ CHECK(parseLocalTimePoint("2022-07-21 12:12:01") ==
roundToNextSecond(parseLocalTimePoint("2022-07-21 12:12:00")));
+}
diff --git a/thirdparty/cron/Cron.h b/thirdparty/cron/Cron.h
deleted file mode 100644
index 6992d47e2..000000000
--- a/thirdparty/cron/Cron.h
+++ /dev/null
@@ -1,121 +0,0 @@
-#include <chrono>
-#include <string>
-#include <sstream>
-#include <vector>
-#include <iterator>
-
-namespace Bosma {
- using Clock = std::chrono::system_clock;
-
- inline void add(std::tm &tm, Clock::duration time) {
- auto tp = Clock::from_time_t(std::mktime(&tm));
- auto tp_adjusted = tp + time;
- auto tm_adjusted = Clock::to_time_t(tp_adjusted);
- tm = *std::localtime(&tm_adjusted);
- }
-
- class BadCronExpression : public std::exception {
- public:
- explicit BadCronExpression(std::string msg) : msg_(std::move(msg)) {}
-
- const char *what() const noexcept override { return (msg_.c_str()); }
-
- private:
- std::string msg_;
- };
-
- inline void
- verify_and_set(const std::string &token, const std::string &expression,
int &field, const int lower_bound,
- const int upper_bound, const bool adjust = false) {
- if (token == "*")
- field = -1;
- else {
- try {
- field = std::stoi(token);
- } catch (const std::invalid_argument &) {
- throw BadCronExpression("malformed cron string (`" + token + "` not
an integer or *): " + expression);
- } catch (const std::out_of_range &) {
- throw BadCronExpression("malformed cron string (`" + token + "` not
convertable to int): " + expression);
- }
- if (field < lower_bound || field > upper_bound) {
- std::ostringstream oss;
- oss << "malformed cron string ('" << token << "' must be <= " <<
upper_bound << " and >= " << lower_bound
- << "): " << expression;
- throw BadCronExpression(oss.str());
- }
- if (adjust)
- field--;
- }
- }
-
- class Cron {
- public:
- explicit Cron(const std::string &expression) {
- std::istringstream iss(expression);
- std::vector<std::string>
tokens{std::istream_iterator<std::string>{iss},
-
std::istream_iterator<std::string>{}};
-
- if (tokens.size() != 5) throw BadCronExpression("malformed cron
string (must be 5 fields): " + expression);
-
- verify_and_set(tokens[0], expression, minute, 0, 59);
- verify_and_set(tokens[1], expression, hour, 0, 23);
- verify_and_set(tokens[2], expression, day, 1, 31);
- verify_and_set(tokens[3], expression, month, 1, 12, true);
- verify_and_set(tokens[4], expression, day_of_week, 0, 6);
- }
-
- // http://stackoverflow.com/a/322058/1284550
- Clock::time_point cron_to_next(const Clock::time_point from =
Clock::now()) const {
- // get current time as a tm object
- auto now = Clock::to_time_t(from);
- std::tm next(*std::localtime(&now));
- // it will always at least run the next minute
- next.tm_sec = 0;
- add(next, std::chrono::minutes(1));
- while (true) {
- if (month != -1 && next.tm_mon != month) {
- // add a month
- // if this will bring us over a year, increment the year instead
and reset the month
- if (next.tm_mon + 1 > 11) {
- next.tm_mon = 0;
- next.tm_year++;
- } else
- next.tm_mon++;
-
- next.tm_mday = 1;
- next.tm_hour = 0;
- next.tm_min = 0;
- continue;
- }
- if (day != -1 && next.tm_mday != day) {
- add(next, std::chrono::hours(24));
- next.tm_hour = 0;
- next.tm_min = 0;
- continue;
- }
- if (day_of_week != -1 && next.tm_wday != day_of_week) {
- add(next, std::chrono::hours(24));
- next.tm_hour = 0;
- next.tm_min = 0;
- continue;
- }
- if (hour != -1 && next.tm_hour != hour) {
- add(next, std::chrono::hours(1));
- next.tm_min = 0;
- continue;
- }
- if (minute != -1 && next.tm_min != minute) {
- add(next, std::chrono::minutes(1));
- continue;
- }
- break;
- }
-
- // telling mktime to figure out dst
- next.tm_isdst = -1;
- return Clock::from_time_t(std::mktime(&next));
- }
-
- int minute, hour, day, month, day_of_week;
- };
-}
diff --git a/thirdparty/cron/LICENSE.TXT b/thirdparty/cron/LICENSE.TXT
deleted file mode 100644
index 8e86f896d..000000000
--- a/thirdparty/cron/LICENSE.TXT
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2017 Bosma
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
\ No newline at end of file