Parsing a date string in C++ - c++

My son is learning C++ and one of his exercises it to have the user input a date in DD/MM/YYY and then output it to Month day, year
So: 19/02/2013
Output: February 19, 2013.
I am trying to help him understand the various ways but now I have confused myself.
getline()
std::string::substr()
std::string::find()
std::string::find_first_of()
std::string::find_last_of()
I can't figure it out with any of these quite right.
my current attempt to parse is:
#include <iostream>
#include <string>
using namespace std;
int main (void)
{
string date;
string line;
cout << "Enter a date in dd/mm/yyyy format: " << endl;
std::getline (std::cin,date);
while (getline(date, line))
{
string day, month, year;
istringstream liness( line );
getline( liness, day, '/' );
getline( liness, month, '/' );
getline( liness, year, '/' );
cout << "Date pieces are: " << day << " " << month << " " << year << endl;
}
}
but I am getting errors like:
`g++ 3_12.cpp -o 3_12`
`3_12.cpp: In function ‘int main()’:`
`3_12.cpp:16: error: cannot convert ‘std::string’ to ‘char**’ for argument ‘1’ to ‘ssize_t getline(char**, size_t*, FILE*)’`
`3_12.cpp:18: error: variable ‘std::istringstream liness’ has initializer but incomplete type`

int day, month, year;
char t;
std::cin >> day >> t >> month >> t >> year;

You've missed the std Regular Expressions Library! I reckon it's the safest and most effective way of doing this.
Back on topic, I think that since getline is an extern "C" function, you can't overload it using a using namespace std (which should be banned by the way). You should try prepending std to all getline calls.

For std::istringstream, you need:
#include <sstream>
P.S. Don't use using namespace std;. It's a bad habit, and it will eventually get you into trouble.

