Parse ISO 8601 durations
Asked Answered
M

3

9

In ISO 8601, durations are given in the format P[n]Y[n]M[n]DT[n]H[n]M[n]S.

Examples:

20 seconds:

PT20.0S

One year, 2 month, 3 days, 4 hours, 5 minutes, 6 seconds:

P1Y2M3DT4H5M6S

Question:

Given a string that contains a duration in iso 8601 format. I want to obtain the overall number of seconds of that duration. What is the recommended way in standard C++11 to achieve this?

Remarks:

E.g., there is ptime from_iso_string(std::string) in boost DateTime which does not fit here. Is there a similar way without doing a regex by hand?

Maitund answered 27/5, 2014 at 9:45 Comment(3)
If you don't have regex available, why do you ask specially for "standard C++11"? Regex is part of the C++11 standard - you're more likely searching for a solution which is C++03 compatible.Madrid
You are right, but I am interested in both: How to do it properly (using C++11) and how to do it 'for the moment'. I also think, the regex solution is a coding of this by hand. But since it is a standard, perhaps there is an existing tool that could be used.Maitund
In general, there is no number of seconds that corresponds to an ISO 8601 duration, because years and months have varying lengths. This Wikipedia page gives an example of P2M meaning either 59 days (5,097,600 seconds) or 62 days (5,356,800 seconds) depending on when it starts.Coccid
N
3

Use the standard regex library, the regex you want is something like:

"P\(\([0-9]+\)Y\)?\(\([0-9]+\)M\)?\(\([0-9]+\)D\)?T\(\([0-9]+\)H\)?\(\([0-9]+\)M\)?\(\([0-9]+\(\.[0-9]+\)?S\)?"

from that you can pull out the number of years, months etc and calculate total seconds.

Nonaligned answered 27/5, 2014 at 9:59 Comment(3)
Thank you, unfortunatly, I am using gcc 4.8, where the standard regex library does not yet work properly. An update to gcc 4.9 would be difficult, since my environment is based on QT which comes with gcc 4.8 in the newest version...Maitund
Then use the Boost.Regex or Boost.Xpressive library.Nonaligned
Thanks. However, I was wondering if there is a standard solution without coding the regex by hand. It seemed like a common problem, e.g. there is ptime from_iso_string(std::string) in boost DateTime but this doesn't fit.Maitund
M
3

Now, C++20 has what I was looking for:

https://en.cppreference.com/w/cpp/chrono/parse

Maitund answered 14/2, 2023 at 22:3 Comment(0)
P
2

Example code for ISO 8601 duration to Unix epoch time converter:

#include <iostream>
#include <vector>
#include <regex>

using namespace std;

void match_duration(const std::string& input, const std::regex& re)
{
    std::smatch match;
    std::regex_search(input, match, re);
    if (match.empty()) {
        std::cout << "Pattern do NOT match" << std::endl;
        return;
    }

    std::vector<double> vec = {0,0,0,0,0,0}; // years, months, days, hours, minutes, seconds

    for (size_t i = 1; i < match.size(); ++i) {

        if (match[i].matched) {
            std::string str = match[i];
            str.pop_back(); // remove last character.
            vec[i-1] = std::stod(str);
        }
    }

    int duration = 31556926   * vec[0] +  // years  
                   2629743.83 * vec[1] +  // months
                   86400      * vec[2] +  // days
                   3600       * vec[3] +  // hours
                   60         * vec[4] +  // minutes
                   1          * vec[5];   // seconds

    if (duration == 0) {
        std::cout << "Not valid input" << std::endl;
        return;
    }

    std::cout << "duration: " << duration << " [sec.]" << std::endl;
}

int main()
{
    std::cout << "-- ISO 8601 duration to Unix epoch time converter--" << std::endl;
    std::cout << "Enter duration (q for quit)" << std::endl;

    std::string input;
    //input = "P1Y2M3DT4H5M6S";
    //input = "PT4H5M6S";
    //
    while(true)
    {
        std::cin >> input;
        if (!std::cin)
            break;
        if (input == "q")
            break;

        std::regex rshort("^((?!T).)*$");

        if (std::regex_match(input, rshort)) // no T (Time) exist
        {
            std::regex r("P([[:d:]]+Y)?([[:d:]]+M)?([[:d:]]+D)?");
            match_duration(input, r);
        }
        else {

            std::regex r("P([[:d:]]+Y)?([[:d:]]+M)?([[:d:]]+D)?T([[:d:]]+H)?([[:d:]]+M)?([[:d:]]+S|[[:d:]]+\\.[[:d:]]+S)?");
            match_duration(input, r);
        }
    }

    return 0;
  }
Prenotion answered 30/12, 2015 at 15:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.