How to read a for-digits number with I/O operators ?

T

Timothy Madden

Hello

My application connects to a server that sends back a date in
"YYYYMMDDhhmmss" format, that is a date like "20100803102438".

How can I read the year, month, day, hour, minute and second from this
string with standard stream classes ? To get the time_t ?

Thank you,
Timothy Madden
 
F

Francesco S. Carta

Hello

My application connects to a server that sends back a date in
"YYYYMMDDhhmmss" format, that is a date like "20100803102438".

How can I read the year, month, day, hour, minute and second from this
string with standard stream classes ? To get the time_t ?

Get a char at a time (or find an appropriate member that reads n chars)
in order to separate the various values as different strings, then
extract the actual values from these strings (either using the extractor
operator on a stringstream or by passing a C-string to the appropriate C
function [*] that read ints from a C-string).

About time_t, look up its specs and see how to build it using the values
that you just extracted from that server string using some variant of
the method delineated above.

[*] When I say "C function" here above I'm speaking about the functions
inherited from C, I know those are actually C++ standard functions, it's
just to make the distinction.
 
T

Timothy Madden

Francesco said:
Hello

My application connects to a server that sends back a date in
"YYYYMMDDhhmmss" format, that is a date like "20100803102438".

How can I read the year, month, day, hour, minute and second from this
string with standard stream classes ? To get the time_t ?

Get a char at a time (or find an appropriate member that reads n chars)
in order to separate the various values as different strings, then
extract the actual values from these strings (either using the extractor
operator on a stringstream or by passing a C-string to the appropriate C
function [*] that read ints from a C-string).

About time_t, look up its specs and see how to build it using the values
that you just extracted from that server string using some variant of
the method delineated above.

Getting the time_t is just a matter of filling one struct tm variable
with the components and calling mktime().

atoi()/atol() have the problem that they silently stop if the string
contains characters other than digits; I was hoping for a method that
would also check the format (or skip non-digits instead of stopping). I
had to do something like

string strDate = "20100803102438";
string::const_iterator
it = strDate.begin();
istringstream
stream;

stream.exceptions(stream.badbit | stream.failbit);

stream.str(string(it, it += 4));
stream >> year;

stream.str(string(it, it += 2));
stream >> month;

stream.str(string(it, it += 2));
stream >> day;
//...
//...

However I think this does not check for non-digits and I think scanf can
do the job in just one call.

Thank you,
Timothy Madden
 
F

Francesco S. Carta

Francesco said:
Hello

My application connects to a server that sends back a date in
"YYYYMMDDhhmmss" format, that is a date like "20100803102438".

How can I read the year, month, day, hour, minute and second from this
string with standard stream classes ? To get the time_t ?

Get a char at a time (or find an appropriate member that reads n
chars) in order to separate the various values as different strings,
then extract the actual values from these strings (either using the
extractor operator on a stringstream or by passing a C-string to the
appropriate C function [*] that read ints from a C-string).

About time_t, look up its specs and see how to build it using the
values that you just extracted from that server string using some
variant of the method delineated above.

Getting the time_t is just a matter of filling one struct tm variable
with the components and calling mktime().

atoi()/atol() have the problem that they silently stop if the string
contains characters other than digits; I was hoping for a method that
would also check the format (or skip non-digits instead of stopping). I
had to do something like

string strDate = "20100803102438";
string::const_iterator
it = strDate.begin();
istringstream
stream;

stream.exceptions(stream.badbit | stream.failbit);

stream.str(string(it, it += 4));
stream >> year;

stream.str(string(it, it += 2));
stream >> month;

stream.str(string(it, it += 2));
stream >> day;
//...
//...

What you're doing above is not good at all, you're writing to "it"
("it+=2") and reading "it" for another purpose (passing the first
parameter) without any intervening sequence point.

Your code incurs in what is called "undefined behavior" (usually
shortened to UB) which means that your code could work fine in a context
and completely mess things up in another.

Don't rely on the fact that it is currently working: your code is
ill-formed under the standard rules and can theoretically do any sort of
things.

Solution: don't do anything like that.

