Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow setting of the session start time and timestamp reference time #144

Merged
merged 8 commits into from
Jan 27, 2025
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
Loading