feat: datetime,uuid

This commit is contained in:
2026-01-27 14:33:19 +01:00
parent 9e3dd6dd1d
commit e517163d23
8 changed files with 18298 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/.cache
/build

39
CMakeLists.txt Normal file
View File

@@ -0,0 +1,39 @@
cmake_minimum_required(VERSION 3.30)
project(cpputils LANGUAGES CXX)
option(BUILD_SHARED_LIBS "Build cpputils as a shared library" OFF)
option(BUILD_TESTS "Build cpputils tests" OFF)
if(BUILD_SHARED_LIBS)
add_library(cpputils SHARED)
else()
add_library(cpputils STATIC)
endif()
set_target_properties(cpputils PROPERTIES
POSITION_INDEPENDENT_CODE ON)
target_sources(cpputils
PRIVATE
src/uuid.cpp
)
target_include_directories(cpputils
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>)
target_compile_features(cpputils PUBLIC cxx_std_17)
if(BUILD_TESTS)
add_executable(cpputils_tests
tests/test_main.cpp
tests/test_uuid.cpp)
target_include_directories(cpputils_tests PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/tests)
target_link_libraries(cpputils_tests PRIVATE cpputils)
target_compile_features(cpputils_tests PRIVATE cxx_std_17)
endif()

View File

@@ -0,0 +1,65 @@
#pragma once
#include <chrono>
#include <cstdint>
#include <iomanip>
#include <sstream>
#include <string>
namespace cpputils {
namespace datetime {
inline auto print_iso8601_utc(std::chrono::system_clock::time_point tp)
-> std::string {
time_t t = std::chrono::system_clock::to_time_t(tp);
std::tm utc_tm = *std::gmtime(&t);
std::ostringstream oss;
oss << std::put_time(&utc_tm, "%Y-%m-%dT%H:%M:%SZ");
return oss.str();
};
inline auto print_iso8601_local(std::chrono::system_clock::time_point tp)
-> std::string {
std::time_t time = std::chrono::system_clock::to_time_t(tp);
std::tm local_tm = *std::localtime(&time);
std::ostringstream oss;
oss << std::put_time(&local_tm, "%Y-%m-%dT%H:%M:%S");
return oss.str();
}
inline int64_t now() {
return static_cast<int64_t>(
std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch())
.count());
}
inline std::chrono::system_clock::time_point to_time_point(
int64_t timestamp_ms) {
return std::chrono::system_clock::time_point(
std::chrono::milliseconds(timestamp_ms));
}
inline int64_t to_timestamp(std::chrono::system_clock::time_point tp) {
return std::chrono::duration_cast<std::chrono::milliseconds>(
tp.time_since_epoch())
.count();
}
inline int64_t to_timestamp_seconds(std::chrono::system_clock::time_point tp) {
return std::chrono::duration_cast<std::chrono::seconds>(tp.time_since_epoch())
.count();
}
template <typename Duration>
inline int64_t add_to_timestamp(int64_t timestamp, Duration duration) {
auto time_point = std::chrono::system_clock::time_point(
std::chrono::milliseconds(timestamp));
time_point += duration;
return std::chrono::duration_cast<std::chrono::milliseconds>(
time_point.time_since_epoch())
.count();
}
} // namespace datetime
} // namespace cpputils

53
include/cpputils/uuid.h Normal file
View File

@@ -0,0 +1,53 @@
#pragma once
#include <array>
#include <atomic>
#include <cstdint>
#include <random>
#include <string>
namespace cpputils {
namespace uuid {
extern std::string uuidToString(std::array<uint8_t, 16> const& uuid) noexcept;
class V4 {
private:
std::mt19937_64 rng;
inline uint64_t random64() { return rng(); }
public:
inline V4() : rng(std::random_device{}()) {}
std::array<uint8_t, 16> generate();
};
class V7 {
private:
std::atomic<int64_t> last_timestamp{0};
std::atomic<uint8_t> sequence{0};
std::mt19937_64 rng;
inline uint64_t random64() { return rng(); }
uint8_t next_sequence(int64_t current_timestamp);
public:
inline V7() : rng(std::random_device{}()) {}
static inline uint64_t get_timestamp_from_uuid(
std::array<uint8_t, 16> const& uuid) noexcept {
uint64_t timestamp = 0;
for (size_t i = 0; i < 6; ++i) {
timestamp = (timestamp << 8) | uuid[i];
}
return timestamp;
}
std::array<uint8_t, 16> generate();
};
} // namespace uuid
} // namespace cpputils

119
src/uuid.cpp Normal file
View File

