You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by fg...@apache.org on 2022/06/27 11:06:33 UTC

[nifi-minifi-cpp] 01/02: MINIFICPP-1842 getTimeStr should use std::chrono

This is an automated email from the ASF dual-hosted git repository.

fgerlits pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi-minifi-cpp.git

commit 58a3041fa0efc635b5f6a8fedafda55f3448e676
Author: Martin Zink <ma...@apache.org>
AuthorDate: Thu May 19 09:37:42 2022 +0200

    MINIFICPP-1842 getTimeStr should use std::chrono
    
    Also fixes MINIFICPP-1700 Change leftover integers to std::chrono in ListSFTP.
    
    Signed-off-by: Ferenc Gerlits <fg...@gmail.com>
    This closes #1352
---
 extensions/sftp/processors/ListSFTP.cpp            |  85 ++++++------
 extensions/sftp/processors/ListSFTP.h              |   6 +-
 extensions/sftp/processors/PutSFTP.cpp             |  19 ++-
 extensions/sftp/tests/ListSFTPTests.cpp            |   3 +-
 .../processors/LogAttribute.cpp                    |   4 +-
 libminifi/include/core/Property.h                  |  10 --
 libminifi/include/utils/TimeUtil.h                 | 144 +++++----------------
 libminifi/include/utils/tls/CertificateUtils.h     |  14 +-
 libminifi/src/controllers/SSLContextService.cpp    |   4 +-
 libminifi/src/utils/tls/CertificateUtils.cpp       |  16 +--
 libminifi/test/unit/PropertyTests.cpp              |  33 +----
 libminifi/test/unit/TimeUtilTests.cpp              | 133 ++++++++++---------
 12 files changed, 167 insertions(+), 304 deletions(-)

diff --git a/extensions/sftp/processors/ListSFTP.cpp b/extensions/sftp/processors/ListSFTP.cpp
index 573eeb50f..83b67b2bd 100644
--- a/extensions/sftp/processors/ListSFTP.cpp
+++ b/extensions/sftp/processors/ListSFTP.cpp
@@ -63,6 +63,21 @@ constexpr char const* ListSFTP::TARGET_SYSTEM_TIMESTAMP_PRECISION_MINUTES;
 constexpr char const* ListSFTP::ENTITY_TRACKING_INITIAL_LISTING_TARGET_TRACKING_TIME_WINDOW;
 constexpr char const* ListSFTP::ENTITY_TRACKING_INITIAL_LISTING_TARGET_ALL_AVAILABLE;
 
