Skip to content

Commit e263eca

Browse files
authored
Introduce a small GMT time utility module (#1820)
Signed-off-by: Juan Cruz Viotti <jv@jviotti.com>
1 parent a38ad5f commit e263eca

File tree

11 files changed

+242
-1
lines changed

11 files changed

+242
-1
lines changed

.github/workflows/website-build.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ jobs:
1616
- run: >
1717
cmake -S . -B ./build
1818
-DCMAKE_BUILD_TYPE:STRING=Release
19+
-DSOURCEMETA_CORE_TIME:BOOL=OFF
1920
-DSOURCEMETA_CORE_UUID:BOOL=OFF
2021
-DSOURCEMETA_CORE_GZIP:BOOL=OFF
2122
-DSOURCEMETA_CORE_REGEX:BOOL=OFF

.github/workflows/website-deploy.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ jobs:
2626
- run: >
2727
cmake -S . -B ./build
2828
-DCMAKE_BUILD_TYPE:STRING=Release
29+
-DSOURCEMETA_CORE_TIME:BOOL=OFF
2930
-DSOURCEMETA_CORE_UUID:BOOL=OFF
3031
-DSOURCEMETA_CORE_GZIP:BOOL=OFF
3132
-DSOURCEMETA_CORE_REGEX:BOOL=OFF

CMakeLists.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ project(core VERSION 0.0.0 LANGUAGES C CXX DESCRIPTION "Sourcemeta Core")
33
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")
44

55
# Options
6+
option(SOURCEMETA_CORE_TIME "Build the Sourcemeta Core time library" ON)
67
option(SOURCEMETA_CORE_UUID "Build the Sourcemeta Core UUID library" ON)
78
option(SOURCEMETA_CORE_GZIP "Build the Sourcemeta Core GZIP library" ON)
89
option(SOURCEMETA_CORE_MD5 "Build the Sourcemeta Core MD5 library" ON)
@@ -50,6 +51,10 @@ if(SOURCEMETA_CORE_INSTALL)
5051
COMPONENT sourcemeta_${PROJECT_NAME}_dev)
5152
endif()
5253

54+
if(SOURCEMETA_CORE_TIME)
55+
add_subdirectory(src/core/time)
56+
endif()
57+
5358
if(SOURCEMETA_CORE_UUID)
5459
add_subdirectory(src/core/uuid)
5560
endif()
@@ -131,6 +136,10 @@ endif()
131136
if(SOURCEMETA_CORE_TESTS)
132137
enable_testing()
133138

139+
if(SOURCEMETA_CORE_TIME)
140+
add_subdirectory(test/time)
141+
endif()
142+
134143
if(SOURCEMETA_CORE_UUID)
135144
add_subdirectory(test/uuid)
136145
endif()

config.cmake.in

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
list(APPEND SOURCEMETA_CORE_COMPONENTS ${Core_FIND_COMPONENTS})
55
list(APPEND SOURCEMETA_CORE_COMPONENTS ${core_FIND_COMPONENTS})
66
if(NOT SOURCEMETA_CORE_COMPONENTS)
7+
list(APPEND SOURCEMETA_CORE_COMPONENTS time)
78
list(APPEND SOURCEMETA_CORE_COMPONENTS uuid)
89
list(APPEND SOURCEMETA_CORE_COMPONENTS gzip)
910
list(APPEND SOURCEMETA_CORE_COMPONENTS md5)
@@ -20,7 +21,9 @@ endif()
2021
include(CMakeFindDependencyMacro)
2122

2223
foreach(component ${SOURCEMETA_CORE_COMPONENTS})
23-
if(component STREQUAL "uuid")
24+
if(component STREQUAL "time")
25+
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_time.cmake")
26+
elseif(component STREQUAL "uuid")
2427
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_uuid.cmake")
2528
elseif(component STREQUAL "gzip")
2629
find_dependency(ZLIB CONFIG)

src/core/time/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME time SOURCES gmt.cc)
2+
3+
if(SOURCEMETA_CORE_INSTALL)
4+
sourcemeta_library_install(NAMESPACE sourcemeta PROJECT core NAME time)
5+
endif()