The error
3_12.cpp:16: error: cannot convert ‘std::string’ to ‘char**’ for argument ‘1’ to ‘ssize_t getline(char**, size_t*, FILE*)’
means the compiler couldn't make sense of this line:
while (getline(date, line))
Both date and line are declared as std::string and there's no overload of getline that
takes two strings. Compiler guessed that you were trying to call this function that is not part of C++ library (apparently, one of standard library headers includes stdio.h which is where that function is coming from).
The error
3_12.cpp:18: error: variable ‘std::istringstream liness’ has initializer but incomplete type
means the compiler doesn't know what std::istringstream is. You forgot to include <sstream>.
If you want to extract lines from a string by means of std::getline, you need to put it in a stringstream first, just like you do inside the loop.
Parsing dates isn't that easy. You don't want the user to be able to enter 44/33/-200 and get away with it. How I'd might approach this:
std::istringstream iss(date); // date is the line you got from the user
unsigned int day, month, year;
char c1, c2;
if (!(iss >> day >> c1 >> month >> c2 >> year)) {
// error
}
if (c1 != '/' || c2 != '/') { /* error */ }
if (month > 12) { /* error / }
// ... more of that, you get the idea

Related

**Compiler error** - getline() function not accepting first parameter "std:ifstream" what is my issue?

I'm writing a program for my homework that counts words and lines. I'm wondering why i get the error: "no instance of overloaded function "getline" matches the argument list. Argument types are (std::ifstream, int)"
I was certain "infile" was of std::ifstream argument type. Could the problem be visual studio or am i quick to blame something without prior knowledge?
P.S i searched a bit but would not find a thread with exactly the same problem.. there are similar ones but they end up being that someone put a string of the file name and not the stream itself. Also keep in mind i'm in the middle of writing this i didn't finish yet.
#include <iostream>
#include <fstream>
#include <string>
#include <iomanip>
using namespace std;
int main()
{
ifstream infile;
infile.open("Lear.txt");
string word;
int countWords = 0, countLines = 0;
while (!infile.eof())
{
infile >> word;
countWords++;
getline(infile, countLines); //issue area here at infile
countLines++;
}
cout << "Words: " << setw(9) << countWords << endl;
cout << "Lines: " << setw(9) << countLines << endl;
infile.close();
}
There is no std::getline overload that takes an int second parameter. I assume you meant to pass your std::string variable instead.
getline(infile, word);
You should remove the infile >> word; line or decide whether you want to use it or std::getline. I don't think you want both in this case.
This will fix the compiler error but not your program logic. If you use std::getline you'll have to parse each line to count words.

Take input of date in format mm/dd/yyyy and split into 3 separate variables?

I'm trying to figure out how to do some date validation. I need to be able to take an input from user in the form of mm/dd/yyyy and do various calculations with it to determine validity. However, I can't figure out how to split the date into three variables day,month,year after I got the input. I've played around with getline and get functions but I can't figure it out. Thanks for any help in advance from a newbie. PS I don't want to use any built in date validation functions.
int main()
{
char fill = '/';
string entered_date;
int entered_month;
int entered_year;
int entered_day;
cout << "Enter a date (mm/dd/yyyy): " << endl;
cin >> entered_date;
//getline(month, 2, '/'); < fix me
//cout << entered_month << "/" << entered_day << "/"
// << entered_year << endl;
system("Pause");
}
You can use scanf in such a case, as it provides much more functionality than cin.
int mm, dd, yyyy;
scanf("%d/%d/%d", &mm, &dd, &yyyy);
This should hopefully do the trick.
EDIT: Another way to do so would be to take the entire input in the form of a string, and then find substrings and validate each part.
string date;
cin >> date;
string delimiter = "/";
auto start = date.begin(); // iterator at beginning of string
auto finish = date.find(delimiter); // gives position of first occurrence of delimiter
if (finish == date.npos) // find returned the end of string
// delimiter does not exist in string.
else
int month = stoi(date.substr(0, finish)); // Extracts month part from date string
date = date.substr(finish+1); // Removes the month part from the date string
// Understand this piece and write further ahead.
If you know your input will be correct, then use the first part as it will be much faster. If there are chances of incorrect input, use the second as it will be more robust.
The simplest way is to use std::string::substr, and then call stoi:
#include <string>
#include <iostream>
using namespace std;
int main()
{
char fill = '/';
string entered_date;
int entered_month;
int entered_year;
int entered_day;
cout << "Enter a date (mm/dd/yyyy): " << endl;
cin >> entered_date;
entered_month = stoi(entered_date.substr(0,2));
entered_day = stoi(entered_date.substr(3,2));
entered_year = stoi(entered_date.substr(6));
}
Live example: http://ideone.com/PWyh8J

C++ ifstream skips 1st line

Here is my code.
#include <iostream>
#include <fstream>
#include <cstdlib>
using namespace std;
int main ( ){
ifstream inFile;
char date1[8], date2[8];
int dayTemp1[24], dayTemp2[24];
inFile.open("weatherdata.txt");
if(inFile.fail()){
cout << "File failed to open.";
exit(1);
}
inFile >> date1 >> date2;
cout << date1 << endl;
cout << date2 << endl;
inFile.close();
return 0;
}
The first two lines of the weatherdata.txt file are:
01/04/13
01/05/13
date1 is supposed to contain the first date but when printed it only prints the '\n' character (an empty line).
I don't know what is going on with the code as to why it is skipping the first date line.
Any and all help is appreciated. I'm a beginner to C++.
use std::string instead:
#include <string>
std::string date1;
std::string date2;
//...
inFile >> date1 >> date2;
OR
std::getline(inFile, date1);
std::getline(inFile, date2);
#billz gave you a solution to the problem, so I'll provide an explanation:
The problem was that your char arrays allocated exactly 8 bytes (or characters in this case) but did not leave room for the obligatory null byte (\0). My assumption is that's causing undefined behavior, and when you print you're not getting the correct output because of this. For example, on Linux I'm not getting the first line as blank, I actually get:
01/04/1301/05/13
01/05/13
This was a clear indicator to me the the insertion did not stop when it reached the presumed null byte. The solution is to allow your char arrays hold at least 9 bytes.
Using std::string is beneficial in this context as it avoids this problem completely (it is a container of a dynamically-sized string). Its size will accompany the extra characters (as well as the null byte).

Using strtok in C++

I'm trying to read a file which has input(time and price) as: 12:23:31
67 12:31:23 78 [...] I created a struct which holds values of hour,
minutes and seconds. I used strtok to tokenize the individual values
and use atof to store them. However, I'm getting an error when I try
to tokenize the time: cannot convert std::string' to 'char*' for argument 1 to 'char*'
struct time
{
int hours;
int minutes;
int seconds;
double price;
};
int main()
{
string file, input;
time* time_array;
char* tok;
cout << "Enter a file name to read input: ";
cin >> file;
ifstream file_name(file.c_str());
file_name >> input;
file_name >> input;
//while(!file_name.eof())
for(int i = 0; i < 4; i++)
{
time_array = new time;
file_name >> input;
tok = strtok(input, ":"); //ERROR HERE
while(tok != NULL)
{
*time_array.hours = atof(tok[0]);
*time_array.minutes = atof(tok[1]);
*time_array.seconds = atof(tok[2]);
}
file_name >> input;
*time_array.prine = atof(input);
}
}
I would not use strtok for this job at all1. If you want to use C-like tools, then read the data with fscanf:
// note there here `file_name` needs to be a FILE * instead of an ifstream.
fscanf(file_name, "%f:%f:%f %f", &hours, &minutes, &seconds, &price);
Most people writing C++ would prefer something more typesafe though. One possibility would be to use essentially the same format string to read the data using Boost.format.
Another possibility would be to use stream extractors:
char ignore1, ignore2;
file >> hours >> ignore1 >> minutes >> ignore2 >> seconds >> price;
As to what this does/how it works: each extractor reads one item from the input stream. the extractors for float each read a number. The extractors for char each read one character. In this case, we expect to see: 99:99:99 99, where 9 means "a digit". So, we read a number, a colon, a number, a colon, a number and another number (the extractor skips whitespace automatically). The two colons are read into char variables, and can either be ignored, or you can check that they really are colons to verify that the input data was in the correct format.
Here's a complete, compileable demo of that technique:
#include <iostream>
int main() {
float hours, minutes, seconds, price;
char ignore1, ignore2;
std::cin >> hours >> ignore1 >> minutes >> ignore2 >> seconds >> price;
std::cout << "H:" << hours
<< " M:" << minutes
<< " S:" << seconds
<< " P:" << price << "\n";
return 0;
}
There are certainly a lot more possibilities, but at least those are a few reasonable ones.
To be honest, I'm not sure there's any job for which I'd use strtok, but there are some where I might be at least a little tempted, or wish strtok weren't so badly designed so I could use it. In this case, however, I don't even see much reason to use anything similar to strtok at all.
strtok doesn't take a string as its argument - it takes a char*. Like all functions in the cstring header it's a C function that works with C strings - not C++ strings - and should generally not be used in C++.
Use the methods of the string class instead.
The short answer is that you cannot directly use a std::string with strtok, as strtok wants a string it can modify. Even if you use c_str() to get a C-style string from a std::string, it is still read only.
If you really want to use strtok, you need to duplicate the string into a modifiable buffer, for example by:
char* str = strdup(input.c_str());
If you do this, make sure you call free(str) at the end of the function, else you will get a memory leak!
Your simple case can easily be built using the string::find method. However, take a look at Boost.Tokenizer.
strtok will not work with std::string.c_str() because it returns const char*. strtok does not take a string as an argument, but rather a char*.

Converting c++ string to int

I have the following data in a c++ string
John Doe 01.01.1970
I need to extract the date and time from it into int variables. I tried it like this:
int last_space = text_string.find_last_of(' ');
int day = int(text_string.substr(last_space + 1, 2));
But I got invalid cast from type ‘std::basic_string’ to type ‘int’. When I extract the "John Doe" part in another string variable, all works fine. What's wrong?
I am trying to compile it with g++ -Wall -Werror.
You need to use
std::stringstream ss;
ss << stringVar;
ss >> intVar;
or
intVar = boost::lexical_cast<int>(stringVar);.
The later is a convenience wrapper from the boost library.
Use streams to decode integers from a string:
#include <iostream>
#include <sstream>
#include <string>
int main()
{
std::string x = "John Doe 02.01.1970";
std::string fname;
std::string lname;
int day;
int month;
int year;
char sep;
std::stringstream data(x);
data >> fname >> lname >> day >> sep >> month >> sep >> year;
std::cout << "Day(" << day << ") Month(" << month << ") Year(" << year << ")\n";
}
The operator >> when used with a string variable will read a single (white) space separate word. When used with an integer variable will read an integer from the stream (discarding any proceeding (white) space).
Try the Boost Data/Time library.
As far as I can tell, atoi does what you need.
"Parses the C string str interpreting its content as an integral number, which is returned as an int value."
http://www.cplusplus.com/reference/clibrary/cstdlib/atoi/
Assuming (and that might be a bad assumption) that all the data was formatted similarly, I would do something like this
char name[_MAX_NAME_LENTGH], last[_MAX_NAME_LENGTH];
int month, day, year;
sscanf( text_string, "%s %s %2d.%02d.%04d", first, last, &month, &day, &year );
This does however, have the problem that the first/last names that appear in your input are only one word (i.e. this wouldn't work for things like "John M. Doe"). You would also need to define some appropriate maximum length for the string.
It's hard to be more definitive about this solution unless we know more about the input.