Skip to content

Commit

Permalink
Merge branch 'main' into enh_unit_coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
oruebel authored Jan 27, 2025
2 parents ac7e3c1 + 00a529c commit 52b8e44
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 9 deletions.
21 changes: 21 additions & 0 deletions src/Utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <cstdint>
#include <ctime>
#include <iomanip>
#include <regex>
#include <sstream>
#include <string>

Expand Down Expand Up @@ -61,6 +62,26 @@ static inline std::string getCurrentTime()
return currentTime;
}

/**
* @brief Check that a string is formatted in ISO8601 format
*
* This function only validates the regex pattern but does not check that
* the time values specified are indeed valid.
*
* @return bool indicating whether the string is in ISO8601 form
*/
static inline bool isISO8601Date(const std::string& dateStr)
{
// Define the regex pattern for ISO 8601 extended format with timezone offset
// Allow one or more fractional seconds digits
const std::string iso8601Pattern =
R"(^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+[+-]\d{2}:\d{2}$)";
std::regex pattern(iso8601Pattern);

// Check if the date string matches the regex pattern
return std::regex_match(dateStr, pattern);
}

/**
* @brief Factory method to create an IO object.
* @return A pointer to a BaseIO object
Expand Down
43 changes: 36 additions & 7 deletions src/nwb/NWBFile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,42 @@ NWBFile::~NWBFile() {}

Status NWBFile::initialize(const std::string& identifierText,
const std::string& description,
const std::string& dataCollection)
const std::string& dataCollection,
const std::string& sessionStartTime,
const std::string& timestampsReferenceTime)
{
if (!m_io->isOpen()) {
return Status::Failure;
}
std::string currentTime = getCurrentTime();
// use the current time if sessionStartTime is empty
std::string useSessionStartTime =
(!sessionStartTime.empty()) ? sessionStartTime : currentTime;
// use the current time if timestampsReferenceTime is empty
std::string useTimestampsReferenceTime = (!timestampsReferenceTime.empty())
? timestampsReferenceTime
: currentTime;
// check that sessionStartTime and timestampsReferenceTime are ISO8601
if (!isISO8601Date(useSessionStartTime)) {
std::cerr << "NWBFile::initialize sessionStartTime not in ISO8601 format: "
<< useSessionStartTime << std::endl;
return Status::Failure;
}
if (!isISO8601Date(useTimestampsReferenceTime)) {
std::cerr
<< "NWBFile::initialize timestampsReferenceTime not in ISO8601 format: "
<< useTimestampsReferenceTime << std::endl;
return Status::Failure;
}

// Check that the file is empty and initialize if it is
bool fileInitialized = isInitialized();
if (!fileInitialized) {
return createFileStructure(identifierText, description, dataCollection);
return createFileStructure(identifierText,
description,
dataCollection,
useSessionStartTime,
useTimestampsReferenceTime);
} else {
return Status::Success; // File is already initialized
}
Expand Down Expand Up @@ -102,7 +129,9 @@ Status NWBFile::finalize()

Status NWBFile::createFileStructure(const std::string& identifierText,
const std::string& description,
const std::string& dataCollection)
const std::string& dataCollection,
const std::string& sessionStartTime,
const std::string& timestampsReferenceTime)
{
if (!m_io->canModifyObjects()) {
return Status::Failure;
Expand Down Expand Up @@ -133,12 +162,12 @@ Status NWBFile::createFileStructure(const std::string& identifierText,
cacheSpecifications("hdmf-experimental",
AQNWB::SPEC::HDMF_EXPERIMENTAL::version,
AQNWB::SPEC::HDMF_EXPERIMENTAL::specVariables);
std::string time = getCurrentTime();
std::vector<std::string> timeVec = {time};
std::vector<std::string> timeVec = {sessionStartTime};
m_io->createStringDataSet("/file_create_date", timeVec);
m_io->createStringDataSet("/session_description", description);
m_io->createStringDataSet("/session_start_time", time);
m_io->createStringDataSet("/timestamps_reference_time", time);
m_io->createStringDataSet("/session_start_time", sessionStartTime);
m_io->createStringDataSet("/timestamps_reference_time",
timestampsReferenceTime);
m_io->createStringDataSet("/identifier", identifierText);
return Status::Success;
}
Expand Down
16 changes: 14 additions & 2 deletions src/nwb/NWBFile.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,16 @@ class NWBFile : public Container
* @param identifierText The identifier text for the NWBFile.
* @param description A description of the NWBFile session.
* @param dataCollection Information about the data collection methods.
* @param sessionStartTime ISO formatted time string with the session start
* time. If empty (default), then the getCurrentTime() will be used.
* @param timestampsReferenceTime ISO formatted time string with the timestamp
* reference time. If empty (default), then the getCurrentTime() will be used.
*/
Status initialize(const std::string& identifierText,
const std::string& description = "a recording session",
const std::string& dataCollection = "");
const std::string& dataCollection = "",
const std::string& sessionStartTime = "",
const std::string& timestampsReferenceTime = "");

