Reputation: 407
I'm working on a project where I have to read a date to make sure that it's a valid date. For example, February 29th is only a valid date on leap years, or June 31st is not a valid date, so the computer would output that information based on the input. My issue is that I can't figure out how to parse the string so that the user can enter "05/11/1996" as a date (for example) and then take that and put it into seperate integers. I was thinking about trying to do something with a while loop and string stream, but I'm a little stuck. If someone could help me with this, I would really appreciate it.
Upvotes: 10
Views: 42174
Reputation: 218780
Adding an answer for C++20's <chrono>
facilities:
One can use std::chrono::parse
and std::chrono::year_month_day
to parse and validate a date from std::cin
like this:
#include <chrono>
#include <iostream>
int
main()
{
std::cout << "Enter date mm/dd/yyyy : ";
std::chrono::year_month_day ymd;
std::cin >> std::chrono::parse("%m/%d/%Y", ymd);
if (std::cin.fail())
std::cout << "date is invalid\n";
else
std::cout << "date is valid\n";
}
If "05/11/1996" is entered, this program will output "date is valid".
Note that it is not clear from the question if the format "%m/%d/%Y"
is desired, or the format "%d/%m/%Y"
. Adjust as necessary.
One can parse from any stream (fstream
, stringstream
, etc.) with the same syntax. Invalid dates, either with incorrect syntax, or values for the year, month and day that don't match a date in the civil calendar, will set failbit
in the stream.
See this table for a complete list of the parse flags that are available for use.
One could also parse into a std::chrono::sys_days
. year_month_day
and sys_days
are both "date types". They are just different data structures for holding the same thing (analogous to vector
and list
both holding a sequence).
year_month_day
is a {year, month, day}
data structure. It is good at retrieving the year, month and day fields, and calendrical month and year arithmetic.
sys_days
is a {count of days from 1970-01-01}
data structure. It is good at day-precision arithmetic, and converting to "date time" types such as system_clock::time_point
.
year_month_day
and sys_days
will convert to one another with implicit conversion syntax.
Upvotes: 1
Reputation: 346
Another option is to use std::get_time
from the <iomanip>
header (available since C++11). A good example of its use can be found here.
Upvotes: 8
Reputation: 393084
I'd prefer to use Boost DateTime:
See it Live on Coliru
#include <iostream>
#include <boost/date_time/local_time/local_time.hpp>
struct dateparser
{
dateparser(std::string fmt)
{
// set format
using namespace boost::local_time;
local_time_input_facet* input_facet = new local_time_input_facet();
input_facet->format(fmt.c_str());
ss.imbue(std::locale(ss.getloc(), input_facet));
}
bool operator()(std::string const& text)
{
ss.clear();
ss.str(text);
bool ok = ss >> pt;
if (ok)
{
auto tm = to_tm(pt);
year = tm.tm_year;
month = tm.tm_mon + 1; // for 1-based (1:jan, .. 12:dec)
day = tm.tm_mday;
}
return ok;
}
boost::posix_time::ptime pt;
unsigned year, month, day;
private:
std::stringstream ss;
};
int main(){
dateparser parser("%d/%m/%Y"); // not thread safe
// parse
for (auto&& txt : { "05/11/1996", "30/02/1983", "29/02/2000", "29/02/2001" })
{
if (parser(txt))
std::cout << txt << " -> " << parser.pt << " is the "
<< parser.day << "th of "
<< std::setw(2) << std::setfill('0') << parser.month
<< " in the year " << parser.year << "\n";
else
std::cout << txt << " is not a valid date\n";
}
}
Outputs:
05/11/1996 -> 1996-Nov-05 00:00:00 is the 5th of 11 in the year 96
30/02/1983 is not a valid date
29/02/2000 -> 2000-Feb-29 00:00:00 is the 29th of 02 in the year 100
29/02/2001 is not a valid date
Upvotes: 7
Reputation: 42083
A possible solution might be also based on strptime
, however note that this function only validates whether the day is from the interval <1;31>
and month from <1;12>
, i.e. "30/02/2013"
is valid still:
#include <iostream>
#include <ctime>
int main() {
struct tm tm;
std::string s("32/02/2013");
if (strptime(s.c_str(), "%d/%m/%Y", &tm))
std::cout << "date is valid" << std::endl;
else
std::cout << "date is invalid" << std::endl;
}
But since strptime
is not always available and additional validation would be nice, here's what you could do:
struct tm
i.e.:
#include <iostream>
#include <sstream>
#include <ctime>
// function expects the string in format dd/mm/yyyy:
bool extractDate(const std::string& s, int& d, int& m, int& y){
std::istringstream is(s);
char delimiter;
if (is >> d >> delimiter >> m >> delimiter >> y) {
struct tm t = {0};
t.tm_mday = d;
t.tm_mon = m - 1;
t.tm_year = y - 1900;
t.tm_isdst = -1;
// normalize:
time_t when = mktime(&t);
const struct tm *norm = localtime(&when);
// the actual date would be:
// m = norm->tm_mon + 1;
// d = norm->tm_mday;
// y = norm->tm_year;
// e.g. 29/02/2013 would become 01/03/2013
// validate (is the normalized date still the same?):
return (norm->tm_mday == d &&
norm->tm_mon == m - 1 &&
norm->tm_year == y - 1900);
}
return false;
}
used as:
int main() {
std::string s("29/02/2013");
int d,m,y;
if (extractDate(s, d, m, y))
std::cout << "date "
<< d << "/" << m << "/" << y
<< " is valid" << std::endl;
else
std::cout << "date is invalid" << std::endl;
}
which in this case would output date is invalid
since normalization would detect that 29/02/2013
has been normalized to 01/03/2013
.
Upvotes: 19
Reputation: 154
If the format is like in your example, you could take out the integer like this:
int day, month, year;
sscanf(buffer, "%2d/%2d/%4d",
&month,
&day,
&year);
where of course in buffer you have the date ("05/11/1996" )
Upvotes: 5