Daan Pape
Daan Pape

Reputation: 1140

C++ get hour and minutes from string

I'm writing C++ code for school in which I can only use the std library, so no boost. I need to parse a string like "14:30" and parse it into:

unsigned char hour;
unsigned char min;

We get the string as a c++ string, so no direct pointer. I tried all variations on this code:

sscanf(hour.c_str(), "%hhd[:]%hhd", &hours, &mins);

but I keep getting wrong data. What am I doing wrong.

Upvotes: 4

Views: 6148

Answers (6)

Joseph Mansfield
Joseph Mansfield

Reputation: 110778

sscanf with %hhd:%hhd seems to work perfectly fine:

std::string time("14:30");
unsigned char hour, min;
sscanf(time.c_str(), "%hhd:%hhd", &hour, &min);

Note that the hh length modifier is simply to allow storing the value in an unsigned char.

However, sscanf is from the C Standard Library and there are better C++ ways to do this. A C++11 way to do this is using stoi:

std::string time("14:30");
unsigned char hour = std::stoi(time);
unsigned char min = std::stoi(time.substr(3));

In C++03, we can use stringstream instead but it's a bit of a pain if you really want it in a char:

std::stringstream stream("14:30");
unsigned int hour, min;
stream >> hour;
stream.ignore();
stream >> min;

Upvotes: 0

James Kanze
James Kanze

Reputation: 154047

Your problem is, of course, that you are using sscanf. And that you're using some very special type for the hours and minutes, instead of int. Since you're parsing a string of exactly 5 characters, the simplest solution is just to ensure that all of the characters are legal in that position, using isdigit for characters 0, 1, 3 and 4, and comparing to ':' for character 2. Once you've done that, it's trivial to create an std::istringstream from the string, and input into an int, a char (which you'll ignore afterwards) and a second int. If you want to be more flexible in the input, for example allowing things like "9:45" as well, you can skip the initial checks, and just input into int, char and int, then check that the char contains ':' (and that the two int are in range).

As to why your sscanf is failing: you're asking it to match something like "12[:]34", which is not what you're giving it. I'm not sure whether you're trying to use "%hhd:%hhd", or if for some reason you really do want a character class, in which case, you have to use [ as a conversion specifier, and then ignore the input: "%hhd%*[:]%hhd". (This would allow accepting more than one character as the separator, but otherwise, I don't see the advantage. Also, technically at least, using %d and then passing the address of an unsigned integral types is not supported, %hhd must be a signed char. In practice, however, I don't think you'll ever run into any problems for non-negative input values less than 128.)

Upvotes: 2

john
john

Reputation: 8027

I would do it like this

unsigned tmp_hour, tmp_mins;
unsigned char hour, mins;

sscanf(hour.c_str(), "%u:%u", &tmp_hours, &tmp_mins);
hour = tmp_hours;
mins = tmp_mins;

Less messing around with obscure scanf options. I would add some error checking too.

Upvotes: 1

user405725
user405725

Reputation:

As everyone else has mentioned, you have to use %d format specified (or %u). As for the alternative approaches, I am not a big fan of the "because C++ has feature XX it must be used" and oftentimes resort to C-level functions. Though I never use scanf()-like stuff as it got its own problems. That being said, here is how I would parse your string using strtol() with error checking:

#include <cstdio>
#include <cstdlib>

int main()
{
    unsigned char hour;
    unsigned char min;

    const char data[] = "12:30";
    char *ep;

    hour = (unsigned char)strtol(data, &ep, 10);
    if (!ep || *ep != ':') {
        fprintf(stderr, "cannot parse hour: '%s' - wrong format\n", data);
        return EXIT_FAILURE;
    }

    min = (unsigned char)strtol(ep+1, &ep, 10);
    if (!ep || *ep != '\0') {
        fprintf(stderr, "cannot parse minutes: '%s' - wrong format\n", data);
        return EXIT_FAILURE;
    }

    printf("Hours: %u, Minutes: %u\n", hour, min);
}

Hope it helps.

Upvotes: 2

Dan
Dan

Reputation: 13412

As mentioned by izomorphius sscanf and variants are not C++ they are C. The C++ way would be to use streams. The following works (it's not amazingly flexible but should give you an idea)

#include <iostream>
#include <string>
#include <sstream>

using namespace std;

int main(int argc, char* argv[])
{
    string str = "14:30";

    stringstream sstrm;

    int hour,min;

    sstrm << str;

    sstrm >> hour;
    sstrm.get(); // get colon
    sstrm >> min;

    cout << hour << endl;
    cout << min << endl;

    return 0;
}

You could also use getline to get everything upto the colon.

Upvotes: 1

Thomas Matthews
Thomas Matthews

Reputation: 57749

My understanding is that h in %hhd is not a valid format specifier. The correct specifier for decimal integers is %d.

As R.Martinho Fernandes says in his comment, %d:%d will match two numbers separated by a colon (':').

Did you want something different?

You can always read the entire text string and parse it any way you want.

Upvotes: 0

Related Questions