+namespace {
+uint64_t toUnixTime(const std::optional<std::chrono::system_clock::time_point> time_point) {
+  if (!time_point)
+    return 0;
+  return std::chrono::duration_cast<std::chrono::milliseconds>(time_point->time_since_epoch()).count();
+}
+
+std::optional<std::chrono::system_clock::time_point> fromUnixTime(const uint64_t timestamp) {
+  if (timestamp == 0)
+    return std::nullopt;
+  return std::chrono::system_clock::time_point{std::chrono::milliseconds{timestamp}};
+}
+
+}  // namespace
+
 void ListSFTP::initialize() {
   logger_->log_trace("Initializing FetchSFTP");
 
@@ -82,8 +97,6 @@ ListSFTP::ListSFTP(const std::string& name, const utils::Identifier& uuid /*= ut
     , minimum_file_size_(0U)
     , maximum_file_size_(0U)
     , already_loaded_from_cache_(false)
-    , last_listed_latest_entry_timestamp_(0U)
-    , last_processed_latest_entry_timestamp_(0U)
     , initial_listing_complete_(false) {
   logger_ = core::logging::LoggerFactory<ListSFTP>::getLogger();
 }
@@ -174,8 +187,8 @@ void ListSFTP::invalidateCache() {
   already_loaded_from_cache_ = false;
 
   last_run_time_ = std::chrono::steady_clock::time_point();
-  last_listed_latest_entry_timestamp_ = 0U;
-  last_processed_latest_entry_timestamp_ = 0U;
+  last_listed_latest_entry_timestamp_.reset();
+  last_processed_latest_entry_timestamp_.reset();
   latest_identifiers_processed_.clear();
 
   initial_listing_complete_ = false;
@@ -323,11 +336,7 @@ bool ListSFTP::createAndTransferFlowFileFromChild(
     logger_->log_error("Modification date %lu of \"%s/%s\" larger than int64_t max", child.attrs.mtime, child.parent_path.c_str(), child.filename.c_str());
     return true;
   }
-  std::string mtime_str;
-  if (!utils::timeutils::getDateTimeStr(gsl::narrow<int64_t>(child.attrs.mtime), mtime_str)) {
-    logger_->log_error("Failed to convert modification date %lu of \"%s/%s\" to string", child.attrs.mtime, child.parent_path.c_str(), child.filename.c_str());
-    return true;
-  }
+  auto mtime_str = utils::timeutils::getDateTimeStr(date::sys_seconds{std::chrono::seconds(child.attrs.mtime)});
 
   /* Create FlowFile */
   auto flow_file = session->create();
@@ -380,8 +389,8 @@ bool ListSFTP::persistTrackingTimestampsCache(const std::shared_ptr<core::Proces
   state["hostname"] = hostname;
   state["username"] = username;
   state["remote_path"] = remote_path;
-  state["listing.timestamp"] = std::to_string(last_listed_latest_entry_timestamp_);
-  state["processed.timestamp"] = std::to_string(last_processed_latest_entry_timestamp_);
+  state["listing.timestamp"] = std::to_string(toUnixTime(last_listed_latest_entry_timestamp_));
+  state["processed.timestamp"] = std::to_string(toUnixTime(last_processed_latest_entry_timestamp_));
   size_t i = 0;
   for (const auto& identifier : latest_identifiers_processed_) {
     state["id." + std::to_string(i)] = identifier;
@@ -464,8 +473,8 @@ bool ListSFTP::updateFromTrackingTimestampsCache(const std::shared_ptr<core::Pro
     return false;
   }
 
-  last_listed_latest_entry_timestamp_ = state_listing_timestamp;
-  last_processed_latest_entry_timestamp_ = state_processed_timestamp;
+  last_listed_latest_entry_timestamp_ = fromUnixTime(state_listing_timestamp);
+  last_processed_latest_entry_timestamp_ = fromUnixTime(state_processed_timestamp);
   latest_identifiers_processed_ = std::move(state_ids);
 
   return true;
@@ -479,7 +488,7 @@ void ListSFTP::listByTrackingTimestamps(
     const std::string& username,
     const std::string& remote_path,
     std::vector<Child>&& files) {
-  uint64_t min_timestamp_to_list = last_listed_latest_entry_timestamp_;
+  auto min_timestamp_to_list = last_listed_latest_entry_timestamp_;
 
   /* Load state from cache file if needed */
   if (!already_loaded_from_cache_) {
@@ -492,16 +501,16 @@ void ListSFTP::listByTrackingTimestamps(
   }
 
   std::chrono::steady_clock::time_point current_run_time = std::chrono::steady_clock::now();
-  time_t now = time(nullptr);
+  auto now = std::chrono::system_clock::now();
 
   /* Order children by timestamp and try to detect timestamp precision if needed  */
-  std::map<uint64_t /*timestamp*/, std::list<Child>> ordered_files;
+  std::map<std::chrono::system_clock::time_point, std::list<Child>> ordered_files;
   bool target_system_has_seconds = false;
   for (auto&& file : files) {
-    uint64_t timestamp = file.attrs.mtime * 1000;
-    target_system_has_seconds |= timestamp % 60000 != 0;
+    std::chrono::system_clock::time_point timestamp{std::chrono::seconds(file.attrs.mtime)};
+    target_system_has_seconds |= std::chrono::round<std::chrono::minutes>(timestamp) != timestamp;
 
-    bool new_file = min_timestamp_to_list == 0U || (timestamp >= min_timestamp_to_list && timestamp >= last_processed_latest_entry_timestamp_);
+    bool new_file = !min_timestamp_to_list.has_value() || (timestamp >= min_timestamp_to_list && timestamp >= last_processed_latest_entry_timestamp_);
     if (new_file) {
       auto& files_for_timestamp = ordered_files[timestamp];
       files_for_timestamp.emplace_back(std::move(file));
@@ -510,9 +519,9 @@ void ListSFTP::listByTrackingTimestamps(
     }
   }
 
-  uint64_t latest_listed_entry_timestamp_this_cycle = 0U;
+  std::optional<std::chrono::system_clock::time_point> latest_listed_entry_timestamp_this_cycle;
   size_t flow_files_created = 0U;
-  if (ordered_files.size() > 0) {
+  if (!ordered_files.empty()) {
     latest_listed_entry_timestamp_this_cycle = ordered_files.crbegin()->first;
 
     std::string remote_system_timestamp_precision;
@@ -533,20 +542,20 @@ void ListSFTP::listByTrackingTimestamps(
        */
       remote_system_timestamp_precision = TARGET_SYSTEM_TIMESTAMP_PRECISION_SECONDS;
     }
-    uint64_t listing_lag = LISTING_LAG_MAP.at(remote_system_timestamp_precision);
-    logger_->log_debug("The listing lag is %lu ms", listing_lag);
+    std::chrono::milliseconds listing_lag{LISTING_LAG_MAP.at(remote_system_timestamp_precision)};
+    logger_->log_debug("The listing lag is %lu ms", listing_lag.count());
 
     /* If the latest listing time is equal to the last listing time, there are no entries with a newer timestamp than previously seen */
-    if (latest_listed_entry_timestamp_this_cycle == last_listed_latest_entry_timestamp_) {
-      const auto& latest_files = ordered_files.at(latest_listed_entry_timestamp_this_cycle);
-      uint64_t elapsed_time = std::chrono::duration_cast<std::chrono::milliseconds>(current_run_time - last_run_time_).count();
+    if (latest_listed_entry_timestamp_this_cycle == last_listed_latest_entry_timestamp_ && latest_listed_entry_timestamp_this_cycle) {
+      const auto& latest_files = ordered_files.at(*latest_listed_entry_timestamp_this_cycle);
+      auto elapsed_time = std::chrono::duration_cast<std::chrono::milliseconds>(current_run_time - last_run_time_);
       /* If a precision-specific listing lag has not yet elapsed since out last execution, we wait. */
       if (elapsed_time < listing_lag) {
         logger_->log_debug("The latest listed entry timestamp is the same as the last listed entry timestamp (%lu) "
                            "and the listing lag has not yet elapsed (%lu ms < % lu ms). Yielding.",
-                           latest_listed_entry_timestamp_this_cycle,
-                           elapsed_time,
-                           listing_lag);
+                           toUnixTime(latest_listed_entry_timestamp_this_cycle),
+                           elapsed_time.count(),
+                           listing_lag.count());
         context->yield();
         return;
       }
@@ -559,24 +568,24 @@ void ListSFTP::listByTrackingTimestamps(
             return latest_identifiers_processed_.count(child.getPath()) == 1U;
           })) {
         logger_->log_debug("The latest listed entry timestamp is the same as the last listed entry timestamp (%lu) "
-                           "and all files for that timestamp has been processed. Yielding.", latest_listed_entry_timestamp_this_cycle);
+                           "and all files for that timestamp has been processed. Yielding.", toUnixTime(latest_listed_entry_timestamp_this_cycle));
         context->yield();
         return;
       }
     } else {
       /* Determine the minimum reliable timestamp based on precision */
-      uint64_t minimum_reliable_timestamp = now * 1000 - listing_lag;
+      auto minimum_reliable_timestamp = now - listing_lag;
       if (remote_system_timestamp_precision == TARGET_SYSTEM_TIMESTAMP_PRECISION_SECONDS) {
-        minimum_reliable_timestamp -= minimum_reliable_timestamp % 1000;
+        std::chrono::floor<std::chrono::seconds>(minimum_reliable_timestamp);
       } else {
-        minimum_reliable_timestamp -= minimum_reliable_timestamp % 60000;
+        std::chrono::floor<std::chrono::minutes>(minimum_reliable_timestamp);
       }
       /* If the latest timestamp is not old enough, we wait another cycle */
-      if (minimum_reliable_timestamp < latest_listed_entry_timestamp_this_cycle) {
+      if (latest_listed_entry_timestamp_this_cycle && minimum_reliable_timestamp < latest_listed_entry_timestamp_this_cycle) {
         logger_->log_debug("Skipping files with latest timestamp because their modification date is not smaller than the minimum reliable timestamp: %lu ms >= %lu ms",
-            latest_listed_entry_timestamp_this_cycle,
-            minimum_reliable_timestamp);
-        ordered_files.erase(latest_listed_entry_timestamp_this_cycle);
+                           toUnixTime(latest_listed_entry_timestamp_this_cycle),
+                           toUnixTime(minimum_reliable_timestamp));
+        ordered_files.erase(*latest_listed_entry_timestamp_this_cycle);
       }
     }
 
@@ -605,7 +614,7 @@ void ListSFTP::listByTrackingTimestamps(
   }
 
   /* If we have a listing timestamp, it is worth persisting the state */
-  if (latest_listed_entry_timestamp_this_cycle != 0U) {
+  if (latest_listed_entry_timestamp_this_cycle) {
     bool processed_new_files = flow_files_created > 0U;
     if (processed_new_files) {
       auto last_files_it = ordered_files.crbegin();
diff --git a/extensions/sftp/processors/ListSFTP.h b/extensions/sftp/processors/ListSFTP.h
index 16d98193f..871aea98a 100644
--- a/extensions/sftp/processors/ListSFTP.h
+++ b/extensions/sftp/processors/ListSFTP.h
@@ -144,7 +144,7 @@ class ListSFTP : public SFTPProcessorBase {
   struct Child {
     Child();
     Child(const std::string& parent_path_, std::tuple<std::string /* filename */, std::string /* longentry */, LIBSSH2_SFTP_ATTRIBUTES /* attrs */>&& sftp_child);
-    std::string getPath() const;
+    [[nodiscard]] std::string getPath() const;
 
     bool directory;
     std::string parent_path;
@@ -155,8 +155,8 @@ class ListSFTP : public SFTPProcessorBase {
   bool already_loaded_from_cache_;
 
   std::chrono::steady_clock::time_point last_run_time_;
-  uint64_t last_listed_latest_entry_timestamp_;
-  uint64_t last_processed_latest_entry_timestamp_;
+  std::optional<std::chrono::system_clock::time_point> last_listed_latest_entry_timestamp_;
+  std::optional<std::chrono::system_clock::time_point> last_processed_latest_entry_timestamp_;
   std::set<std::string> latest_identifiers_processed_;
 
   bool initial_listing_complete_;
diff --git a/extensions/sftp/processors/PutSFTP.cpp b/extensions/sftp/processors/PutSFTP.cpp
index 1efe850cb..b167f649e 100644
--- a/extensions/sftp/processors/PutSFTP.cpp
+++ b/extensions/sftp/processors/PutSFTP.cpp
@@ -117,8 +117,7 @@ bool PutSFTP::processOne(const std::shared_ptr<core::ProcessContext> &context, c
   std::string remote_path;
   bool disable_directory_listing = false;
   std::string temp_file_name;
-  bool last_modified_time_set = false;
-  int64_t last_modified_time = 0U;
+  std::optional<std::chrono::system_clock::time_point> last_modified_;
   bool permissions_set = false;
   uint32_t permissions = 0U;
   bool remote_owner_set = false;
@@ -143,11 +142,9 @@ bool PutSFTP::processOne(const std::shared_ptr<core::ProcessContext> &context, c
     disable_directory_listing = utils::StringUtils::toBool(value).value_or(false);
   }
   context->getProperty(TempFilename, temp_file_name, flow_file);
-  if (context->getProperty(LastModifiedTime, value, flow_file)) {
-    if (core::Property::StringToDateTime(value, last_modified_time)) {
-      last_modified_time_set = true;
-    }
-  }
+  if (context->getProperty(LastModifiedTime, value, flow_file))
+    last_modified_ = utils::timeutils::parseDateTimeStr(value);
+
   if (context->getProperty(Permissions, value, flow_file)) {
     if (core::Property::StringToPermissions(value, permissions)) {
       permissions_set = true;
@@ -328,21 +325,21 @@ bool PutSFTP::processOne(const std::shared_ptr<core::ProcessContext> &context, c
   }
 
   /* Set file attributes if needed */
-  if (last_modified_time_set ||
+  if (last_modified_ ||
       permissions_set ||
       remote_owner_set ||
       remote_group_set) {
     utils::SFTPClient::SFTPAttributes attrs;
     attrs.flags = 0U;
-    if (last_modified_time_set) {
+    if (last_modified_) {
       /*
        * NiFi doesn't set atime, only mtime, but because they can only be set together,
        * if we don't want to modify atime, we first have to get it.
        * Therefore setting them both saves an extra protocol round.
        */
       attrs.flags |= utils::SFTPClient::SFTP_ATTRIBUTE_MTIME | utils::SFTPClient::SFTP_ATTRIBUTE_ATIME;
-      attrs.mtime = last_modified_time;
-      attrs.atime = last_modified_time;
+      attrs.mtime = std::chrono::duration_cast<std::chrono::seconds>(last_modified_->time_since_epoch()).count();
+      attrs.atime = std::chrono::duration_cast<std::chrono::seconds>(last_modified_->time_since_epoch()).count();
     }
     if (permissions_set) {
       attrs.flags |= utils::SFTPClient::SFTP_ATTRIBUTE_PERMISSIONS;
diff --git a/extensions/sftp/tests/ListSFTPTests.cpp b/extensions/sftp/tests/ListSFTPTests.cpp
index 5f9c7c415..a51961653 100644
--- a/extensions/sftp/tests/ListSFTPTests.cpp
+++ b/extensions/sftp/tests/ListSFTPTests.cpp
@@ -234,8 +234,7 @@ TEST_CASE_METHOD(ListSFTPTestsFixture, "ListSFTP list one file writes attributes
   testController.runSession(plan, true);
 
   auto file = src_dir + "/vfs/nifi_test/tstFile.ext";
-  std::string mtime_str;
-  REQUIRE(true == utils::timeutils::getDateTimeStr(utils::file::to_time_t(utils::file::last_write_time(file).value()), mtime_str));
+  auto mtime_str = utils::timeutils::getDateTimeStr(std::chrono::time_point_cast<std::chrono::seconds>(utils::file::to_sys(utils::file::last_write_time(file).value())));
   uint64_t uid, gid;
   REQUIRE(true == utils::file::FileUtils::get_uid_gid(file, uid, gid));
   uint32_t permissions;
diff --git a/extensions/standard-processors/processors/LogAttribute.cpp b/extensions/standard-processors/processors/LogAttribute.cpp
index dec826cb9..6791a952b 100644
--- a/extensions/standard-processors/processors/LogAttribute.cpp
+++ b/extensions/standard-processors/processors/LogAttribute.cpp
@@ -111,8 +111,8 @@ void LogAttribute::onTrigger(const std::shared_ptr<core::ProcessContext> &contex
     message << dashLine;
     message << "\nStandard FlowFile Attributes";
     message << "\n" << "UUID:" << flow->getUUIDStr();
-    message << "\n" << "EntryDate:" << utils::timeutils::getTimeStr(std::chrono::duration_cast<std::chrono::milliseconds>(flow->getEntryDate().time_since_epoch()).count());
-    message << "\n" << "lineageStartDate:" << utils::timeutils::getTimeStr(std::chrono::duration_cast<std::chrono::milliseconds>(flow->getlineageStartDate().time_since_epoch()).count());
+    message << "\n" << "EntryDate:" << utils::timeutils::getTimeStr(flow->getEntryDate());
+    message << "\n" << "lineageStartDate:" << utils::timeutils::getTimeStr(flow->getlineageStartDate());
     message << "\n" << "Size:" << flow->getSize() << " Offset:" << flow->getOffset();
     message << "\nFlowFile Attributes Map Content";
     std::map<std::string, std::string> attrs = flow->getAttributes();
diff --git a/libminifi/include/core/Property.h b/libminifi/include/core/Property.h
index 2b81b39c2..5ba79d1b4 100644
--- a/libminifi/include/core/Property.h
+++ b/libminifi/include/core/Property.h
@@ -165,16 +165,6 @@ class Property {
 // Compare
   bool operator <(const Property & right) const;
 
-
-  static bool StringToDateTime(const std::string& input, int64_t& output) {
-    int64_t temp = utils::timeutils::parseDateTimeStr(input);
-    if (temp == -1) {
-      return false;
-    }
-    output = temp;
-    return true;
-  }
-
   static bool StringToPermissions(const std::string& input, uint32_t& output) {
     uint32_t temp = 0U;
     if (input.size() == 9U) {
diff --git a/libminifi/include/utils/TimeUtil.h b/libminifi/include/utils/TimeUtil.h
index 5a352947c..dbcf31014 100644
--- a/libminifi/include/utils/TimeUtil.h
+++ b/libminifi/include/utils/TimeUtil.h
@@ -86,125 +86,45 @@ class SteadyClock : public Clock {
   }
 };
 
-/**
- * Returns a string based on TIME_FORMAT, converting
- * the parameter to a string
- * @param msec milliseconds since epoch
- * @returns string representing the time
- */
-inline std::string getTimeStr(uint64_t msec, bool enforce_locale = false) {
-  char date[120];
-  time_t second = (time_t) (msec / 1000);
-  msec = msec % 1000;
-  strftime(date, sizeof(date) / sizeof(*date), TIME_FORMAT, (enforce_locale == true ? gmtime(&second) : localtime(&second)));
-
-  std::string ret = date;
-  date[0] = '\0';
-  sprintf(date, ".%03llu", (unsigned long long) msec); // NOLINT
-
-  ret += date;
-  return ret;
+inline std::string getTimeStr(std::chrono::system_clock::time_point tp) {
+  std::ostringstream stream;
+  date::to_stream(stream, TIME_FORMAT, std::chrono::floor<std::chrono::milliseconds>(tp));
+  return stream.str();
 }
 
-inline time_t mkgmtime(struct tm* date_time) {
-#ifdef WIN32
-  return _mkgmtime(date_time);
-#else
-  static const int month_lengths[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
-  static const int month_lengths_leap[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
-  static const auto is_leap_year = [](int year) -> bool {
-    return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
-  };
-  static const auto num_leap_days = [](int year) -> int {
-    return (year - 1968) / 4 - (year - 1900) / 100 + (year - 1600) / 400;
-  };
-
-  int year = date_time->tm_year + 1900;
-  time_t result = year - 1970;
-  result *= 365;
-  result += num_leap_days(year - 1);
-
-  for (int i = 0; i < 12 && i < date_time->tm_mon; ++i) {
-    result += is_leap_year(year) ? month_lengths_leap[i] : month_lengths[i];
-  }
-
-  result += date_time->tm_mday - 1;
-  result *= 24;
-
-  result += date_time->tm_hour;
-  result *= 60;
-
-  result += date_time->tm_min;
-  result *= 60;
-
-  result += date_time->tm_sec;
-  return result;
-#endif
+inline std::optional<std::chrono::sys_seconds> parseDateTimeStr(const std::string& str) {
+  std::istringstream stream(str);
+  std::chrono::sys_seconds tp;
+  date::from_stream(stream, "%Y-%m-%dT%H:%M:%SZ", tp);
+  if (stream.fail() || (stream.peek() && !stream.eof()))
+    return std::nullopt;
+  return tp;
 }
 
-/**
- * Parse a datetime in yyyy-MM-dd'T'HH:mm:ssZ format
- * @param str the datetime string
- * @returns Unix timestamp
- */
-inline int64_t parseDateTimeStr(const std::string& str) {
-  /**
-   * There is no strptime on Windows. As long as we have to parse a single date format this is not so bad,
-   * but if multiple formats will have to be supported in the future, it might be worth it to include
-   * an strptime implementation from some BSD on Windows.
-   */
-  uint32_t year;
-  uint8_t month;
-  uint8_t day;
-  uint8_t hours;
-  uint8_t minutes;
-  uint8_t seconds;
-  int read = 0;
-  if (sscanf(str.c_str(), "%4u-%2hhu-%2hhuT%2hhu:%2hhu:%2hhuZ%n", &year, &month, &day, &hours, &minutes, &seconds, &read) != 6) {
-    return -1;
-  }
-  // while it is unlikely that read will be < 0, the conditional adds little cost for a little defensiveness.
-  if (read < 0 || static_cast<size_t>(read) != str.size()) {
-    return -1;
-  }
-
-  if (year < 1970U ||
-      month > 12U ||
-      day > 31U ||
-      hours > 23U ||
-      minutes > 59U ||
-      seconds > 60U) {
-    return -1;
-  }
-
-  struct tm timeinfo{};
-  timeinfo.tm_year = year - 1900;
-  timeinfo.tm_mon = month - 1;
-  timeinfo.tm_mday = day;
-  timeinfo.tm_hour = hours;
-  timeinfo.tm_min = minutes;
-  timeinfo.tm_sec = seconds;
-  timeinfo.tm_isdst = 0;
-
-  return static_cast<int64_t>(mkgmtime(&timeinfo));
+inline std::string getDateTimeStr(std::chrono::sys_seconds tp) {
+  std::ostringstream stream;
+  date::to_stream(stream, "%Y-%m-%dT%H:%M:%SZ", std::chrono::floor<std::chrono::seconds>(tp));
+  return stream.str();
 }
 
-inline bool getDateTimeStr(int64_t unix_timestamp, std::string& date_time_str) {
-  if (unix_timestamp > (std::numeric_limits<time_t>::max)() || unix_timestamp < (std::numeric_limits<time_t>::lowest)()) {
-    return false;
-  }
-  time_t time = static_cast<time_t>(unix_timestamp);
-  struct tm* gmt = gmtime(&time); // NOLINT
-  if (gmt == nullptr) {
-    return false;
-  }
-  std::array<char, 64U> buf;
-  if (strftime(buf.data(), buf.size(), "%Y-%m-%dT%H:%M:%SZ", gmt) == 0U) {
-    return false;
-  }
+inline date::sys_seconds to_sys_time(const std::tm& t) {
+  using date::year;
+  using date::month;
+  using date::day;
+  using std::chrono::hours;
+  using std::chrono::minutes;
+  using std::chrono::seconds;
+  return date::sys_days{year{t.tm_year + 1900}/(t.tm_mon+1)/t.tm_mday} + hours{t.tm_hour} + minutes{t.tm_min} + seconds{t.tm_sec};
+}
 
-  date_time_str = buf.data();
-  return true;
+inline date::local_seconds to_local_time(const std::tm& t) {
+  using date::year;
+  using date::month;
+  using date::day;
+  using std::chrono::hours;
+  using std::chrono::minutes;
+  using std::chrono::seconds;
+  return date::local_days{year{t.tm_year + 1900}/(t.tm_mon+1)/t.tm_mday} + hours{t.tm_hour} + minutes{t.tm_min} + seconds{t.tm_sec};
 }
 
 namespace details {
diff --git a/libminifi/include/utils/tls/CertificateUtils.h b/libminifi/include/utils/tls/CertificateUtils.h
index 73e7011fc..64f0d65f7 100644
--- a/libminifi/include/utils/tls/CertificateUtils.h
+++ b/libminifi/include/utils/tls/CertificateUtils.h
@@ -33,12 +33,7 @@
 
 #include "WindowsCertStoreLocation.h"
 
-namespace org {
-namespace apache {
-namespace nifi {
-namespace minifi {
-namespace utils {
-namespace tls {
+namespace org::apache::nifi::minifi::utils::tls {
 
 class ssl_error_category : public std::error_category {
  public:
@@ -114,11 +109,6 @@ std::error_code processP12Certificate(const std::string& cert_file, const std::s
 
 std::error_code processPEMCertificate(const std::string& cert_file, const std::optional<std::string>& passphrase, const CertHandler& handler);
 
-}  // namespace tls
-}  // namespace utils
-}  // namespace minifi
-}  // namespace nifi
-}  // namespace apache
-}  // namespace org
+}  // namespace org::apache::nifi::minifi::utils::tls
 
 #endif  // OPENSSL_SUPPORT
diff --git a/libminifi/src/controllers/SSLContextService.cpp b/libminifi/src/controllers/SSLContextService.cpp
index d91563f9f..76c178aa7 100644
--- a/libminifi/src/controllers/SSLContextService.cpp
+++ b/libminifi/src/controllers/SSLContextService.cpp
@@ -149,7 +149,7 @@ bool SSLContextService::configure_ssl_context(SSL_CTX *ctx) {
   SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, nullptr);
 
   if (!IsNullOrEmpty(ca_certificate_)) {
-    if (SSL_CTX_load_verify_locations(ctx, ca_certificate_.c_str(), 0) == 0) {
+    if (SSL_CTX_load_verify_locations(ctx, ca_certificate_.c_str(), nullptr) == 0) {
       core::logging::LOG_ERROR(logger_) << "Cannot load CA certificate, exiting, " << getLatestOpenSSLErrorString();
       return false;
     }
@@ -545,7 +545,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 = utils::timeutils::getTimeStr(std::chrono::duration_cast<std::chrono::milliseconds>(end_date->time_since_epoch()).count());
+      std::string end_date_str = utils::timeutils::getTimeStr(*end_date);
       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/tls/CertificateUtils.cpp b/libminifi/src/utils/tls/CertificateUtils.cpp
index 6fe4fe1bf..453a8d750 100644
--- a/libminifi/src/utils/tls/CertificateUtils.cpp
+++ b/libminifi/src/utils/tls/CertificateUtils.cpp
@@ -30,12 +30,7 @@
 #include "utils/tls/TLSUtils.h"
 #include "utils/TimeUtil.h"
 
-namespace org {
-namespace apache {
-namespace nifi {
-namespace minifi {
-namespace utils {
-namespace tls {
+namespace org::apache::nifi::minifi::utils::tls {
 
 const ssl_error_category& ssl_error_category::get() {
   static ssl_error_category instance;
@@ -181,7 +176,7 @@ std::optional<std::chrono::system_clock::time_point> getCertificateExpiration(co
   if (ret == -1) {
     return {};
   }
-  return std::chrono::system_clock::from_time_t(utils::timeutils::mkgmtime(&end));
+  return utils::timeutils::to_sys_time(end);
 }
 
 std::error_code processP12Certificate(const std::string& cert_file, const std::string& passphrase, const CertHandler& handler) {
@@ -265,11 +260,6 @@ std::error_code processPEMCertificate(const std::string& cert_file, const std::o
   return {};
 }
 
-}  // namespace tls
-}  // namespace utils
-}  // namespace minifi
-}  // namespace nifi
-}  // namespace apache
-}  // namespace org
+}  // namespace org::apache::nifi::minifi::utils::tls
 
 #endif  // OPENSSL_SUPPORT
diff --git a/libminifi/test/unit/PropertyTests.cpp b/libminifi/test/unit/PropertyTests.cpp
index b35b0aa18..c2eb1cfc3 100644
--- a/libminifi/test/unit/PropertyTests.cpp
+++ b/libminifi/test/unit/PropertyTests.cpp
@@ -19,10 +19,10 @@
 #include <string>
 
 #include "core/Property.h"
-#include "../../include/core/Property.h"
 #include "utils/StringUtils.h"
 #include "../TestBase.h"
 #include "../Catch.h"
+
 namespace {
 enum class ConversionTestTarget { MS, NS };
 
@@ -126,35 +126,4 @@ TEST_CASE("Test Permissions Conversion", "[testPermissions]") {
 
   REQUIRE(false == org::apache::nifi::minifi::core::Property::StringToPermissions("foobar", permissions));
 }
-
-TEST_CASE("Test DateTime Conversion", "[testDateTime]") {
-  int64_t timestamp = 0LL;
-
-  REQUIRE(true == org::apache::nifi::minifi::core::Property::StringToDateTime("1970-01-01T00:00:00Z", timestamp));
-  REQUIRE(0LL == timestamp);
-
-  REQUIRE(true == org::apache::nifi::minifi::core::Property::StringToDateTime("1970-01-01T00:59:59Z", timestamp));
-  REQUIRE(3600 - 1 == timestamp);
-
-  REQUIRE(true == org::apache::nifi::minifi::core::Property::StringToDateTime("2000-06-17T12:34:21Z", timestamp));
-  REQUIRE(961245261LL == timestamp);
-
-  REQUIRE(true == org::apache::nifi::minifi::core::Property::StringToDateTime("2038-01-19T03:14:07Z", timestamp));
-  REQUIRE(2147483647LL == timestamp);
-
-  REQUIRE(true == org::apache::nifi::minifi::core::Property::StringToDateTime("2065-01-24T05:20:00Z", timestamp));
-  REQUIRE(3000000000LL == timestamp);
-
-  REQUIRE(false == org::apache::nifi::minifi::core::Property::StringToDateTime("1970-01-01A00:00:00Z", timestamp));
-
-  REQUIRE(false == org::apache::nifi::minifi::core::Property::StringToDateTime("1970-01-01T00:00:00", timestamp));
-
-  REQUIRE(false == org::apache::nifi::minifi::core::Property::StringToDateTime("1970-01-01T00:00:00Zfoo", timestamp));
-
-  REQUIRE(false == org::apache::nifi::minifi::core::Property::StringToDateTime("1969-01-01T00:00:00Z", timestamp));
-
-  REQUIRE(false == org::apache::nifi::minifi::core::Property::StringToDateTime("1970-13-01T00:00:00Z", timestamp));
-
-  REQUIRE(false == org::apache::nifi::minifi::core::Property::StringToDateTime("foobar", timestamp));
-}
 }   // namespace
diff --git a/libminifi/test/unit/TimeUtilTests.cpp b/libminifi/test/unit/TimeUtilTests.cpp
index 0a96e5134..bd2518c02 100644
--- a/libminifi/test/unit/TimeUtilTests.cpp
+++ b/libminifi/test/unit/TimeUtilTests.cpp
@@ -21,79 +21,78 @@
 
 using namespace std::literals::chrono_literals;
 
-namespace {
-  constexpr int ONE_HOUR = 60 * 60;
-  constexpr int ONE_DAY = 24 * ONE_HOUR;
-
-  struct tm createTm(int year, int month, int day, int hour, int minute, int second, bool is_dst = false) {
-    struct tm date_time;
-    date_time.tm_year = year - 1900;
-    date_time.tm_mon = month - 1;
-    date_time.tm_mday = day;
-    date_time.tm_hour = hour;
-    date_time.tm_min = minute;
-    date_time.tm_sec = second;
-    date_time.tm_isdst = is_dst ? 1 : 0;
-    return date_time;
-  }
-
-  void mkgmtimeTestHelper(time_t expected, int year, int month, int day, int hour, int minute, int second) {
-    using org::apache::nifi::minifi::utils::timeutils::mkgmtime;
-    struct tm date_time = createTm(year, month, day, hour, minute, second);
-    REQUIRE(mkgmtime(&date_time) == expected);
-  }
-}  // namespace
+TEST_CASE("parseDateTimeStr() works correctly", "[parseDateTimeStr]") {
+  using org::apache::nifi::minifi::utils::timeutils::parseDateTimeStr;
 
-TEST_CASE("mkgmtime() works correctly", "[mkgmtime]") {
-  mkgmtimeTestHelper(0, 1970, 1, 1, 0, 0, 0);
-  for (int hour = 0; hour < 24; ++hour) {
-    mkgmtimeTestHelper((hour + 1) * ONE_HOUR - 1, 1970, 1, 1, hour, 59, 59);
-  }
-
-  mkgmtimeTestHelper(ONE_DAY,       1970, 1, 2, 0, 0, 0);
-  mkgmtimeTestHelper(31 * ONE_DAY,  1970, 2, 1, 0, 0, 0);
-  mkgmtimeTestHelper(365 * ONE_DAY, 1971, 1, 1, 0, 0, 0);
-
-  mkgmtimeTestHelper(793929600,            1995, 2, 28, 0, 0, 0);
-  mkgmtimeTestHelper(793929600 + ONE_DAY,  1995, 3,  1, 0, 0, 0);
-  mkgmtimeTestHelper(825465600,            1996, 2, 28, 0, 0, 0);
-  mkgmtimeTestHelper(825465600 + ONE_DAY,  1996, 2, 29, 0, 0, 0);
-  mkgmtimeTestHelper(951696000,            2000, 2, 28, 0, 0, 0);
-  mkgmtimeTestHelper(951696000 + ONE_DAY,  2000, 2, 29, 0, 0, 0);
-  mkgmtimeTestHelper(4107456000,           2100, 2, 28, 0, 0, 0);
-  mkgmtimeTestHelper(4107456000 + ONE_DAY, 2100, 3,  1, 0, 0, 0);
-
-  mkgmtimeTestHelper(1513104856,  2017, 12, 12, 18, 54, 16);
-  mkgmtimeTestHelper(1706655675,  2024,  1, 30, 23, 01, 15);
-  mkgmtimeTestHelper(3710453630,  2087,  7, 31, 01, 33, 50);
+  CHECK(*parseDateTimeStr("1970-01-01T00:00:00Z") == std::chrono::sys_seconds{0s});
+  CHECK(*parseDateTimeStr("1970-01-01T00:59:59Z") == std::chrono::sys_seconds{1h - 1s});
+
+  CHECK(*parseDateTimeStr("1970-01-02T00:00:00Z") == std::chrono::sys_seconds{std::chrono::days(1)});
+  CHECK(*parseDateTimeStr("1970-02-01T00:00:00Z") == std::chrono::sys_seconds{31 * std::chrono::days(1)});
+  CHECK(*parseDateTimeStr("1971-01-01T00:00:00Z") == std::chrono::sys_seconds{365 * std::chrono::days(1)});
+
+  CHECK(*parseDateTimeStr("1995-02-28T00:00:00Z") == std::chrono::sys_seconds{793929600s});
+  CHECK(*parseDateTimeStr("1995-03-01T00:00:00Z") == std::chrono::sys_seconds{793929600s + std::chrono::days(1)});
+  CHECK(*parseDateTimeStr("1996-02-28T00:00:00Z") == std::chrono::sys_seconds{825465600s});
+  CHECK(*parseDateTimeStr("1996-02-29T00:00:00Z") == std::chrono::sys_seconds{825465600s + std::chrono::days(1)});
+  CHECK(*parseDateTimeStr("2000-02-28T00:00:00Z") == std::chrono::sys_seconds{951696000s});
+  CHECK(*parseDateTimeStr("2000-02-29T00:00:00Z") == std::chrono::sys_seconds{951696000s + std::chrono::days(1)});
+  CHECK(*parseDateTimeStr("2100-02-28T00:00:00Z") == std::chrono::sys_seconds{4107456000s});
+  CHECK(*parseDateTimeStr("2100-03-01T00:00:00Z") == std::chrono::sys_seconds{4107456000s + std::chrono::days(1)});
+
+  CHECK(*parseDateTimeStr("2017-12-12T18:54:16Z") == std::chrono::sys_seconds{1513104856s});
+  CHECK(*parseDateTimeStr("2024-01-30T23:01:15Z") == std::chrono::sys_seconds{1706655675s});
+  CHECK(*parseDateTimeStr("2087-07-31T01:33:50Z") == std::chrono::sys_seconds{3710453630s});
 }
 
-TEST_CASE("parseDateTimeStr() works correctly", "[parseDateTimeStr]") {
-  using org::apache::nifi::minifi::utils::timeutils::parseDateTimeStr;
-  REQUIRE(parseDateTimeStr("1970-01-01T00:00:00Z") == 0);
-  REQUIRE(parseDateTimeStr("1970-01-01T00:59:59Z") == ONE_HOUR - 1);
-
-  REQUIRE(parseDateTimeStr("1970-01-02T00:00:00Z") == ONE_DAY);
-  REQUIRE(parseDateTimeStr("1970-02-01T00:00:00Z") == 31 * ONE_DAY);
-  REQUIRE(parseDateTimeStr("1971-01-01T00:00:00Z") == 365 * ONE_DAY);
-
-  REQUIRE(parseDateTimeStr("1995-02-28T00:00:00Z") == 793929600);
-  REQUIRE(parseDateTimeStr("1995-03-01T00:00:00Z") == 793929600 + ONE_DAY);
-  REQUIRE(parseDateTimeStr("1996-02-28T00:00:00Z") == 825465600);
-  REQUIRE(parseDateTimeStr("1996-02-29T00:00:00Z") == 825465600 + ONE_DAY);
-  REQUIRE(parseDateTimeStr("2000-02-28T00:00:00Z") == 951696000);
-  REQUIRE(parseDateTimeStr("2000-02-29T00:00:00Z") == 951696000 + ONE_DAY);
-  REQUIRE(parseDateTimeStr("2100-02-28T00:00:00Z") == 4107456000);
-  REQUIRE(parseDateTimeStr("2100-03-01T00:00:00Z") == 4107456000 + ONE_DAY);
-
-  REQUIRE(parseDateTimeStr("2017-12-12T18:54:16Z") == 1513104856);
-  REQUIRE(parseDateTimeStr("2024-01-30T23:01:15Z") == 1706655675);
-  REQUIRE(parseDateTimeStr("2087-07-31T01:33:50Z") == 3710453630);
+TEST_CASE("getDateTimeStr() works correctly", "[getDateTimeStr]") {
+  using org::apache::nifi::minifi::utils::timeutils::getDateTimeStr;
+
+  CHECK("1970-01-01T00:00:00Z" == getDateTimeStr(std::chrono::sys_seconds{0s}));
+  CHECK("1970-01-01T00:59:59Z" == getDateTimeStr(std::chrono::sys_seconds{1h - 1s}));
+
+  CHECK("1970-01-02T00:00:00Z" == getDateTimeStr(std::chrono::sys_seconds{std::chrono::days(1)}));
+  CHECK("1970-02-01T00:00:00Z" == getDateTimeStr(std::chrono::sys_seconds{31 * std::chrono::days(1)}));
+  CHECK("1971-01-01T00:00:00Z" == getDateTimeStr(std::chrono::sys_seconds{365 * std::chrono::days(1)}));
+
+  CHECK("1995-02-28T00:00:00Z" == getDateTimeStr(std::chrono::sys_seconds{793929600s}));
+  CHECK("1995-03-01T00:00:00Z" == getDateTimeStr(std::chrono::sys_seconds{793929600s + std::chrono::days(1)}));
+  CHECK("1996-02-28T00:00:00Z" == getDateTimeStr(std::chrono::sys_seconds{825465600s}));
+  CHECK("1996-02-29T00:00:00Z" == getDateTimeStr(std::chrono::sys_seconds{825465600s + std::chrono::days(1)}));
+  CHECK("2000-02-28T00:00:00Z" == getDateTimeStr(std::chrono::sys_seconds{951696000s}));
+  CHECK("2000-02-29T00:00:00Z" == getDateTimeStr(std::chrono::sys_seconds{951696000s + std::chrono::days(1)}));
+  CHECK("2100-02-28T00:00:00Z" == getDateTimeStr(std::chrono::sys_seconds{4107456000s}));
+  CHECK("2100-03-01T00:00:00Z" == getDateTimeStr(std::chrono::sys_seconds{4107456000s + std::chrono::days(1)}));
+
+  CHECK("2017-12-12T18:54:16Z" == getDateTimeStr(std::chrono::sys_seconds{1513104856s}));
+  CHECK("2024-01-30T23:01:15Z" == getDateTimeStr(std::chrono::sys_seconds{1706655675s}));
+  CHECK("2087-07-31T01:33:50Z" == getDateTimeStr(std::chrono::sys_seconds{3710453630s}));
 }
 
 TEST_CASE("Test time conversion", "[testtimeconversion]") {
   using org::apache::nifi::minifi::utils::timeutils::getTimeStr;
-  REQUIRE("2017-02-16 20:14:56.196" == getTimeStr(1487276096196, true));
+  CHECK("2017-02-16 20:14:56.196" == getTimeStr(std::chrono::system_clock::time_point{1487276096196ms}));
+}
+
+TEST_CASE("Test DateTime Conversion", "[testDateTime]") {
+  using namespace date::literals;
+  using namespace std::literals::chrono_literals;
+  using date::year_month_day;
+  using date::sys_days;
+  using utils::timeutils::parseDateTimeStr;
+
+  CHECK(sys_days(date::year_month_day(1970_y/01/01)) == parseDateTimeStr("1970-01-01T00:00:00Z"));
+  CHECK(sys_days(year_month_day(1970_y/01/01)) + 0h + 59min + 59s == parseDateTimeStr("1970-01-01T00:59:59Z"));
+  CHECK(sys_days(year_month_day(2000_y/06/17)) + 12h + 34min + 21s == parseDateTimeStr("2000-06-17T12:34:21Z"));
+  CHECK(sys_days(year_month_day(2038_y/01/19)) + 3h + 14min + 7s == parseDateTimeStr("2038-01-19T03:14:07Z"));
+  CHECK(sys_days(year_month_day(2065_y/01/24)) + 5h + 20min + 0s == parseDateTimeStr("2065-01-24T05:20:00Z"));
+  CHECK(sys_days(year_month_day(1969_y/01/01)) == parseDateTimeStr("1969-01-01T00:00:00Z"));
+
+  CHECK_FALSE(utils::timeutils::parseDateTimeStr("1970-01-01A00:00:00Z"));
+  CHECK_FALSE(utils::timeutils::parseDateTimeStr("1970-01-01T00:00:00"));
+  CHECK_FALSE(utils::timeutils::parseDateTimeStr("1970-01-01T00:00:00Zfoo"));
+  CHECK_FALSE(utils::timeutils::parseDateTimeStr("1970-13-01T00:00:00Z"));
+  CHECK_FALSE(utils::timeutils::parseDateTimeStr("foobar"));
 }
 
 TEST_CASE("Test system_clock epoch", "[systemclockepoch]") {
@@ -107,7 +106,7 @@ TEST_CASE("Test system_clock epoch", "[systemclockepoch]") {
 TEST_CASE("Test clock resolutions", "[clockresolutiontests]") {
   using namespace std::chrono;
   CHECK(std::is_constructible<system_clock::duration, std::chrono::microseconds>::value);  // The resolution of the system_clock is at least microseconds
-  CHECK(std::is_constructible<steady_clock::duration, std::chrono::microseconds>::value);  // The resolution of the system_clock is at least microseconds
+  CHECK(std::is_constructible<steady_clock::duration, std::chrono::microseconds>::value);  // The resolution of the steady_clock is at least microseconds
   CHECK(std::is_constructible<high_resolution_clock::duration, std::chrono::nanoseconds>::value);  // The resolution of the high_resolution_clock is at least nanoseconds
 }