diff --git a/dynadjust/CMakeLists.txt b/dynadjust/CMakeLists.txt index 3cdfd36a..c6d52326 100644 --- a/dynadjust/CMakeLists.txt +++ b/dynadjust/CMakeLists.txt @@ -942,6 +942,13 @@ if (BUILD_TESTING) target_link_libraries(test_snx_file_writer PRIVATE ${DNA_LIBRARIES}) target_compile_definitions(test_snx_file_writer PRIVATE __BINARY_NAME__="test_snx_file_writer" __BINARY_DESC__="Unit tests for SNX file writer") + # Test: test_format_elapsed_time + add_executable(test_format_elapsed_time + ${UNIT_TEST_DIR}/test_format_elapsed_time.cpp + ) + target_include_directories(test_format_elapsed_time PRIVATE ${UNIT_TEST_DIR} ${CMAKE_SOURCE_DIR}/include) + target_compile_definitions(test_format_elapsed_time PRIVATE __BINARY_NAME__="test_format_elapsed_time" __BINARY_DESC__="Unit tests for FormatElapsedTime helper") + # Register unit tests with CTest add_test(NAME unit-MatrixTest COMMAND $) add_test(NAME unit-MsrToStnSortTest COMMAND $) @@ -957,6 +964,7 @@ if (BUILD_TESTING) add_test(NAME unit-AslFileLoaderTest COMMAND $) add_test(NAME unit-BmsFileLoaderTest COMMAND $) add_test(NAME unit-SnxFileWriterTest COMMAND $) + add_test(NAME unit-FormatElapsedTimeTest COMMAND $) # ........................................................................ # Functional tests @@ -2026,7 +2034,7 @@ if (BUILD_TESTING) unit-AmlFileLoaderTest unit-BmsFileTest unit-NetworkDataLoaderTest unit-MeasurementProcessorTest unit-DynAdjustPrinterTest unit-GNSSNstatSortTest unit-BstFileLoaderTest unit-AslFileLoaderTest unit-BmsFileLoaderTest - unit-SnxFileWriterTest + unit-SnxFileWriterTest unit-FormatElapsedTimeTest ) set_tests_properties(${UNIT_TESTS} PROPERTIES RUN_SERIAL FALSE diff --git a/dynadjust/dynadjust/dnaadjust/dnaadjust-multi.cpp b/dynadjust/dynadjust/dnaadjust/dnaadjust-multi.cpp index 442536cd..7e01b337 100644 --- a/dynadjust/dynadjust/dnaadjust/dnaadjust-multi.cpp +++ b/dynadjust/dynadjust/dnaadjust/dnaadjust-multi.cpp @@ -192,7 +192,7 @@ void dna_adjust::AdjustPhasedMultiThread() if (IsCancelled()) break; - std::string iteration_time_str = format_wall_time(it_time.elapsed().wall); + std::string iteration_time_str = FormatElapsedTime(it_time.elapsed().wall.count() / 1.0e9); /////////////////////////////////// // protected write to adj file (not needed here since write to diff --git a/dynadjust/dynadjust/dnaadjust/dnaadjust.cpp b/dynadjust/dynadjust/dnaadjust/dnaadjust.cpp index 0b1b5725..dc931b26 100644 --- a/dynadjust/dynadjust/dnaadjust/dnaadjust.cpp +++ b/dynadjust/dynadjust/dnaadjust/dnaadjust.cpp @@ -2433,7 +2433,7 @@ void dna_adjust::AdjustSimultaneous() // update data for messages iterationCorrections_.add_message(corr_msg); - iterationTimes_.add_message(format_wall_time(it_time.elapsed().wall)); + iterationTimes_.add_message(FormatElapsedTime(it_time.elapsed().wall.count() / 1.0e9)); iterationQueue_.push_and_notify(CurrentIteration()); // currentIteration begins at 1, so not zero-indexed isIterationComplete_ = true; @@ -2571,7 +2571,7 @@ void dna_adjust::AdjustPhased() OutputLargestCorrection(corr_msg); iterationCorrections_.add_message(corr_msg); - iterationTimes_.add_message(format_wall_time(it_time.elapsed().wall)); + iterationTimes_.add_message(FormatElapsedTime(it_time.elapsed().wall.count() / 1.0e9)); iterationQueue_.push_and_notify(CurrentIteration()); // currentIteration begins at 1, so not zero-indexed isIterationComplete_ = true; @@ -2650,7 +2650,7 @@ void dna_adjust::AdjustPhasedBlock1() adjustStatus_ = ADJUST_THRESHOLD_EXCEEDED; iterationCorrections_.add_message(corr_msg); - iterationTimes_.add_message(format_wall_time(it_time.elapsed().wall)); + iterationTimes_.add_message(FormatElapsedTime(it_time.elapsed().wall.count() / 1.0e9)); iterationQueue_.push_and_notify(CurrentIteration()); // currentIteration begins at 1, so not zero-indexed ValidateandFinaliseAdjustment(tot_time); diff --git a/dynadjust/dynadjust/dnaadjust/dnaadjust.hpp b/dynadjust/dynadjust/dnaadjust/dnaadjust.hpp index c7f1adfc..c3c09813 100644 --- a/dynadjust/dynadjust/dnaadjust/dnaadjust.hpp +++ b/dynadjust/dynadjust/dnaadjust/dnaadjust.hpp @@ -102,7 +102,7 @@ namespace networkadjust { extern std::mutex maxCorrMutex; using dynadjust::cpu_timer; -using dynadjust::format_wall_time; +using dynadjust::FormatElapsedTime; // forward declaration of dna_adjust class dna_adjust; diff --git a/dynadjust/dynadjust/dnaadjust/dnaadjust_printer.cpp b/dynadjust/dynadjust/dnaadjust/dnaadjust_printer.cpp index de0c0ba3..2d65253d 100644 --- a/dynadjust/dynadjust/dnaadjust/dnaadjust_printer.cpp +++ b/dynadjust/dynadjust/dnaadjust/dnaadjust_printer.cpp @@ -80,13 +80,13 @@ void DynAdjustPrinter::PrintIteration(const UINT32& iteration) { } void DynAdjustPrinter::PrintAdjustmentTime(cpu_timer& time, int timer_type) { - std::string time_str = format_wall_time(time.elapsed().wall); + std::string formatted = FormatElapsedTime(time.elapsed().wall.count() / 1.0e9); if (timer_type == 0) // iteration_time equivalent - adjust_.adj_file << std::setw(PRINT_VAR_PAD) << std::left << "Elapsed time" << time_str << std::endl; + adjust_.adj_file << std::setw(PRINT_VAR_PAD) << std::left << "Elapsed time" << formatted << std::endl; else { - adjust_.adj_file << std::setw(PRINT_VAR_PAD) << std::left << "Total time" << time_str << std::endl << std::endl; + adjust_.adj_file << std::setw(PRINT_VAR_PAD) << std::left << "Total time" << formatted << std::endl << std::endl; } } diff --git a/dynadjust/include/functions/dnatemplatedatetimefuncs.hpp b/dynadjust/include/functions/dnatemplatedatetimefuncs.hpp index d5171ce1..10c9a97e 100644 --- a/dynadjust/include/functions/dnatemplatedatetimefuncs.hpp +++ b/dynadjust/include/functions/dnatemplatedatetimefuncs.hpp @@ -37,6 +37,7 @@ #include #include +#include const UINT32 TIME_IMMEMORIAL = 1900; @@ -562,39 +563,8 @@ T formattedDateTimeString() template S formatedElapsedTime(boost::posix_time::milliseconds* elapsed_time, S app_message) { - std::ostringstream ss_time; - boost::posix_time::ptime pt(boost::posix_time::ptime(boost::gregorian::day_clock::local_day(), *elapsed_time)); - - if (*elapsed_time < boost::posix_time::seconds(3)) - { - boost::posix_time::time_facet* facet(new boost::posix_time::time_facet("%s")); - ss_time.imbue(std::locale(ss_time.getloc(), facet)); - ss_time.str(""); - ss_time << pt << "s"; - } - else if (*elapsed_time < boost::posix_time::seconds(61)) - { - boost::posix_time::time_facet* facet(new boost::posix_time::time_facet("%S")); - ss_time.imbue(std::locale(ss_time.getloc(), facet)); - ss_time.str(""); - ss_time << pt << "s"; - } - else - ss_time << boost::posix_time::seconds(static_cast(elapsed_time->total_seconds())); - - size_t pos = std::string::npos; - std::string time_message = ss_time.str(); - while ((pos = time_message.find("0s")) != std::string::npos) - time_message = time_message.substr(0, pos) + "s"; - - time_message = app_message + time_message + "."; - - if ((pos = time_message.find(" 00.")) != std::string::npos) - time_message = time_message.replace(pos, 4, " 0."); - if ((pos = time_message.find(" 0.s")) != std::string::npos) - time_message = time_message.replace(pos, 4, " 0s"); - - return time_message; + double seconds = static_cast(elapsed_time->total_milliseconds()) / 1000.0; + return app_message + dynadjust::FormatElapsedTime(seconds) + "."; } diff --git a/dynadjust/include/functions/dnatimer.hpp b/dynadjust/include/functions/dnatimer.hpp index 2cea86b1..48af8330 100644 --- a/dynadjust/include/functions/dnatimer.hpp +++ b/dynadjust/include/functions/dnatimer.hpp @@ -7,9 +7,9 @@ // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at -// +// // http ://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -23,15 +23,15 @@ #define DNATIMER_H_ #include -#include -#include #include +#include +#include namespace dynadjust { // High-precision timer class to replace boost::timer::cpu_timer class cpu_timer { -public: + public: struct cpu_times { std::chrono::nanoseconds wall; std::chrono::nanoseconds user; @@ -39,49 +39,56 @@ class cpu_timer { }; cpu_timer() { start(); } - - void start() { - start_time_ = std::chrono::high_resolution_clock::now(); - } - - void resume() { - start(); - } - + + void start() { start_time_ = std::chrono::high_resolution_clock::now(); } + + void resume() { start(); } + void stop() { // For compatibility, but no-op since we calculate elapsed on demand } - + cpu_times elapsed() const { auto end_time = std::chrono::high_resolution_clock::now(); auto wall_duration = std::chrono::duration_cast(end_time - start_time_); - return {wall_duration, wall_duration, wall_duration}; // For simplicity, user and system = wall + return {wall_duration, wall_duration, wall_duration}; // For simplicity, user and system = wall } - + std::string format(int places = 6) const { auto times = elapsed(); double wall_seconds = times.wall.count() / 1e9; - + std::ostringstream oss; oss << std::fixed << std::setprecision(places); oss << wall_seconds << "s wall"; return oss.str(); } -private: + private: std::chrono::high_resolution_clock::time_point start_time_; }; -inline std::string format_wall_time(std::chrono::nanoseconds wall) { - double seconds = wall.count() / 1.0e9; +// Format an elapsed duration (in seconds) for display. +// < 1 second -> "X.XXXms" +// 1..60 second -> "X.XXXs" +// >= 60 second -> "hh:mm:ss" +inline std::string FormatElapsedTime(double seconds) { std::ostringstream oss; - if (seconds >= 1.0) - oss << std::fixed << std::setprecision(3) << seconds << "s"; - else + if (seconds >= 60.0) { + long total_seconds = static_cast(seconds); + long hours = total_seconds / 3600; + long minutes = (total_seconds % 3600) / 60; + long secs = total_seconds % 60; + oss << std::setfill('0') << std::setw(2) << hours << ':' << std::setw(2) << minutes << ':' << std::setw(2) + << secs; + } else if (seconds >= 1.0) { + oss << std::fixed << std::setprecision(3) << seconds << 's'; + } else { oss << std::fixed << std::setprecision(3) << (seconds * 1000.0) << "ms"; + } return oss.str(); } } // namespace dynadjust -#endif // DNATIMER_H_ \ No newline at end of file +#endif // DNATIMER_H_ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ae23ce47..f1ddfa00 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -219,6 +219,16 @@ target_compile_definitions(test_gnss_nstat_sort PRIVATE __BINARY_DESC__="Unit tests for GNSS n-stat sort in alternate units" ) +# Test 11: FormatElapsedTime helper (issue #351) +add_executable(test_format_elapsed_time + test_format_elapsed_time.cpp +) + +target_compile_definitions(test_format_elapsed_time PRIVATE + __BINARY_NAME__="test_format_elapsed_time" + __BINARY_DESC__="Unit tests for FormatElapsedTime helper (issue #351)" +) + # Enable testing enable_testing() @@ -233,16 +243,17 @@ add_test(NAME NetworkDataLoaderTest COMMAND test_network_data_loader) add_test(NAME MeasurementProcessorTest COMMAND test_measurement_processor) add_test(NAME DynAdjustPrinterTest COMMAND test_dnaadjust_printer) add_test(NAME GNSSNstatSortTest COMMAND test_gnss_nstat_sort) +add_test(NAME FormatElapsedTimeTest COMMAND test_format_elapsed_time) # Custom target to run all tests add_custom_target(run_tests COMMAND ${CMAKE_CTEST_COMMAND} --verbose - DEPENDS test_matrix test_msr_to_stn_sort test_bst_file test_asl_file test_aml_file_loader test_bms_file test_network_data_loader test_measurement_processor test_dnaadjust_printer test_gnss_nstat_sort + DEPENDS test_matrix test_msr_to_stn_sort test_bst_file test_asl_file test_aml_file_loader test_bms_file test_network_data_loader test_measurement_processor test_dnaadjust_printer test_gnss_nstat_sort test_format_elapsed_time COMMENT "Running all tests" ) # Custom target equivalent to 'make all' add_custom_target(tests_all - DEPENDS test_matrix test_msr_to_stn_sort test_bst_file test_asl_file test_aml_file_loader test_bms_file test_network_data_loader test_measurement_processor test_dnaadjust_printer test_gnss_nstat_sort + DEPENDS test_matrix test_msr_to_stn_sort test_bst_file test_asl_file test_aml_file_loader test_bms_file test_network_data_loader test_measurement_processor test_dnaadjust_printer test_gnss_nstat_sort test_format_elapsed_time COMMENT "Building all tests" ) diff --git a/tests/test_format_elapsed_time.cpp b/tests/test_format_elapsed_time.cpp new file mode 100644 index 00000000..45f99e88 --- /dev/null +++ b/tests/test_format_elapsed_time.cpp @@ -0,0 +1,36 @@ +#define TESTING_MAIN +#ifndef __BINARY_NAME__ +#define __BINARY_NAME__ "test_format_elapsed_time" +#endif +#ifndef __BINARY_DESC__ +#define __BINARY_DESC__ "Unit tests for FormatElapsedTime helper (issue #351)" +#endif + +#include "testing.hpp" + +#include "../dynadjust/include/functions/dnatimer.hpp" + +using dynadjust::FormatElapsedTime; + +TEST_CASE("Sub-second durations report milliseconds", "[time][issue351]") { + REQUIRE(FormatElapsedTime(0.000078) == "0.078ms"); + REQUIRE(FormatElapsedTime(0.0) == "0.000ms"); + REQUIRE(FormatElapsedTime(0.5) == "500.000ms"); + REQUIRE(FormatElapsedTime(0.999) == "999.000ms"); +} + +TEST_CASE("Durations between 1 and 60 seconds report seconds", "[time][issue351]") { + REQUIRE(FormatElapsedTime(1.0) == "1.000s"); + REQUIRE(FormatElapsedTime(12.580) == "12.580s"); + REQUIRE(FormatElapsedTime(59.999) == "59.999s"); +} + +TEST_CASE("Durations of 60 seconds or more report hh:mm:ss", "[time][issue351]") { + REQUIRE(FormatElapsedTime(60.0) == "00:01:00"); + REQUIRE(FormatElapsedTime(68.0) == "00:01:08"); + REQUIRE(FormatElapsedTime(139.419) == "00:02:19"); + REQUIRE(FormatElapsedTime(3600.0) == "01:00:00"); + REQUIRE(FormatElapsedTime(5841.0) == "01:37:21"); + REQUIRE(FormatElapsedTime(26697.777) == "07:24:57"); + REQUIRE(FormatElapsedTime(36000.0) == "10:00:00"); +}