/**
* @brief Check if the NWB file is initialized.
Expand Down Expand Up @@ -155,11 +161,17 @@ class NWBFile : public Container
* @param identifierText The identifier text for the NWBFile.
* @param description A description of the NWBFile session.
* @param dataCollection Information about the data collection methods.
* @param sessionStartTime ISO formatted time string with the session start
* time
* @param timestampsReferenceTime ISO formatted time string with the timestamp
* reference time
* @return Status The status of the file structure creation.
*/
Status createFileStructure(const std::string& identifierText,
const std::string& description,
const std::string& dataCollection);
const std::string& dataCollection,
const std::string& sessionStartTime,
const std::string& timestampsReferenceTime);

private:
/**
Expand Down
33 changes: 33 additions & 0 deletions tests/testNWBFile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,39 @@ TEST_CASE("saveNWBFile", "[nwb]")
nwbfile.finalize();
}

TEST_CASE("initialize", "[nwb]")
{
std::string filename = getTestFilePath("testInitializeNWBFile.nwb");

// initialize nwbfile object and create base structure
auto io = std::make_shared<IO::HDF5::HDF5IO>(filename);
io->open();
NWB::NWBFile nwbfile(io);

// bad session start time
Status initStatus = nwbfile.initialize(generateUuid(),
"test file",
"no collection",
"bad time",
AQNWB::getCurrentTime());
REQUIRE(initStatus == Status::Failure);

// bad timestamp reference time
initStatus = nwbfile.initialize(generateUuid(),
"test file",
"no collection",
AQNWB::getCurrentTime(),
"bad time");
REQUIRE(initStatus == Status::Failure);

// check that regular init with current times works
initStatus = nwbfile.initialize(generateUuid());
REQUIRE(initStatus == Status::Success);
REQUIRE(nwbfile.isInitialized());
nwbfile.finalize();
io->close();
}

TEST_CASE("createElectricalSeries", "[nwb]")
{
std::string filename = getTestFilePath("createElectricalSeries.nwb");
Expand Down
31 changes: 31 additions & 0 deletions tests/testUtilsFunctions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,37 @@

#include "Utils.hpp"

TEST_CASE("isISO8601Date function tests", "[utils]")
{
SECTION("Valid ISO 8601 date strings")
{
REQUIRE(AQNWB::isISO8601Date("2018-09-28T14:43:54.123+02:00"));
REQUIRE(AQNWB::isISO8601Date("2025-01-19T00:40:03.214144-08:00"));
REQUIRE(AQNWB::isISO8601Date("2021-12-31T23:59:59.999999+00:00"));
REQUIRE(AQNWB::isISO8601Date("2000-01-01T00:00:00.0+01:00"));
REQUIRE(AQNWB::isISO8601Date(
"2018-09-28T14:43:54.12345+02:00")); // Allow for too many fractional
// seconds
}

SECTION("Invalid ISO 8601 date strings")
{
REQUIRE_FALSE(AQNWB::isISO8601Date(
"2018-09-28 14:43:54.123+02:00")); // Space instead of 'T'
REQUIRE_FALSE(AQNWB::isISO8601Date(
"2018-09-28T14:43:54+02:00")); // Missing fractional seconds
REQUIRE_FALSE(AQNWB::isISO8601Date(
"2018-09-28T14:43:54.123+0200")); // Missing colon in timezone
REQUIRE_FALSE(AQNWB::isISO8601Date(
"2018-09-28T14:43:54.123Z")); // Missing timezone offset
REQUIRE_FALSE(AQNWB::isISO8601Date(
"2018-09-28T14:43:54.123-0800")); // Incorrect timezone format
REQUIRE_FALSE(
AQNWB::isISO8601Date("2018-09-28T14:43:54.123")); // Missing timezone
REQUIRE_FALSE(AQNWB::isISO8601Date("Random text 1213"));
}
}

TEST_CASE("Test UUID generation", "[utils]")
{
// Test that generated UUIDs are valid
Expand Down

0 comments on commit 52b8e44

Please sign in to comment.