From 9f8026828cd124aa3ea5fd84451e3153eec24fa6 Mon Sep 17 00:00:00 2001 From: Vincent Cloarec Date: Fri, 13 Dec 2019 04:15:44 -0400 Subject: [PATCH] time handling (#179) Introduce DateTime and RelativeTimestamp. Implemented reading of the reference/relative time for all formats we have testcases for. Changed API that it always returns UTF datetime. fixes https://github.com/lutraconsulting/MDAL/issues/177 --- mdal/CMakeLists.txt | 2 + mdal/api/mdal.h | 7 +- mdal/frmts/mdal_3di.cpp | 2 +- mdal/frmts/mdal_ascii_dat.cpp | 45 +- mdal/frmts/mdal_ascii_dat.hpp | 26 +- mdal/frmts/mdal_binary_dat.cpp | 34 +- mdal/frmts/mdal_binary_dat.hpp | 3 +- mdal/frmts/mdal_cf.cpp | 33 +- mdal/frmts/mdal_cf.hpp | 6 +- mdal/frmts/mdal_driver.cpp | 2 +- mdal/frmts/mdal_driver.hpp | 2 +- mdal/frmts/mdal_flo2d.cpp | 13 +- mdal/frmts/mdal_gdal.cpp | 10 +- mdal/frmts/mdal_gdal.hpp | 9 +- mdal/frmts/mdal_gdal_grib.cpp | 17 +- mdal/frmts/mdal_gdal_grib.hpp | 6 +- mdal/frmts/mdal_gdal_netcdf.cpp | 23 +- mdal/frmts/mdal_gdal_netcdf.hpp | 9 +- mdal/frmts/mdal_hec2d.cpp | 147 +++- mdal/frmts/mdal_hec2d.hpp | 11 +- mdal/frmts/mdal_selafin.cpp | 20 +- mdal/frmts/mdal_selafin.hpp | 8 +- mdal/frmts/mdal_sww.cpp | 4 +- mdal/frmts/mdal_xdmf.cpp | 15 +- mdal/frmts/mdal_xdmf.hpp | 13 +- mdal/frmts/mdal_xmdf.cpp | 28 +- mdal/frmts/mdal_xmdf.hpp | 2 - mdal/mdal.cpp | 9 +- mdal/mdal_data_model.cpp | 16 +- mdal/mdal_data_model.hpp | 14 +- mdal/mdal_datetime.cpp | 345 ++++++++ mdal/mdal_datetime.hpp | 118 +++ mdal/mdal_utils.cpp | 135 ++++ mdal/mdal_utils.hpp | 13 + tests/CMakeLists.txt | 1 + tests/data/datetime/julianDay.txt | 1000 ++++++++++++++++++++++++ tests/mdal_testutils.cpp | 15 + tests/mdal_testutils.hpp | 11 + tests/test_3di.cpp | 12 + tests/test_ascii_dat.cpp | 6 +- tests/test_binary_dat.cpp | 5 + tests/test_flo2d.cpp | 15 +- tests/test_gdal_grib.cpp | 27 +- tests/test_gdal_netcdf.cpp | 6 + tests/test_hec2d.cpp | 13 +- tests/test_selafin.cpp | 4 +- tests/test_sww.cpp | 5 + tests/test_tuflowfv.cpp | 18 +- tests/test_ugrid.cpp | 18 +- tests/test_xdmf.cpp | 24 +- tests/test_xmdf.cpp | 14 + tests/unittests/test_mdal_datetime.cpp | 114 +++ tests/unittests/test_mdal_utils.cpp | 35 + 53 files changed, 2228 insertions(+), 262 deletions(-) create mode 100644 mdal/mdal_datetime.cpp create mode 100644 mdal/mdal_datetime.hpp create mode 100644 tests/data/datetime/julianDay.txt create mode 100644 tests/unittests/test_mdal_datetime.cpp diff --git a/mdal/CMakeLists.txt b/mdal/CMakeLists.txt index c2f9d6f8..921747df 100644 --- a/mdal/CMakeLists.txt +++ b/mdal/CMakeLists.txt @@ -6,6 +6,7 @@ SET(MDAL_SOURCES mdal_utils.cpp mdal_driver_manager.cpp mdal_data_model.cpp + mdal_datetime.cpp mdal_memory_data_model.cpp frmts/mdal_driver.cpp frmts/mdal_2dm.cpp @@ -20,6 +21,7 @@ SET(MDAL_HEADERS mdal_utils.hpp mdal_driver_manager.hpp mdal_data_model.hpp + mdal_datetime.hpp mdal_memory_data_model.hpp frmts/mdal_driver.hpp frmts/mdal_2dm.hpp diff --git a/mdal/api/mdal.h b/mdal/api/mdal.h index 38f1390e..9e689bb2 100644 --- a/mdal/api/mdal.h +++ b/mdal/api/mdal.h @@ -298,7 +298,7 @@ MDAL_EXPORT void MDAL_G_minimumMaximum( DatasetGroupH group, double *min, double //! Only for 2D datasets //! //! \param group parent group handle -//! \param time time for dataset +//! \param time time for dataset (hours) //! \param values For scalar data on vertices, the size must be vertex count //! For scalar data on faces, the size must be faces count //! For vector data on vertices, the size must be vertex count * 2 (x1, y1, x2, y2, ..., xN, yN) @@ -321,8 +321,7 @@ MDAL_EXPORT bool MDAL_G_isInEditMode( DatasetGroupH group ); //! When closed, minimum and maximum dataset group values are automatically calculated MDAL_EXPORT void MDAL_G_closeEditMode( DatasetGroupH group ); -//! Returns reference time for dataset group -//! If returned value begins with word JULIAN, following number represents date in Julian format +//! Returns reference time for dataset group expressed in date with ISO8601 format, return "" if reference time is not defined MDAL_EXPORT const char *MDAL_G_referenceTime( DatasetGroupH group ); /////////////////////////////////////////////////////////////////////////////////////// @@ -332,7 +331,7 @@ MDAL_EXPORT const char *MDAL_G_referenceTime( DatasetGroupH group ); //! Returns dataset parent group MDAL_EXPORT DatasetGroupH MDAL_D_group( DatasetH dataset ); -//! Returns dataset time +//! Returns dataset time (hours) MDAL_EXPORT double MDAL_D_time( DatasetH dataset ); //! Returns volumes count for the mesh (for 3D meshes) diff --git a/mdal/frmts/mdal_3di.cpp b/mdal/frmts/mdal_3di.cpp index d5439241..17cbd4a0 100644 --- a/mdal/frmts/mdal_3di.cpp +++ b/mdal/frmts/mdal_3di.cpp @@ -139,7 +139,7 @@ void MDAL::Driver3Di::addBedElevation( MemoryMesh *mesh ) group->setIsScalar( true ); std::shared_ptr dataset = std::make_shared< MemoryDataset2D >( group.get() ); - dataset->setTime( 0.0 ); + dataset->setTime( MDAL::RelativeTimestamp() ); for ( size_t i = 0; i < faceCount; ++i ) { dataset->setScalarValue( i, MDAL::safeValue( coordZ[i], fillZ ) ); diff --git a/mdal/frmts/mdal_ascii_dat.cpp b/mdal/frmts/mdal_ascii_dat.cpp index 4c8f48e5..6e6486d4 100644 --- a/mdal/frmts/mdal_ascii_dat.cpp +++ b/mdal/frmts/mdal_ascii_dat.cpp @@ -116,7 +116,8 @@ void MDAL::DriverAsciiDat::loadOldFormat( std::ifstream &in, } else if ( cardType == "TS" && items.size() >= 2 ) { - double t = toDouble( items[ 1 ] ); + double rawTime = toDouble( items[ 1 ] ); + MDAL::RelativeTimestamp t( rawTime, MDAL::RelativeTimestamp::hours ); readVertexTimestep( mesh, group, t, isVector, false, in ); } else @@ -147,7 +148,7 @@ void MDAL::DriverAsciiDat::loadNewFormat( std::shared_ptr group; // DAT outputs data std::string groupName( MDAL::baseName( mDatFile ) ); std::string line; - std::string referenceTime; + MDAL::DateTime referenceTime; // see if it contains face-centered results - supported by BASEMENT bool faceCentered = false; if ( contains( groupName, "_els" ) ) @@ -231,7 +232,7 @@ void MDAL::DriverAsciiDat::loadNewFormat( } else if ( cardType == "RT_JULIAN" && items.size() >= 2 ) { - referenceTime = "JULIAN " + items[1]; + referenceTime = DateTime( MDAL::toDouble( items[1] ), DateTime::JulianDay ); } else if ( cardType == "TIMEUNITS" && items.size() >= 2 ) { @@ -245,8 +246,8 @@ void MDAL::DriverAsciiDat::loadNewFormat( } else if ( cardType == "TS" && items.size() >= 3 ) { - double t = toDouble( items[2] ); - t = convertTimeDataToHours( t, group->getMetadata( "TIMEUNITS" ) ); + double rawTime = toDouble( items[2] ); + MDAL::RelativeTimestamp t( rawTime, MDAL::parseDurationTimeUnit( group->getMetadata( "TIMEUNITS" ) ) ); if ( faceCentered ) { @@ -329,7 +330,7 @@ void MDAL::DriverAsciiDat::load( const std::string &datFile, MDAL::Mesh *mesh, M void MDAL::DriverAsciiDat::readVertexTimestep( const MDAL::Mesh *mesh, std::shared_ptr group, - double t, + MDAL::RelativeTimestamp t, bool isVector, bool hasStatus, std::ifstream &stream ) const @@ -398,7 +399,7 @@ void MDAL::DriverAsciiDat::readVertexTimestep( void MDAL::DriverAsciiDat::readFaceTimestep( const MDAL::Mesh *mesh, std::shared_ptr group, - double t, + MDAL::RelativeTimestamp t, bool isVector, std::ifstream &stream ) const { @@ -477,17 +478,11 @@ bool MDAL::DriverAsciiDat::persist( MDAL::DatasetGroup *group ) out << "ND " << nodeCount << "\n"; out << "NC " << elemCount << "\n"; out << "NAME " "\"" << group->name() << "\"" "\n"; - std::string referenceTimeStr = group->referenceTime(); + std::string referenceTimeStr = group->referenceTime().toJulianDayString(); if ( !referenceTimeStr.empty() ) { - // Cutting of the JULIAN prefix - std::vector referenceTimeStrWords = split( referenceTimeStr, ' ' ); - - if ( referenceTimeStrWords.size() > 1 ) - out << "RT_JULIAN " << referenceTimeStrWords[1] << "\n"; - else - out << "RT_JULIAN " << referenceTimeStr << "\n"; + out << "RT_JULIAN " << referenceTimeStr << "\n"; } out << "TIMEUNITS " << 0 << "\n"; @@ -498,7 +493,7 @@ bool MDAL::DriverAsciiDat::persist( MDAL::DatasetGroup *group ) = std::dynamic_pointer_cast( group->datasets[time_index] ); bool hasActiveStatus = isOnVertices && dataset->supportsActiveFlag(); - out << "TS " << hasActiveStatus << " " << std::to_string( dataset->time() ) << "\n"; + out << "TS " << hasActiveStatus << " " << std::to_string( dataset->time( RelativeTimestamp::hours ) ) << "\n"; if ( hasActiveStatus ) { @@ -528,21 +523,3 @@ bool MDAL::DriverAsciiDat::persist( MDAL::DatasetGroup *group ) return false; } - -double MDAL::DriverAsciiDat::convertTimeDataToHours( double time, const std::string &originalTimeDataUnit ) const -{ - if ( originalTimeDataUnit == "se" || originalTimeDataUnit == "2" || originalTimeDataUnit == "Seconds" - || originalTimeDataUnit.empty() ) - { - time /= 3600.0; - } - else if ( originalTimeDataUnit == "mi" || originalTimeDataUnit == "1" || originalTimeDataUnit == "Minutes" ) - { - time /= 60.0; - } - else if ( originalTimeDataUnit == "days" ) - { - time *= 24; - } - return time; -} diff --git a/mdal/frmts/mdal_ascii_dat.hpp b/mdal/frmts/mdal_ascii_dat.hpp index 6467422f..9df86446 100644 --- a/mdal/frmts/mdal_ascii_dat.hpp +++ b/mdal/frmts/mdal_ascii_dat.hpp @@ -70,22 +70,18 @@ namespace MDAL //! maximum native index of the vertex in defined in the mesh size_t maximumId( const Mesh *mesh ) const; - void readVertexTimestep( - const Mesh *mesh, - std::shared_ptr group, - double t, - bool isVector, - bool hasStatus, - std::ifstream &stream ) const; + void readVertexTimestep( const Mesh *mesh, + std::shared_ptr group, + RelativeTimestamp t, + bool isVector, + bool hasStatus, + std::ifstream &stream ) const; - void readFaceTimestep( - const Mesh *mesh, - std::shared_ptr group, - double t, - bool isVector, - std::ifstream &stream ) const; - - double convertTimeDataToHours( double time, const std::string &originalTimeDataUnit ) const; + void readFaceTimestep( const Mesh *mesh, + std::shared_ptr group, + RelativeTimestamp t, + bool isVector, + std::ifstream &stream ) const; std::string mDatFile; }; diff --git a/mdal/frmts/mdal_binary_dat.cpp b/mdal/frmts/mdal_binary_dat.cpp index 5c694408..c6a50465 100644 --- a/mdal/frmts/mdal_binary_dat.cpp +++ b/mdal/frmts/mdal_binary_dat.cpp @@ -251,7 +251,7 @@ void MDAL::DriverBinaryDat::load( const std::string &datFile, MDAL::Mesh *mesh, return exit_with_error( status, MDAL_Status::Err_UnknownFormat, "unable to read reference time" ); referenceTime = static_cast( time ); - group->setReferenceTime( "JULIAN " + std::to_string( referenceTime ) ); + group->setReferenceTime( DateTime( referenceTime, DateTime::JulianDay ) ); break; case CT_TIMEUNITS: @@ -288,8 +288,8 @@ void MDAL::DriverBinaryDat::load( const std::string &datFile, MDAL::Mesh *mesh, if ( read( in, reinterpret_cast< char * >( &time ), 4 ) ) return exit_with_error( status, MDAL_Status::Err_UnknownFormat, "Invalid time step" ); - double t = static_cast( time ); - t = convertTimeDataToHours( t, timeUnit ); + double rawTime = static_cast( time ); + MDAL::RelativeTimestamp t( rawTime, MDAL::parseDurationTimeUnit( timeUnitStr ) ); if ( readVertexTimestep( mesh, group, groupMax, t, istat, sflg, in ) ) return exit_with_error( status, MDAL_Status::Err_UnknownFormat, "Unable to read vertex timestep" ); @@ -315,7 +315,7 @@ bool MDAL::DriverBinaryDat::readVertexTimestep( const MDAL::Mesh *mesh, std::shared_ptr group, std::shared_ptr groupMax, - double time, + MDAL::RelativeTimestamp time, bool hasStatus, int sflg, std::ifstream &in ) @@ -364,7 +364,7 @@ bool MDAL::DriverBinaryDat::readVertexTimestep( } } - if ( MDAL::equals( time, 99999.0 ) ) // Special TUFLOW dataset with maximus + if ( MDAL::equals( time.value( MDAL::RelativeTimestamp::hours ), 99999.0 ) ) // Special TUFLOW dataset with maximus { dataset->setTime( time ); dataset->setStatistics( MDAL::calculateStatistics( dataset ) ); @@ -372,7 +372,7 @@ bool MDAL::DriverBinaryDat::readVertexTimestep( } else { - dataset->setTime( time ); // TODO read TIMEUNITS + dataset->setTime( time ); dataset->setStatistics( MDAL::calculateStatistics( dataset ) ); group->datasets.push_back( dataset ); } @@ -458,7 +458,7 @@ bool MDAL::DriverBinaryDat::persist( MDAL::DatasetGroup *group ) writeRawData( out, reinterpret_cast< const char * >( &CT_TS ), 4 ); writeRawData( out, reinterpret_cast< const char * >( &istat ), 1 ); - float ftime = static_cast( dataset->time() ); + float ftime = static_cast( dataset->time( RelativeTimestamp::hours ) ); writeRawData( out, reinterpret_cast< const char * >( &ftime ), 4 ); if ( istat ) @@ -493,23 +493,3 @@ bool MDAL::DriverBinaryDat::persist( MDAL::DatasetGroup *group ) return false; } - -double MDAL::DriverBinaryDat::convertTimeDataToHours( double time, int originalTimeDataUnit ) -{ - switch ( originalTimeDataUnit ) - { - case 1: - time /= 60.0; - break; - case 2: - time /= 3600.0; - break; - case 4: - time *= 24; - break; - case 0: - default: - break; - } - return time; -} diff --git a/mdal/frmts/mdal_binary_dat.hpp b/mdal/frmts/mdal_binary_dat.hpp index 5bd4a3dd..251afc68 100644 --- a/mdal/frmts/mdal_binary_dat.hpp +++ b/mdal/frmts/mdal_binary_dat.hpp @@ -35,12 +35,11 @@ namespace MDAL bool readVertexTimestep( const Mesh *mesh, std::shared_ptr group, std::shared_ptr groupMax, - double time, + RelativeTimestamp time, bool hasStatus, int sflg, std::ifstream &in ); - double convertTimeDataToHours( double time, int originalTimeDataUnit ); std::string mDatFile; }; diff --git a/mdal/frmts/mdal_cf.cpp b/mdal/frmts/mdal_cf.cpp index 4dde5f66..2633ab01 100644 --- a/mdal/frmts/mdal_cf.cpp +++ b/mdal/frmts/mdal_cf.cpp @@ -164,7 +164,7 @@ static void populate_vals( bool is_vector, double *vals, size_t i, } } -void MDAL::DriverCF::addDatasetGroups( MDAL::Mesh *mesh, const std::vector ×, const MDAL::cfdataset_info_map &dsinfo_map ) +void MDAL::DriverCF::addDatasetGroups( MDAL::Mesh *mesh, const std::vector ×, const MDAL::cfdataset_info_map &dsinfo_map, const MDAL::DateTime &referenceTime ) { /* PHASE 2 - add dataset groups */ for ( const auto &it : dsinfo_map ) @@ -206,7 +206,6 @@ void MDAL::DriverCF::addDatasetGroups( MDAL::Mesh *mesh, const std::vector dataset; - double time = times[ts]; if ( dsi.outputType == CFDimensions::Volume3D ) { dataset = create3DDataset( @@ -223,7 +222,7 @@ void MDAL::DriverCF::addDatasetGroups( MDAL::Mesh *mesh, const std::vectorsetTime( time ); + dataset->setTime( times[ts] ); group->datasets.push_back( dataset ); } } @@ -232,12 +231,13 @@ void MDAL::DriverCF::addDatasetGroups( MDAL::Mesh *mesh, const std::vectordatasets.empty() ) { group->setStatistics( MDAL::calculateStatistics( group ) ); + group->setReferenceTime( referenceTime ); mesh->datasetGroups.push_back( group ); } } } -void MDAL::DriverCF::parseTime( std::vector × ) +MDAL::DateTime MDAL::DriverCF::parseTime( std::vector × ) { size_t nTimesteps = mDimensions.size( CFDimensions::Time ); @@ -245,17 +245,24 @@ void MDAL::DriverCF::parseTime( std::vector × ) { //if no time dimension is present creates only one time step to store the potential time-independent variable nTimesteps = 1; - times = std::vector( 1, 0 ); - return; + times = std::vector( 1, RelativeTimestamp() ); + return MDAL::DateTime(); } const std::string timeArrName = getTimeVariableName(); - times = mNcFile->readDoubleArr( timeArrName, nTimesteps ); - std::string units = mNcFile->getAttrStr( timeArrName, "units" ); - double div_by = MDAL::parseTimeUnits( units ); + std::vector rawTimes = mNcFile->readDoubleArr( timeArrName, nTimesteps ); + + std::string timeUnitInformation = mNcFile->getAttrStr( timeArrName, "units" ); + std::string calendar = mNcFile->getAttrStr( timeArrName, "calendar" ); + MDAL::DateTime referenceTime = parseCFReferenceTime( timeUnitInformation, calendar ); + MDAL::RelativeTimestamp::Unit unit = parseCFTimeUnit( timeUnitInformation ); + + times = std::vector( nTimesteps ); for ( size_t i = 0; i < nTimesteps; ++i ) { - times[i] /= div_by; + times[i] = RelativeTimestamp( rawTimes[i], unit ); } + + return referenceTime; } std::shared_ptr MDAL::DriverCF::create2DDataset( std::shared_ptr group, size_t ts, const MDAL::CFDatasetGroupInfo &dsi, double fill_val_x, double fill_val_y ) @@ -371,7 +378,7 @@ std::unique_ptr< MDAL::Mesh > MDAL::DriverCF::load( const std::string &fileName, if ( status ) *status = MDAL_Status::None; //Dimensions dims; - std::vector times; + std::vector times; try { @@ -401,13 +408,13 @@ std::unique_ptr< MDAL::Mesh > MDAL::DriverCF::load( const std::string &fileName, setProjection( mesh.get() ); // Parse time array - parseTime( times ); + MDAL::DateTime referenceTime = parseTime( times ); // Parse dataset info cfdataset_info_map dsinfo_map = parseDatasetGroupInfo(); // Create datasets - addDatasetGroups( mesh.get(), times, dsinfo_map ); + addDatasetGroups( mesh.get(), times, dsinfo_map, referenceTime ); return std::unique_ptr( mesh.release() ); } diff --git a/mdal/frmts/mdal_cf.hpp b/mdal/frmts/mdal_cf.hpp index f9a8623d..305f73e0 100644 --- a/mdal/frmts/mdal_cf.hpp +++ b/mdal/frmts/mdal_cf.hpp @@ -138,10 +138,10 @@ namespace MDAL void setProjection( MDAL::Mesh *m ); cfdataset_info_map parseDatasetGroupInfo(); - void parseTime( std::vector × ); + DateTime parseTime( std::vector × ); //Return the reference time void addDatasetGroups( Mesh *mesh, - const std::vector ×, - const cfdataset_info_map &dsinfo_map ); + const std::vector ×, + const cfdataset_info_map &dsinfo_map, const DateTime &referenceTime ); std::string mFileName; std::shared_ptr mNcFile; diff --git a/mdal/frmts/mdal_driver.cpp b/mdal/frmts/mdal_driver.cpp index e0132e34..a857f9cf 100644 --- a/mdal/frmts/mdal_driver.cpp +++ b/mdal/frmts/mdal_driver.cpp @@ -82,7 +82,7 @@ void MDAL::Driver::createDatasetGroup( MDAL::Mesh *mesh, const std::string &grou mesh->datasetGroups.push_back( grp ); } -void MDAL::Driver::createDataset( MDAL::DatasetGroup *group, double time, const double *values, const int *active ) +void MDAL::Driver::createDataset( MDAL::DatasetGroup *group, MDAL::RelativeTimestamp time, const double *values, const int *active ) { bool supportsActiveFlag = ( active != nullptr ); std::shared_ptr dataset = std::make_shared< MemoryDataset2D >( group, supportsActiveFlag ); diff --git a/mdal/frmts/mdal_driver.hpp b/mdal/frmts/mdal_driver.hpp index d250a253..81d5bbca 100644 --- a/mdal/frmts/mdal_driver.hpp +++ b/mdal/frmts/mdal_driver.hpp @@ -64,7 +64,7 @@ namespace MDAL // create new dataset from array virtual void createDataset( DatasetGroup *group, - double time, + RelativeTimestamp time, const double *values, const int *active ); diff --git a/mdal/frmts/mdal_flo2d.cpp b/mdal/frmts/mdal_flo2d.cpp index e7dc7986..789a017b 100644 --- a/mdal/frmts/mdal_flo2d.cpp +++ b/mdal/frmts/mdal_flo2d.cpp @@ -89,7 +89,7 @@ void MDAL::DriverFlo2D::addStaticDataset( std::shared_ptr dataset = std::make_shared< MemoryDataset2D >( group.get() ); assert( vals.size() == dataset->valuesCount() ); - dataset->setTime( 0.0 ); + dataset->setTime( MDAL::RelativeTimestamp() ); double *values = dataset->values(); memcpy( values, vals.data(), vals.size() * sizeof( double ) ); dataset->setStatistics( MDAL::calculateStatistics( dataset ) ); @@ -187,7 +187,7 @@ void MDAL::DriverFlo2D::parseTIMDEPFile( const std::string &datFileName, const s size_t nVertexs = mMesh->verticesCount(); size_t ntimes = 0; - double time = 0.0; + RelativeTimestamp time = RelativeTimestamp(); size_t face_idx = 0; std::shared_ptr depthDsGroup = std::make_shared< DatasetGroup >( @@ -228,7 +228,7 @@ void MDAL::DriverFlo2D::parseTIMDEPFile( const std::string &datFileName, const s std::vector lineParts = MDAL::split( line, ' ' ); if ( lineParts.size() == 1 ) { - time = MDAL::toDouble( line ); + time = RelativeTimestamp( MDAL::toDouble( line ), RelativeTimestamp::hours ); ntimes++; if ( depthDataset ) addDatasetToGroup( depthDsGroup, depthDataset ); @@ -531,6 +531,9 @@ bool MDAL::DriverFlo2D::parseHDF5Datasets( MemoryMesh *mesh, const std::string & HdfAttribute groupType = grp.attribute( "Grouptype" ); if ( !groupType.isValid() ) return true; + HdfAttribute timeUnitAttribute = grp.attribute( "TimeUnits" ); + std::string timeUnitString = timeUnitAttribute.readString(); + /* Min and Max arrays in TIMDEP.HDF5 files have dimensions 1xntimesteps . HdfDataset minDs = grp.dataset("Mins"); if (!minDs.isValid()) return true; @@ -570,7 +573,7 @@ bool MDAL::DriverFlo2D::parseHDF5Datasets( MemoryMesh *mesh, const std::string & for ( size_t ts = 0; ts < timesteps; ++ts ) { std::shared_ptr< MemoryDataset2D > output = std::make_shared< MemoryDataset2D >( ds.get() ); - output->setTime( times[ts] ); + output->setTime( times[ts], parseDurationTimeUnit( timeUnitString ) ); if ( isVector ) { @@ -819,7 +822,7 @@ bool MDAL::DriverFlo2D::appendGroup( HdfFile &file, MDAL::DatasetGroup *dsGroup, const Statistics st = dataset->statistics(); maximums[i] = static_cast( st.maximum ); minimums[i] = static_cast( st.minimum ); - times.push_back( dataset->time() ); + times.push_back( dataset->time( RelativeTimestamp::hours ) ); } // store data diff --git a/mdal/frmts/mdal_gdal.cpp b/mdal/frmts/mdal_gdal.cpp index 27ce2d4d..64e336e0 100644 --- a/mdal/frmts/mdal_gdal.cpp +++ b/mdal/frmts/mdal_gdal.cpp @@ -214,7 +214,7 @@ void MDAL::DriverGdal::parseRasterBands( const MDAL::GdalDataset *cfGDALDataset metadata_hash metadata = parseMetadata( gdalBand ); std::string band_name; - double time = std::numeric_limits::min(); + MDAL::RelativeTimestamp time; bool is_vector; bool is_x; if ( parseBandInfo( cfGDALDataset, metadata, band_name, &time, &is_vector, &is_x ) ) @@ -235,7 +235,6 @@ void MDAL::DriverGdal::parseRasterBands( const MDAL::GdalDataset *cfGDALDataset raster_bands[data_index] = gdalBand; qMap[time] = raster_bands; mBands[band_name] = qMap; - } else { @@ -247,7 +246,6 @@ void MDAL::DriverGdal::parseRasterBands( const MDAL::GdalDataset *cfGDALDataset std::vector raster_bands( data_count ); raster_bands[data_index] = gdalBand; mBands[band_name][time] = raster_bands; - } else { @@ -313,6 +311,11 @@ void MDAL::DriverGdal::fixRasterBands() } } +MDAL::DateTime MDAL::DriverGdal::referenceTime() const +{ + return MDAL::DateTime(); +} + void MDAL::DriverGdal::addDataToOutput( GDALRasterBandH raster_band, std::shared_ptr tos, bool is_vector, bool is_x ) { assert( raster_band ); @@ -429,6 +432,7 @@ void MDAL::DriverGdal::addDatasetGroups() // TODO use GDALComputeRasterMinMax group->setStatistics( MDAL::calculateStatistics( group ) ); + group->setReferenceTime( referenceTime() ); mMesh->datasetGroups.push_back( group ); } } diff --git a/mdal/frmts/mdal_gdal.hpp b/mdal/frmts/mdal_gdal.hpp index fce3bea1..17c2ea47 100644 --- a/mdal/frmts/mdal_gdal.hpp +++ b/mdal/frmts/mdal_gdal.hpp @@ -65,16 +65,15 @@ namespace MDAL /* return true on failure */ virtual bool parseBandInfo( const GdalDataset *cfGDALDataset, - const metadata_hash &metadata, std::string &band_name, double *time, bool *is_vector, bool *is_x ) = 0; + const metadata_hash &metadata, std::string &band_name, MDAL::RelativeTimestamp *time, bool *is_vector, bool *is_x ) = 0; virtual double parseMetadataTime( const std::string &time_s ); virtual std::string GDALFileName( const std::string &fileName ); /* some formats require e.g. adding driver name at the beginning */ virtual std::vector parseDatasetNames( const std::string &fileName ); virtual void parseGlobals( const metadata_hash &metadata ) {MDAL_UNUSED( metadata );} - - void parseBandIsVector( std::string &band_name, bool *is_vector, bool *is_x ); + virtual void parseBandIsVector( std::string &band_name, bool *is_vector, bool *is_x ); private: - typedef std::map > timestep_map; //TIME (sorted), [X, Y] + typedef std::map > timestep_map; //TIME (sorted), [X, Y] typedef std::map data_hash; //Data Type, TIME (sorted), [X, Y] typedef std::vector> gdal_datasets_vector; //GDAL (Sub)Datasets, @@ -95,6 +94,8 @@ namespace MDAL void parseRasterBands( const GdalDataset *cfGDALDataset ); void fixRasterBands(); + virtual MDAL::DateTime referenceTime() const; + std::string mFileName; const std::string mGdalDriverName; /* GDAL driver name */ double *mPafScanline; /* temporary buffer for reading one raster line */ diff --git a/mdal/frmts/mdal_gdal_grib.cpp b/mdal/frmts/mdal_gdal_grib.cpp index 391666b1..6ee3496f 100644 --- a/mdal/frmts/mdal_gdal_grib.cpp +++ b/mdal/frmts/mdal_gdal_grib.cpp @@ -15,7 +15,7 @@ MDAL::DriverGdalGrib::DriverGdalGrib( ) "GDAL Grib", "*.grb;;*.grb2;;*.bin;;*.grib;;*.grib1;;*.grib2" , "GRIB" ), - mRefTime( std::numeric_limits::min() ) + mRefTime( DateTime() ) {} MDAL::DriverGdalGrib *MDAL::DriverGdalGrib::create() @@ -27,7 +27,7 @@ MDAL::DriverGdalGrib::~DriverGdalGrib() = default; bool MDAL::DriverGdalGrib::parseBandInfo( const MDAL::GdalDataset *cfGDALDataset, const metadata_hash &metadata, std::string &band_name, - double *time, bool *is_vector, bool *is_x + MDAL::RelativeTimestamp *time, bool *is_vector, bool *is_x ) { MDAL_UNUSED( cfGDALDataset ); @@ -39,21 +39,26 @@ bool MDAL::DriverGdalGrib::parseBandInfo( const MDAL::GdalDataset *cfGDALDataset if ( iter == metadata.end() ) return true; //FAILURE band_name = iter->second; - if ( MDAL::equals( mRefTime, std::numeric_limits::min() ) ) + if ( !mRefTime.isValid() ) { iter = metadata.find( "grib_ref_time" ); if ( iter == metadata.end() ) return true; //FAILURE - mRefTime = parseMetadataTime( iter->second ); + mRefTime = DateTime( parseMetadataTime( iter->second ), DateTime::Unix ); } // TIME iter = metadata.find( "grib_valid_time" ); if ( iter == metadata.end() ) return true; //FAILURE - double valid_time = parseMetadataTime( iter->second ); - *time = ( valid_time - mRefTime ) / 3600.0; // input times are always in seconds UTC, we need them back in hours + DateTime valid_time = DateTime( parseMetadataTime( iter->second ), DateTime::Unix ); + *time = ( valid_time - mRefTime ); // Parse X, Y components if present parseBandIsVector( band_name, is_vector, is_x ); return false; // success } + +MDAL::DateTime MDAL::DriverGdalGrib::referenceTime() const +{ + return mRefTime; +} diff --git a/mdal/frmts/mdal_gdal_grib.hpp b/mdal/frmts/mdal_gdal_grib.hpp index 1ae75cfa..715fd88b 100644 --- a/mdal/frmts/mdal_gdal_grib.hpp +++ b/mdal/frmts/mdal_gdal_grib.hpp @@ -26,9 +26,11 @@ namespace MDAL private: bool parseBandInfo( const MDAL::GdalDataset *cfGDALDataset, const metadata_hash &metadata, std::string &band_name, - double *time, bool *is_vector, bool *is_x + RelativeTimestamp *time, bool *is_vector, bool *is_x ) override; + MDAL::DateTime referenceTime() const override; + /** * ref time (UTC sec) * @@ -36,7 +38,7 @@ namespace MDAL * some GRIB files do not use FORECAST_SEC, but VALID_TIME * metadata, so ref time varies with dataset-to-dataset */ - double mRefTime; + DateTime mRefTime; }; } // namespace MDAL diff --git a/mdal/frmts/mdal_gdal_netcdf.cpp b/mdal/frmts/mdal_gdal_netcdf.cpp index 1c54c1ac..c4d239f1 100644 --- a/mdal/frmts/mdal_gdal_netcdf.cpp +++ b/mdal/frmts/mdal_gdal_netcdf.cpp @@ -12,7 +12,6 @@ MDAL::DriverGdalNetCDF::DriverGdalNetCDF() "GDAL NetCDF", "*.nc" , "GRIB" ) - , mTimeDiv( 1.0 ) { } @@ -33,7 +32,7 @@ std::string MDAL::DriverGdalNetCDF::GDALFileName( const std::string &fileName ) #endif } -bool MDAL::DriverGdalNetCDF::parseBandInfo( const MDAL::GdalDataset *cfGDALDataset, const MDAL::DriverGdal::metadata_hash &metadata, std::string &band_name, double *time, bool *is_vector, bool *is_x ) +bool MDAL::DriverGdalNetCDF::parseBandInfo( const MDAL::GdalDataset *cfGDALDataset, const MDAL::DriverGdal::metadata_hash &metadata, std::string &band_name, RelativeTimestamp *time, bool *is_vector, bool *is_x ) { MDAL_UNUSED( cfGDALDataset ); @@ -41,7 +40,7 @@ bool MDAL::DriverGdalNetCDF::parseBandInfo( const MDAL::GdalDataset *cfGDALDatas iter = metadata.find( "netcdf_dim_time" ); if ( iter == metadata.end() ) return true; //FAILURE, skip no-time bands - *time = parseMetadataTime( iter->second ) / mTimeDiv; + *time = MDAL::RelativeTimestamp( parseMetadataTime( iter->second ), mTimeUnit ); // NAME iter = metadata.find( "long_name" ); @@ -78,11 +77,19 @@ bool MDAL::DriverGdalNetCDF::parseBandInfo( const MDAL::GdalDataset *cfGDALDatas void MDAL::DriverGdalNetCDF::parseGlobals( const MDAL::DriverGdal::metadata_hash &metadata ) { - metadata_hash::const_iterator iter = metadata.find( "time#units" ); - if ( iter != metadata.end() ) + metadata_hash::const_iterator iterTimeUnit = metadata.find( "time#units" ); + metadata_hash::const_iterator iterCalendar = metadata.find( "time#calendar" ); + std::string calendar; + if ( iterCalendar != metadata.end() ) + calendar = iterCalendar->second; + + if ( iterTimeUnit != metadata.end() ) { - std::string units = iter->second; - mTimeDiv = MDAL::parseTimeUnits( units ); - // TODO store reference time from iter->second too, see crayfish_netcdf.cpp + std::string units = iterTimeUnit->second; + mTimeUnit = MDAL::parseCFTimeUnit( units ); + if ( !mRefTime.isValid() ) + mRefTime = MDAL::parseCFReferenceTime( units, calendar ); } } + +MDAL::DateTime MDAL::DriverGdalNetCDF::referenceTime() const {return mRefTime;} diff --git a/mdal/frmts/mdal_gdal_netcdf.hpp b/mdal/frmts/mdal_gdal_netcdf.hpp index 45c0d221..d471d5e5 100644 --- a/mdal/frmts/mdal_gdal_netcdf.hpp +++ b/mdal/frmts/mdal_gdal_netcdf.hpp @@ -27,12 +27,15 @@ namespace MDAL std::string GDALFileName( const std::string &fileName ) override; bool parseBandInfo( const MDAL::GdalDataset *cfGDALDataset, const metadata_hash &metadata, std::string &band_name, - double *time, bool *is_vector, bool *is_x + MDAL::RelativeTimestamp *time, bool *is_vector, bool *is_x ) override; void parseGlobals( const metadata_hash &metadata ) override; - //! delimiter to get time in hours - double mTimeDiv; + MDAL::DateTime referenceTime() const override; + + RelativeTimestamp::Unit mTimeUnit; + //! Take the first reference time parsed + DateTime mRefTime; }; } // namespace MDAL diff --git a/mdal/frmts/mdal_hec2d.cpp b/mdal/frmts/mdal_hec2d.cpp index 93b693b0..23b20f9a 100644 --- a/mdal/frmts/mdal_hec2d.cpp +++ b/mdal/frmts/mdal_hec2d.cpp @@ -99,20 +99,85 @@ static std::string getDataTimeUnit( HdfDataset &dsTime ) return dataTimeUnit; } -static void convertTimeDataToHours( std::vector ×, const std::string &originalTimeDataUnit ) +static std::vector convertTimeData( std::vector ×, const std::string &originalTimeDataUnit ) { - if ( originalTimeDataUnit != "Hours" ) + std::vector convertedTime( times.size() ); + + MDAL::RelativeTimestamp::Unit unit = MDAL::parseDurationTimeUnit( originalTimeDataUnit ); + + for ( size_t i = 0; i < times.size(); i++ ) { - for ( size_t i = 0; i < times.size(); i++ ) - { - if ( originalTimeDataUnit == "Seconds" ) { times[i] /= 3600.0f; } - else if ( originalTimeDataUnit == "Minutes" ) { times[i] /= 60.0f; } - else if ( originalTimeDataUnit == "Days" ) { times[i] *= 24; } - } + convertedTime[i] = MDAL::RelativeTimestamp( double( times[i] ), unit ); + } + return convertedTime; +} + + +static MDAL::DateTime convertToDateTime( const std::string strDateTime ) +{ + //HECRAS format date is 01JAN2000 + + auto data = MDAL::split( strDateTime, " " ); + if ( data.size() < 2 ) + return MDAL::DateTime(); + + std::string dateStr = data[0]; + + int year = 0; + int month = 0; + int day = 0; + + if ( dateStr.size() == 9 ) + { + day = MDAL::toInt( dateStr.substr( 0, 2 ) ); + std::string monthStr = dateStr.substr( 2, 3 ); + year = MDAL::toInt( dateStr.substr( 5, 4 ) ); + + if ( monthStr == "JAN" ) + month = 1; + else if ( monthStr == "FEB" ) + month = 2; + else if ( monthStr == "MAR" ) + month = 3; + else if ( monthStr == "APR" ) + month = 4; + else if ( monthStr == "MAY" ) + month = 5; + else if ( monthStr == "JUN" ) + month = 6; + else if ( monthStr == "JUL" ) + month = 7; + else if ( monthStr == "AUG" ) + month = 8; + else if ( monthStr == "SEP" ) + month = 9; + else if ( monthStr == "OCT" ) + month = 10; + else if ( monthStr == "NOV" ) + month = 11; + else if ( monthStr == "DEC" ) + month = 12; + } + + std::string timeStr = data[1]; + + auto timeData = MDAL::split( timeStr, ':' ); + + int hours = 0; + int min = 0; + double sec = 0; + + if ( timeData.size() == 3 ) + { + hours = MDAL::toInt( timeData[0] ); + min = MDAL::toInt( timeData[1] ); + sec = MDAL::toDouble( timeData[2] ); } + + return MDAL::DateTime( year, month, day, hours, min, sec ); } -static std::string readReferenceTime( const HdfFile &hdfFile ) +static MDAL::DateTime readReferenceDateTime( const HdfFile &hdfFile ) { std::string refTime; HdfGroup gBaseO = getBaseOutputGroup( hdfFile ); @@ -121,19 +186,19 @@ static std::string readReferenceTime( const HdfFile &hdfFile ) std::vector timeStamps = dsTimeDateStamp.readArrayString(); if ( timeStamps.size() > 0 ) - refTime = timeStamps[0]; - return refTime; + return convertToDateTime( timeStamps[0] ); + + return MDAL::DateTime(); } -static std::vector readTimes( const HdfFile &hdfFile ) +static std::vector readTimes( const HdfFile &hdfFile ) { HdfGroup gBaseO = getBaseOutputGroup( hdfFile ); HdfGroup gUnsteadTS = openHdfGroup( gBaseO, "Unsteady Time Series" ); HdfDataset dsTime = openHdfDataset( gUnsteadTS, "Time" ); std::string dataTimeUnits = getDataTimeUnit( dsTime ); std::vector times = dsTime.readArray(); - convertTimeDataToHours( times, dataTimeUnits ); - return times; + return convertTimeData( times, dataTimeUnits ); } static std::vector readFace2Cells( const HdfFile &hdfFile, const std::string &flowAreaName, size_t *nFaces ) @@ -157,8 +222,8 @@ void MDAL::DriverHec2D::readFaceOutput( const HdfFile &hdfFile, const std::vector &flowAreaNames, const std::string rawDatasetName, const std::string datasetName, - const std::vector ×, - const std::string &referenceTime ) + const std::vector ×, + const DateTime &referenceTime ) { double eps = std::numeric_limits::min(); @@ -177,8 +242,7 @@ void MDAL::DriverHec2D::readFaceOutput( const HdfFile &hdfFile, for ( size_t tidx = 0; tidx < times.size(); ++tidx ) { std::shared_ptr dataset = std::make_shared< MemoryDataset2D >( group.get() ); - double time = static_cast( times[tidx] ); - dataset->setTime( time ); + dataset->setTime( times[tidx] ); datasets.push_back( dataset ); } @@ -236,18 +300,16 @@ void MDAL::DriverHec2D::readFaceResults( const HdfFile &hdfFile, { // UNSTEADY HdfGroup flowGroup = get2DFlowAreasGroup( hdfFile, "Unsteady Time Series" ); - std::vector times = readTimes( hdfFile ); - const std::string referenceTime = readReferenceTime( hdfFile ); - readFaceOutput( hdfFile, flowGroup, areaElemStartIndex, flowAreaNames, "Face Shear Stress", "Face Shear Stress", times, referenceTime ); - readFaceOutput( hdfFile, flowGroup, areaElemStartIndex, flowAreaNames, "Face Velocity", "Face Velocity", times, referenceTime ); + MDAL::DateTime referenceDateTime = readReferenceDateTime( hdfFile ); + readFaceOutput( hdfFile, flowGroup, areaElemStartIndex, flowAreaNames, "Face Shear Stress", "Face Shear Stress", mTimes, referenceDateTime ); + readFaceOutput( hdfFile, flowGroup, areaElemStartIndex, flowAreaNames, "Face Velocity", "Face Velocity", mTimes, referenceDateTime ); // SUMMARY flowGroup = get2DFlowAreasGroup( hdfFile, "Summary Output" ); - times.clear(); - times.push_back( 0.0f ); + std::vector dummyTimes( 1, MDAL::RelativeTimestamp() ); - readFaceOutput( hdfFile, flowGroup, areaElemStartIndex, flowAreaNames, "Maximum Face Shear Stress", "Face Shear Stress/Maximums", times, referenceTime ); - readFaceOutput( hdfFile, flowGroup, areaElemStartIndex, flowAreaNames, "Maximum Face Velocity", "Face Velocity/Maximums", times, referenceTime ); + readFaceOutput( hdfFile, flowGroup, areaElemStartIndex, flowAreaNames, "Maximum Face Shear Stress", "Face Shear Stress/Maximums", dummyTimes, referenceDateTime ); + readFaceOutput( hdfFile, flowGroup, areaElemStartIndex, flowAreaNames, "Maximum Face Velocity", "Face Velocity/Maximums", dummyTimes, referenceDateTime ); } @@ -256,9 +318,9 @@ std::shared_ptr MDAL::DriverHec2D::readElemOutput( const const std::vector &flowAreaNames, const std::string rawDatasetName, const std::string datasetName, - const std::vector ×, + const std::vector ×, std::shared_ptr bed_elevation, - const std::string &referenceTime ) + const DateTime &referenceTime ) { double eps = std::numeric_limits::min(); @@ -277,8 +339,7 @@ std::shared_ptr MDAL::DriverHec2D::readElemOutput( const for ( size_t tidx = 0; tidx < times.size(); ++tidx ) { std::shared_ptr dataset = std::make_shared< MemoryDataset2D >( group.get() ); - double time = static_cast( times[tidx] ); - dataset->setTime( time ); + dataset->setTime( times[tidx] ); datasets.push_back( dataset ); } @@ -348,8 +409,8 @@ std::shared_ptr MDAL::DriverHec2D::readBedElevation( const std::vector &areaElemStartIndex, const std::vector &flowAreaNames ) { - std::vector times( 1, 0.0f ); - std::string referenceTime; + std::vector times( 1 ); + DateTime referenceTime; return readElemOutput( gGeom2DFlowAreas, @@ -371,8 +432,6 @@ void MDAL::DriverHec2D::readElemResults( { // UNSTEADY HdfGroup flowGroup = get2DFlowAreasGroup( hdfFile, "Unsteady Time Series" ); - std::vector times = readTimes( hdfFile ); - std::string referenceTime = readReferenceTime( hdfFile ); readElemOutput( flowGroup, @@ -380,24 +439,23 @@ void MDAL::DriverHec2D::readElemResults( flowAreaNames, "Water Surface", "Water Surface", - times, + mTimes, bed_elevation, - referenceTime ); + mReferenceTime ); readElemOutput( flowGroup, areaElemStartIndex, flowAreaNames, "Depth", "Depth", - times, + mTimes, bed_elevation, - referenceTime ); + mReferenceTime ); // SUMMARY flowGroup = get2DFlowAreasGroup( hdfFile, "Summary Output" ); - times.clear(); - times.push_back( 0.0f ); - referenceTime.clear(); + + std::vector dummyTimes( 1, MDAL::RelativeTimestamp() ); readElemOutput( flowGroup, @@ -405,9 +463,9 @@ void MDAL::DriverHec2D::readElemResults( flowAreaNames, "Maximum Water Surface", "Water Surface/Maximums", - times, + dummyTimes, bed_elevation, - referenceTime + mReferenceTime ); } @@ -648,6 +706,9 @@ std::unique_ptr MDAL::DriverHec2D::load( const std::string &resultsF parseMesh( gGeom2DFlowAreas, areaElemStartIndex, flowAreaNames ); setProjection( hdfFile ); + mTimes = readTimes( hdfFile ); + mReferenceTime = readReferenceDateTime( hdfFile ); + //Elevation std::shared_ptr bed_elevation = readBedElevation( gGeom2DFlowAreas, areaElemStartIndex, flowAreaNames ); diff --git a/mdal/frmts/mdal_hec2d.hpp b/mdal/frmts/mdal_hec2d.hpp index d0547884..d6d6a39f 100644 --- a/mdal/frmts/mdal_hec2d.hpp +++ b/mdal/frmts/mdal_hec2d.hpp @@ -49,6 +49,9 @@ namespace MDAL std::unique_ptr< MDAL::MemoryMesh > mMesh; std::string mFileName; + std::vector mTimes ; + DateTime mReferenceTime; + // Pre 5.0.5 format bool canReadOldFormat( const std::string &fileType ) const; std::vector read2DFlowAreasNamesOld( HdfGroup gGeom2DFlowAreas ) const; @@ -64,8 +67,8 @@ namespace MDAL const std::vector &flowAreaNames, const std::string rawDatasetName, const std::string datasetName, - const std::vector ×, - const std::string &referenceTime ); + const std::vector ×, + const DateTime &referenceTime ); void readFaceResults( const HdfFile &hdfFile, const std::vector &areaElemStartIndex, @@ -77,9 +80,9 @@ namespace MDAL const std::vector &flowAreaNames, const std::string rawDatasetName, const std::string datasetName, - const std::vector ×, + const std::vector ×, std::shared_ptr bed_elevation, - const std::string &referenceTime ); + const DateTime &referenceTime ); std::shared_ptr readBedElevation( const HdfGroup &gGeom2DFlowAreas, diff --git a/mdal/frmts/mdal_selafin.cpp b/mdal/frmts/mdal_selafin.cpp index 952338ca..77f3d5bc 100644 --- a/mdal/frmts/mdal_selafin.cpp +++ b/mdal/frmts/mdal_selafin.cpp @@ -232,7 +232,8 @@ void MDAL::DriverSelafin::parseFile( std::vector &var_names, std::vector &ikle, std::vector &x, std::vector &y, - std::vector &data ) + std::vector &data, + DateTime &referenceTime ) { /* 1 record containing the title of the study (72 characters) and a 8 characters @@ -293,7 +294,7 @@ void MDAL::DriverSelafin::parseFile( std::vector &var_names, if ( params[9] == 1 ) { std::vector datetime = mReader.read_int_arr( 6 ); - MDAL_UNUSED( datetime ) + referenceTime = DateTime( datetime[0], datetime[1], datetime[2], datetime[3], datetime[4], double( datetime[5] ) ); } /* 1 record containing the integers NELEM,NPOIN,NDP,1 (number of @@ -411,7 +412,10 @@ void MDAL::DriverSelafin::createMesh( mMesh->vertices = nodes; } -void MDAL::DriverSelafin::addData( const std::vector &var_names, const std::vector &data, size_t nPoints ) +void MDAL::DriverSelafin::addData( const std::vector &var_names, + const std::vector &data, + size_t nPoints, + const DateTime &referenceTime ) { for ( size_t nName = 0; nName < var_names.size(); ++nName ) { @@ -465,6 +469,8 @@ void MDAL::DriverSelafin::addData( const std::vector &var_names, co mMesh->datasetGroups.push_back( group ); } + group->setReferenceTime( referenceTime ); + size_t i = 0; for ( timestep_map::const_iterator it = data[nName].begin(); it != data[nName].end(); ++it, ++i ) { @@ -476,7 +482,7 @@ void MDAL::DriverSelafin::addData( const std::vector &var_names, co else { dataset = std::make_shared< MemoryDataset2D >( group.get(), true ); - dataset->setTime( it->first ); + dataset->setTime( it->first, RelativeTimestamp::seconds ); // Seems that time unit in this format is only seconds group->datasets.push_back( dataset ); } for ( size_t nP = 0; nP < nPoints; nP++ ) @@ -567,6 +573,7 @@ std::unique_ptr MDAL::DriverSelafin::load( const std::string &meshFi std::vector x; std::vector y; std::vector data; + DateTime referenceTime; try { @@ -579,7 +586,8 @@ std::unique_ptr MDAL::DriverSelafin::load( const std::string &meshFi ikle, x, y, - data ); + data, + referenceTime ); createMesh( xOrigin, yOrigin, @@ -590,7 +598,7 @@ std::unique_ptr MDAL::DriverSelafin::load( const std::string &meshFi x, y ); - addData( var_names, data, nPoints ); + addData( var_names, data, nPoints, referenceTime ); } catch ( MDAL_Status error ) { diff --git a/mdal/frmts/mdal_selafin.hpp b/mdal/frmts/mdal_selafin.hpp index afd674c2..43eaeeb7 100644 --- a/mdal/frmts/mdal_selafin.hpp +++ b/mdal/frmts/mdal_selafin.hpp @@ -76,7 +76,10 @@ namespace MDAL std::vector &ikle, std::vector &x, std::vector &y ); - void addData( const std::vector &var_names, const std::vector &data, size_t nPoints ); + void addData( const std::vector &var_names, + const std::vector &data, + size_t nPoints, + const DateTime &referenceTime ); void parseFile( std::vector &var_names, double *xOrigin, double *yOrigin, @@ -86,7 +89,8 @@ namespace MDAL std::vector &ikle, std::vector &x, std::vector &y, - std::vector &data ); + std::vector &data, + DateTime &referenceTime ); bool getStreamPrecision( std::ifstream &in ); diff --git a/mdal/frmts/mdal_sww.cpp b/mdal/frmts/mdal_sww.cpp index 9504ee1b..3db59411 100644 --- a/mdal/frmts/mdal_sww.cpp +++ b/mdal/frmts/mdal_sww.cpp @@ -299,7 +299,7 @@ std::shared_ptr MDAL::DriverSWW::readScalarGroup( { // TIME INDEPENDENT std::shared_ptr o = std::make_shared( mds.get() ); - o->setTime( 0.0 ); + o->setTime( RelativeTimestamp() ); std::vector valuesX = ncFile.readDoubleArr( arrName, nPoints ); for ( size_t i = 0; i < nPoints; ++i ) { @@ -314,7 +314,7 @@ std::shared_ptr MDAL::DriverSWW::readScalarGroup( for ( size_t t = 0; t < times.size(); ++t ) { std::shared_ptr mto = std::make_shared( mds.get() ); - mto->setTime( static_cast( times[t] ) / 3600. ); + mto->setTime( static_cast( times[t] ), RelativeTimestamp::seconds ); // Time is always in seconds double *values = mto->values(); // fetching data for one timestep diff --git a/mdal/frmts/mdal_xdmf.cpp b/mdal/frmts/mdal_xdmf.cpp index 83d501d6..d5703c7b 100644 --- a/mdal/frmts/mdal_xdmf.cpp +++ b/mdal/frmts/mdal_xdmf.cpp @@ -20,7 +20,10 @@ #include "mdal_data_model.hpp" #include "mdal_xml.hpp" -MDAL::XdmfDataset::XdmfDataset( MDAL::DatasetGroup *grp, const MDAL::HyperSlab &slab, const HdfDataset &valuesDs, double time ) +MDAL::XdmfDataset::XdmfDataset( MDAL::DatasetGroup *grp, + const MDAL::HyperSlab &slab, + const HdfDataset &valuesDs, + RelativeTimestamp time ) : MDAL::Dataset2D( grp ) , mHdf5DatasetValues( valuesDs ) , mHyperSlab( slab ) @@ -109,7 +112,7 @@ size_t MDAL::XdmfDataset::vectorData( size_t indexStart, size_t count, double *b MDAL::XdmfFunctionDataset::XdmfFunctionDataset( MDAL::DatasetGroup *grp, MDAL::XdmfFunctionDataset::FunctionType type, - double time ) + const RelativeTimestamp &time ) : MDAL::Dataset2D( grp ) , mType( type ) , mBaseReferenceGroup( "XDMF", grp->mesh(), grp->uri() ) @@ -122,7 +125,10 @@ MDAL::XdmfFunctionDataset::XdmfFunctionDataset( MDAL::XdmfFunctionDataset::~XdmfFunctionDataset() = default; -void MDAL::XdmfFunctionDataset::addReferenceDataset( const HyperSlab &slab, const HdfDataset &hdfDataset, double time ) +void MDAL::XdmfFunctionDataset::addReferenceDataset( + const HyperSlab &slab, + const HdfDataset &hdfDataset, + const MDAL::RelativeTimestamp &time ) { std::shared_ptr xdmfDataset = std::make_shared( &mBaseReferenceGroup, @@ -435,8 +441,7 @@ MDAL::DatasetGroups MDAL::DriverXdmf::parseXdmfXml( ) { ++nTimesteps; xmlNodePtr timeNod = xmfFile.getCheckChild( gridNod, "Time" ); - double time = xmfFile.queryDoubleAttribute( timeNod, "Value" ); - + RelativeTimestamp time( xmfFile.queryDoubleAttribute( timeNod, "Value" ), RelativeTimestamp::hours ); //units, supposed to be hours xmlNodePtr scalarNod = xmfFile.getCheckChild( gridNod, "Attribute" ); for ( ; diff --git a/mdal/frmts/mdal_xdmf.hpp b/mdal/frmts/mdal_xdmf.hpp index f298bb04..f3ac0180 100644 --- a/mdal/frmts/mdal_xdmf.hpp +++ b/mdal/frmts/mdal_xdmf.hpp @@ -61,7 +61,7 @@ namespace MDAL XdmfDataset( DatasetGroup *grp, const HyperSlab &slab, const HdfDataset &valuesDs, - double time + MDAL::RelativeTimestamp time ); ~XdmfDataset() override; @@ -108,15 +108,14 @@ namespace MDAL Flow, //!< scalar: flow velocity (abs) = sqrt($0/($2-$3)*$0/($2-$3) + $1/($2-$3)*$1/($2-$3)) }; - XdmfFunctionDataset( - DatasetGroup *grp, - FunctionType type, - double time - ); + XdmfFunctionDataset( DatasetGroup *grp, + FunctionType type, + const RelativeTimestamp &time + ); ~XdmfFunctionDataset() override; //! Adds reference XMDF dataset - void addReferenceDataset( const HyperSlab &slab, const HdfDataset &hdfDataset, double time ); + void addReferenceDataset( const HyperSlab &slab, const HdfDataset &hdfDataset, const RelativeTimestamp &time ); //! Swaps first and second reference dataset void swap(); diff --git a/mdal/frmts/mdal_xmdf.cpp b/mdal/frmts/mdal_xmdf.cpp index ceb1bc36..69f5eed5 100644 --- a/mdal/frmts/mdal_xmdf.cpp +++ b/mdal/frmts/mdal_xmdf.cpp @@ -248,8 +248,15 @@ std::shared_ptr MDAL::DriverXmdf::readXmdfGroupAsDatasetGrou bool isVector = dimValues.size() == 3; std::vector times = dsTimes.readArrayDouble(); - std::string timeUnit = rootGroup.attribute( "TimeUnits" ).readString(); - convertTimeDataToHours( times, timeUnit ); + std::string timeUnitString = rootGroup.attribute( "TimeUnits" ).readString(); + MDAL::RelativeTimestamp::Unit timeUnit = parseDurationTimeUnit( timeUnitString ); + HdfAttribute refTime = rootGroup.attribute( "Reftime" ); + if ( refTime.isValid() ) + { + std::string referenceTimeJulianDay = rootGroup.attribute( "Reftime" ).readString(); + group->setReferenceTime( DateTime( MDAL::toDouble( referenceTimeJulianDay ), DateTime::JulianDay ) ); + } + // all fine, set group and return group = std::make_shared( name(), @@ -259,7 +266,7 @@ std::shared_ptr MDAL::DriverXmdf::readXmdfGroupAsDatasetGrou ); group->setIsScalar( !isVector ); group->setDataLocation( MDAL_DataLocation::DataOnVertices2D ); - group->setMetadata( "TIMEUNITS", timeUnit ); + group->setMetadata( "TIMEUNITS", timeUnitString ); // lazy loading of min and max of the dataset group std::vector mins = dsMins.readArray(); @@ -272,7 +279,7 @@ std::shared_ptr MDAL::DriverXmdf::readXmdfGroupAsDatasetGrou for ( hsize_t i = 0; i < nTimeSteps; ++i ) { std::shared_ptr dataset = std::make_shared< XmdfDataset >( group.get(), dsValues, dsActive, i ); - dataset->setTime( times[i] ); + dataset->setTime( times[i], timeUnit ); Statistics stats; stats.minimum = static_cast( mins[i] ); stats.maximum = static_cast( maxs[i] ); @@ -282,16 +289,3 @@ std::shared_ptr MDAL::DriverXmdf::readXmdfGroupAsDatasetGrou return group; } - -void MDAL::DriverXmdf::convertTimeDataToHours( std::vector ×, const std::string &originalTimeDataUnit ) -{ - if ( originalTimeDataUnit != "Hours" ) - { - for ( size_t i = 0; i < times.size(); i++ ) - { - if ( originalTimeDataUnit == "Seconds" ) { times[i] /= 3600.0; } - else if ( originalTimeDataUnit == "Minutes" ) { times[i] /= 60.0; } - else if ( originalTimeDataUnit == "Days" ) { times[i] *= 24; } - } - } -} diff --git a/mdal/frmts/mdal_xmdf.hpp b/mdal/frmts/mdal_xmdf.hpp index cf0162de..595bc684 100644 --- a/mdal/frmts/mdal_xmdf.hpp +++ b/mdal/frmts/mdal_xmdf.hpp @@ -100,8 +100,6 @@ namespace MDAL const std::string &nameSuffix, size_t vertexCount, size_t faceCount ); - - void convertTimeDataToHours( std::vector ×, const std::string &originalTimeDataUnit ); }; } // namespace MDAL diff --git a/mdal/mdal.cpp b/mdal/mdal.cpp index 3de39804..84d743b3 100644 --- a/mdal/mdal.cpp +++ b/mdal/mdal.cpp @@ -697,8 +697,9 @@ DatasetH MDAL_G_addDataset( DatasetGroupH group, double time, const double *valu } const size_t index = g->datasets.size(); + MDAL::RelativeTimestamp t( time, MDAL::RelativeTimestamp::hours ); dr->createDataset( g, - time, + t, values, active ); @@ -765,7 +766,7 @@ const char *MDAL_G_referenceTime( DatasetGroupH group ) return EMPTY_STR; } MDAL::DatasetGroup *g = static_cast< MDAL::DatasetGroup * >( group ); - return _return_str( g->referenceTime() ); + return _return_str( g->referenceTime().toStandartCalendarISO8601() ); } void MDAL_G_setMetadata( DatasetGroupH group, const char *key, const char *val ) @@ -827,8 +828,7 @@ double MDAL_D_time( DatasetH dataset ) return NODATA; } MDAL::Dataset *d = static_cast< MDAL::Dataset * >( dataset ); - return d->time(); - + return d->time( MDAL::RelativeTimestamp::hours ); } int MDAL_D_volumesCount( DatasetH dataset ) @@ -1064,3 +1064,4 @@ bool MDAL_D_hasActiveFlagCapability( DatasetH dataset ) MDAL::Dataset *ds = static_cast< MDAL::Dataset * >( dataset ); return ds->supportsActiveFlag(); } + diff --git a/mdal/mdal_data_model.cpp b/mdal/mdal_data_model.cpp index 21ae25e7..1d0dea3b 100644 --- a/mdal/mdal_data_model.cpp +++ b/mdal/mdal_data_model.cpp @@ -56,12 +56,17 @@ MDAL::Mesh *MDAL::Dataset::mesh() const return mParent->mesh(); } -double MDAL::Dataset::time() const +double MDAL::Dataset::time( RelativeTimestamp::Unit unit ) const { - return mTime; + return mTime.value( unit ); } -void MDAL::Dataset::setTime( double time ) +void MDAL::Dataset::setTime( double time, RelativeTimestamp::Unit unit ) +{ + mTime = RelativeTimestamp( time, unit ); +} + +void MDAL::Dataset::setTime( const MDAL::RelativeTimestamp &time ) { mTime = time; } @@ -88,7 +93,6 @@ MDAL::Dataset2D::Dataset2D( MDAL::DatasetGroup *parent ) MDAL::Dataset2D::~Dataset2D() = default; - size_t MDAL::Dataset2D::volumesCount() const { return 0; } size_t MDAL::Dataset2D::maximumVerticalLevelsCount() const { return 0; } @@ -207,12 +211,12 @@ void MDAL::DatasetGroup::setStatistics( const Statistics &statistics ) mStatistics = statistics; } -std::string MDAL::DatasetGroup::referenceTime() const +MDAL::DateTime MDAL::DatasetGroup::referenceTime() const { return mReferenceTime; } -void MDAL::DatasetGroup::setReferenceTime( const std::string &referenceTime ) +void MDAL::DatasetGroup::setReferenceTime( const DateTime &referenceTime ) { mReferenceTime = referenceTime; } diff --git a/mdal/mdal_data_model.hpp b/mdal/mdal_data_model.hpp index 3d19150d..bc5a8c55 100644 --- a/mdal/mdal_data_model.hpp +++ b/mdal/mdal_data_model.hpp @@ -13,6 +13,7 @@ #include #include #include "mdal.h" +#include "mdal_datetime.hpp" namespace MDAL { @@ -75,14 +76,15 @@ namespace MDAL DatasetGroup *group() const; Mesh *mesh() const; - double time() const; - void setTime( double time ); + double time( RelativeTimestamp::Unit unit ) const; + void setTime( double time, RelativeTimestamp::Unit unit = RelativeTimestamp::hours ); + void setTime( const RelativeTimestamp &time ); bool supportsActiveFlag() const; void setSupportsActiveFlag( bool value ); private: - double mTime = std::numeric_limits::quiet_NaN(); + RelativeTimestamp mTime; bool mIsValid = true; bool mSupportsActiveFlag = false; DatasetGroup *mParent = nullptr; @@ -166,8 +168,8 @@ namespace MDAL Statistics statistics() const; void setStatistics( const Statistics &statistics ); - std::string referenceTime() const; - void setReferenceTime( const std::string &referenceTime ); + DateTime referenceTime() const; + void setReferenceTime( const DateTime &referenceTime ); Mesh *mesh() const; @@ -186,7 +188,7 @@ namespace MDAL MDAL_DataLocation mDataLocation = MDAL_DataLocation::DataOnVertices2D; std::string mUri; // file/uri from where it came Statistics mStatistics; - std::string mReferenceTime; + DateTime mReferenceTime; }; typedef std::vector> DatasetGroups; diff --git a/mdal/mdal_datetime.cpp b/mdal/mdal_datetime.cpp new file mode 100644 index 00000000..dfb11217 --- /dev/null +++ b/mdal/mdal_datetime.cpp @@ -0,0 +1,345 @@ +/* + MDAL - Mesh Data Abstraction Library (MIT License) + Copyright (C) 2019 Vincent Cloarec (vcloarec at gmail dot com) +*/ +#include "mdal_datetime.hpp" +#include "mdal_utils.hpp" + + +constexpr double MILLISECONDS_IN_SECOND = 1000; +constexpr double MILLISECONDS_IN_MINUTE = 1000 * 60; +constexpr double MILLISECONDS_IN_HOUR = 1000 * 60 * 60; +constexpr double MILLISECONDS_IN_DAY = 1000 * 60 * 60 * 24; +constexpr double MILLISECONDS_IN_WEEK = 1000 * 60 * 60 * 24 * 7; + +//https://www.unidata.ucar.edu/software/netcdf-java/current/CDM/CalendarDateTime.html +constexpr double MILLISECONDS_IN_EXACT_YEAR = 3.15569259747e10; //CF Compliant +constexpr double MILLISECONDS_IN_MONTH_CF = MILLISECONDS_IN_EXACT_YEAR / 12.0; //CF Compliant + +MDAL::DateTime::DateTime( int year, int month, int day, int hours, int minutes, double seconds, MDAL::DateTime::Calendar calendar ) +{ + DateTimeValues value{year, month, day, hours, minutes, seconds}; + + switch ( calendar ) + { + case MDAL::DateTime::Gregorian: + setWithGregorianJulianCalendarDate( value ); + break; + case MDAL::DateTime::ProlepticGregorian: + setWithGregorianCalendarDate( value ); + break; + case MDAL::DateTime::Julian: + setWithJulianCalendarDate( value ); + break; + } +} + +MDAL::DateTime::DateTime( double value, Epoch epoch ): mValid( true ) +{ + switch ( epoch ) + { + case MDAL::DateTime::Unix: + mJulianTime = ( DateTime( 1970, 01, 01, 0, 0, 0, Gregorian ) + RelativeTimestamp( value, RelativeTimestamp::seconds ) ).mJulianTime; + break; + case MDAL::DateTime::JulianDay: + mJulianTime = int64_t( value * MILLISECONDS_IN_DAY + 0.5 ); + break; + } +} + +std::string MDAL::DateTime::toStandartCalendarISO8601() const +{ + if ( mValid ) + { + DateTimeValues value = dateTimeGregorianProleptic(); + if ( value.year > 0 ) + return toString( value ); + } + + return ""; +} + +double MDAL::DateTime::toJulianDay() const +{ + return mJulianTime / MILLISECONDS_IN_DAY; +} + +std::string MDAL::DateTime::toJulianDayString() const +{ + return std::to_string( toJulianDay() ); +} + + +MDAL::DateTime MDAL::DateTime::operator+( const MDAL::RelativeTimestamp &duration ) const +{ + if ( !mValid ) + return DateTime(); + return DateTime( mJulianTime + duration.mDuration ); +} + + +MDAL::DateTime MDAL::DateTime::operator-( const MDAL::RelativeTimestamp &duration ) const +{ + if ( !mValid ) + return DateTime(); + return DateTime( mJulianTime - duration.mDuration ); +} + +bool MDAL::DateTime::operator==( const MDAL::DateTime &other ) const +{ + if ( !mValid && !other.mValid ) + return true; + + return ( mValid && other.mValid ) && ( mJulianTime == other.mJulianTime ); +} + +bool MDAL::DateTime::operator<( const MDAL::DateTime &other ) const +{ + if ( !mValid && !other.mValid ) + return false; + return ( mValid && other.mValid ) && ( mJulianTime < other.mJulianTime ); +} + +bool MDAL::DateTime::isValid() const { return mValid; } + +MDAL::DateTime::DateTime( int64_t julianTime ): mJulianTime( julianTime ), mValid( true ) +{} + +MDAL::DateTime::DateTimeValues MDAL::DateTime::dateTimeGregorianJulianCalendar() const +{ + // https://fr.wikipedia.org/wiki/Jour_julien + DateTimeValues values; + int Z = int( mJulianTime / MILLISECONDS_IN_DAY + 0.5 ); // integer part of julian days count + double F = ( mJulianTime - MILLISECONDS_IN_DAY * ( Z - 0.5 ) ) / MILLISECONDS_IN_DAY; // fractional part of julian days count; + int S; + + if ( Z < 2299161 ) + S = Z; + else + { + int alpha = int( ( Z - 1867216.25 ) / 36524.25 ); + S = Z + 1 + alpha - int( alpha / 4 ); + } + + int B = S + 1524; + int C = int( ( B - 122.1 ) / 365.25 ); + int D = int( 365.25 * C ); + int E = int( ( B - D ) / 30.6001 ); + + values.day = B - D - int( 30.6001 * E ); + if ( E < 14 ) + values.month = E - 1; + else + values.month = E - 13; + + if ( values.month > 2 ) + values.year = C - 4716; + else + values.year = C - 4715; + + values.hours = int( F / MILLISECONDS_IN_HOUR ); + F = int( F - values.hours * MILLISECONDS_IN_HOUR ); + values.minutes = int( F / MILLISECONDS_IN_MINUTE ); + F = int( F - values.minutes * MILLISECONDS_IN_MINUTE ); + values.seconds = int( F / MILLISECONDS_IN_SECOND ); + + return values; +} + +MDAL::DateTime::DateTimeValues MDAL::DateTime::dateTimeGregorianProleptic() const +{ + // https://fr.wikipedia.org/wiki/Jour_julien + DateTimeValues values; + int Z = int( mJulianTime / MILLISECONDS_IN_DAY + 0.5 ); // integer part of julian days count + int F = int( mJulianTime - MILLISECONDS_IN_DAY * ( Z - 0.5 ) ) ; // fractional part of julian days count in ms; + + int alpha = int( ( Z - 1867216.25 ) / 36524.25 ); + int S = Z + 1 + alpha - int( alpha / 4 ); + + int B = S + 1524; + int C = int( ( B - 122.1 ) / 365.25 ); + int D = int( 365.25 * C ); + int E = int( ( B - D ) / 30.6001 ); + + values.day = B - D - int( 30.6001 * E ); + if ( E < 14 ) + values.month = E - 1; + else + values.month = E - 13; + + if ( values.month > 2 ) + values.year = C - 4716; + else + values.year = C - 4715; + + values.hours = int( F / MILLISECONDS_IN_HOUR ); + F = int( F - values.hours * MILLISECONDS_IN_HOUR ); + values.minutes = int( F / MILLISECONDS_IN_MINUTE ); + F = int( F - values.minutes * MILLISECONDS_IN_MINUTE ); + values.seconds = int( F / MILLISECONDS_IN_SECOND ); + + return values; +} + + +void MDAL::DateTime::setWithGregorianCalendarDate( MDAL::DateTime::DateTimeValues values ) +{ + // https://quasar.as.utexas.edu/BillInfo/JulianDatesG.html + if ( values.month <= 2 ) + { + values.year--; + values.month += 12; + } + + int A = values.year / 100; + int B = A / 4; + int C = 2 - A + B; + int E = int( 365.25 * ( values.year + 4716 ) ); + int F = int( 30.6001 * ( values.month + 1 ) ); + double julianDay = C + values.day + E + F - 1524.5; + + mValid = true; + mJulianTime = int64_t( julianDay * MILLISECONDS_IN_DAY + + ( values.hours ) * MILLISECONDS_IN_HOUR + + values.minutes * MILLISECONDS_IN_MINUTE + + values.seconds * MILLISECONDS_IN_SECOND ); +} + +void MDAL::DateTime::setWithJulianCalendarDate( MDAL::DateTime::DateTimeValues values ) +{ + // https://quasar.as.utexas.edu/BillInfo/JulianDatesG.html + if ( values.month <= 2 ) + { + values.year--; + values.month += 12; + } + + int E = int( 365.25 * ( values.year + 4716 ) ); + int F = int( 30.6001 * ( values.month + 1 ) ); + double julianDay = values.day + E + F - 1524.5; + + mValid = true; + mJulianTime = int64_t( julianDay * MILLISECONDS_IN_DAY + + ( values.hours ) * MILLISECONDS_IN_HOUR + + values.minutes * MILLISECONDS_IN_MINUTE + + values.seconds * MILLISECONDS_IN_SECOND ); +} + +void MDAL::DateTime::setWithGregorianJulianCalendarDate( MDAL::DateTime::DateTimeValues values ) +{ + // https://quasar.as.utexas.edu/BillInfo/JulianDatesG.html + + mValid = true; + + if ( values.year > 1582 || + ( values.year == 1582 && ( values.month > 10 || ( values.month == 10 && values.day >= 15 ) ) ) ) // gregorian calendar + { + setWithGregorianCalendarDate( values ); + } + else + setWithJulianCalendarDate( values ); +} + +std::string MDAL::DateTime::toString( MDAL::DateTime::DateTimeValues values ) const +{ + int miliseconds = int( ( values.seconds - int( values.seconds ) ) * 1000 + 0.5 ); + std::string msStr; + if ( miliseconds > 0 ) + { + if ( miliseconds < 10 ) + msStr = prependZero( std::to_string( miliseconds ), 3 ); + else if ( miliseconds < 100 ) + msStr = prependZero( std::to_string( miliseconds ), 2 ); + else if ( miliseconds < 1000 ) + msStr = std::to_string( miliseconds ); + + msStr = std::string( "," ).append( msStr ); + } + + std::string strDateTime = prependZero( std::to_string( values.year ), 4 ) + "-" + + prependZero( std::to_string( values.month ), 2 ) + "-" + + prependZero( std::to_string( values.day ), 2 ) + "T" + + prependZero( std::to_string( values.hours ), 2 ) + ":" + + prependZero( std::to_string( values.minutes ), 2 ) + ":" + + prependZero( std::to_string( int( values.seconds ) ), 2 ) + + msStr; + + return strDateTime; +} + +MDAL::RelativeTimestamp MDAL::DateTime::operator-( const MDAL::DateTime &other ) const +{ + if ( !mValid || !other.mValid ) + return RelativeTimestamp(); + return RelativeTimestamp( mJulianTime - other.mJulianTime ); +} + + +MDAL::RelativeTimestamp::RelativeTimestamp( double duration, MDAL::RelativeTimestamp::Unit unit ) +{ + switch ( unit ) + { + case MDAL::RelativeTimestamp::milliseconds: + mDuration = int64_t( duration ); + break; + case MDAL::RelativeTimestamp::seconds: + mDuration = int64_t( duration * MILLISECONDS_IN_SECOND + 0.5 ); + break; + case MDAL::RelativeTimestamp::minutes: + mDuration = int64_t( duration * MILLISECONDS_IN_MINUTE + 0.5 ); + break; + case MDAL::RelativeTimestamp::hours: + mDuration = int64_t( duration * MILLISECONDS_IN_HOUR + 0.5 ); + break; + case MDAL::RelativeTimestamp::days: + mDuration = int64_t( duration * MILLISECONDS_IN_DAY + 0.5 ); + break; + case MDAL::RelativeTimestamp::weeks: + mDuration = int64_t( duration * MILLISECONDS_IN_WEEK + 0.5 ); + break; + case MDAL::RelativeTimestamp::months_CF: + mDuration = int64_t( duration * MILLISECONDS_IN_MONTH_CF + 0.5 ); + break; + case MDAL::RelativeTimestamp::exact_years: + mDuration = int64_t( duration * MILLISECONDS_IN_EXACT_YEAR + 0.5 ); + break; + } +} + +double MDAL::RelativeTimestamp::value( MDAL::RelativeTimestamp::Unit unit ) const +{ + switch ( unit ) + { + case MDAL::RelativeTimestamp::milliseconds: + return double( mDuration ); + case MDAL::RelativeTimestamp::seconds: + return mDuration / MILLISECONDS_IN_SECOND; + case MDAL::RelativeTimestamp::minutes: + return mDuration / MILLISECONDS_IN_MINUTE; + case MDAL::RelativeTimestamp::hours: + return mDuration / MILLISECONDS_IN_HOUR; + case MDAL::RelativeTimestamp::days: + return double( mDuration ) / MILLISECONDS_IN_DAY; + case MDAL::RelativeTimestamp::weeks: + return double( mDuration ) / MILLISECONDS_IN_WEEK; + case MDAL::RelativeTimestamp::months_CF: + return double( mDuration ) / MILLISECONDS_IN_MONTH_CF; + case MDAL::RelativeTimestamp::exact_years: + return double( mDuration ) / MILLISECONDS_IN_EXACT_YEAR; + } + + return 0; +} + +bool MDAL::RelativeTimestamp::operator==( const MDAL::RelativeTimestamp &other ) const +{ + return mDuration == other.mDuration; +} + +bool MDAL::RelativeTimestamp::operator<( const MDAL::RelativeTimestamp &other ) const +{ + return mDuration < other.mDuration; +} + +MDAL::RelativeTimestamp::RelativeTimestamp( int64_t ms ): mDuration( ms ) +{} diff --git a/mdal/mdal_datetime.hpp b/mdal/mdal_datetime.hpp new file mode 100644 index 00000000..73deb286 --- /dev/null +++ b/mdal/mdal_datetime.hpp @@ -0,0 +1,118 @@ +/* + MDAL - Mesh Data Abstraction Library (MIT License) + Copyright (C) 2019 Vincent Cloarec (vcloarec at gmail dot com) +*/ + +#ifndef MDAL_DATE_TIME_HPP +#define MDAL_DATE_TIME_HPP + +#include +#include + +#include "mdal.h" + +namespace MDAL +{ + + class RelativeTimestamp + { + public: + enum Unit + { + milliseconds = 0, + seconds, + minutes, + hours, + days, + weeks, + months_CF, + exact_years + }; + + RelativeTimestamp() = default; + RelativeTimestamp( double duration, Unit unit ); + + double value( Unit unit ) const; + + bool operator==( const RelativeTimestamp &other ) const; + bool operator<( const RelativeTimestamp &other ) const; + + private: + RelativeTimestamp( int64_t ms ); + int64_t mDuration = 0; //in ms + + friend class DateTime; + }; + + class DateTime + { + public: + + enum Calendar + { + Gregorian = 0, + ProlepticGregorian, + Julian, + }; + + enum Epoch + { + Unix = 0, + JulianDay + }; + + DateTime() = default; + //! Constructor with date/time values and calendar type + DateTime( int year, int month, int day, int hours = 0, int minutes = 0, double seconds = 0, Calendar calendar = Gregorian ); + //! Constructor with Julian day or Unix Epoch + DateTime( double value, Epoch epoch ); + + //! Returns a string with the date/time expressed in Greogrian proleptic calendar with ISO8601 format (local time zone) + //! Do not support negative year + std::string toStandartCalendarISO8601() const; + + //! Returns the Julian day value + double toJulianDay() const; + + //! Returns the Julain day value expressed with a string + std::string toJulianDayString() const; + + //! operators + RelativeTimestamp operator-( const DateTime &other ) const; + DateTime operator+( const RelativeTimestamp &duration ) const; + DateTime operator-( const RelativeTimestamp &duration ) const; + bool operator==( const DateTime &other ) const; + bool operator<( const DateTime &other ) const; + + bool isValid() const; + + private: + + struct DateTimeValues + { + int year; + int month; + int day; + int hours; + int minutes; + double seconds; + }; + + DateTime( int64_t julianTime ); + + DateTimeValues dateTimeGregorianJulianCalendar() const; + DateTimeValues dateTimeGregorianProleptic() const; + + void setWithGregorianCalendarDate( DateTimeValues values ); + void setWithJulianCalendarDate( DateTimeValues values ); + void setWithGregorianJulianCalendarDate( DateTimeValues values );//Uses the adapted formula depending of the date (< or > 1582-10-15) + + std::string toString( DateTimeValues values ) const; + + int64_t mJulianTime = 0; //Julian day in ms + + bool mValid = false; + }; +} + +#endif // MDAL_DATE_TIME_HPP diff --git a/mdal/mdal_utils.cpp b/mdal/mdal_utils.cpp index 6b46db45..945e7f7e 100644 --- a/mdal/mdal_utils.cpp +++ b/mdal/mdal_utils.cpp @@ -637,3 +637,138 @@ std::string MDAL::doubleToString( double value, int precision ) oss << value; return oss.str(); } + +std::string MDAL::prependZero( const std::string &str, size_t length ) +{ + if ( length <= str.size() ) + return str; + + return std::string( length - str.size(), '0' ).append( str ); +} + +MDAL::RelativeTimestamp::Unit MDAL::parseDurationTimeUnit( const std::string &timeUnit ) +{ + MDAL::RelativeTimestamp::Unit unit = MDAL::RelativeTimestamp::hours; //default unit + + if ( timeUnit == "millisec" || + timeUnit == "msec" || + timeUnit == "millisecs" || + timeUnit == "msecs" + ) + { + unit = MDAL::RelativeTimestamp::milliseconds; + } + else if ( timeUnit == "second" || + timeUnit == "seconds" || + timeUnit == "Seconds" || + timeUnit == "sec" || + timeUnit == "secs" || + timeUnit == "s" || + timeUnit == "se" || // ascii_dat format + timeUnit == "2" ) // ascii_dat format + { + unit = MDAL::RelativeTimestamp::seconds; + } + else if ( timeUnit == "minute" || + timeUnit == "minutes" || + timeUnit == "Minutes" || + timeUnit == "min" || + timeUnit == "mins" || + timeUnit == "mi" || // ascii_dat format + timeUnit == "1" ) // ascii_dat format + { + unit = MDAL::RelativeTimestamp::minutes; + } + else if ( timeUnit == "day" || + timeUnit == "days" || + timeUnit == "Days" ) + { + unit = MDAL::RelativeTimestamp::days; + } + else if ( timeUnit == "week" || + timeUnit == "weeks" ) + { + unit = MDAL::RelativeTimestamp::weeks; + } + + + return unit; +} + +MDAL::RelativeTimestamp::Unit MDAL::parseCFTimeUnit( std::string timeInformation ) +{ + auto strings = MDAL::split( timeInformation, ' ' ); + if ( strings.size() < 3 ) + return MDAL::RelativeTimestamp::hours; //default value + + if ( strings[1] == "since" ) + { + std::string timeUnit = strings[0]; + if ( timeUnit == "month" || + timeUnit == "months" || + timeUnit == "mon" || + timeUnit == "mons" ) + { + return MDAL::RelativeTimestamp::months_CF; + } + else if ( timeUnit == "year" || + timeUnit == "years" || + timeUnit == "yr" || + timeUnit == "yrs" ) + { + return MDAL::RelativeTimestamp::exact_years; + } + + return MDAL::parseDurationTimeUnit( strings[0] ); + } + + return MDAL::RelativeTimestamp::hours;//default value +} + +MDAL::DateTime MDAL::parseCFReferenceTime( const std::string &timeInformation, const std::string &calendarString ) +{ + auto strings = MDAL::split( timeInformation, ' ' ); + if ( strings.size() < 3 ) + return MDAL::DateTime(); + + if ( strings[1] != "since" ) + return MDAL::DateTime(); + + std::string dateString = strings[2]; + + auto dateStringValues = MDAL::split( dateString, '-' ); + if ( dateStringValues.size() != 3 ) + return MDAL::DateTime(); + + int year = MDAL::toInt( dateStringValues[0] ); + int month = MDAL::toInt( dateStringValues[1] ); + int day = MDAL::toInt( dateStringValues[2] ); + + int hours = 0; + int minutes = 0; + double seconds = 0; + + if ( strings.size() > 3 ) + { + std::string timeString = strings[3]; + auto timeStringsValue = MDAL::split( timeString, ":" ); + if ( timeStringsValue.size() == 3 ) + { + hours = MDAL::toInt( timeStringsValue[0] ); + minutes = MDAL::toInt( timeStringsValue[1] ); + seconds = MDAL::toDouble( timeStringsValue[2] ); + } + } + + MDAL::DateTime::Calendar calendar; + if ( calendarString == "gregorian" || calendarString == "standard" || calendarString.empty() ) + calendar = MDAL::DateTime::Gregorian; + else if ( calendarString == "proleptic_gregorian" ) + calendar = MDAL::DateTime::ProlepticGregorian; + else if ( calendarString == "julian" ) + calendar = MDAL::DateTime::Julian; + else + return MDAL::DateTime(); + + return MDAL::DateTime( year, month, day, hours, minutes, seconds, calendar ); +} diff --git a/mdal/mdal_utils.hpp b/mdal/mdal_utils.hpp index 36cb0bf7..b6058c31 100644 --- a/mdal/mdal_utils.hpp +++ b/mdal/mdal_utils.hpp @@ -141,5 +141,18 @@ namespace MDAL return true; } + //! Prepend 0 to string to have n char + std::string prependZero( const std::string &str, size_t length ); + + RelativeTimestamp::Unit parseDurationTimeUnit( const std::string &timeUnit ); + + //! parse the time unit in the CF convention string format "XXXX since 2019-01-01 00:00:00" + //! https://www.unidata.ucar.edu/software/netcdf-java/current/CDM/CalendarDateTime.html + RelativeTimestamp::Unit parseCFTimeUnit( std::string timeInformation ); + + //! parse the reference time in the CF convention string format "XXXX since 2019-01-01 00:00:00" + //! https://www.unidata.ucar.edu/software/netcdf-java/current/CDM/CalendarDateTime.html + MDAL::DateTime parseCFReferenceTime( const std::string &timeInformation, const std::string &calendarString ); + } // namespace MDAL #endif //MDAL_UTILS_HPP diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 21f38167..a35ccdc9 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -132,6 +132,7 @@ SET_TESTS_PROPERTIES(mdalinfo_test_err1 PROPERTIES WILL_FAIL TRUE) SET(UNITTESTS_SRC unittests/mdal_unittests.cpp unittests/test_mdal_utils.cpp + unittests/test_mdal_datetime.cpp mdal_testutils.hpp mdal_testutils.cpp ) diff --git a/tests/data/datetime/julianDay.txt b/tests/data/datetime/julianDay.txt new file mode 100644 index 00000000..e6deb476 --- /dev/null +++ b/tests/data/datetime/julianDay.txt @@ -0,0 +1,1000 @@ +1987-07-20T01:38:41 2446996.568530092 +8352-01-19T04:43:50 4771582.697106482 +4359-02-21T12:23:32 3313203.016342592 +1556-08-14T04:45:16 2289602.698101852 +5505-12-07T10:57:19 3732059.956469907 +5281-02-26T21:19:47 3649963.388738426 +7730-10-19T07:54:15 4544675.829340278 +3635-03-26T13:19:35 3048801.055266204 +6232-06-10T13:25:48 3997412.059583333 +8795-06-10T00:34:10 4933527.523726853 +6271-07-05T04:39:55 4011680.694386574 +8345-03-12T01:49:19 4769078.575914352 +1133-03-15T14:39:29 2134953.110752315 +7793-06-21T03:21:45 4567566.640104167 +5718-04-18T08:10:52 3809623.840879630 +9622-03-14T11:04:54 5235495.961736111 +3002-11-10T23:07:43 2817831.463692130 +2649-04-22T18:22:47 2688699.265821759 +9488-09-09T03:08:21 5186732.630798611 +0102-05-04T04:28:37 1758437.686539352 +4622-03-01T08:36:05 3409269.858391204 +1827-05-04T08:46:34 2388480.865671297 +4383-10-26T14:47:19 3322216.116192130 +1377-05-01T17:15:04 2224120.218796297 +6429-03-20T04:21:18 4069282.681458333 +6827-08-17T16:13:13 4214799.175844908 +8763-10-07T19:29:22 4921959.312060185 +8406-04-18T02:42:05 4791395.612557870 +3121-03-03T05:57:21 2861042.748159722 +3501-04-15T04:13:38 2999877.676134259 +0491-11-21T13:37:35 1900719.067766204 +8899-05-01T12:38:56 4971474.027037038 +7440-12-20T12:25:44 4438818.017870370 +3203-10-28T03:54:53 2891231.663113426 +4411-12-22T22:10:35 3332500.424016204 +6888-08-22T03:33:48 4237084.648472222 +4661-09-04T22:42:24 3423702.446111111 +2832-06-03T15:56:57 2755581.164548611 +1375-04-05T08:57:21 2223362.873159722 +7695-03-26T10:12:58 4531685.925671297 +2068-08-09T21:18:52 2476603.388101852 +6415-10-26T04:12:28 4064388.675324074 +3955-12-21T10:27:28 3165947.935740741 +4579-10-06T16:53:38 3393784.203912037 +3765-06-07T04:12:55 3096355.675636574 +9031-08-06T07:17:29 5019781.803807870 +8454-10-25T02:50:22 4809117.618310184 +7731-08-08T18:17:42 4544969.262291667 +6355-11-09T16:47:49 4042488.199872685 +7291-08-14T18:26:29 4384269.268391203 +5202-10-24T10:07:21 3621347.921770833 +7603-04-01T03:51:25 4498088.660706018 +8181-05-22T15:05:31 4709251.128831019 +6107-06-23T09:03:40 3951768.877546296 +9796-06-04T23:45:42 5299131.490069444 +7283-01-11T04:33:34 4381131.689976851 +8432-06-25T05:58:17 4800960.748807871 +2524-09-04T13:18:08 2643179.054259259 +9104-03-02T08:07:26 5046287.838495370 +8924-09-12T06:36:21 4980738.775243056 +0687-06-14T17:43:09 1972146.238298611 +2210-04-26T08:13:57 2528360.843020834 +9366-08-03T07:07:13 5142135.796678240 +7817-12-02T01:18:42 4576495.554652778 +1873-05-19T09:18:08 2405297.887592593 +6146-01-11T23:57:23 3965851.498182870 +9627-03-06T01:03:54 5237313.544375001 +0081-12-09T14:16:56 1750988.095092593 +6480-10-18T10:49:08 4088122.950787037 +8599-04-08T02:33:37 4861877.606678240 +7384-09-01T23:58:34 4418255.499004629 +0480-01-13T04:39:11 1896388.693877315 +3915-11-07T19:22:23 3151294.307210648 +6305-02-04T14:35:44 4023948.108148148 +2110-05-24T09:29:07 2491864.895219908 +3598-09-26T17:15:53 3035471.219363426 +7888-01-02T17:19:28 4602094.221851852 +5486-08-11T05:35:14 3725002.732800926 +2735-06-21T11:44:12 2720168.989027778 +8931-08-25T16:09:24 4983277.173194445 +4463-11-28T14:49:13 3351469.117511574 +2848-02-25T16:36:19 2761326.191886574 +7708-10-01T07:06:23 4536622.796099537 +0893-07-08T17:35:39 2047411.233090278 +0097-05-19T04:12:51 1756627.675590278 +1412-08-03T19:35:18 2236997.316180555 +7242-03-06T23:38:36 4366211.485138888 +8963-06-22T15:10:32 4994901.132314814 +9776-04-08T12:45:44 5291769.031759259 +4427-04-14T04:01:57 3338091.668020833 +0420-02-22T16:16:21 1874514.178020834 +2755-11-24T03:44:15 2727629.655729167 +4171-09-21T17:27:49 3244750.227650463 +8428-03-17T15:01:22 4799400.125949074 +9014-04-28T05:02:52 5013472.710324074 +7457-01-25T08:54:44 4444697.871342592 +4932-09-04T15:40:35 3522683.153182871 +2601-03-04T13:56:08 2671118.080648148 +5276-10-28T15:53:44 3648381.162314815 +5958-10-20T18:46:56 3897467.282592593 +4513-11-21T20:34:03 3369724.356979167 +2564-06-16T16:50:02 2657709.201412037 +9792-06-28T14:29:32 5297694.103842592 +9692-06-14T09:27:16 5261155.893935185 +2805-07-25T08:43:11 2745770.863321759 +9240-08-14T22:45:54 5096127.448541667 +8751-06-01T07:01:05 4917447.792418982 +0882-11-21T15:18:07 2043529.137581018 +8990-11-25T19:34:46 5004919.315810186 +7669-09-19T07:09:37 4522366.798344907 +8331-07-13T10:25:25 4764087.934317130 +0554-08-01T09:07:44 1923616.880370370 +8110-08-03T10:43:37 4683390.946956019 +4579-07-26T06:30:17 3393711.771030093 +9388-01-10T23:35:12 5149966.482777778 +2235-06-25T07:38:32 2537551.818425925 +6327-10-13T05:31:49 4032233.730428241 +1197-09-27T13:39:41 2158525.069224537 +2012-08-05T02:03:23 2456144.585682870 +9089-07-11T19:28:48 5040941.311666667 +1874-01-14T10:53:33 2405537.953854166 +0168-03-04T02:34:05 1782483.607002315 +1628-08-21T17:43:12 2315908.238333334 +7821-04-09T01:05:54 4577719.545763889 +6690-04-15T06:42:53 4164636.779780093 +6652-10-10T20:07:08 4150936.338287037 +7623-07-13T05:51:27 4505496.744062499 +6219-03-11T21:14:39 3992572.385173611 +9649-11-08T01:07:58 5245596.547199074 +7558-07-09T04:11:24 4481751.674583334 +4322-04-03T15:30:22 3299730.146087963 +1349-12-16T08:28:10 2214121.852893518 +6701-05-20T00:14:30 4168688.510069444 +6759-08-03T11:08:16 4189947.964074074 +1018-01-05T08:34:02 2092880.856967593 +3479-08-13T11:55:30 2991962.996875000 +2736-07-20T02:06:46 2720563.588032408 +4762-07-13T09:32:09 3460537.897326389 +0622-05-16T23:56:25 1948376.497511574 +7809-06-18T02:24:52 4573406.600601852 +7928-04-17T14:01:19 4616809.084247685 +3864-04-12T05:23:48 3132458.724861111 +0949-09-25T14:56:52 2067943.122824074 +3503-04-23T17:20:21 3000616.222465278 +3480-08-09T16:34:35 2992325.190682870 +4404-01-12T05:04:18 3329598.711319444 +3109-11-11T01:02:06 2856912.543125000 +5400-01-06T13:34:51 3693375.065868055 +9157-08-09T11:14:53 5065805.968668981 +0910-08-16T01:31:11 2053657.563321759 +2798-03-10T12:13:47 2743077.009571759 +6856-03-27T07:20:47 4225248.806099538 +7745-12-19T05:05:19 4550215.712025463 +0126-09-20T00:14:04 1767342.509768518 +5397-03-24T11:02:16 3692356.959907407 +6937-01-06T03:24:28 4254752.641990741 +3715-12-01T23:38:56 3078270.485370371 +5555-12-08T18:27:57 3750323.269409722 +3436-02-11T08:54:07 2976073.870914352 +1691-02-03T03:55:52 2338718.663796296 +5742-04-01T21:02:21 3818373.376631944 +5868-04-09T02:55:23 3864401.621793982 +1527-12-11T06:20:53 2279128.764502315 +1926-05-02T20:35:50 2424638.358217593 +1184-01-27T15:24:56 2153533.142314815 +0011-08-03T23:06:46 1725292.463032407 +0943-07-13T16:47:18 2065677.199513889 +3544-09-06T21:08:10 3015728.380671296 +3948-10-01T07:45:43 3163310.823414352 +1796-09-04T06:33:12 2377282.773055556 +7008-07-15T07:20:37 4280874.805983797 +0236-05-10T12:17:50 1807387.012384259 +9654-02-08T16:20:02 5247150.180578704 +1910-07-07T08:45:03 2418859.864618056 +3988-12-24T18:24:27 3178005.266979167 +8997-11-09T10:22:50 5007459.932523148 +7126-09-25T11:53:36 4324044.995555555 +3729-09-04T23:25:48 3083296.476250000 +5550-10-27T00:04:33 3748454.503159722 +3241-10-24T11:08:33 2905107.964270833 +6223-09-08T10:42:06 3994213.945902778 +4260-02-23T05:57:06 3277045.747986111 +0557-10-26T17:17:46 1924799.220671296 +9383-06-18T18:15:19 5148299.260636575 +5932-03-09T11:33:11 3887745.981377315 +7741-01-05T06:04:13 4548406.752928240 +4423-06-28T18:08:54 3336706.256180556 +0856-09-23T04:02:33 2033973.668437500 +9116-08-24T15:27:27 5050846.144062500 +6808-03-02T23:21:08 4207692.473009259 +1366-07-04T08:19:22 2220165.846782408 +3227-03-16T23:35:38 2899772.483078704 +3260-04-15T17:24:34 2911856.225393519 +4129-08-14T22:40:26 3229372.444745371 +6341-05-18T15:54:13 4037200.162650463 +9062-07-10T23:21:14 5031078.473078704 +8032-09-20T01:05:57 4654950.545798611 +5302-12-02T09:39:00 3657910.902083333 +5625-10-01T09:07:19 3775822.880081019 +2037-02-16T09:38:20 2465105.901620370 +2015-06-20T10:46:13 2457193.948761574 +2281-02-08T20:48:29 2554217.367002315 +4060-10-18T02:56:06 3204235.622291667 +3388-12-13T15:19:49 2958849.138761574 +5141-06-22T23:45:07 3598944.489664352 +8984-09-24T17:09:54 5002666.215208333 +6438-01-12T19:35:31 4072503.316331018 +3820-05-15T23:53:10 3116421.495254630 +8529-07-01T06:51:05 4836394.785474537 +8600-12-17T02:36:21 4862495.608576389 +8276-06-13T09:52:15 4743970.911284722 +6379-05-03T08:38:26 4051063.860023148 +9123-12-15T20:58:42 5053515.374097222 +5365-02-06T02:38:56 3680622.610370371 +3412-11-04T17:46:36 2967575.240694445 +5566-06-14T05:51:39 3754163.744201389 +1446-10-13T18:05:41 2249486.253946759 +5351-02-05T12:15:40 3675508.010879629 +0805-09-16T20:56:21 2015339.372465278 +1538-11-07T15:37:54 2283113.151319444 +0854-12-03T15:10:14 2033314.132106482 +6847-12-04T21:45:52 4222213.406851852 +0077-04-07T17:36:06 1749281.233402778 +8225-12-02T16:53:18 4725515.203680555 +7130-07-21T13:52:45 4325440.078298612 +4144-08-28T06:18:34 3234864.762893519 +7404-05-20T02:31:40 4425454.605324074 +3280-02-19T21:26:09 2919105.393159722 +0012-04-09T07:03:14 1725541.793912037 +1512-10-08T15:34:23 2273587.148877315 +2832-06-11T22:28:10 2755589.436226851 +2278-08-22T15:02:08 2553316.126481481 +7345-01-03T21:34:47 4403769.399155093 +6536-01-12T10:11:37 4108295.924733796 +0864-04-07T23:42:21 2036727.487743055 +9264-03-14T16:52:45 5104740.203298612 +3621-10-05T21:32:14 3043881.397384259 +4931-01-20T14:22:10 3522090.098726852 +0887-01-14T00:09:31 2045043.506608796 +2568-03-21T11:32:35 2659082.980960649 +0739-02-26T14:25:20 1991030.100925926 +3440-09-12T04:13:47 2977748.676238426 +7257-12-06T01:57:47 4371964.581793983 +5097-06-13T17:21:33 3582865.223298611 +4090-01-21T18:37:28 3214923.276018518 +1502-04-03T04:11:19 2269745.674525463 +9409-05-16T07:25:02 5157761.809050926 +6954-02-27T04:18:33 4261013.679548612 +1484-12-08T11:17:43 2263421.970636575 +3822-03-11T23:46:52 3117086.490879630 +5041-11-16T01:52:32 3562566.578148148 +0719-11-08T06:55:07 1983979.788275463 +3843-07-23T08:27:20 3124889.852314815 +7261-09-24T06:52:58 4373352.786782407 +1040-02-11T03:31:14 2100952.646689815 +5676-08-02T17:55:56 3794391.247175926 +8469-09-17T13:06:36 4814559.046250000 +1193-05-28T12:25:47 2156942.017905092 +4386-02-04T19:13:32 3323048.301064814 +3621-02-18T10:16:52 3043651.928379629 +6601-10-03T13:19:53 4132301.055474537 +6947-12-05T04:00:02 4258737.666689815 +9692-08-03T16:40:26 5261206.194745371 +5792-07-09T13:17:27 3836735.053784722 +6391-06-24T11:06:25 4055498.962789352 +5505-07-21T20:14:06 3731921.343125000 +0452-02-07T23:09:37 1886187.465011574 +3888-10-10T22:58:12 3141406.457083333 +6340-02-24T15:18:41 4036751.137974537 +7237-08-15T03:23:08 4364546.641064815 +7319-01-20T23:44:24 4394289.489166667 +4865-12-11T15:17:43 3498310.137303241 +7306-12-11T10:37:18 4389865.942569445 +6236-11-18T03:57:01 3999033.664594907 +6322-02-22T02:34:30 4030174.607291667 +4517-07-11T07:49:47 3371051.826238425 +0910-01-23T03:49:29 2053452.659363426 +0787-10-09T11:45:55 2008786.990219907 +0885-08-03T15:51:27 2044515.160729167 +2314-10-27T20:17:20 2566530.345370371 +1746-12-16T16:25:16 2359123.184212963 +8430-08-24T14:08:57 4800290.089548610 +2468-06-04T22:18:06 2622634.429236111 +0332-06-23T17:31:27 1842494.230173611 +3378-10-27T10:07:28 2955148.921851852 +8373-05-10T03:10:21 4779364.632187500 +2678-10-08T23:57:51 2699460.498506945 +3644-10-13T14:52:00 3052290.119444445 +8035-03-02T23:45:06 4655844.489652777 +3015-01-05T17:35:14 2822270.232800926 +5455-07-27T05:01:08 3713664.709120370 +5726-07-13T23:23:05 3812632.474363426 +3203-11-20T17:10:34 2891255.215671297 +2447-04-15T00:57:27 2614912.539895833 +0622-11-21T06:23:49 1948564.766539352 +8651-02-14T06:17:23 4880816.762071759 +5747-09-16T12:10:07 3820367.007025463 +6095-09-15T16:35:31 3947471.191331018 +8031-07-22T17:07:53 4654525.213807870 +9933-02-09T13:16:53 5349053.053391204 +5282-10-16T03:02:27 3650559.626701389 +5480-10-23T01:53:47 3722884.579016204 +0702-10-26T03:13:36 1977757.634444444 +4884-01-03T08:22:37 3504906.849039352 +3323-03-23T01:56:51 2934841.581145833 +1559-05-19T14:52:03 2290611.119479167 +6083-09-18T02:32:03 3943090.605590278 +9086-08-09T21:23:56 5039874.391620371 +7519-07-02T07:33:53 4467499.815196759 +9297-01-26T21:49:23 5116746.409293981 +1298-07-17T16:35:02 2195343.190995370 +9394-06-16T04:40:19 5152314.694664353 +8305-01-07T19:06:36 4754405.296250000 +3613-09-19T14:23:00 3040943.099305556 +3342-03-22T17:47:51 2941781.241562500 +3013-11-25T16:17:47 2821864.179016204 +4311-09-19T08:35:44 3295880.858148148 +6070-12-02T16:17:48 3938418.179027778 +2218-06-02T23:09:09 2531320.464687500 +8080-02-17T21:55:52 4672267.413796296 +0724-10-20T14:42:19 1985788.112719907 +8327-12-07T16:22:03 4762774.181979167 +0048-12-09T04:23:13 1738934.682789352 +1649-03-08T23:41:45 2323412.487326389 +0397-04-23T17:47:53 1866174.241585648 +7635-10-20T19:19:08 4509979.304953705 +1520-08-13T06:21:18 2276452.764791667 +1319-05-23T17:25:08 2202957.225787037 +8090-08-01T01:15:38 4676084.552523149 +7540-04-04T05:09:44 4475081.715092592 +3383-06-27T19:53:10 2956853.328587963 +5354-11-08T09:46:01 3676879.906956018 +2929-09-06T17:22:35 2791104.224016204 +2109-10-07T09:39:07 2491635.902164352 +1855-08-06T16:14:01 2398802.176400463 +7744-09-14T02:18:31 4549754.596192130 +4459-11-19T05:54:56 3349998.746481482 +7734-02-21T07:28:53 4545896.811724537 +5718-08-04T10:40:38 3809731.944884259 +8797-06-03T14:04:29 4934252.086446758 +6296-01-13T15:27:47 4020639.144293981 +3175-06-18T04:35:26 2880872.691273148 +9320-07-13T14:35:53 5125314.108252315 +8578-02-26T01:45:17 4854166.573113427 +9832-04-19T08:05:52 5312232.837407407 +4662-12-08T11:52:48 3424161.995000000 +2519-03-13T08:17:50 2641176.845717593 +5058-06-22T21:26:42 3568629.393541667 +9311-03-11T17:33:30 5121902.231597221 +8643-02-28T07:31:43 4877908.813692129 +8995-07-19T12:33:30 5006616.023263888 +6816-03-27T14:36:11 4210639.108460648 +8499-06-15T23:41:28 4825422.487129630 +7684-05-04T19:01:04 4527708.292407407 +7473-08-13T09:32:12 4450741.897361111 +4178-11-19T11:57:58 3247365.998587963 +4232-11-07T10:48:30 3267076.950347222 +7217-11-18T00:53:09 4357336.536909722 +5122-07-11T14:07:39 3592023.088645834 +6937-09-12T16:26:57 4255002.185381944 +5666-06-10T02:42:00 3790684.612500000 +4704-09-09T18:55:30 3439412.288541667 +2884-06-18T01:27:41 2774588.560891204 +9832-06-16T21:57:25 5312291.414872685 +8063-10-10T14:47:42 4666293.116458333 +8551-03-13T03:42:08 4844319.654259260 +3263-05-24T14:11:47 2912990.091516204 +6945-04-18T08:56:28 4257776.872546296 +8912-08-03T13:38:41 4976316.068530093 +9988-10-17T07:52:24 5369391.828055556 +2783-12-19T20:45:45 2737882.365104167 +1237-07-03T18:23:08 2173049.266064815 +0075-04-02T21:08:56 1748545.381203704 +1505-05-28T21:26:07 2270897.393136574 +8800-12-13T19:12:36 4935541.300416667 +2436-08-13T08:06:05 2611015.837557871 +1327-08-14T11:57:06 2205961.997986111 +9453-07-18T03:02:47 5173895.626932871 +3532-03-22T13:16:12 3011177.052916666 +3975-02-21T20:26:54 3172950.352013889 +7714-04-13T10:53:18 4538642.953680555 +1742-02-21T12:45:53 2357364.031863426 +4289-02-07T19:06:20 3287623.296064815 +5711-12-04T10:00:56 3807296.917314815 +1112-04-01T17:29:41 2127300.228946760 +8129-07-24T23:01:27 4690321.459340277 +0733-02-24T16:04:23 1988837.169710648 +0785-02-18T15:51:10 2007824.160532407 +8543-11-11T20:47:07 4841641.366053240 +8604-04-17T19:41:20 4863713.320370371 +1659-06-21T18:13:38 2327169.259467592 +4281-06-23T05:27:35 3284836.727488426 +8505-09-14T11:37:00 4827703.984027778 +6614-07-17T15:11:48 4136971.133194444 +3999-07-27T03:14:14 3181871.634884259 +5993-05-02T06:04:43 3910079.753275463 +1012-04-18T05:29:33 2090792.728854167 +6928-07-12T22:56:08 4251653.455648148 +6525-10-02T12:41:58 4104542.029143519 +0285-07-06T04:25:40 1825340.684490741 +6528-11-15T04:24:09 4105681.683437500 +1490-01-26T04:34:08 2265296.690370370 +4056-04-01T03:25:30 3202574.642708333 +2269-03-10T18:45:28 2549864.281574074 +3560-01-26T17:30:11 3021348.229293982 +9684-06-17T14:10:36 5258237.090694443 +7327-06-02T16:39:22 4397344.194004630 +1054-11-27T06:41:54 2106355.779097222 +4706-07-01T22:00:22 3440072.416921296 +8454-06-08T10:39:00 4808978.943750001 +1400-04-03T15:37:02 2232492.150717592 +4002-06-15T12:34:29 3182926.023946759 +7652-10-22T17:32:42 4516191.231041666 +4708-03-22T21:22:11 3440702.390405092 +7525-07-13T01:30:22 4469702.562754629 +2299-12-18T20:12:53 2561104.342280093 +2959-09-04T16:58:01 2802059.206956018 +4192-04-01T16:54:27 3252248.204479167 +6734-11-06T08:51:24 4180911.869027778 +8059-06-21T02:18:16 4664720.596018518 +0559-11-12T16:06:02 1925546.170856482 +9796-03-18T12:42:13 5299053.029317129 +9921-10-26T18:29:27 5344929.270451388 +9660-06-27T21:42:38 5249481.404606482 +6534-02-03T16:56:43 4107588.206053241 +9351-08-04T13:13:17 5136658.050891205 +8556-12-15T03:18:23 4846423.637766204 +1656-12-25T08:04:35 2326260.836516204 +4007-05-12T19:47:41 3184718.324780093 +8991-04-20T03:53:14 5005064.661967592 +6265-11-10T01:49:47 4009617.576238425 +1315-12-24T20:10:52 2201711.340879630 +9103-05-09T03:14:47 5045989.635266204 +4988-12-05T15:10:00 3543229.131944444 +6582-05-05T12:33:49 4125211.023483797 +8426-05-07T18:49:05 4798720.284085648 +2621-06-20T19:43:19 2678531.321747685 +1849-12-15T08:40:31 2396741.861469908 +0816-11-28T03:43:30 2019429.655208333 +8517-08-21T22:27:30 4832063.435763889 +8116-02-03T05:47:34 4685400.741365740 +8034-03-11T14:13:54 4655488.092986112 +1045-11-23T00:00:47 2103064.500543981 +3080-02-05T22:41:42 2846042.445625000 +3379-01-15T13:03:06 2955229.043819444 +9877-06-11T01:16:50 5328721.553356482 +4568-05-10T15:55:56 3389618.163842593 +9185-01-17T21:43:38 5075829.405300926 +9891-09-21T11:22:50 5333936.974189814 +3234-01-03T11:01:06 2902256.959097222 +9572-06-09T03:03:06 5217320.627152777 +8842-01-27T09:05:41 4950560.878946759 +3078-03-21T15:47:38 2845356.158078704 +2570-07-26T18:00:48 2659940.250555555 +9183-11-03T07:20:49 5075387.806122686 +6684-03-10T15:28:51 4162410.145034722 +0287-09-12T17:30:55 1826139.229803241 +0713-12-03T08:22:50 1981813.849189815 +1983-12-26T02:58:19 2445694.623831019 +2340-11-02T04:17:39 2576032.678923611 +2037-07-23T02:51:12 2465262.618888889 +0092-10-09T02:54:09 1754944.620937500 +7813-06-15T01:03:24 4574864.544027778 +9235-03-18T23:43:01 5094151.488206019 +7644-04-14T23:47:40 4513078.491435185 +6479-10-18T05:42:52 4087756.738101852 +2177-09-20T15:36:27 2516456.150312500 +4719-04-16T15:17:36 3444744.137222223 +6131-11-06T04:22:32 3960670.682314815 +7539-03-16T04:56:34 4474696.705949074 +4980-05-09T04:54:33 3540096.704548611 +8581-07-01T03:20:26 4855387.639189815 +1567-12-14T08:07:45 2293741.838715278 +0857-03-18T01:02:49 2034149.543622685 +0454-10-18T16:38:05 1887171.193113426 +1058-04-13T04:47:04 2107588.699351852 +6249-04-14T20:50:36 4003564.368472222 +8762-10-05T08:37:32 4921591.859398148 +9413-05-16T05:17:06 5159222.720208333 +4942-10-28T23:32:17 3526389.480752315 +6865-02-10T02:49:13 4228490.617511573 +8604-10-23T15:38:33 4863902.151770834 +5558-09-26T06:22:08 3751345.765370370 +9702-06-12T19:47:23 5264805.324571759 +2526-01-24T01:44:36 2643685.572638889 +7966-09-23T08:26:31 4630846.851747685 +8106-09-08T23:46:53 4681966.490891203 +4045-04-18T19:41:53 3198574.320752315 +6123-07-01T01:30:57 3957620.563159722 +4819-09-15T00:40:24 3481420.528055556 +3884-05-19T04:01:34 3139800.667754630 +2414-05-18T15:58:54 2602893.165902778 +2413-10-08T09:45:47 2602670.906793981 +0801-11-22T16:38:33 2013945.193437500 +8347-10-20T08:55:50 4770030.872106480 +6445-03-14T23:37:28 4075121.484351852 +2277-01-28T14:30:42 2552745.104652778 +8899-08-05T20:20:21 4971570.347465278 +3637-12-02T06:06:31 3049782.754525463 +5767-03-15T23:51:42 3827487.494236112 +0712-03-12T09:09:28 1981182.881574074 +6719-02-10T08:18:36 4175163.846250000 +2207-05-08T10:23:14 2527276.932800926 +3735-12-04T18:14:13 3085578.259872685 +5686-12-06T06:35:20 3798168.774537037 +9065-12-08T16:33:24 5032325.189861111 +8671-10-12T04:13:05 4888361.675752316 +6851-08-10T14:26:46 4223558.101921296 +3399-08-10T22:55:53 2962741.455474537 +3683-08-10T06:29:47 3066469.770682870 +4882-11-26T14:38:54 3504504.110347222 +2033-05-16T19:58:06 2463734.332013889 +5301-03-15T21:47:50 3657284.408217593 +9976-02-27T03:26:08 5364775.643148148 +5582-04-11T16:46:35 3759944.199016204 +8980-02-24T04:34:27 5000991.690590278 +0588-11-07T06:21:52 1936133.765185185 +9206-10-11T18:02:55 5083766.252025463 +2508-10-21T11:51:29 2637381.994085649 +1975-03-09T05:01:54 2442480.709652778 +4558-06-05T21:24:55 3385991.392303240 +1892-04-16T21:29:48 2412205.395694444 +2894-07-19T08:36:38 2778271.858773148 +3813-05-12T02:26:22 3113860.601643519 +9976-07-25T09:18:20 5364924.887731481 +7757-09-16T08:10:39 4554504.840729166 +3022-09-15T14:44:41 2825080.114363426 +0424-06-03T00:44:20 1876076.530787037 +8135-04-14T04:39:03 4692410.693784723 +8267-10-20T11:09:45 4740811.965104166 +8208-10-28T14:01:49 4719271.084594907 +5306-08-04T12:47:40 3659252.033101852 +9185-09-11T16:37:52 5076066.192962964 +9710-09-26T13:56:05 5267833.080613426 +0659-07-01T21:11:57 1961936.383298611 +7266-03-10T11:24:53 4374980.975613425 +4645-01-06T03:11:00 3417616.632638889 +0449-09-14T07:04:16 1885310.794629630 +4188-03-02T19:46:34 3250757.324004630 +2221-04-16T17:01:07 2532369.209108796 +5965-03-24T09:07:01 3899813.879872685 +9970-06-26T05:08:30 5362703.714236110 +1101-12-11T18:12:11 2123536.258460648 +9762-04-15T21:14:27 5286662.385034722 +6516-01-12T18:22:38 4100991.265717593 +9602-01-26T21:53:49 5228144.412372685 +7339-03-03T23:39:55 4401636.486053240 +0126-11-22T10:06:16 1767405.921018519 +7693-07-28T13:52:44 4531080.078287037 +9015-03-21T09:04:27 5013799.878090277 +3031-11-02T13:24:44 2828415.058842592 +1101-11-16T23:57:17 2123511.498113426 +4614-11-08T18:23:02 3406600.265995370 +4134-03-08T19:42:43 3231039.321331019 +6422-03-09T04:17:23 4066714.678738426 +3490-04-14T23:18:15 2995860.471006945 +4195-07-02T20:55:20 3253435.371759260 +0780-11-08T23:49:03 2006261.492395833 +1120-03-14T11:41:20 2130203.987037037 +6406-01-08T14:35:57 4060811.108298611 +3510-06-13T10:02:02 3003223.918078703 +1281-11-08T07:06:02 2189247.795856481 +7968-11-14T12:09:10 4631630.006365741 +9263-03-21T12:05:09 5104381.003576389 +9158-07-01T12:33:57 5066132.023576388 +3859-11-09T20:41:34 3130843.362199075 +4042-01-23T03:08:26 3197392.630856482 +1972-08-28T19:45:28 2441558.323240741 +9756-01-20T15:34:32 5284385.148981482 +4859-09-18T05:13:06 3496033.717430556 +3304-10-07T10:21:05 2928100.931307870 +3022-06-12T21:32:42 2824985.397708334 +4240-08-27T16:15:56 3269927.177731481 +1797-04-18T22:20:53 2377509.431168981 +8372-03-09T23:06:27 4778938.462812499 +5612-11-09T18:51:19 3771114.285636574 +6713-06-20T06:47:25 4173102.782928240 +5474-06-02T08:50:02 3720549.868078704 +6731-11-20T22:02:52 4179830.418657407 +7745-04-08T04:44:24 4549960.697500001 +9708-07-10T01:04:15 5267024.544618056 +8301-08-17T11:44:09 4753165.988993055 +9188-10-21T10:10:13 5077201.923761574 +3030-09-11T04:31:11 2827997.688321759 +5877-12-13T06:27:23 3867936.769016203 +2842-10-17T18:26:58 2759369.268726852 +7067-03-05T18:20:44 4302292.264398148 +4424-02-18T02:01:53 3336940.584641204 +5215-12-01T03:06:09 3626133.629270833 +7180-03-24T11:03:14 4343583.960578703 +1245-07-05T11:39:25 2175972.985706018 +6081-11-04T19:22:35 3942408.307349537 +5388-08-23T21:34:37 3689222.399039352 +7560-04-28T10:32:23 4482410.939155092 +4221-05-13T02:44:36 3262880.614305556 +1417-05-18T06:02:41 2238745.751863426 +6572-03-09T10:02:38 4121501.918495370 +8947-09-13T00:21:47 4989139.515127315 +8770-09-09T19:32:52 4924488.314490741 +5395-10-06T15:01:37 3691822.126122685 +1900-10-01T18:41:09 2415294.278576389 +1832-02-13T01:37:43 2390226.567858796 +7876-07-11T11:01:41 4597901.959502314 +3286-08-21T03:16:48 2921479.636666666 +8481-07-20T20:58:26 4818883.373912037 +7067-12-16T01:32:35 4302577.564293982 +2338-11-27T08:16:47 2575326.844988426 +1475-10-23T09:04:57 2260087.878437500 +1410-09-19T10:53:23 2236312.953738426 +2038-04-01T03:12:07 2465514.633414352 +4763-05-22T08:09:47 3460850.840127315 +4856-10-08T06:31:38 3494958.771967593 +2171-12-08T09:45:23 2514342.906516204 +5805-02-22T22:54:50 3841345.454745370 +9710-01-10T14:07:05 5267574.088252314 +6203-08-09T00:58:40 3986878.540740740 +1093-03-15T09:18:36 2120343.887916667 +3376-06-21T22:57:44 2954291.456759259 +8560-03-25T16:50:35 4847620.201793982 +7758-01-28T02:32:26 4554638.605856481 +4734-04-09T00:18:38 3450215.512939815 +0259-09-23T12:36:47 1815923.025543981 +3280-11-06T18:23:56 2919366.266620371 +8757-11-25T20:14:09 4919817.343159722 +7181-07-05T17:19:08 4344052.221620371 +2639-01-09T04:46:53 2684942.699224537 +1825-08-16T08:58:37 2387854.874039352 +0146-04-04T14:11:52 1774479.091574074 +1107-04-05T15:58:30 2125477.165625000 +1914-09-09T19:41:21 2420385.320381945 +1863-01-12T14:39:51 2401518.111006944 +2221-07-17T01:36:21 2532460.566909722 +7328-04-18T17:39:00 4397665.235416667 +9794-11-07T13:42:30 5298556.071180556 +2824-02-23T23:01:43 2752558.459525463 +1450-08-15T15:53:46 2250888.162337963 +7834-06-05T00:10:22 4582524.507199073 +4149-07-19T13:34:08 3236651.065370370 +8376-12-18T13:00:04 4780683.041712963 +0497-02-16T01:19:18 1902632.555069444 +3703-09-11T17:24:11 3073806.225127315 +4299-12-06T14:08:32 3291577.089259259 +1712-08-02T14:52:25 2346569.119733796 +8598-01-10T12:06:41 4861425.004641203 +3571-07-14T06:52:03 3025534.786145833 +8345-02-26T23:52:53 4769065.495057870 +0990-12-28T21:16:45 2083012.386631944 +6918-09-26T19:04:36 4248076.294861111 +5367-01-03T14:10:49 3681319.090844908 +5628-10-27T05:28:46 3776944.728310185 +1564-10-15T03:56:38 2292586.664328704 +9148-02-18T13:20:50 5062346.056134259 +1907-05-23T17:00:55 2417719.208969907 +7782-05-05T09:27:17 4563501.893946759 +3147-01-07T02:25:41 2870483.601168982 +3045-10-01T04:44:07 2833496.697303241 +8810-05-02T13:14:25 4938968.051678241 +1396-01-08T21:06:15 2230946.379340278 +0777-02-14T10:30:07 2004897.937581019 +3705-08-13T10:34:23 3074507.940543981 +4991-05-21T20:35:49 3544126.358206019 +4827-11-14T02:14:37 3484402.593483796 +6451-11-06T19:05:15 4077549.295312500 +7352-12-02T21:47:56 4406659.408287037 +5092-01-07T02:50:41 3580880.618530093 +9872-06-15T21:11:46 5326900.383171296 +5003-10-04T08:07:09 3548643.838298611 +8560-02-07T00:57:04 4847572.539629630 +3907-06-28T10:29:09 3148239.936909722 +4986-02-19T06:04:05 3542208.752835648 +0712-04-25T22:27:01 1981227.435428241 +4099-02-18T18:06:39 3218238.254618056 +3566-11-12T06:23:30 3023829.766319444 +3424-10-28T06:19:33 2971950.763576389 +2428-11-20T10:29:31 2608192.937164352 +4454-06-17T07:40:19 3348017.819664352 +9321-01-24T20:57:09 5125509.373020833 +6106-08-04T14:39:16 3951446.110601852 +5845-08-17T15:35:12 3856131.149444445 +0034-01-22T09:21:54 1733499.890208333 +3238-08-19T01:31:03 2903945.563229166 +5022-10-01T16:01:29 3555581.167696759 +7931-10-25T11:18:44 4618094.971342593 +7157-02-17T17:40:07 4335148.236192130 +8985-09-01T23:28:14 5003008.477939814 +6344-12-15T17:07:53 4038507.213807871 +6818-10-14T14:18:15 4211570.096006945 +2660-08-22T21:17:52 2692839.387407408 +0829-12-28T02:04:48 2024207.586666667 +0580-06-04T08:17:42 1933055.845625000 +3668-09-06T23:17:24 3061019.470416667 +4030-01-12T04:02:08 3192998.668148148 +2975-09-05T12:34:57 2807904.024270833 +9971-02-08T19:38:42 5362931.318541667 +0448-09-01T17:09:55 1884933.215219907 +9341-02-21T02:06:10 5132841.587615740 +3012-02-06T00:20:06 2821205.513958334 +1613-07-23T09:47:50 2310399.908217593 +4526-07-17T09:26:29 3374344.893391204 +6034-10-05T20:05:51 3925211.337395833 +7320-07-06T00:01:31 4394821.501053241 +5457-05-11T19:56:01 3714319.330567129 +8472-08-09T06:33:41 4815615.773391203 +3279-05-14T09:17:24 2918823.887083333 +1605-01-03T06:25:07 2307276.767442130 +1518-04-09T21:26:00 2275596.393055555 +9141-02-09T14:20:58 5059781.097893518 +9265-05-09T03:48:42 5105160.658819444 +0238-12-15T11:00:33 1808335.958715278 +3427-11-02T19:32:23 2973051.314155092 +1201-06-07T02:46:07 2159873.615358796 +9111-01-18T18:57:22 5048801.289837963 +7148-07-22T08:07:05 4332015.838252314 +4442-03-14T07:42:32 3343539.821203704 +5012-10-05T14:36:20 3551933.108564815 +0315-07-24T06:26:26 1836314.768356482 +8957-03-20T09:19:38 4992615.888634260 +0978-08-16T13:02:18 2078495.043263889 +9838-04-26T22:21:47 5314431.431793982 +9006-07-28T12:56:28 5010642.039212963 +6720-05-23T02:19:26 4175631.596828704 +4398-08-21T05:58:51 3327628.749201389 +4185-10-24T05:26:42 3249896.726875000 +4285-05-18T01:01:43 3286261.542858796 +7805-01-27T19:08:30 4571804.297569444 +4359-08-24T15:28:35 3313387.144849537 +8945-02-14T03:11:11 4988198.632766203 +5711-02-20T09:18:44 3807009.888009259 +0072-11-10T08:28:03 1747671.852812500 +9241-07-02T01:13:17 5096448.550891205 +6678-01-15T15:45:33 4160164.156631944 +9606-01-07T13:36:21 5229586.066909723 +3508-12-20T17:09:02 3002684.214606482 +8782-04-13T20:04:19 4928722.336331018 +8161-01-22T13:48:54 4701826.075625001 +4893-12-07T14:39:25 3508533.110706018 +1981-09-01T21:16:05 2444849.386168981 +3037-01-16T15:04:28 2830317.128101852 +6384-10-11T16:07:09 4053052.171631944 +8686-04-15T06:02:46 4893660.751921296 +4354-05-28T05:52:57 3311472.745104167 +7106-03-06T10:00:20 4316536.916898148 +5093-11-15T11:09:35 3581558.964988426 +8498-02-21T00:32:40 4824942.522685185 +4687-05-15T04:58:44 3433085.707453704 +0003-08-14T18:47:07 1722381.282719907 +1292-08-20T16:29:27 2193186.187118056 +3305-04-28T20:31:58 2928304.355532408 +2678-03-26T03:07:07 2699263.629942130 +3024-12-08T19:47:05 2825895.324363426 +6102-10-12T13:58:01 3950054.081956018 +6459-12-27T21:38:21 4080522.401631944 +0275-04-08T13:44:51 1821599.072812500 +7217-05-01T16:45:23 4357136.198182871 +2242-07-26T10:25:46 2540139.934560185 +6043-03-06T03:00:50 3928284.625578704 +2447-07-16T18:29:00 2615005.270138889 +9449-01-12T13:27:21 5172248.060659722 +4691-01-12T21:56:09 3434424.413993055 +1050-11-23T02:10:07 2104890.590358797 +0913-05-11T02:37:39 2054656.609479167 +6711-05-28T20:50:48 4172349.368611111 +7524-03-15T12:55:20 4469218.038425925 +3281-08-17T05:02:45 2919649.710243056 +5489-10-05T11:13:31 3726153.967719907 +1231-11-17T14:06:54 2170994.088125000 +1647-06-01T09:00:25 2322765.875289352 +4442-03-23T15:23:42 3343549.141458333 +4411-01-28T13:46:11 3332172.073738426 +3448-05-11T18:33:29 2980547.273252315 +6454-10-23T02:53:52 4078630.620740741 +2520-12-10T10:36:54 2641814.942291666 +9753-06-13T21:19:26 5283434.388495371 +5380-09-17T03:16:50 3686324.636689815 +4048-06-12T02:29:44 3199724.603981482 +2596-09-07T13:33:50 2669480.065162037 +8922-08-21T14:49:02 4979986.117384259 +2879-03-07T16:18:51 2772659.179756945 +3128-12-20T17:42:49 2863892.238067130 +3347-07-05T07:39:05 2943711.818807870 +8715-01-25T23:01:52 4904172.459629630 +1737-12-27T07:19:09 2355846.804965278 +8029-09-07T19:52:34 4653842.328171296 +4262-03-19T06:38:58 3277800.777060185 +0812-06-01T19:58:42 2017789.332430555 +5312-06-26T21:26:10 3661405.393171296 +8792-02-03T23:18:08 4932305.470925926 +0439-12-19T22:25:34 1881754.434421296 +3369-09-13T18:13:57 2951818.259687500 +6690-01-08T23:51:28 4164540.494074075 +0469-05-26T04:22:44 1892504.682453704 +3283-10-18T17:55:09 2920442.246631945 +2954-01-25T14:17:11 2800011.095266204 +8141-07-19T18:23:32 4694699.266342592 +7950-01-27T11:04:17 4624763.961307870 +2205-04-15T18:52:21 2526524.286354167 +2284-08-10T23:57:28 2555496.498240741 +5193-03-23T16:04:37 3617846.169872685 +2788-07-20T00:38:29 2739556.526724537 +9757-02-12T13:44:10 5284774.072337964 +0212-12-27T21:04:36 1798852.378194445 +1997-07-15T04:54:31 2450644.704525463 +5365-02-15T16:21:24 3680632.181527778 +4290-06-16T14:14:34 3288117.093449074 +0382-06-25T18:53:34 1860758.287199074 +2321-09-25T15:19:30 2569055.138541667 +9689-08-06T09:12:54 5260112.883958334 +2293-04-01T08:00:14 2558651.833495371 +1262-11-27T20:37:56 2182327.359675926 +2930-06-28T20:11:24 2791399.341250000 +0371-09-06T23:47:09 1856813.491076389 +2713-01-18T07:45:45 2711979.823437500 +4247-08-25T10:32:08 3272480.938981481 +6529-08-23T20:19:17 4105963.346724537 +0725-01-04T18:29:35 1985864.270543981 +8340-06-12T20:39:40 4767345.360879630 +3052-04-04T10:03:52 2835873.919351852 +1877-10-16T22:56:06 2406909.455625000 +6410-09-10T12:07:32 4062517.005231482 +2963-07-20T13:36:25 2803474.066956018 +3009-05-01T11:30:51 2820194.979756945 +1909-07-07T23:18:33 2418495.471215278 +8954-06-02T12:12:54 4991594.008958334 +8110-11-10T05:24:37 4683489.725428240 +4181-12-14T17:14:22 3248487.218310185 +3290-05-14T14:39:10 2922842.110532407 +8408-09-20T13:26:27 4792282.060034722 +2639-05-21T07:46:07 2685074.823692129 +7795-10-27T02:08:57 4568424.589548610 +8554-07-26T11:38:49 4845550.985289352 +6592-11-05T05:39:02 4129047.735439815 +0332-11-10T09:25:54 1842633.892986111 +2176-12-05T02:25:05 2516166.600752315 +0904-01-13T10:37:27 2051250.942673611 +5907-08-09T19:19:23 3878767.305127315 +0671-12-28T17:57:56 1966499.248564815 +0554-11-18T21:40:29 1923726.403113426 +0176-06-05T16:21:05 1785499.181307870 +9546-10-23T03:02:41 5207959.626863426 +2478-07-16T15:35:48 2626328.149861111 +4500-02-01T16:12:35 3364683.175405093 +4688-06-13T13:42:49 3433481.071400463 +9697-04-02T10:07:22 5262908.921782407 +0821-08-18T01:08:38 2021153.547662037 +4394-09-23T04:57:51 3326200.706840278 +8094-08-17T22:17:32 4677562.428842593 +5560-08-26T08:42:18 3752045.862708333 +9139-08-09T02:25:04 5059230.600740740 +9969-04-21T06:50:48 5362272.785277777 +1874-06-20T02:19:18 2405694.596736111 +3966-05-10T12:45:34 3169741.031643519 +5670-11-09T19:20:34 3792298.305949074 +0018-09-12T17:30:44 1727889.229675926 +3069-03-28T21:00:16 2842076.375185185 +1213-01-06T12:42:05 2164105.029224537 +8335-03-02T01:36:38 4765415.567106483 +1831-07-14T08:52:18 2390012.869652778 +0613-08-07T21:45:57 1945172.406909722 +4401-02-06T21:39:35 3328529.402488426 +7206-01-10T05:58:31 4353006.748969908 +0461-02-20T06:43:19 1889487.780081019 +9603-06-26T00:15:03 5228659.510451389 +8223-08-15T10:26:37 4724674.935150463 +2647-07-25T09:38:06 2688061.901458333 +3945-01-06T03:27:11 3161946.643877314 +4752-06-24T03:03:23 3456866.627349537 +0223-09-20T16:31:37 1802771.188622685 +0700-08-03T07:40:09 1976943.819548611 +1942-05-27T23:09:48 2430507.465138889 +0500-03-22T08:13:53 1903761.842974537 +2836-02-24T19:24:21 2756942.308576389 +8471-10-09T16:04:42 4815311.169930556 +9420-12-28T21:22:18 5162006.390486111 +5142-06-03T21:31:29 3599290.396863426 +5334-11-12T21:32:47 3669579.397766204 +2350-08-06T22:34:14 2579597.440439815 +4190-11-15T19:53:37 3251745.328900463 +0890-06-17T10:28:07 2046293.936192130 +5229-09-26T12:53:48 3631182.037361111 +2643-09-07T02:53:40 2686644.620601852 +8649-09-25T07:32:17 4880309.814085648 +9203-03-24T03:03:50 5082468.627662037 +9803-04-10T14:29:48 5301631.104027777 +5004-07-04T23:13:37 3548918.467789352 +2106-10-24T20:46:14 2490557.365439815 +8756-02-15T16:18:54 4919168.179791667 +2565-05-04T21:58:20 2658031.415509259 +1307-08-23T20:37:32 2198666.359398148 +8188-05-10T16:05:36 4711796.170555555 +7317-01-15T03:53:46 4393553.662337963 +2385-03-21T06:52:15 2592242.786284722 +6539-04-01T17:04:50 4109471.211689815 +1984-07-16T20:22:38 2445898.349050926 +7323-02-26T17:38:31 4395787.235081019 +5212-06-09T10:06:06 3624863.920902778 +0116-04-24T21:45:28 1763542.406574074 +2489-06-05T18:58:26 2630305.290578704 +8666-05-07T13:50:23 4886378.076655093 +4789-07-26T14:23:14 3470413.099467593 +9175-09-17T19:03:48 5072419.294305556 +3479-12-20T00:35:11 2992091.524432870 +5303-11-23T01:03:11 3658266.543877314 +8789-05-20T08:27:49 4931315.852650463 +2688-05-11T13:43:09 2702963.071631944 +2613-08-06T03:10:52 2675655.632546296 +7405-02-11T00:03:13 4425721.502233796 +5168-07-14T15:52:15 3608828.161284722 +0462-01-26T09:57:36 1889827.915000000 +0924-11-22T17:35:46 2058870.233171296 +2351-09-22T11:24:53 2580008.975613426 +8085-03-02T22:38:41 4674107.443530093 +4981-10-08T22:45:10 3540614.448032407 +2968-02-19T03:43:04 2805148.654907408 +8144-09-21T05:51:07 4695858.743831018 +1422-07-18T04:23:39 2240632.683090277 +9664-07-01T03:24:19 5250945.641886574 +7946-07-17T12:57:13 4623474.039733796 +2959-07-22T15:47:14 2802015.157800926 +5589-04-17T19:18:50 3762507.304745371 +6161-04-27T03:03:53 3971435.627696759 +0387-11-13T04:09:34 1862724.673310185 +4472-02-28T14:13:31 3354483.092719907 +1783-05-12T17:05:13 2372419.211956019 +2695-07-06T10:44:15 2705574.947395833 +5600-12-14T02:22:22 3766765.598865741 +6678-01-15T22:10:27 4160164.423923611 +9288-02-03T20:42:57 5113466.363159722 +9666-02-16T02:45:45 5251540.615104167 +6697-10-15T00:42:13 4167376.529317130 +4743-06-08T13:03:23 3453563.044016203 +0285-08-23T02:44:56 1825388.614537037 +3996-04-02T11:24:53 3180660.975613426 +7062-05-28T04:52:39 4300549.703229167 +4345-04-14T02:26:35 3308141.601793982 +2944-11-28T09:33:20 2796665.898148148 +3216-11-14T12:17:01 2895998.011817130 +6677-11-06T09:24:31 4160093.892025463 +0759-07-21T04:13:54 1998479.676319445 +5020-05-10T23:57:57 3554707.498576389 +5924-07-14T19:41:02 3884951.320162037 +0516-05-14T18:43:49 1909659.280428241 +3302-04-16T16:00:47 2927196.167210648 +0829-05-02T03:12:33 2023967.633715278 +0058-05-06T08:10:25 1742369.840567130 +8363-03-24T17:09:21 4775665.214826388 +8590-02-22T19:55:09 4858546.329965278 +6816-03-08T12:31:53 4210620.022141203 +8349-03-21T01:51:51 4770548.577673611 +6870-01-24T20:44:27 4230300.364201388 +1282-02-09T02:08:00 2189340.588888889 +5657-03-20T07:53:05 3787315.828530093 +8171-02-26T20:47:33 4705513.366354167 +6534-12-25T15:14:17 4107913.134918981 +4174-03-15T03:28:21 3245655.644687500 +1907-01-08T10:40:58 2417583.945115741 +9064-11-15T21:35:27 5031937.399618056 +1463-01-15T02:24:21 2255423.600243056 +6923-05-01T10:27:46 4249753.935949074 +8084-04-20T14:21:34 4673791.098310185 +6596-04-19T08:18:43 4130308.846331019 +0353-10-07T03:36:34 1850269.650393518 +5897-12-27T22:14:45 3875256.426909722 +5807-12-16T22:46:28 3842372.448935185 +6427-09-13T03:31:22 4068728.646782408 +4266-10-27T05:55:48 3279483.747083333 +9522-01-08T18:48:51 5198906.283923611 +4378-04-13T16:45:05 3320194.197974537 +4237-03-28T23:00:34 3268679.458726852 +1293-09-24T08:00:22 2193585.833587963 +3051-06-13T06:18:25 2835577.762789352 +9329-07-08T17:40:16 5128596.236296296 +6086-04-15T06:19:09 3944030.763298611 +4444-10-17T21:03:48 3344488.377638889 +9628-11-07T01:48:19 5237925.575219908 +4148-06-28T11:19:27 3236264.971840278 +8843-01-20T22:30:57 4950919.438159722 +0070-02-09T14:11:00 1746667.090972222 +8737-07-02T10:04:19 4912365.919664352 +0681-03-23T19:46:39 1969872.324062500 +9608-04-03T09:45:12 5230402.906388889 +7957-12-05T06:25:43 4627632.767858796 +4093-06-10T23:20:01 3216159.472233796 +5638-04-18T10:56:48 3780404.956111111 +3773-03-16T15:45:19 3099195.156469908 +6366-08-24T00:12:49 4046428.508900463 +6628-01-14T17:36:46 4141900.233865741 +8619-09-26T16:45:01 4869353.197928241 +4405-01-01T12:54:07 3329954.037581019 +0252-07-18T10:20:04 1813299.930601852 +2522-08-04T03:27:28 2642416.644074074 +4392-06-27T16:22:14 3325383.182106481 +2867-10-12T14:21:37 2768495.098344908 +8580-09-16T13:35:11 4855100.066099538 +2672-02-26T18:53:15 2697044.286979167 +6176-03-26T10:46:49 3976882.949178241 +2928-12-25T00:13:35 2790848.509432870 +4074-08-26T19:24:44 3209296.308842592 +8090-10-11T23:43:46 4676156.488726852 +2395-12-21T19:33:31 2596170.314942129 +0663-08-03T23:35:12 1963430.482777778 diff --git a/tests/mdal_testutils.cpp b/tests/mdal_testutils.cpp index 5127d093..9582b71d 100644 --- a/tests/mdal_testutils.cpp +++ b/tests/mdal_testutils.cpp @@ -309,3 +309,18 @@ void init_test() void finalize_test() { } + +bool compareDurationInHours( double h1, double h2 ) +{ + return fabs( h1 - h2 ) < 1.0 / 3600 / 1000; +} + +bool hasReferenceTime( DatasetGroupH group ) +{ + return std::strcmp( MDAL_G_referenceTime( group ), "" ) != 0; +} + +bool compareReferenceTime( DatasetGroupH group, const char *referenceTime ) +{ + return std::strcmp( MDAL_G_referenceTime( group ), referenceTime ) == 0; +} diff --git a/tests/mdal_testutils.hpp b/tests/mdal_testutils.hpp index 0eb3adcf..dfd107f2 100644 --- a/tests/mdal_testutils.hpp +++ b/tests/mdal_testutils.hpp @@ -7,6 +7,7 @@ #define MDAL_TESTUTILS_HPP #include +#include #include #include "mdal.h" @@ -56,4 +57,14 @@ bool compareVectors( const std::vector &a, const std::vector &b ); //! Same vertices (coords), faces and connectivity between them bool compareMeshFrames( MeshH meshA, MeshH meshB ); + +//! Compare duration with millisecond precision +bool compareDurationInHours( double h1, double h2 ); + +//! test if reference time is defined +bool hasReferenceTime( DatasetGroupH group ); + +//! Compare the reference time with the string +bool compareReferenceTime( DatasetGroupH group, const char *referenceTime ); + #endif // MDAL_TESTUTILS_HPP diff --git a/tests/test_3di.cpp b/tests/test_3di.cpp index 8445b563..2088d130 100644 --- a/tests/test_3di.cpp +++ b/tests/test_3di.cpp @@ -185,6 +185,11 @@ TEST( Mesh3DiTest, Mesh2D4cells301steps ) EXPECT_DOUBLE_EQ( 0, min ); EXPECT_DOUBLE_EQ( 8.4487915942199819e-14, max ); + EXPECT_TRUE( compareReferenceTime( g, "2014-01-01T00:00:00" ) ); + + double time = MDAL_D_time( ds ); + EXPECT_TRUE( compareDurationInHours( time, 0.22222222222 ) ); + MDAL_CloseMesh( m ); } @@ -212,6 +217,13 @@ TEST( Mesh3DiTest, Mesh2D16cells7steps ) ASSERT_EQ( 7, MDAL_G_datasetCount( g ) ); DatasetH ds = MDAL_G_dataset( g, 0 ); ASSERT_NE( ds, nullptr ); + + EXPECT_TRUE( compareReferenceTime( g, "2014-01-01T00:00:00" ) ); + + ds = MDAL_G_dataset( g, 6 ); + double time = MDAL_D_time( ds ); + EXPECT_TRUE( compareDurationInHours( time, 0.01666666667 ) ); + MDAL_CloseMesh( m ); } diff --git a/tests/test_ascii_dat.cpp b/tests/test_ascii_dat.cpp index 022c18e3..c320edf3 100644 --- a/tests/test_ascii_dat.cpp +++ b/tests/test_ascii_dat.cpp @@ -191,8 +191,7 @@ TEST( MeshAsciiDatTest, QuadAndTriangleFaceScalarFile ) double time = MDAL_D_time( ds ); EXPECT_DOUBLE_EQ( 1, time ); - const char *referenceTime = MDAL_G_referenceTime( g ); - EXPECT_EQ( std::string( "JULIAN 2433282.500000" ), std::string( referenceTime ) ); + EXPECT_TRUE( compareReferenceTime( g, "1950-01-01T00:00:00" ) ); MDAL_CloseMesh( m ); } @@ -261,8 +260,7 @@ TEST( MeshAsciiDatTest, QuadAndTriangleFaceVectorFile ) double time = MDAL_D_time( ds ); EXPECT_DOUBLE_EQ( 1, time ); - const char *referenceTime = MDAL_G_referenceTime( g ); - EXPECT_EQ( std::string( "JULIAN 2433288.500000" ), std::string( referenceTime ) ); + EXPECT_TRUE( compareReferenceTime( g, "1950-01-07T00:00:00" ) ); MDAL_CloseMesh( m ); } diff --git a/tests/test_binary_dat.cpp b/tests/test_binary_dat.cpp index 40a69e6e..847c8e0e 100644 --- a/tests/test_binary_dat.cpp +++ b/tests/test_binary_dat.cpp @@ -113,6 +113,11 @@ TEST( MeshBinaryDatTest, RegularGridVectorFile ) double value = getValueX( ds, 1000 ); EXPECT_DOUBLE_EQ( 0, value ); + EXPECT_FALSE( hasReferenceTime( g ) ); + + double time = MDAL_D_time( ds ); + EXPECT_TRUE( compareDurationInHours( time, 4.1666666666 ) ); + MDAL_CloseMesh( m ); } diff --git a/tests/test_flo2d.cpp b/tests/test_flo2d.cpp index 6b5993dc..c0dfe206 100644 --- a/tests/test_flo2d.cpp +++ b/tests/test_flo2d.cpp @@ -468,7 +468,9 @@ TEST( MeshFlo2dTest, BarnHDF5 ) ASSERT_NE( ds, nullptr ); double time = MDAL_D_time( ds ); - EXPECT_DOUBLE_EQ( 0.10124753560882101, time ); + EXPECT_TRUE( compareDurationInHours( 0.10124753560882101, time ) ); + + EXPECT_FALSE( hasReferenceTime( g ) ); valid = MDAL_D_isValid( ds ); EXPECT_EQ( true, valid ); @@ -666,7 +668,9 @@ TEST( MeshFlo2dTest, basic ) EXPECT_DOUBLE_EQ( 3, max ); double time = MDAL_D_time( ds ); - EXPECT_DOUBLE_EQ( 0.5, time ); + EXPECT_TRUE( compareDurationInHours( 0.5, time ) ); + + EXPECT_FALSE( hasReferenceTime( g ) ); // /////////// // Max Depth @@ -771,6 +775,8 @@ TEST( MeshFlo2dTest, basic_required_files_only ) double value = getValue( ds, 0 ); EXPECT_DOUBLE_EQ( 1.48, value ); + EXPECT_FALSE( hasReferenceTime( g ) ); + MDAL_CloseMesh( m ); } @@ -856,7 +862,9 @@ TEST( MeshFlo2dTest, pro_16_02_14 ) ASSERT_NE( ds, nullptr ); double time = MDAL_D_time( ds ); - EXPECT_DOUBLE_EQ( 150.0, time ); + EXPECT_TRUE( compareDurationInHours( 150.0, time ) ); + + EXPECT_FALSE( hasReferenceTime( g ) ); valid = MDAL_D_isValid( ds ); EXPECT_EQ( true, valid ); @@ -869,6 +877,7 @@ TEST( MeshFlo2dTest, pro_16_02_14 ) value = getValue( ds, 1 ); EXPECT_DOUBLE_EQ( 0.098000000000000004, value ); + MDAL_CloseMesh( m ); } diff --git a/tests/test_gdal_grib.cpp b/tests/test_gdal_grib.cpp index 981045bb..e762f899 100644 --- a/tests/test_gdal_grib.cpp +++ b/tests/test_gdal_grib.cpp @@ -48,7 +48,13 @@ TEST( MeshGdalGribTest, ScalarFile ) ASSERT_EQ( 1683, count ); double value = getValue( ds, 1600 ); - EXPECT_DOUBLE_EQ( 15.34, value ); + EXPECT_TRUE( compareDurationInHours( 15.34, value ) ); + + EXPECT_TRUE( compareReferenceTime( g, "2016-03-01T06:00:00" ) ); + + ds = MDAL_G_dataset( g, 1 ); + double time = MDAL_D_time( ds ); + EXPECT_TRUE( compareDurationInHours( 12, time ) ); MDAL_CloseMesh( m ); } @@ -96,6 +102,12 @@ TEST( MeshGdalGribTest, VectorFile ) double valueY = getValueY( ds, 1600 ); EXPECT_DOUBLE_EQ( 2.8200097656250001, valueY ); + EXPECT_TRUE( compareReferenceTime( g, "2016-03-01T06:00:00" ) ); + + ds = MDAL_G_dataset( g, 1 ); + double time = MDAL_D_time( ds ); + EXPECT_TRUE( compareDurationInHours( 12, time ) ); + MDAL_CloseMesh( m ); } @@ -144,6 +156,13 @@ TEST( MeshGdalGribTest, WithoutNODATA ) double valueY = getValueY( ds, 1600 ); EXPECT_DOUBLE_EQ( 1, valueY ); + + EXPECT_TRUE( compareReferenceTime( g, "1970-01-01T00:00:00" ) ); + + ds = MDAL_G_dataset( g, 0 ); + double time = MDAL_D_time( ds ); + EXPECT_TRUE( compareDurationInHours( 0, time ) ); + MDAL_CloseMesh( m ); } #endif @@ -190,6 +209,12 @@ TEST( MeshGdalGribTest, ScalarFileWithUComponent ) double value = getValue( ds, 1600 ); EXPECT_DOUBLE_EQ( -0.818756103515625, value ); + EXPECT_TRUE( compareReferenceTime( g, "2018-10-01T00:00:00" ) ); + + ds = MDAL_G_dataset( g, 1 ); + double time = MDAL_D_time( ds ); + EXPECT_TRUE( compareDurationInHours( 24, time ) ); + MDAL_CloseMesh( m ); } diff --git a/tests/test_gdal_netcdf.cpp b/tests/test_gdal_netcdf.cpp index 59a52dcb..043a034f 100644 --- a/tests/test_gdal_netcdf.cpp +++ b/tests/test_gdal_netcdf.cpp @@ -64,6 +64,12 @@ TEST( MeshGdalNetCDFTest, Indonesia ) double value = getValue( ds, 50 ); EXPECT_DOUBLE_EQ( 0.99988487698798889, value ); + EXPECT_TRUE( compareReferenceTime( g, "1900-01-01T00:00:00" ) ); + + ds = MDAL_G_dataset( g, 0 ); + double time = MDAL_D_time( ds ); + EXPECT_TRUE( compareDurationInHours( 1008072, time ) ); + MDAL_CloseMesh( m ); } } diff --git a/tests/test_hec2d.cpp b/tests/test_hec2d.cpp index 57867094..3e1bca8a 100644 --- a/tests/test_hec2d.cpp +++ b/tests/test_hec2d.cpp @@ -144,6 +144,10 @@ TEST( MeshHec2dTest, simpleArea ) double time = MDAL_D_time( ds ); EXPECT_DOUBLE_EQ( 0.0, time ); + EXPECT_TRUE( compareDurationInHours( 0, time ) ); + + EXPECT_TRUE( compareReferenceTime( g, "1899-12-30T00:00:00" ) ); + MDAL_CloseMesh( m ); } @@ -254,7 +258,9 @@ TEST( MeshHec2dTest, MultiAreas ) EXPECT_DOUBLE_EQ( 706.2740478515625, max ); double time = MDAL_D_time( ds ); - EXPECT_DOUBLE_EQ( 60, time ); + EXPECT_TRUE( compareDurationInHours( 60, time ) ); + + EXPECT_TRUE( compareReferenceTime( g, "1999-01-01T12:00:00" ) ); MDAL_CloseMesh( m ); } @@ -357,10 +363,9 @@ TEST( MeshHec2dTest, model_505 ) EXPECT_DOUBLE_EQ( 43.28509521484375, max ); double time = MDAL_D_time( ds ); - EXPECT_DOUBLE_EQ( 0.083333335816860199, time ); + EXPECT_TRUE( compareDurationInHours( 0.083333335816860199, time ) ); - const char *referenceTime = MDAL_G_referenceTime( g ); - EXPECT_EQ( std::string( "01JAN2018 00:00:00" ), std::string( referenceTime ) ); + EXPECT_TRUE( compareReferenceTime( g, "2018-01-01T00:00:00" ) ); MDAL_CloseMesh( m ); } diff --git a/tests/test_selafin.cpp b/tests/test_selafin.cpp index 2e3ac1f7..a680b53d 100644 --- a/tests/test_selafin.cpp +++ b/tests/test_selafin.cpp @@ -141,7 +141,7 @@ TEST( MeshSLFTest, MalpassetResultFrench ) ASSERT_NE( ds, nullptr ); double time = MDAL_D_time( ds ); - EXPECT_DOUBLE_EQ( 4000, time ); + EXPECT_TRUE( compareDurationInHours( 1.111111111, time ) ); bool valid = MDAL_D_isValid( ds ); EXPECT_EQ( true, valid ); @@ -204,6 +204,8 @@ TEST( MeshSLFTest, MalpassetResultFrench ) EXPECT_DOUBLE_EQ( 2.3694833011052991e-12, min ); EXPECT_DOUBLE_EQ( 7.5673562379016834, max ); + EXPECT_TRUE( compareReferenceTime( r, "1900-01-01T00:00:00" ) ); + MDAL_CloseMesh( m ); } diff --git a/tests/test_sww.cpp b/tests/test_sww.cpp index 2880694e..e512b7ee 100644 --- a/tests/test_sww.cpp +++ b/tests/test_sww.cpp @@ -149,6 +149,11 @@ TEST( MeshSWWTest, Cairns ) EXPECT_DOUBLE_EQ( 0, min ); EXPECT_DOUBLE_EQ( 6.7305092811584473, max ); + EXPECT_FALSE( hasReferenceTime( g ) ); + + double time = MDAL_D_time( ds ); + EXPECT_TRUE( compareDurationInHours( time, 0.083333333333 ) ); + MDAL_CloseMesh( m ); } diff --git a/tests/test_tuflowfv.cpp b/tests/test_tuflowfv.cpp index efc7ad13..626efa6a 100644 --- a/tests/test_tuflowfv.cpp +++ b/tests/test_tuflowfv.cpp @@ -174,7 +174,11 @@ TEST( MeshTuflowFVTest, TrapSteady053D ) EXPECT_EQ( 10, MDAL_G_maximumVerticalLevelCount( g ) ); double time = MDAL_D_time( ds ); - EXPECT_DOUBLE_EQ( 0.502121734619141, time ); + EXPECT_TRUE( compareDurationInHours( 0.502121734619141, time ) ); + + EXPECT_FALSE( hasReferenceTime( g ) ); + + } // ///////////////////////////////// @@ -231,7 +235,10 @@ TEST( MeshTuflowFVTest, TrapSteady053D ) EXPECT_DOUBLE_EQ( 1.7670363355554111, max ); double time = MDAL_D_time( ds ); - EXPECT_DOUBLE_EQ( 0.667265041139391, time ); + EXPECT_TRUE( compareDurationInHours( 0.667265041139391, time ) ); + + EXPECT_FALSE( hasReferenceTime( g ) ); + } // ///////////////////////////////// @@ -282,7 +289,9 @@ TEST( MeshTuflowFVTest, TrapSteady053D ) EXPECT_DOUBLE_EQ( 2.488835334777832, max ); double time = MDAL_D_time( ds ); - EXPECT_DOUBLE_EQ( 1.16755709277259, time ); + EXPECT_TRUE( compareDurationInHours( 1.16755709277259, time ) ); + + EXPECT_FALSE( hasReferenceTime( g ) ); } // Close mesh @@ -348,8 +357,11 @@ TEST( MeshTuflowFVTest, TrapSteady053DWithMaxes ) double time = MDAL_D_time( ds ); EXPECT_DOUBLE_EQ( 0, time ); + + EXPECT_FALSE( hasReferenceTime( g ) ); } + // Close mesh MDAL_CloseMesh( m ); } diff --git a/tests/test_ugrid.cpp b/tests/test_ugrid.cpp index 0c80d022..1e0144d2 100644 --- a/tests/test_ugrid.cpp +++ b/tests/test_ugrid.cpp @@ -153,7 +153,9 @@ TEST( MeshUgridTest, DFlow11Manzese ) EXPECT_DOUBLE_EQ( 15.907056943512494, max ); double time = MDAL_D_time( ds ); - EXPECT_DOUBLE_EQ( 0.0, time ); + EXPECT_TRUE( compareDurationInHours( 0.0, time ) ); + + EXPECT_TRUE( compareReferenceTime( g, "2017-01-01T00:00:00" ) ); MDAL_CloseMesh( m ); } @@ -247,7 +249,9 @@ TEST( MeshUgridTest, DFlow11Simplebox ) EXPECT_DOUBLE_EQ( 6.3681219945588952, max ); double time = MDAL_D_time( ds ); - EXPECT_DOUBLE_EQ( 0.0097222222222222224, time ); + EXPECT_TRUE( compareDurationInHours( .0097222222222222224, time ) ); + + EXPECT_TRUE( compareReferenceTime( g, "2001-05-05T00:00:00" ) ); MDAL_CloseMesh( m ); } @@ -325,7 +329,9 @@ TEST( MeshUgridTest, DFlow12RivierGridClm ) EXPECT_DOUBLE_EQ( 12, max ); double time = MDAL_D_time( ds ); - EXPECT_DOUBLE_EQ( 183.5, time ); + EXPECT_TRUE( compareDurationInHours( 183.5, time ) ); + + EXPECT_TRUE( compareReferenceTime( g, "2002-10-15T00:00:00" ) ); std::string crs = MDAL_M_projection( m ); EXPECT_EQ( "EPSG:28992", crs ); @@ -446,13 +452,15 @@ TEST( MeshUgridTest, DFlow12RivierGridMap ) EXPECT_DOUBLE_EQ( 0, min ); EXPECT_DOUBLE_EQ( 0.66413616798770714, max ); + EXPECT_TRUE( compareReferenceTime( g, "2002-10-15T00:00:00" ) ); + std::string crs = MDAL_M_projection( m ); EXPECT_EQ( "EPSG:28992", crs ); MDAL_CloseMesh( m ); } -TEST( MeshUgridTest, UGRIFFormatWithoutTime ) +TEST( MeshUgridTest, UGRIDFormatWithoutTime ) { std::string path = test_file( "/ugrid/without_time/TINUGRID.tin" ); MeshH m = MDAL_LoadMesh( path.c_str() ); @@ -582,6 +590,8 @@ TEST( MeshUgridTest, ADCIRC ) EXPECT_DOUBLE_EQ( 0, min ); EXPECT_DOUBLE_EQ( 1.3282330120641679, max ); + EXPECT_TRUE( compareReferenceTime( g, "1970-01-01T00:00:00" ) ); + MDAL_CloseMesh( m ); } diff --git a/tests/test_xdmf.cpp b/tests/test_xdmf.cpp index 0b9b61d0..93bac8c6 100644 --- a/tests/test_xdmf.cpp +++ b/tests/test_xdmf.cpp @@ -64,7 +64,9 @@ TEST( XdmfTest, Basement3HumpsTest ) EXPECT_DOUBLE_EQ( 2.9100000000000001, max ); double time = MDAL_D_time( ds ); - EXPECT_DOUBLE_EQ( 20, time ); + EXPECT_TRUE( compareDurationInHours( 20, time ) ); + + EXPECT_FALSE( hasReferenceTime( g ) ); } // FUNCTION: JOIN($0, $1, 0*$1) dataset @@ -112,7 +114,9 @@ TEST( XdmfTest, Basement3HumpsTest ) EXPECT_DOUBLE_EQ( 4.9994493491047303, max ); double time = MDAL_D_time( ds ); - EXPECT_DOUBLE_EQ( 20, time ); + EXPECT_TRUE( compareDurationInHours( 20, time ) ); + + EXPECT_FALSE( hasReferenceTime( g ) ); } MDAL_CloseMesh( m ); @@ -175,7 +179,9 @@ TEST( XdmfTest, Basement3Slopes ) EXPECT_DOUBLE_EQ( 494.816927222965329, max ); double time = MDAL_D_time( ds ); - EXPECT_DOUBLE_EQ( 1, time ); + EXPECT_TRUE( compareDurationInHours( 1, time ) ); + + EXPECT_FALSE( hasReferenceTime( g ) ); } // FUNCTION: $1 - $0 @@ -220,7 +226,9 @@ TEST( XdmfTest, Basement3Slopes ) EXPECT_DOUBLE_EQ( 0.0030000000000001137, max ); double time = MDAL_D_time( ds ); - EXPECT_DOUBLE_EQ( 1, time ); + EXPECT_TRUE( compareDurationInHours( 1, time ) ); + + EXPECT_FALSE( hasReferenceTime( g ) ); } // FUNCTION: $0 - $1 @@ -372,7 +380,9 @@ TEST( XdmfTest, Basement3SimpleChannel ) EXPECT_DOUBLE_EQ( 0.48486232713374577, max ); double time = MDAL_D_time( ds ); - EXPECT_DOUBLE_EQ( 30, time ); + EXPECT_TRUE( compareDurationInHours( 30, time ) ); + + EXPECT_FALSE( hasReferenceTime( g ) ); } MDAL_CloseMesh( m ); @@ -564,7 +574,7 @@ TEST( XdmfTest, Simple ) EXPECT_DOUBLE_EQ( 3.5179872000733399, max ); double time = MDAL_D_time( ds ); - EXPECT_DOUBLE_EQ( 100.895, time ); + EXPECT_TRUE( compareDurationInHours( 100.895, time ) ); // lets try another timestep too ds = MDAL_G_dataset( g, 10 ); @@ -575,6 +585,8 @@ TEST( XdmfTest, Simple ) valueY = getValueY( ds, 196 ); EXPECT_DOUBLE_EQ( 0, valueY ); + + EXPECT_FALSE( hasReferenceTime( g ) ); } MDAL_CloseMesh( m ); diff --git a/tests/test_xmdf.cpp b/tests/test_xmdf.cpp index bf8ce943..b950e484 100644 --- a/tests/test_xmdf.cpp +++ b/tests/test_xmdf.cpp @@ -107,6 +107,11 @@ TEST( MeshXmdfTest, RegularGridScalarDataset ) EXPECT_DOUBLE_EQ( 0, min ); EXPECT_DOUBLE_EQ( 1.0765361785888672, max ); + double time = MDAL_D_time( ds ); + EXPECT_TRUE( compareDurationInHours( 4.166666666666, time ) ); + + EXPECT_FALSE( hasReferenceTime( g ) ); + MDAL_CloseMesh( m ); } @@ -185,6 +190,9 @@ TEST( MeshXmdfTest, RegularGridVectorMaxDataset ) MDAL_G_minimumMaximum( g, &min, &max ); EXPECT_DOUBLE_EQ( 0, min ); EXPECT_DOUBLE_EQ( 0.38855308294296265, max ); + + EXPECT_FALSE( hasReferenceTime( g ) ); + MDAL_CloseMesh( m ); } @@ -254,6 +262,12 @@ TEST( MeshXmdfTest, CustomGroupsDataset ) EXPECT_DOUBLE_EQ( 180, max ); EXPECT_DOUBLE_EQ( -179.99665832519531, min ); + ds = MDAL_G_dataset( g, 1 ); + double time = MDAL_D_time( ds ); + EXPECT_TRUE( compareDurationInHours( 0.083333333333, time ) ); + + EXPECT_FALSE( hasReferenceTime( g ) ); + MDAL_CloseMesh( m ); } diff --git a/tests/unittests/test_mdal_datetime.cpp b/tests/unittests/test_mdal_datetime.cpp new file mode 100644 index 00000000..b0981b66 --- /dev/null +++ b/tests/unittests/test_mdal_datetime.cpp @@ -0,0 +1,114 @@ +/* + MDAL - Mesh Data Abstraction Library (MIT License) + Copyright (C) 2019 Vincent Cloarec (vcloarec at gmail dot com) +*/ +#include "gtest/gtest.h" +#include +#include +#include +#include +#include + +//mdal +#include "mdal.h" +#include "mdal_utils.hpp" +#include "mdal_testutils.hpp" +#include "mdal_datetime.hpp" + + +TEST( MdalDateTimeTest, Duration ) +{ + std::vector> tests = + { + { MDAL::RelativeTimestamp( 2, MDAL::RelativeTimestamp::minutes ), MDAL::RelativeTimestamp( 120, MDAL::RelativeTimestamp::seconds ) }, + { MDAL::RelativeTimestamp( 90, MDAL::RelativeTimestamp::minutes ), MDAL::RelativeTimestamp( 1.5, MDAL::RelativeTimestamp::hours ) }, + { MDAL::RelativeTimestamp( 2, MDAL::RelativeTimestamp::weeks ), MDAL::RelativeTimestamp( 336, MDAL::RelativeTimestamp::hours ) }, + { MDAL::RelativeTimestamp( 90, MDAL::RelativeTimestamp::seconds ), MDAL::RelativeTimestamp( 1.5, MDAL::RelativeTimestamp::minutes ) }, + { MDAL::RelativeTimestamp( 36, MDAL::RelativeTimestamp::hours ), MDAL::RelativeTimestamp( 1.5, MDAL::RelativeTimestamp::days ) }, + }; + for ( const std::pair &test : tests ) + { + EXPECT_EQ( test.first, test.second ); + EXPECT_DOUBLE_EQ( test.first.value( MDAL::RelativeTimestamp::milliseconds ), test.second.value( MDAL::RelativeTimestamp::milliseconds ) ); + EXPECT_DOUBLE_EQ( test.first.value( MDAL::RelativeTimestamp::seconds ), test.second.value( MDAL::RelativeTimestamp::seconds ) ); + EXPECT_DOUBLE_EQ( test.first.value( MDAL::RelativeTimestamp::minutes ), test.second.value( MDAL::RelativeTimestamp::minutes ) ); + EXPECT_DOUBLE_EQ( test.first.value( MDAL::RelativeTimestamp::hours ), test.second.value( MDAL::RelativeTimestamp::hours ) ); + EXPECT_DOUBLE_EQ( test.first.value( MDAL::RelativeTimestamp::days ), test.second.value( MDAL::RelativeTimestamp::days ) ); + EXPECT_DOUBLE_EQ( test.first.value( MDAL::RelativeTimestamp::weeks ), test.second.value( MDAL::RelativeTimestamp::weeks ) ); + EXPECT_DOUBLE_EQ( test.first.value( MDAL::RelativeTimestamp::months_CF ), test.second.value( MDAL::RelativeTimestamp::months_CF ) ); + EXPECT_DOUBLE_EQ( test.first.value( MDAL::RelativeTimestamp::exact_years ), test.second.value( MDAL::RelativeTimestamp::exact_years ) ); + + EXPECT_TRUE( test.first == test.second ) ; + } +} + +TEST( MdalDateTimeTest, DateTime ) +{ + std::vector> dateTests = + { + { MDAL::DateTime(), MDAL::DateTime()}, + { MDAL::DateTime( 2019, 02, 28, 10, 2, 1, MDAL::DateTime::Gregorian ), MDAL::DateTime( 1551348121, MDAL::DateTime::Unix ) }, + { MDAL::DateTime( 2019, 02, 28, 10, 0, 0, MDAL::DateTime::Gregorian ), MDAL::DateTime( 2458542.916666666667, MDAL::DateTime::JulianDay ) }, + { MDAL::DateTime( 2457125.5, MDAL::DateTime::JulianDay ), MDAL::DateTime( 2015, 04, 13, 00, 0, 0, MDAL::DateTime::Gregorian ) }, + { MDAL::DateTime( 2241532, MDAL::DateTime::JulianDay ), MDAL::DateTime( 1425, 01, 02, 12, 0, 0, MDAL::DateTime::ProlepticGregorian ) }, + { MDAL::DateTime( 2241532, MDAL::DateTime::JulianDay ), MDAL::DateTime( 1424, 12, 24, 12, 0, 0, MDAL::DateTime::Julian ) }, + { MDAL::DateTime( 2241532, MDAL::DateTime::JulianDay ), MDAL::DateTime( 1424, 12, 24, 12, 0, 0, MDAL::DateTime::Gregorian ) }, + { MDAL::DateTime( 2241532, MDAL::DateTime::JulianDay ) + MDAL::RelativeTimestamp( 24, MDAL::RelativeTimestamp::hours ), MDAL::DateTime( 1424, 12, 25, 12, 0, 0, MDAL::DateTime::Gregorian ) }, + { MDAL::DateTime( 2241532, MDAL::DateTime::JulianDay ) + MDAL::RelativeTimestamp( 240, MDAL::RelativeTimestamp::minutes ), MDAL::DateTime( 1424, 12, 24, 16, 0, 0, MDAL::DateTime::Gregorian ) }, + }; + + for ( const std::pair &test : dateTests ) + { + EXPECT_EQ( test.first, test.second ); + + if ( test.first.isValid() && test.second.isValid() ) + { + EXPECT_TRUE( test.first < test.second + MDAL::RelativeTimestamp( 2, MDAL::RelativeTimestamp::hours ) ); + } + + } + + std::vector> dateStringTests = + { + { MDAL::DateTime(), "" }, + { MDAL::DateTime( 2019, 02, 28, 10, 2, 1, MDAL::DateTime::Gregorian ), "2019-02-28T10:02:01" }, + { MDAL::DateTime( 2457125.5, MDAL::DateTime::JulianDay ), "2015-04-13T00:00:00" }, + { MDAL::DateTime( 2241532, MDAL::DateTime::JulianDay ), "1425-01-02T12:00:00" }, + { + MDAL::DateTime( 2241532, MDAL::DateTime::JulianDay ) + MDAL::RelativeTimestamp( 26, MDAL::RelativeTimestamp::hours ) + + MDAL::RelativeTimestamp( 20, MDAL::RelativeTimestamp::minutes ) + MDAL::RelativeTimestamp( 25, MDAL::RelativeTimestamp::seconds ), "1425-01-03T14:20:25" + }, + { MDAL::DateTime( 2241532, MDAL::DateTime::JulianDay ) - MDAL::RelativeTimestamp( 240, MDAL::RelativeTimestamp::minutes ), "1425-01-02T08:00:00" }, + }; + + for ( const std::pair &test : dateStringTests ) + { + EXPECT_EQ( test.first.toStandartCalendarISO8601(), test.second ); + } +} + +TEST( MdalDateTimeTest, ConvertJulianDay ) +{ + std::string path = test_file( "/datetime/julianDay.txt" ); + std::ifstream stream( path, std::ifstream::in ); + + EXPECT_TRUE( stream.is_open() ); + + while ( !stream.eof() ) + { + std::string line; + std::getline( stream, line ); + + std::vector dates = MDAL::split( line, ' ' ); + + if ( dates.size() != 2 ) + continue; + std::string stringDate = dates[0]; + double julianDay = MDAL::toDouble( dates[1] ); + + MDAL::DateTime dateTime( julianDay, MDAL::DateTime::JulianDay ); + + EXPECT_EQ( stringDate, dateTime.toStandartCalendarISO8601() ); + } +} + diff --git a/tests/unittests/test_mdal_utils.cpp b/tests/unittests/test_mdal_utils.cpp index c7f3ed32..aac03686 100644 --- a/tests/unittests/test_mdal_utils.cpp +++ b/tests/unittests/test_mdal_utils.cpp @@ -87,3 +87,38 @@ TEST( MdalUtilsTest, TimeParsing ) EXPECT_EQ( test.second, MDAL::parseTimeUnits( test.first ) ); } } + +TEST( MdalUtilsTest, CF_TimeUnitParsing ) +{ + std::vector> tests = + { + { "seconds since 2001-05-05 00:00:00", MDAL::RelativeTimestamp::seconds }, + { "minutes since 2001-05-05 00:00:00", MDAL::RelativeTimestamp::minutes }, + { "hours since 1900-01-01 00:00:0.0", MDAL::RelativeTimestamp::hours }, + { "days since 1961-01-01 00:00:00", MDAL::RelativeTimestamp::days }, + { "weeks since 1961-01-01 00:00:00", MDAL::RelativeTimestamp::weeks }, + { "month since 1961-01-01 00:00:00", MDAL::RelativeTimestamp::months_CF }, + { "months since 1961-01-01 00:00:00", MDAL::RelativeTimestamp::months_CF }, + { "year since 1961-01-01 00:00:00", MDAL::RelativeTimestamp::exact_years }, + }; + for ( const auto &test : tests ) + { + EXPECT_EQ( test.second, MDAL::parseCFTimeUnit( test.first ) ); + } +} + +TEST( MdalUtilsTest, CF_ReferenceTimePArsing ) +{ + std::vector> tests = + { + { "seconds since 2001-05-05 00:00:00", MDAL::DateTime( 2001, 5, 5, 00, 00, 00 ) }, + { "hours since 1900-05-05 10:00:0.0", MDAL::DateTime( 1900, 5, 5, 10, 00, 00 ) }, + { "days since 1200-05-05 00:05:00", MDAL::DateTime( 1200, 5, 5, 00, 5, 00 ) }, + { "weeks since 1961-05-05 00:01:10", MDAL::DateTime( 1961, 5, 5, 00, 1, 10 ) }, + }; + for ( const auto &test : tests ) + { + EXPECT_EQ( test.second, MDAL::parseCFReferenceTime( test.first, "gregorian" ) ); + } +} +