@@ -0,0 +1,119 @@
#include <cpputils/datetime.h>
#include <cpputils/uuid.h>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <string>
using cpputils::uuid::V4;
using cpputils::uuid::V7;
std::string cpputils::uuid::uuidToString(
std::array<uint8_t, 16> const& uuid) noexcept {
static constexpr char hex[] = "0123456789abcdef";
std::string out(36, '\0');
size_t p = 0;
auto w = [&](size_t i) {
uint8_t b = uuid[i];
out[p++] = hex[b >> 4];
out[p++] = hex[b & 0xF];
};
w(0);
w(1);
w(2);
w(3);
out[p++] = '-';
w(4);
w(5);
out[p++] = '-';
w(6);
w(7);
out[p++] = '-';
w(8);
w(9);
out[p++] = '-';
w(10);
w(11);
w(12);
w(13);
w(14);
w(15);
return out;
}
std::array<uint8_t, 16> V4::generate() {
std::array<uint8_t, 16> uuid{};
uint64_t rand_a = random64();
uint64_t rand_b = random64();
uint64_t rand_c = random64();
// First 48 bits
uuid[0] = (rand_a >> 40) & 0xFF; // 0-7
uuid[1] = (rand_a >> 32) & 0xFF; // 8-15
uuid[2] = (rand_a >> 24) & 0xFF; // 16-23
uuid[3] = (rand_a >> 16) & 0xFF; // 24-31
uuid[4] = (rand_a >> 8) & 0xFF; // 32-39
uuid[5] = rand_a & 0xFF; // 40-47
uuid[6] = (rand_b) & 0x0F; // fill lower 4 bits with random
uuid[6] |= 0x40; // set top 4 bits to 0100
uuid[7] = (rand_b >> 8) & 0xFF;
uuid[8] = (rand_b >> 16) & 0xFF;
uuid[8] |= (0b10 << 6);
uuid[9] = (rand_c) & 0xFF;
uuid[10] = (rand_c >> 8) & 0xFF;
uuid[11] = (rand_c >> 16) & 0xFF;
uuid[12] = (rand_c >> 24) & 0xFF;
uuid[13] = (rand_c >> 32) & 0xFF;
uuid[14] = (rand_c >> 40) & 0xFF;
uuid[15] = (rand_c >> 48) & 0xFF;
return uuid;
}
uint8_t V7::next_sequence(int64_t current_timestamp) {
uint64_t prev_timestamp = last_timestamp.load(std::memory_order_relaxed);
if (current_timestamp > prev_timestamp) {
last_timestamp.store(current_timestamp, std::memory_order_relaxed);
sequence.store(0, std::memory_order_relaxed);
return 0;
} else {
return sequence.fetch_add(1, std::memory_order_relaxed) & 0xFF;
}
}
std::array<uint8_t, 16> V7::generate() {
std::array<uint8_t, 16> uuid{};
int64_t timestamp = cpputils::datetime::now();
uint8_t seq = next_sequence(timestamp);
uint64_t rand_a = random64();
uint64_t rand_b = random64();
// Encode timestamp (big-endian)
uuid[0] = (timestamp >> 40) & 0xFF;
uuid[1] = (timestamp >> 32) & 0xFF;
uuid[2] = (timestamp >> 24) & 0xFF;
uuid[3] = (timestamp >> 16) & 0xFF;
uuid[4] = (timestamp >> 8) & 0xFF;
uuid[5] = timestamp & 0xFF;
// Version 7 (UUIDv7)
uuid[6] = ((timestamp >> 4) & 0x0F) | 0x70;
uuid[7] = ((timestamp << 4) & 0xF0) | ((seq >> 8) & 0x0F);
// Variant 1 (RFC 4122 standard)
uuid[8] = (seq & 0xFF) | 0x80;
// Fill remaining bytes with randomness
for (size_t i = 9; i < 16; i++) {
uuid[i] = (rand_b >> ((15 - i) * 8)) & 0xFF;
}
return uuid;
}

17975
tests/catch.hpp Normal file

File diff suppressed because it is too large Load Diff

2
tests/test_main.cpp Normal file
View File

@@ -0,0 +1,2 @@
#define CATCH_CONFIG_MAIN
#include "catch.hpp"

43
tests/test_uuid.cpp Normal file
View File

@@ -0,0 +1,43 @@
#include "catch.hpp"
#include <cpputils/uuid.h>
#include <array>
TEST_CASE("uuidToString formats UUIDs correctly") {
struct TestVector {
std::array<uint8_t, 16> uuid;
char const* expected;
};
constexpr TestVector tests[] = {
{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00},
"00000000-0000-0000-0000-000000000000"},
{{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67,
0x89, 0xab, 0xcd, 0xef},
"01234567-89ab-cdef-0123-456789abcdef"},
{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff},
"ffffffff-ffff-ffff-ffff-ffffffffffff"},
{{0x55, 0x0e, 0x84, 0x00, 0xe2, 0x9b, 0x41, 0xd4, 0xa7, 0x16, 0x44, 0x66,
0x55, 0x44, 0x00, 0x00},
"550e8400-e29b-41d4-a716-446655440000"},
{{0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe, 0xba, 0xbe, 0x80, 0x00, 0x01, 0x23,
0x45, 0x67, 0x89, 0xab},
"deadbeef-cafe-babe-8000-0123456789ab"},
{{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
0x0c, 0x0d, 0x0e, 0x0f},
"00010203-0405-0607-0809-0a0b0c0d0e0f"},
};
for (auto const& t : tests) {
REQUIRE(cpputils::uuid::uuidToString(t.uuid) == t.expected);
}
}