diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt
index 696826f949cf982953df06f0bd6799dc658722aa..140754cebb2314c5b0a0b52714b4e826f45038d0 100644
--- a/Source/CMakeLists.txt
+++ b/Source/CMakeLists.txt
@@ -236,6 +236,8 @@ set(SRCS
   cmFileLockResult.h
   cmFilePathChecksum.cxx
   cmFilePathChecksum.h
+  cmFileTime.cxx
+  cmFileTime.h
   cmFileTimeComparison.cxx
   cmFileTimeComparison.h
   cmFortranParserImpl.cxx
@@ -796,6 +798,8 @@ foreach(check
   else()
     set(CMake_${check} 0)
   endif()
+  set_property(SOURCE cmFileTime.cxx APPEND PROPERTY
+    COMPILE_DEFINITIONS CMake_${check}=${CMake_${check}})
   set_property(SOURCE cmFileTimeComparison.cxx APPEND PROPERTY
     COMPILE_DEFINITIONS CMake_${check}=${CMake_${check}})
 endforeach()
diff --git a/Source/cmFileTime.cxx b/Source/cmFileTime.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..253457ff2472059230ea99db5ecb47853b02ad74
--- /dev/null
+++ b/Source/cmFileTime.cxx
@@ -0,0 +1,49 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include "cmFileTime.h"
+
+#include <string>
+#include <time.h>
+
+// Use a platform-specific API to get file times efficiently.
+#if !defined(_WIN32) || defined(__CYGWIN__)
+#  include "cm_sys_stat.h"
+#else
+#  include "cmsys/Encoding.hxx"
+#  include <windows.h>
+#endif
+
+bool cmFileTime::Load(std::string const& fileName)
+{
+#if !defined(_WIN32) || defined(__CYGWIN__)
+  // POSIX version.  Use the stat function.
+  struct stat fst;
+  if (::stat(fileName.c_str(), &fst) != 0) {
+    return false;
+  }
+#  if CMake_STAT_HAS_ST_MTIM
+  // Nanosecond resolution
+  this->NS = fst.st_mtim.tv_sec * NsPerS + fst.st_mtim.tv_nsec;
+#  elif CMake_STAT_HAS_ST_MTIMESPEC
+  // Nanosecond resolution
+  this->NS = fst.st_mtimespec.tv_sec * NsPerS + fst.st_mtimespec.tv_nsec;
+#  else
+  // Second resolution
+  this->NS = fst.st_mtime * NsPerS;
+#  endif
+#else
+  // Windows version.  Get the modification time from extended file attributes.
+  WIN32_FILE_ATTRIBUTE_DATA fdata;
+  if (!GetFileAttributesExW(cmsys::Encoding::ToWide(fileName).c_str(),
+                            GetFileExInfoStandard, &fdata)) {
+    return false;
+  }
+
+  // Copy the file time to the output location.
+  this->NS = (static_cast<NSC>(fdata.ftLastWriteTime.dwHighDateTime) << 32) |
+    static_cast<NSC>(fdata.ftLastWriteTime.dwLowDateTime);
+  // The file time resolution is 100 ns.
+  this->NS *= 100;
+#endif
+  return true;
+}
diff --git a/Source/cmFileTime.h b/Source/cmFileTime.h
new file mode 100644
index 0000000000000000000000000000000000000000..4c8e5562dd6082a2740e1c9d42ef3c7193934480
--- /dev/null
+++ b/Source/cmFileTime.h
@@ -0,0 +1,130 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#ifndef cmFileTime_h
+#define cmFileTime_h
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <string>
+
+/** \class cmFileTime
+ * \brief Abstract file modification time with support for comparison with
+ *        other file modification times.
+ */
+class cmFileTime
+{
+public:
+  typedef long long NSC;
+  static constexpr NSC NsPerS = 1000000000;
+
+  cmFileTime() = default;
+  ~cmFileTime() = default;
+
+  /**
+   * @brief Loads the file time of fileName from the file system
+   * @return true on success
+   */
+  bool Load(std::string const& fileName);
+
+  /**
+   * @brief Return true if this is older than ftm
+   */
+  bool Older(cmFileTime const& ftm) const { return (this->NS - ftm.NS) < 0; }
+
+  /**
+   * @brief Return true if this is newer than ftm
+   */
+  bool Newer(cmFileTime const& ftm) const { return (ftm.NS - this->NS) < 0; }
+
+  /**
+   * @brief Return true if this is the same as ftm
+   */
+  bool Equal(cmFileTime const& ftm) const { return this->NS == ftm.NS; }
+
+  /**
+   * @brief Return true if this is not the same as ftm
+   */
+  bool Differ(cmFileTime const& ftm) const { return this->NS != ftm.NS; }
+
+  /**
+   * @brief Compare file modification times.
+   * @return -1, 0, +1 for this older, same, or newer than ftm.
+   */
+  int Compare(cmFileTime const& ftm)
+  {
+    NSC const diff = this->NS - ftm.NS;
+    if (diff == 0) {
+      return 0;
+    }
+    return (diff < 0) ? -1 : 1;
+  }
+
+  // -- Comparison in second resolution
+
+  /**
+   * @brief Return true if this is at least a second older than ftm
+   */
+  bool OlderS(cmFileTime const& ftm) const
+  {
+    return (ftm.NS - this->NS) >= cmFileTime::NsPerS;
+  }
+
+  /**
+   * @brief Return true if this is at least a second newer than ftm
+   */
+  bool NewerS(cmFileTime const& ftm) const
+  {
+    return (this->NS - ftm.NS) >= cmFileTime::NsPerS;
+  }
+
+  /**
+   * @brief Return true if this is within the same second as ftm
+   */
+  bool EqualS(cmFileTime const& ftm) const
+  {
+    NSC diff = this->NS - ftm.NS;
+    if (diff < 0) {
+      diff = -diff;
+    }
+    return (diff < cmFileTime::NsPerS);
+  }
+
+  /**
+   * @brief Return true if this is older or newer than ftm by at least a second
+   */
+  bool DifferS(cmFileTime const& ftm) const
+  {
+    NSC diff = this->NS - ftm.NS;
+    if (diff < 0) {
+      diff = -diff;
+    }
+    return (diff >= cmFileTime::NsPerS);
+  }
+
+  /**
+   * @brief Compare file modification times.
+   * @return -1: this at least a second older, 0: this within the same second
+   *         as ftm, +1: this at least a second newer than ftm.
+   */
+  int CompareS(cmFileTime const& ftm) const
+  {
+    NSC const diff = this->NS - ftm.NS;
+    if (diff <= -cmFileTime::NsPerS) {
+      return -1;
+    }
+    if (diff >= cmFileTime::NsPerS) {
+      return 1;
+    }
+    return 0;
+  }
+
+  /**
+   * @brief The file modification time in nanoseconds
+   */
+  NSC GetNS() const { return this->NS; }
+
+private:
+  NSC NS = 0;
+};
+
+#endif
diff --git a/bootstrap b/bootstrap
index af050244b10470917b111888fc2ba81e56111771..77293c8602b933b01e9420128fc4b5b267d4e000 100755
--- a/bootstrap
+++ b/bootstrap
@@ -304,6 +304,7 @@ CMAKE_CXX_SOURCES="\
   cmFileCommand \
   cmFileCopier \
   cmFileInstaller \
+  cmFileTime \
   cmFileTimeComparison \
   cmFindBase \
   cmFindCommon \