Instead, you could do it separately:

stream.str(string(it, it + 4));
it += 4;

But if it were up to me I would extract substring using string::substr()
and avoid iterators at all.
However I think this does not check for non-digits and I think scanf can
do the job in just one call.

If you need to do the job quick and dirty, by all means use scanf() - if
you are able to use it right and intercept and handle the errors
correctly - just be aware that you're not forced to use it, you can do
this all in the "C++ way".

In particular, look up what happens when the extraction of some value
fails on a stream, and also consider that you can validate the input in
first place by parsing its chars and checking them with isdigit().
 
J

Juha Nieminen

Timothy Madden said:
My application connects to a server that sends back a date in
"YYYYMMDDhhmmss" format, that is a date like "20100803102438".

How can I read the year, month, day, hour, minute and second from this
string with standard stream classes ? To get the time_t ?

In general, the standard C++ library offers basicaly *no* tools to
parse formatted strings (or binary data or anything). The Boost library
offers some parsing tools, but in this particular case it's probably easier
to write a simple parser yourself (which goes through the string and
interprets each digit as appropriate and constructs the date data you
want).

Is there a particular reason why you explicitly want to use the
standard stream classes for this? As said, what you want to do isn't
exactly very complicated to do with your own parser.
 
T

Timothy Madden

Francesco said:
Francesco said:
Hello

My application connects to a server that sends back a date in
"YYYYMMDDhhmmss" format, that is a date like "20100803102438".

How can I read the year, month, day, hour, minute and second from this
string with standard stream classes ? To get the time_t ?

Get a char at a time (or find an appropriate member that reads n
chars) in order to separate the various values as different strings,
then extract the actual values from these strings (either using the
extractor operator on a stringstream or by passing a C-string to the
appropriate C function [*] that read ints from a C-string).

About time_t, look up its specs and see how to build it using the
values that you just extracted from that server string using some
variant of the method delineated above.

Getting the time_t is just a matter of filling one struct tm variable
with the components and calling mktime().

atoi()/atol() have the problem that they silently stop if the string
contains characters other than digits; I was hoping for a method that
would also check the format (or skip non-digits instead of stopping). I
had to do something like

string strDate = "20100803102438";
string::const_iterator
it = strDate.begin();
istringstream
stream;

stream.exceptions(stream.badbit | stream.failbit);

stream.str(string(it, it += 4));
stream >> year;

stream.str(string(it, it += 2));
stream >> month;

stream.str(string(it, it += 2));
stream >> day;
//...
//...

What you're doing above is not good at all, you're writing to "it"
("it+=2") and reading "it" for another purpose (passing the first
parameter) without any intervening sequence point.

Your code incurs in what is called "undefined behavior" (usually
shortened to UB) which means that your code could work fine in a context
and completely mess things up in another.

Don't rely on the fact that it is currently working: your code is
ill-formed under the standard rules and can theoretically do any sort of
things.

Actually I found out it was not working !
And I did the addition as a separate step, as you said.

It just seems a lot of work to read some numbers.

Thank you,
Timothy Madden
 
T

Timothy Madden

Juha said:
In general, the standard C++ library offers basicaly *no* tools to
parse formatted strings (or binary data or anything). The Boost library
offers some parsing tools, but in this particular case it's probably easier
to write a simple parser yourself (which goes through the string and
interprets each digit as appropriate and constructs the date data you
want).

Is there a particular reason why you explicitly want to use the
standard stream classes for this? As said, what you want to do isn't
exactly very complicated to do with your own parser.

No particular reason, just that this is a good opportunity to learn how
stream formatting works. Writing my own parser seem too much for a task
that should be as simple as a one function call. I though that the
stream classes offer some way to do this ...

Thank you,
Timothy Madden
 
J

James Kanze

In general, the standard C++ library offers basicaly *no*
tools to parse formatted strings (or binary data or anything).
The Boost library offers some parsing tools, but in this
particular case it's probably easier to write a simple parser
yourself (which goes through the string and interprets each
digit as appropriate and constructs the date data you want).