src/core/time/gmt.cc

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#include <sourcemeta/core/time.h>
2+
3+
#include <cassert> // assert
4+
#include <ctime> // std::time_t, std::tm, std::gmtime, std::mktime, timegm
5+
#include <iomanip> // std::put_time, std::get_time
6+
#include <sstream> // std::ostringstream, std::istringstream
7+
#include <stdexcept> // std::invalid_argument, std::runtime_error
8+
9+
#if defined(_MSC_VER)
10+
#include <errno.h>
11+
#endif
12+
13+
namespace {
14+
constexpr auto FORMAT_GMT{"%a, %d %b %Y %H:%M:%S GMT"};
15+
}
16+
17+
namespace sourcemeta::core {
18+
19+
auto to_gmt(const std::chrono::system_clock::time_point time) -> std::string {
20+
const std::time_t ctime = std::chrono::system_clock::to_time_t(time);
21+
#if defined(_MSC_VER)
22+
std::tm buffer;
23+
if (gmtime_s(&buffer, &ctime) != 0) {
24+
throw std::runtime_error("Could not convert time point to GMT");
25+
}
26+
27+
std::tm *parts = &buffer;
28+
#else
29+
std::tm *parts = std::gmtime(&ctime);
30+
#endif
31+
assert(parts);
32+
std::ostringstream stream;
33+
stream << std::put_time(parts, FORMAT_GMT);
34+
return stream.str();
35+
}
36+
37+
auto from_gmt(const std::string &time)
38+
-> std::chrono::system_clock::time_point {
39+
std::istringstream stream{time};
40+
std::tm parts = {};
41+
stream >> std::get_time(&parts, FORMAT_GMT);
42+
if (stream.fail()) {
43+
throw std::invalid_argument("Invalid GMT timestamp");
44+
}
45+
46+
#if defined(_MSC_VER)
47+
return std::chrono::system_clock::from_time_t(_mkgmtime(&parts));
48+
#else
49+
return std::chrono::system_clock::from_time_t(timegm(&parts));
50+
#endif
51+
}
52+
53+
} // namespace sourcemeta::core
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
#ifndef SOURCEMETA_CORE_TIME_H_
2+
#define SOURCEMETA_CORE_TIME_H_
3+
4+
#ifndef SOURCEMETA_CORE_TIME_EXPORT
5+
#include <sourcemeta/core/time_export.h>
6+
#endif
7+
8+
#include <chrono> // std::chrono::system_clock::time_point
9+
#include <string> // std::string
10+
11+
/// @defgroup time Time
12+
/// @brief A growing implementation of time-related utilities for standard such
13+
/// as RFC 7231 (GMT).
14+
///
15+
/// This functionality is included as follows:
16+
///
17+
/// ```cpp
18+
/// #include <sourcemeta/core/time.h>
19+
/// ```
20+
21+
namespace sourcemeta::core {
22+
23+
/// @ingroup time
24+
/// Convert a time point into a GMT string. For example:
25+
///
26+
/// ```cpp
27+
/// #include <sourcemeta/core/time.h>
28+
///
29+
/// #include <chrono>
30+
/// #include <ctime>
31+
/// #include <cassert>
32+
///
33+
/// std::tm parts = {};
34+
/// parts.tm_year = 115;
35+
/// parts.tm_mon = 9;
36+
/// parts.tm_mday = 21;
37+
/// parts.tm_hour = 11;
38+
/// parts.tm_min = 28;
39+
/// parts.tm_sec = 0;
40+
/// parts.tm_isdst = 0;
41+
///
42+
/// const auto point{std::chrono::system_clock::from_time_t(timegm(&parts))};
43+
///
44+
/// assert(sourcemeta::core::to_gmt(point) ==
45+
/// "Wed, 21 Oct 2015 11:28:00 GMT");
46+
/// ```
47+
///
48+
/// On Windows, you might need to use `_mkgmtime` instead of `timegm`.
49+
SOURCEMETA_CORE_TIME_EXPORT
50+
auto to_gmt(const std::chrono::system_clock::time_point time) -> std::string;
51+
52+
/// @ingroup time
53+
/// Parse a GMT string into a time point. For example:
54+
///
55+
/// ```cpp
56+
/// #include <sourcemeta/core/time.h>
57+
///
58+
/// #include <chrono>
59+
/// #include <ctime>
60+
/// #include <cassert>
61+
///
62+
/// const auto point{
63+
/// sourcemeta::core::from_gmt("Wed, 21 Oct 2015 11:28:00 GMT")};
64+
///
65+
/// std::tm parts = {};
66+
/// parts.tm_year = 115;
67+
/// parts.tm_mon = 9;
68+
/// parts.tm_mday = 21;
69+
/// parts.tm_hour = 11;
70+
/// parts.tm_min = 28;
71+
/// parts.tm_sec = 0;
72+
/// parts.tm_isdst = 0;
73+
/// const auto expected{std::chrono::system_clock::from_time_t(timegm(&parts))};
74+
///
75+
/// assert(point = expected);
76+
/// ```
77+
///
78+
/// On Windows, you might need to use `_mkgmtime` instead of `timegm`.
79+
SOURCEMETA_CORE_TIME_EXPORT
80+
auto from_gmt(const std::string &time) -> std::chrono::system_clock::time_point;
81+
82+
} // namespace sourcemeta::core
83+
84+
#endif

test/packaging/find_package/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
55
set(CMAKE_CXX_EXTENSIONS OFF)
66
find_package(Core REQUIRED)
77
add_executable(core_hello hello.cc)
8+
target_link_libraries(core_hello PRIVATE sourcemeta::core::time)
89
target_link_libraries(core_hello PRIVATE sourcemeta::core::uuid)
910
target_link_libraries(core_hello PRIVATE sourcemeta::core::gzip)
1011
target_link_libraries(core_hello PRIVATE sourcemeta::core::md5)

test/packaging/find_package/hello.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include <sourcemeta/core/jsonpointer.h>
66
#include <sourcemeta/core/jsonschema.h>
77
#include <sourcemeta/core/md5.h>
8+
#include <sourcemeta/core/time.h>
89
#include <sourcemeta/core/uri.h>
910
#include <sourcemeta/core/uuid.h>
1011
#include <sourcemeta/core/yaml.h>

test/time/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
sourcemeta_googletest(NAMESPACE sourcemeta PROJECT core NAME time
2+
SOURCES gmt_test.cc)
3+
4+
target_link_libraries(sourcemeta_core_time_unit
5+
PRIVATE sourcemeta::core::time)

0 commit comments

Comments
 (0)