psb
psb

Reputation: 392

Advice on converting timestamp string in "HH:MM:SS.microseconds" format

I'm given a list of timestamps (suppose we have a ready-made std::vector<std::string>) in a string format of a kind std::vector<std::string> = {"12:27:37.740002", "19:37:17.314002", "20:00:07.140902",...}. No dates, no timezones. What would be a preferable way to parse these strings to some kind of C++ type (std::chrono::time_point ?) to be able to perform some comparisons and sorting later.

For example: compare value, which was parsed from "20:00:07.140902" and value, was parsed from "20:00:07.000000".

C++17 is ok, but I can't use any third-party library (Boost, Date etc). Keeping microseconds precision essential.

Upvotes: 0

Views: 681

Answers (2)

rturrado
rturrado

Reputation: 8064

I don't see why this shouldn't work. Using std::chrono::from_stream to parse the string into a time point, then just compare the two time points.

However, I've been trying it now with Visual Studio 2022 17.0.2 (Community Edition) and it fails to parse the string into a tp.

There is this answer from Ted Lyngmo's talking about a bug (fixed in VS2022 17.0.3) when parsing seconds with subseconds. I have to say though that his solution didn't work for me either in my VS2022.

Anyway, you may want to give it a try.

#include <chrono>
#include <iomanip>  // boolalpha
#include <iostream>  // cout
#include <sstream>  // istringstream
#include <string>

auto parse_string_to_tp(const std::string& str)
{
    std::istringstream iss{ str };
    std::chrono::sys_time<std::chrono::microseconds> tp{};
    std::chrono::from_stream(iss, "%H:%M:%S", tp);  // or simply "%T"
    return tp;
}

int main()
{
    const std::string str1{ "12:27:37.740002" };
    const std::string str2{ "13:00:00.500000" };

    auto tp1{ parse_string_to_tp(str1) };
    auto tp2{ parse_string_to_tp(str2) };

    std::cout << "tp1 < tp2: " << std::boolalpha << (tp1 < tp2) << "\n";
    std::cout << "tp2 < tp1: " << std::boolalpha << (tp2 < tp1) << "\n";
}

EDIT: it works if you just use durations instead of time points:

#include <chrono>
#include <iomanip>  // boolalpha
#include <iostream>  // cout
#include <sstream>  // istringstream
#include <string>

auto parse_string_to_duration(const std::string& str)
{
    std::istringstream iss{ str };
    std::chrono::microseconds d{};
    std::chrono::from_stream(iss, "%T", d);
    return d;
}

int main()
{
    const std::string str1{ "12:27:37.740002" };
    const std::string str2{ "23:39:48.500000" };

    auto d1{ parse_string_to_duration(str1) };
    auto d2{ parse_string_to_duration(str2) };

    std::cout << "d1 < d2: " << std::boolalpha << (d1 < d2) << "\n";
    std::cout << "d2 < d1: " << std::boolalpha << (d2 < d1) << "\n";
}

Upvotes: 0

Pepijn Kramer
Pepijn Kramer

Reputation: 12956

You can build this functionality completly with C++ standard library functionality. For parsing the string use std::regex. For time related datatypes use std::chrono

Example :

#include <stdexcept>
#include <regex>
#include <chrono>
#include <iostream>

auto parse_to_timepoint(const std::string& input)
{
    // setup a regular expression to parse the input string
    // https://regex101.com/
    // each part between () is a group and will end up in the match
    // [0-2] will match any character from 0 to 2 etc..
    // [0-9]{6} will match exactly 6 digits
    static const std::regex rx{ "([0-2][0-9]):([0-5][0-9]):([0-5][0-9])\\.([0-9]{6})" };
    std::smatch match;

    if (!std::regex_search(input, match, rx))
    {
        throw std::invalid_argument("input string is not a valid time string");
    }

    // convert each matched group to the corresponding value
    // note match[0] is the complete matched string by the regular expression
    // we only need the groups which start at index 1
    const auto& hours = std::stoul(match[1]);
    const auto& minutes = std::stoul(match[2]);
    const auto& seconds = std::stoul(match[3]);
    const auto& microseconds = std::stoul(match[4]);
    
    // build up a duration
    std::chrono::high_resolution_clock::duration duration{};
    duration += std::chrono::hours(hours);
    duration += std::chrono::minutes(minutes);
    duration += std::chrono::seconds(seconds);
    duration += std::chrono::microseconds(microseconds);

    // then return a time_point (note this will not help you with correctly handling day boundaries)
    // since there is no date in the input string
    return std::chrono::high_resolution_clock::time_point{ duration };
}

int main()
{
    std::string input1{ "20:00:07.140902" };
    std::string input2{ "20:00:07.000000" };

    auto tp1 = parse_to_timepoint(input1);
    auto tp2 = parse_to_timepoint(input2);

    std::cout << "start time = " << ((tp1 < tp2) ? input1 : input2) << "\n";
    std::cout << "end time = " << ((tp1 >= tp2) ? input1 : input2) << "\n";

    return 0;
}

Upvotes: 1

Related Questions