I've found boost::regex very useful for this sort of thing. It
can verify the format and break the string up into fields in one
go. Afterwards, you can use istringstream to convert the
various fields.
 
A

Alf P. Steinbach /Usenet

* Timothy Madden, on 12.08.2010 15:38:
Hello

My application connects to a server that sends back a date in
"YYYYMMDDhhmmss" format, that is a date like "20100803102438".

How can I read the year, month, day, hour, minute and second from this
string with standard stream classes ? To get the time_t ?

<code>
#include <iostream>
#include <string>
#include <time.h>
#include <stdio.h>

tm tmFrom( std::string const& spec )
{
tm t = {};

sscanf( &spec[0], "%4d%2d%2d%2d%2d%2d",
&t.tm_year, &t.tm_mon, &t.tm_mday,
&t.tm_hour, &t.tm_min, &t.tm_sec
);
t.tm_year -= 1900;
return t;
}

std::eek:stream& operator<<( std::eek:stream& s, tm const& t )
{
return (s << asctime( &t ));
}

int main()
{
std::cout << tmFrom( "20100803102438" ) << std::endl;
}
</code>


Note: eror cheking omited.

There is also a C++ standard library time formatter/parser called std::time_get,
but it's in practice unusable since it requires a fixed format.


Cheers & hth.,

- Alf
 
F

Francesco S. Carta

Francesco said:
Francesco S. Carta wrote:

Hello

My application connects to a server that sends back a date in
"YYYYMMDDhhmmss" format, that is a date like "20100803102438".

How can I read the year, month, day, hour, minute and second from this
string with standard stream classes ? To get the time_t ?

Get a char at a time (or find an appropriate member that reads n
chars) in order to separate the various values as different strings,
then extract the actual values from these strings (either using the
extractor operator on a stringstream or by passing a C-string to the
appropriate C function [*] that read ints from a C-string).

About time_t, look up its specs and see how to build it using the
values that you just extracted from that server string using some
variant of the method delineated above.

Getting the time_t is just a matter of filling one struct tm variable
with the components and calling mktime().

atoi()/atol() have the problem that they silently stop if the string
contains characters other than digits; I was hoping for a method that
would also check the format (or skip non-digits instead of stopping). I
had to do something like

string strDate = "20100803102438";
string::const_iterator
it = strDate.begin();
istringstream
stream;

stream.exceptions(stream.badbit | stream.failbit);

stream.str(string(it, it += 4));
stream >> year;

stream.str(string(it, it += 2));
stream >> month;

stream.str(string(it, it += 2));
stream >> day;
//...
//...

What you're doing above is not good at all, you're writing to "it"
("it+=2") and reading "it" for another purpose (passing the first
parameter) without any intervening sequence point.

Your code incurs in what is called "undefined behavior" (usually
shortened to UB) which means that your code could work fine in a
context and completely mess things up in another.

Don't rely on the fact that it is currently working: your code is
ill-formed under the standard rules and can theoretically do any sort
of things.

Actually I found out it was not working !
And I did the addition as a separate step, as you said.

It just seems a lot of work to read some numbers.

That doesn't seem all that work to me. The "work" here below is
ParseDateTime(), it's just a bunch of quite straightforward lines of
code that does all the error checking at once.

//-------

#include <iostream>
#include <sstream>
#include <cctype>

using namespace std;

struct DateTime {
int year;
int month;
int day;
int hour;
int minute;
int second;
};

ostream& operator<<(ostream& os, const DateTime& dt) {
os << dt.year << "-" << dt.month << "-" << dt.day << " ";
os << dt.hour << ":" << dt.minute << ":" << dt.second;
return os;
}

const DateTime* ParseDateTime(string text) {
static DateTime res;
// expects a string formatted as "YYYYMMDDhhmmss"
// leaves stream.str() == "YYYY MM DD hh mm ss"
stringstream stream;
if(text.size() != 14) return 0;
for (int i = 0; i < text.size(); ++i) {
char ch = text;
if (!isdigit(ch)) return 0;
switch (i) {
case 4: case 6: case 8: case 10: case 12:
stream << " " << ch;
break;
default:
stream << ch;
}
}
stream >> res.year >> res.month >> res.day;
stream >> res.hour >> res.minute >> res.second;
if (!stream) return 0;
return &res;
}

