aqnwb 0.4.0
Loading...
Searching...
No Matches
Utils.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm>
4#include <array>
5#include <chrono>
6#include <cmath>
7#include <cstdint>
8#include <ctime>
9#include <iomanip>
10#include <random>
11#include <regex>
12#include <sstream>
13#include <string>
14
15#include "Types.hpp"
16#include "io/BaseIO.hpp"
17#include "io/hdf5/HDF5IO.hpp"
18
19namespace AQNWB
20{
21namespace detail
22{
28inline std::tm to_local_time(std::time_t time_value)
29{
30 std::tm local_tm {};
31#if defined(_WIN32)
32 localtime_s(&local_tm, &time_value);
33#elif defined(__unix__) || defined(__APPLE__)
34 localtime_r(&time_value, &local_tm);
35#else
36 const std::tm* local_tm_ptr = std::localtime(&time_value);
37 if (local_tm_ptr) {
38 local_tm = *local_tm_ptr;
39 }
40#endif
41 return local_tm;
42}
43
49inline std::tm to_utc_time(std::time_t time_value)
50{
51 std::tm utc_tm {};
52#if defined(_WIN32)
53 gmtime_s(&utc_tm, &time_value);
54#elif defined(__unix__) || defined(__APPLE__)
55 gmtime_r(&time_value, &utc_tm);
56#else
57 const std::tm* utc_tm_ptr = std::gmtime(&time_value);
58 if (utc_tm_ptr) {
59 utc_tm = *utc_tm_ptr;
60 }
61#endif
62 return utc_tm;
63}
64
70inline long get_utc_offset_seconds(std::time_t time_value)
71{
72#if defined(__unix__) || defined(__APPLE__)
73 std::tm local_tm = to_local_time(time_value);
74 return local_tm.tm_gmtoff;
75#elif defined(_WIN32)
76 long tz_seconds = 0;
77 _get_timezone(&tz_seconds);
78 std::tm local_tm = to_local_time(time_value);
79 if (local_tm.tm_isdst > 0) {
80 long dstbias = 0;
81 _get_dstbias(&dstbias);
82 tz_seconds += dstbias;
83 }
84 return -tz_seconds;
85#else
86 // Fallback: force utc_tm.tm_isdst to match local so mktime treats both
87 // consistently, avoiding the DST double-count.
88 std::tm local_tm = to_local_time(time_value);
89 std::tm utc_tm = to_utc_time(time_value);
90 utc_tm.tm_isdst = local_tm.tm_isdst;
91 std::time_t local_time = std::mktime(&local_tm);
92 std::time_t utc_time = std::mktime(&utc_tm);
93 if (local_time == static_cast<std::time_t>(-1)
94 || utc_time == static_cast<std::time_t>(-1))
95 {
96 return 0;
97 }
98 return static_cast<long>(std::difftime(local_time, utc_time));
99#endif
100}
101
107inline std::string format_utc_offset(long offset_seconds)
108{
109 const char sign = (offset_seconds < 0) ? '-' : '+';
110 long abs_offset = (offset_seconds < 0) ? -offset_seconds : offset_seconds;
111 long hours = abs_offset / 3600;
112 long minutes = (abs_offset % 3600) / 60;
113
114 std::ostringstream oss;
115 oss << sign << std::setw(2) << std::setfill('0') << hours << ':'
116 << std::setw(2) << std::setfill('0') << minutes;
117 return oss.str();
118}
119
125inline uint16_t to_little_endian_u16(uint16_t value)
126{
127#if defined(_WIN32) \
128 || (defined(__BYTE_ORDER__) \
129 && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__))
130 return value;
131#else
132 return static_cast<uint16_t>((value >> 8) | (value << 8));
133#endif
134}
135} // namespace detail
136
141static inline std::string generateUuid()
142{
143 std::array<uint8_t, 16> bytes {};
144 std::random_device rd;
145 std::mt19937 gen(rd());
146 std::uniform_int_distribution<uint32_t> dist(0, 0xFFFFFFFF);
147
148 for (size_t i = 0; i < bytes.size(); i += 4) {
149 uint32_t random_value = dist(gen);
150 bytes[i] = static_cast<uint8_t>(random_value & 0xFF);
151 bytes[i + 1] = static_cast<uint8_t>((random_value >> 8) & 0xFF);
152 bytes[i + 2] = static_cast<uint8_t>((random_value >> 16) & 0xFF);
153 bytes[i + 3] = static_cast<uint8_t>((random_value >> 24) & 0xFF);
154 }
155
156 // RFC 4122 version 4 UUID.
157 bytes[6] = static_cast<uint8_t>((bytes[6] & 0x0F) | 0x40);
158 bytes[8] = static_cast<uint8_t>((bytes[8] & 0x3F) | 0x80);
159
160 std::ostringstream oss;
161 oss << std::hex << std::nouppercase << std::setfill('0');
162 for (size_t i = 0; i < bytes.size(); ++i) {
163 if (i == 4 || i == 6 || i == 8 || i == 10) {
164 oss << '-';
165 }
166 oss << std::setw(2) << static_cast<int>(bytes[i]);
167 }
168 return oss.str();
169}
170
175static inline std::string getCurrentTime()
176{
177 auto now = std::chrono::system_clock::now();
178 auto seconds = std::chrono::time_point_cast<std::chrono::seconds>(now);
179 auto micros =
180 std::chrono::duration_cast<std::chrono::microseconds>(now - seconds)
181 .count();
182 std::time_t time_value = std::chrono::system_clock::to_time_t(seconds);
183 std::tm local_tm = detail::to_local_time(time_value);
184 long offset_seconds = detail::get_utc_offset_seconds(time_value);
185
186 std::ostringstream oss;
187 oss << std::put_time(&local_tm, "%Y-%m-%dT%H:%M:%S");
188 oss << '.' << std::setw(6) << std::setfill('0') << micros;
189 oss << detail::format_utc_offset(offset_seconds);
190 return oss.str();
191}
192
202static inline bool isISO8601Date(const std::string& dateStr)
203{
204 // Require extended date/time fields and a UTC or numeric timezone designator.
205 // Fractional seconds and the colon in numeric offsets are optional.
206 const std::string iso8601Pattern =
207 R"(^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:?\d{2})$)";
208 std::regex pattern(iso8601Pattern);
209
210 // Check if the date string matches the regex pattern
211 return std::regex_match(dateStr, pattern);
212}
213
221static inline std::shared_ptr<IO::BaseIO> createIO(const std::string& type,
222 const std::string& filename)
223{
224 if (type == "HDF5") {
225 return std::make_shared<AQNWB::IO::HDF5::HDF5IO>(filename);
226 } else {
227 throw std::invalid_argument("Invalid IO type");
228 }
229}
230
241static inline bool isPathOrDescendant(const std::string& path,
242 const std::string& parentPath)
243{
244 if (parentPath == "/") {
245 return !path.empty() && path.front() == '/';
246 }
247 return path.compare(0, parentPath.size(), parentPath) == 0
248 && (path.size() == parentPath.size() || path[parentPath.size()] == '/');
249}
250
264static inline std::string mergePaths(const std::string& path1,
265 const std::string& path2)
266{
267 std::string result = path1;
268 // Remove trailing "/" from path1
269 while (!result.empty() && result.back() == '/' && result != "/") {
270 result.pop_back();
271 }
272 // Remove leading "/" from path2
273 size_t start = 0;
274 while (start < path2.size() && path2[start] == '/') {
275 start++;
276 }
277 // Get path2 without trailing slashes
278 std::string path2Clean = path2.substr(start);
279 while (!path2Clean.empty() && path2Clean.back() == '/' && path2Clean != "/") {
280 path2Clean.pop_back();
281 }
282 // Append path2 to path1 with a "/" in between
283 if (!result.empty() && !path2Clean.empty()) {
284 result += '/';
285 }
286 result += path2Clean;
287
288 // Remove any potential occurrences of "//" and replace with "/"
289 size_t pos = result.find("//");
290 while (pos != std::string::npos) {
291 result.replace(pos, 2, "/");
292 pos = result.find("//", pos);
293 }
294
295 // Remove trailing "/" from final result if not root path
296 while (!result.empty() && result.back() == '/' && result != "/") {
297 result.pop_back();
298 }
299
300 return result;
301}
302
311static inline void convertFloatToInt16LE(const float* source,
312 void* dest,
313 SizeType numSamples)
314{
315 // TODO - several steps in this function may be unnecessary for our use
316 // case. Consider simplifying the intermediate cast to char and the
317 // final cast to uint16_t.
318 auto maxVal = static_cast<double>(0x7fff);
319 auto intData = static_cast<char*>(dest);
320
321 for (SizeType i = 0; i < numSamples; ++i) {
322 auto clampedValue =
323 std::clamp(maxVal * static_cast<double>(source[i]), -maxVal, maxVal);
324 auto intValue =
325 static_cast<uint16_t>(static_cast<int16_t>(std::round(clampedValue)));
326 intValue = detail::to_little_endian_u16(intValue);
327 *reinterpret_cast<uint16_t*>(intData) = intValue;
328 intData += 2; // destBytesPerSample is always 2
329 }
330}
331
338static inline std::unique_ptr<int16_t[]> transformToInt16(
339 SizeType numSamples, float conversion_factor, const float* data)
340{
341 std::unique_ptr<float[]> scaledData = std::make_unique<float[]>(numSamples);
342 std::unique_ptr<int16_t[]> intData = std::make_unique<int16_t[]>(numSamples);
343
344 // copy data and multiply by scaling factor
345 float multFactor = 1.0f / (32767.0f * conversion_factor);
346 std::transform(data,
347 data + numSamples,
348 scaledData.get(),
349 [multFactor](float value) { return value * multFactor; });
350
351 // convert float to int16
352 convertFloatToInt16LE(scaledData.get(), intData.get(), numSamples);
353
354 return intData;
355}
356
362static inline bool isValidIndex(SizeType index)
363{
364 return (index != AQNWB::Types::SizeTypeNotSet);
365}
366
373static inline Status intToStatus(int status)
374{
375 return (status < 0) ? Status::Failure : Status::Success;
376}
377
383static inline void checkStatus(Status status, const std::string& operation)
384{
385 if (status != Status::Success) {
386 std::cerr << operation << " failed" << std::endl;
387 }
388}
389
398 const Types::CellValue& value)
399{
400 return std::visit(
401 [&value](auto& vec) -> Status
402 {
403 using VecType = std::decay_t<decltype(vec)>;
404 if constexpr (std::is_same_v<VecType, std::monostate>) {
405 return Status::Failure;
406 } else {
407 using ElementType = typename VecType::value_type;
408 if (value.holds_alternative<ElementType>()) {
409 vec.push_back(value.get<ElementType>());
410 return Status::Success;
411 } else if (value.holds_alternative<std::vector<ElementType>>()) {
412 const auto& vals = value.get<std::vector<ElementType>>();
413 vec.insert(vec.end(), vals.begin(), vals.end());
414 return Status::Success;
415 }
416 return Status::Failure;
417 }
418 },
419 buffer);
420}
421
422} // namespace AQNWB
AQNWB::Types::Status Status
Definition BaseIO.hpp:21
AQNWB::Types::SizeType SizeType
Definition Channel.hpp:8
AQNWB::Types::VectorDataVariant BaseDataVectorVariant
Definition BaseIO.hpp:104
constexpr SizeType SizeTypeNotSet
Value to use to indicate that a SizeType index is not set.
Definition Types.hpp:109
Definition Utils.hpp:22
std::tm to_utc_time(std::time_t time_value)
Convert a std::time_t value to a UTC std::tm structure.
Definition Utils.hpp:49
std::tm to_local_time(std::time_t time_value)
Convert a std::time_t value to a local std::tm structure.
Definition Utils.hpp:28
std::string format_utc_offset(long offset_seconds)
Format a UTC offset in seconds as a string (+HH:MM or -HH:MM).
Definition Utils.hpp:107
uint16_t to_little_endian_u16(uint16_t value)
Convert a 16-bit unsigned integer to little-endian byte order.
Definition Utils.hpp:125
long get_utc_offset_seconds(std::time_t time_value)
Get the UTC offset in seconds for a given time_t value.
Definition Utils.hpp:70
The main namespace for AqNWB.
Definition Channel.hpp:11
static Status intToStatus(int status)
Convert an integer status code to a Types::Status enum value. Shorthand for return (status < 0) ?...
Definition Utils.hpp:373
static std::unique_ptr< int16_t[]> transformToInt16(SizeType numSamples, float conversion_factor, const float *data)
Method to scale float values and convert to int16 values.
Definition Utils.hpp:338
static void convertFloatToInt16LE(const float *source, void *dest, SizeType numSamples)
Method to convert float values to uint16 values. This method was adapted from JUCE AudioDataConverter...
Definition Utils.hpp:311
static std::string mergePaths(const std::string &path1, const std::string &path2)
Merge two paths into a single path, handling extra trailing and starting "/".
Definition Utils.hpp:264
static bool isISO8601Date(const std::string &dateStr)
Check that a string is formatted as an ISO 8601 datetime.
Definition Utils.hpp:202
static std::string generateUuid()
Generates a UUID (Universally Unique Identifier) as a string.
Definition Utils.hpp:141
static void checkStatus(Status status, const std::string &operation)
Check status and print to standard error.
Definition Utils.hpp:383
static std::shared_ptr< IO::BaseIO > createIO(const std::string &type, const std::string &filename)
Factory method to create an IO object of the specified type.
Definition Utils.hpp:221
static Status appendCellValueToBuffer(IO::BaseDataType::BaseDataVectorVariant &buffer, const Types::CellValue &value)
Appends a CellValue to a BaseDataVectorVariant buffer.
Definition Utils.hpp:396
static std::string getCurrentTime()
Get the current time in ISO 8601 format with the UTC offset.
Definition Utils.hpp:175
static bool isValidIndex(SizeType index)
Check if a SizeType index is valid (i.e., not equal to SizeTypeNotSet).
Definition Utils.hpp:362
static bool isPathOrDescendant(const std::string &path, const std::string &parentPath)
Check whether a path is equal to or a descendant of a parent path.
Definition Utils.hpp:241
Represents a single cell value in a DynamicTable row.
Definition Types.hpp:190
const T & get() const
Helper method to extract the underlying value.
Definition Types.hpp:276
bool holds_alternative() const
Checks if the CellValue holds a specific type.
Definition Types.hpp:249