Browse Source

Merge topic 'unix-timestamps'

6727270b CMake: Extend TIMESTAMP sub-commands with new unix time format specifier
Brad King 9 years ago
parent
commit
c387325d4a

+ 1 - 0
Help/command/string.rst

@@ -277,6 +277,7 @@ specifiers:
    %j        The day of the current year (001-366).
    %m        The month of the current year (01-12).
    %M        The minute of the current hour (00-59).
+   %s        Seconds since midnight (UTC) 1-Jan-1970 (UNIX time).
    %S        The second of the current minute.
              60 represents a leap second. (00-60)
    %U        The week number of the current year (00-53).

+ 6 - 0
Help/release/dev/unix-timestamps.rst

@@ -0,0 +1,6 @@
+unix-timestamps
+---------------
+
+* The :command:`string(TIMESTAMP)` and :command:`file(TIMESTAMP)`
+  commands gained support for the ``%s`` placeholder.  This is
+  the number of seconds since the UNIX Epoch.

+ 59 - 5
Source/cmTimestamp.cxx

@@ -12,9 +12,11 @@
 #include "cmTimestamp.h"
 
 #include <cstring>
+#include <cstdlib>
 
 #include <sys/types.h>
 #include <sys/stat.h>
+#include <sstream>
 
 //----------------------------------------------------------------------------
 std::string cmTimestamp::CurrentTime(
@@ -44,7 +46,7 @@ std::string cmTimestamp::FileModificationTime(const char* path,
 
 //----------------------------------------------------------------------------
 std::string cmTimestamp::CreateTimestampFromTimeT(time_t timeT,
-    std::string formatString, bool utcFlag)
+  std::string formatString, bool utcFlag) const
 {
   if(formatString.empty())
     {
@@ -79,12 +81,12 @@ std::string cmTimestamp::CreateTimestampFromTimeT(time_t timeT,
   for(std::string::size_type i = 0; i < formatString.size(); ++i)
     {
     char c1 = formatString[i];
-    char c2 = (i+1 < formatString.size()) ?
-      formatString[i+1] : static_cast<char>(0);
+    char c2 = (i + 1 < formatString.size()) ?
+      formatString[i + 1] : static_cast<char>(0);
 
     if(c1 == '%' && c2 != 0)
       {
-      result += AddTimestampComponent(c2, timeStruct);
+      result += AddTimestampComponent(c2, timeStruct, timeT);
       ++i;
       }
     else
@@ -96,9 +98,41 @@ std::string cmTimestamp::CreateTimestampFromTimeT(time_t timeT,
   return result;
 }
 
+//----------------------------------------------------------------------------
+time_t cmTimestamp::CreateUtcTimeTFromTm(struct tm &tm) const
+{
+#if defined(_MSC_VER) && _MSC_VER >= 1400
+  return _mkgmtime(&tm);
+#else
+  // From Linux timegm() manpage.
+
+  std::string tz_old = "TZ=";
+  if (const char* tz = cmSystemTools::GetEnv("TZ"))
+    {
+    tz_old += tz;
+    }
+
+  // The standard says that "TZ=" or "TZ=[UNRECOGNIZED_TZ]" means UTC.
+  // It seems that "TZ=" does NOT work, at least under Windows
+  // with neither MSVC nor MinGW, so let's use explicit "TZ=UTC"
+
+  cmSystemTools::PutEnv("TZ=UTC");
+
+  tzset();
+
+  time_t result = mktime(&tm);
+
+  cmSystemTools::PutEnv(tz_old);
+
+  tzset();
+
+  return result;
+#endif
+}
+
 //----------------------------------------------------------------------------
 std::string cmTimestamp::AddTimestampComponent(
-  char flag, struct tm& timeStruct)
+  char flag, struct tm& timeStruct, const time_t timeT) const
 {
   std::string formatString = "%";
   formatString += flag;
@@ -117,6 +151,26 @@ std::string cmTimestamp::AddTimestampComponent(
     case 'y':
     case 'Y':
       break;
+    case 's': // Seconds since UNIX epoch (midnight 1-jan-1970)
+      {
+      // Build a time_t for UNIX epoch and substract from the input "timeT":
+      struct tm tmUnixEpoch;
+      memset(&tmUnixEpoch, 0, sizeof(tmUnixEpoch));
+      tmUnixEpoch.tm_mday = 1;
+      tmUnixEpoch.tm_year = 1970-1900;
+
+      const time_t unixEpoch = this->CreateUtcTimeTFromTm(tmUnixEpoch);
+      if (unixEpoch == -1)
+        {
+        cmSystemTools::Error("Error generating UNIX epoch in "
+          "STRING(TIMESTAMP ...). Please, file a bug report aginst CMake");
+        return std::string();
+        }
+
+      std::stringstream ss;
+      ss << static_cast<long int>(difftime(timeT, unixEpoch));
+      return ss.str();
+      }
     default:
       {
       return formatString;

+ 7 - 4
Source/cmTimestamp.h

@@ -16,7 +16,7 @@
 #include <time.h>
 
 /** \class cmTimestamp
- * \brief Utility class to generate sting representation of a timestamp
+ * \brief Utility class to generate string representation of a timestamp
  *
  */
 class cmTimestamp
@@ -30,10 +30,13 @@ public:
     const std::string& formatString, bool utcFlag);
 
 private:
-  std::string CreateTimestampFromTimeT(time_t timeT,
-      std::string formatString, bool utcFlag);
+  time_t CreateUtcTimeTFromTm(struct tm& timeStruct) const;
 
-  std::string AddTimestampComponent(char flag, struct tm& timeStruct);
+  std::string CreateTimestampFromTimeT(
+    time_t timeT, std::string formatString, bool utcFlag) const;
+
+  std::string AddTimestampComponent(
+    char flag, struct tm& timeStruct, time_t timeT) const;
 };
 
 

+ 22 - 0
Tests/CMakeTests/String-TIMESTAMP-UnixTime.cmake

@@ -0,0 +1,22 @@
+string(TIMESTAMP timestamp "[%Y-%m-%d %H:%M:%S] %s" UTC)
+
+string(TIMESTAMP unix_time "%s")
+
+string(TIMESTAMP year "%Y" UTC)
+string(TIMESTAMP days "%j" UTC)
+
+# Doing proper date calculations here to verify unix timestamps
+# could be error prone.
+# At the very least use some safe lower and upper bounds to
+# see if we are somewhere in the right region.
+
+math(EXPR years_since_epoch "${year} - 1970")
+math(EXPR lower_bound "((${years_since_epoch} * 365) + ${days}) * 86400")
+math(EXPR upper_bound "((${years_since_epoch} * 366) + ${days}) * 86400")
+
+
+if(unix_time GREATER lower_bound AND unix_time LESS upper_bound)
+  message("~${unix_time}~")
+else()
+  message(FATAL_ERROR "${timestamp} unix time not in expected range [${lower_bound}, ${upper_bound}]")
+endif()

+ 6 - 0
Tests/CMakeTests/StringTest.cmake.in

@@ -36,6 +36,8 @@ set(TIMESTAMP-IncompleteSpecifier-RESULT 0)
 set(TIMESTAMP-IncompleteSpecifier-STDERR "~foobar%~")
 set(TIMESTAMP-AllSpecifiers-RESULT 0)
 set(TIMESTAMP-AllSpecifiers-STDERR "~[0-9]+(;[0-9]+)*~")
+set(TIMESTAMP-UnixTime-RESULT 0)
+set(TIMESTAMP-UnixTime-STDERR "~[1-9][0-9]+~")
 
 include("@CMAKE_CURRENT_SOURCE_DIR@/CheckCMakeTest.cmake")
 check_cmake_test(String
@@ -58,6 +60,7 @@ check_cmake_test(String
   TIMESTAMP-UnknownSpecifier
   TIMESTAMP-IncompleteSpecifier
   TIMESTAMP-AllSpecifiers
+  TIMESTAMP-UnixTime
   )
 
 # Execute each test listed in StringTestScript.cmake:
@@ -68,9 +71,12 @@ set(number_of_tests_expected 70)
 include("@CMAKE_CURRENT_SOURCE_DIR@/ExecuteScriptTests.cmake")
 execute_all_script_tests(${scriptname} number_of_tests_executed)
 
+string(TIMESTAMP timestamp "[%Y-%m-%d %H:%M:%S] UTC %s" UTC)
+
 # And verify that number_of_tests_executed is at least as many as we know
 # about as of this writing...
 #
+message(STATUS "timestamp='${timestamp}'")
 message(STATUS "scriptname='${scriptname}'")
 message(STATUS "number_of_tests_executed='${number_of_tests_executed}'")
 message(STATUS "number_of_tests_expected='${number_of_tests_expected}'")