int main() {
string good = "20100812211110";
cout << good << endl;
const DateTime* dt = ParseDateTime(good);
if (dt) {
cout << *dt << endl;
} else {
cout << "Bad String!" << endl;
}
cout << endl;

string bad = "abcdefghijklmn";
cout << bad << endl;
dt = ParseDateTime(bad);
if (dt) {
cout << *dt << endl;
} else {
cout << "Bad String!" << endl;
}
cout << endl;
}
//-------
 
W

wij

Get a char at a time (or find an appropriate member that reads n chars)
in order to separate the various values as different strings, then
extract the actual values from these strings (either using the extractor
operator on a stringstream or by passing a C-string to the appropriate C
function [*] that read ints from a C-string).
About time_t, look up its specs and see how to build it using the values
that you just extracted from that server string using some variant of
the method delineated above.

Getting the time_t is just a matter of filling one struct tm variable
with the components and calling mktime().

atoi()/atol() have the problem that they silently stop if the string
contains characters other than digits; I was hoping for a method that
would also check the format (or skip non-digits instead of stopping). I
had to do something like

string strDate = "20100803102438";
string::const_iterator
        it = strDate.begin();
istringstream
        stream;

stream.exceptions(stream.badbit | stream.failbit);

stream.str(string(it, it += 4));
stream >> year;

stream.str(string(it, it += 2));
stream >> month;

stream.str(string(it, it += 2));
stream >> day;
//...
//...

However I think this does not check for non-digits and I think scanf can
  do the job in just one call.

Thank you,
Timothy Madden
Here is the basic one. You can modify it or add various checking.
--------------------------------------
#include <iostream>

inline int charnum(char ch)
{ return ch-'0'; };

static int parse_pnum(const char* str, size_t len)
{
int sum=0;
for(const char *p=str, *end=str+len; p!=end; ++p) {
int d=charnum(*p);
if((d<0)||(d>9)) {
throw "Invalid digit in " __FILE__;
}
sum*=10;
sum+=d;
}
return sum;
};

int main(int,char*[])
try {
const char str[]="20100803102438";

std::cout << "year = " << parse_pnum(str ,4) << '\n'
<< "month = " << parse_pnum(str+4 ,2) << '\n'
<< "mday = " << parse_pnum(str+6 ,2) << '\n'
<< "hour = " << parse_pnum(str+8 ,2) << '\n'
<< "minute= " << parse_pnum(str+10,2) << '\n'
<< "second= " << parse_pnum(str+12,2) << '\n'
<< '\n';
return 0;
}
catch(...) {
std::cerr << "unknown throw object\n";
throw;
};
 
J

Jorgen Grahn

Hello

My application connects to a server that sends back a date in
"YYYYMMDDhhmmss" format, that is a date like "20100803102438".

How can I read the year, month, day, hour, minute and second from this
string with standard stream classes ? To get the time_t ?

If you can use POSIX functions, there's

struct tm tm;
const char* p = strptime("20011112183101", "%Y%m%d%H%M%S", &tm);
assert(p && *p=='\0');
and then from struct tm to time_t.

Watch out for the timezone.

/Jorgen
 
T

Timothy Madden

Jorgen said:
If you can use POSIX functions, there's

struct tm tm;
const char* p = strptime("20011112183101", "%Y%m%d%H%M%S", &tm);
assert(p && *p=='\0');
and then from struct tm to time_t.

Watch out for the timezone.

Excelent ! Thank you very much !

This is the kind of thing that I was looking for. I am sorry to find I
was looking for it in the wrong place, as strptime() is a POSIX
function, not a C/C++ one.

Also it happens that the compiler my company uses (MSVC) is not
posix-conformant and does not have the function, but I find that to be a
different issue.

Thank you,
Timothy Madden
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,773
Messages
2,569,594
Members
45,119
Latest member
IrmaNorcro
Top