How to compare two datetime structures in C++ efficiently? - c++

I have the following DateTime structure:
struct DateTime
{
std::uint16_t year;
std::uint8_t month;
std::uint8_t day;
std::uint8_t hour;
std::uint8_t minute;
std::uint8_t second;
std::uint16_t milisecond;
};
My doubt is about the LessThan and GreaterThan methods. I have implemented as follows in order to avoid a bunch of ifs and elses, but I might have not covered all possible situations:
bool GreaterThan(const DateTime& datetime)
{
bool greater{true};
// When found a different value for the most significant value, the evaluation is interrupted
if ((year <= datetime.year) && (month <= datetime.month || year < datetime.year) &&
(day <= datetime.day || month < datetime.month) && (hour <= datetime.hour || day < datetime.day) &&
(minute <= datetime.minute || hour < datetime.hour) &&
(second <= datetime.second || minute < datetime.minute) &&
(milisecond <= datetime.milisecond || second < datetime.second))
{
greater = false;
}
return greater;
}
bool LessThan(const DateTime& datetime)
{
bool less{true};
// When found a different value for the most significant value, the evaluation is interrupted
if ((year >= datetime.year) && (month >= datetime.month || year > datetime.year) &&
(day >= datetime.day || month > datetime.month) && (hour >= datetime.hour || day > datetime.day) &&
(minute >= datetime.minute || hour > datetime.hour) &&
(second >= datetime.second || minute > datetime.minute) &&
(milisecond >= datetime.milisecond || second > datetime.second))
{
less = false;
}
return less;
}
Please, let me know which possible situation is not covered.

Your implementation looks overly painful. There's a simpler, logical way to acheive this. If you know of the greedy algorithm, its a similar thought process to that.
bool GreaterThan(const datetime& datetime)
{
if(year != datetime.year) return year > datetime.year;
if(month != datetime.month) return month > datetime.month;
//... and so on (omitted)
return false; // they are the same
}
You can implement LessThan similarly.

From all the comments, I think the best solution was proposed by –
Richard Critten:
bool GreaterThan(const DateTime& date_time)
{
return (std::tie(year, month, day, hour, minute, second, milisecond) >
std::tie(date_time.year, date_time.month, date_time.day, date_time.hour, date_time.minute, date_time.second, date_time.milisecond));
}
bool LessThan(const DateTime& date_time)
{
return (std::tie(year, month, day, hour, minute, second, milisecond) <
std::tie(date_time.year, date_time.month, date_time.day, date_time.hour, date_time.minute, date_time.second, date_time.milisecond));
}

Related

adding dates in C++, using class

I'm a novice in coding...
I've written a code in C++ to calculate the date after adding new numbers to date, month, and year. However, it's become too long and my computer can't process it if the date goes over 50.
Could you give me a few tips if there was a way to shorten this?
#include <iostream>
class Date{
int year; int month; int day;
}
public:
void set_date(int _year, int _month, int _day){
year=_year;
month=_month;
day=_day;
}
void add_day(int inc){
day+=inc;
}
void add_month(int inc){
month+=inc;
}
void add_year(int inc){
year+=inc;
}
void get_date(){
while (day>31){
if ((year%4)==0 && month==2 && day > 29){
month+=1;day-=29;
}
else if(month==2 && day > 28){
month+=1;day-=28;
}
else if((day>30)&&(month==4||month==6||month==9||month==11)){
month+=1;day-=30;
}
else if((day>31)&&(month==3||month==5||month==7||month==8||month==10||month==12||month==1)){
month+=1;day-=31;
}
}
if (month>12){
year+=month/12;
month=month%12;
}
std::cout<<"year"<<year<<std::endl;
std::cout<<"month"<<month<<std::endl;
std::cout<<"day"<<day<<std::endl;
}
int main(){
Date date;
date.set_data(191,2,10);
date.add_day(60);
date.add_month(10);
date.add_year(3);
date.get_date();
return 0;
}
When I run your code it loops forever with day=39 and month=13, which is a condition you don't check for. You might want to move the whole if (month>12) into your loop, so that the months get corrected while processing the days. You also want to add an else that stops the loop:
bool doneProcessing = false;
do
{
if ((year % 4) == 0 && month == 2 && day > 29)
{
month += 1;
day -= 29;
}
else if (month == 2 && day > 28)
{
month += 1;
day -= 28;
}
else if ((day > 30) && (month == 4 || month == 6 || month == 9 || month == 11))
{
month += 1;
day -= 30;
}
else if ((day > 31) && (month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12 || month == 1))
{
month += 1;
day -= 31;
}
else
{
doneProcessing = true;
}
if (month > 12)
{
year += month / 12;
month = month % 12;
}
}
while (!doneProcessing);
I didn't check the validity of your semantics, but I did notice one thing: When you have month=2 and day=30, you'd want to process that as well, but you only check day>31, thus I changed the loop to simply run until the else is hit (all days are valid now).
With this change your code properly terminates after printing this result:
year195
month2
day8
PS: With a debugger you can easily and quickly find such issues. See What is a debugger and how can it help me diagnose problems?

Taking Date input in the form of "yyyy/mm/dd, hh:mm" from user and validate the date in C++?

I'm pretty new in C++ and trying to figure out how to solve this problem, so any help is absolutely appreciated. I need to take Date input from user in the form of "yyyy/mm/dd, hh:mm" in an istarem& function and validate its parts.
So far, I have tried everything that I've learned and my best solution with minimum error is the following code. There is also a predefined tester client code which I can't modify. There are many steps in the client code in which every validation gets tested. for example:
one input is "2000/1/50" and I get the DAY_ERROR which is true.
another input is "2000/1/1, 25:10" and I get the HOUR_ERROR which is true as well. Everything goes fine until the last part (checking minute). When the input is "2000/1/1, 23:60" I get the HOUR_ERROR again instead of MIN_ERROR.
I'm not sure if it's because of the whitespace after "," or not. However, if that's the case I don't know how to fix it.
std::istream& Date::read(std::istream& is = std::cin) {
int year;
int mon;
int day;
int hour;
int min;
bool valid;
((((is >> year).ignore(100, '/') >> mon).ignore(100, '/') >> day).ignore(100, ',') >> hour).ignore(100, ':') >> min;
bool val_year = sizeof(year) == 4 && year >= MIN_YEAR && year <= MAX_YEAR;
bool val_mon = mon >= 1 && mon <= 12;
bool val_day = day >= 1 && day <= mday();
bool val_hour = sizeof(hour) == 2 && hour >= 0 && hour <= 23;
bool val_min = sizeof(min) == 2 && min >= 0 && min <= 59;
valid = val_year && val_mon && val_day && val_hour && val_min;
if (valid) {
errCode(NO_ERROR);
Date D2(year, mon, day, hour, min);
}
else {
if (!val_year)
errCode(YEAR_ERROR);
else if (!val_mon)
errCode(MON_ERROR);
else if (!val_day)
errCode(DAY_ERROR);
else if (!val_hour)
errCode(HOUR_ERROR);
else if (!val_min)
errCode(MIN_ERROR);
else
errCode(NO_ERROR);
}
}
return is;
}
Here's one suggestion:
/*
* EXAMPLE INPUT: 2/22/17, 20:15
* EXAMPLE OUTPUT: input: 02/22/17, 20:15
*/
#include <stdio.h>
int main()
{
int year, month, day, hour, min;
char buff[80];
fgets (buff, sizeof(buff), stdin);
int n = sscanf(buff, "%d/%d/%d, %d:%d", &year, &month, &day, &hour, &min);
if (n != 5) {
printf ("Error parsing input: expected 5 values, got %d...\n", n);
return 1;
}
printf ("input: %02d/%02d/%d, %02d:%02d\n", year, month, day, hour, min);
return 0;
}
As far as validating the date: your code looks fine.
Or you might want to consider reading your date/time values into a struct tm_date (instead of year, month, day, etc.), then using strftime to validate.
Here is an example:
taking date as dd/mm/yy in c language

In C++, using a loop to find the # of days on any given date?

And yes this is an assignment-- So please don't post solutions, but detailed pseudocodes are extremely helpful!
I already have a program in C++ that accepts a date from the user and will determine if it is a leap year or not.
Here is my leap year function so far (I do hope that this is the correct logic):
bool isLeapYear (int year){
int leapYear = 0;
//leapyear = 1 when true, = 0 when false
if ((year%400 == 0) && (year%100 != 100)) {
leapYear = 1;
}
else if (year%4 == 0) {
leapYear = 1;
}
else {
leapYear = 0;
}
if (leapYear == 1) {
return 1;
}
else {
return 0;
}
}
Here is a paraphrase of what I must do next:
You MUST use a loop that adds months one at a time to an accumulated sum.
Don't use any hardcoded values
Such as 152 for the first 5 months in a leap year
And to clarify, this is my first programming class and have been in this C++ class for just about a month now.
So it would be greatly appreciated if anyone could help me figure out how to do the loop statements to add the number of the months?
(IE: 12/31/2013 should be "365" in a non leap year, and "366" in a leap year).
I know this is wrong but this is what I have so far for a function "dayNumber" that simply return the number of days in the year to the main function (which is calling the dayNumber function):
int dayNumber (int day, int month, int year){
//variable declarations
int sumTotal = 0;
for ( int loop = 1; loop < month; loop++) {
sumTotal = (( month-1 ) * 31);
sumTotal = sumTotal + day + 1;
if ( (loop==4) || (loop=6) || (loop=9) ||
(loop==11) ) {
sumTotal = ( sumTotal - 1 );
}
else if ( isLeapYear(year) == 1 ) {
sumTotal = (sumTotal - 2);
}
else {
sumTotal = (sumTotal - 3);
}
}
return sumTotal;
}
I started to mess around with it to get to a proper value for days I knew but it kind of messed it up more, haha.
If anyone has any guidance on how to appropriately do a loop, I would be extremely greatful!:)
EDIT:
Alright, I think I may have answered my own question.
int dayNumber (int day, int month, int year){
//variable declarations
int sumTotal = 0;
for ( int loop = 1; loop < month; loop++) {
sumTotal = ( sumTotal + 31 );
if ( (loop==4) || (loop==6) || (loop==9) ||
(loop==11) ) {
sumTotal = ( sumTotal - 1 );
}
}
if ((month !=2) && (month > 1)) {
if (isLeapYear(year) ==1) {
sumTotal = ( sumTotal - 2 );
}
else {
sumTotal = ( sumTotal - 3);
}
}
else {
sumTotal = sumTotal;
}
sumTotal = sumTotal + day;
return sumTotal;
}
I definitely need to work on my loops.
I appreciate letting me know that my '=' should have been '=='!
I believe this is an appropriate code using a simple enough loop?
I will test some more dates. I've only tested the few provided on the class site so far.
I can't answer my own posts, I don't have enough reputation.
I know an answer has been accepted, but it took me some time writing my own, let's see if it can adds some informations overall.
Let's review this slowly.
First, your isLeapYear() function isn't completely right.
Without delving into the algorithm part, two or three things can be improved.
You're returning a bool, yet your return statements are returning ints. This isn't wrong in itself, but using the true and false keywords can improve the readability and consistency.
Instead of creating, assigning and returning a variable, you should instantly return your result.
Add spaces around your operators : year%400 should become year % 400.
Now your code.
This condition :
if ((year%400 == 0) && (year%100 != 100))
... especially this part :
(year%100 != 100)
Isn't doing what you expect.
Overall, the algorithm is as follow :
if year is not divisible by 4 then common year
else if year is not divisible by 100 then leap year
else if year is not divisible by 400 then common year
else leap year
Translating it in code:
/**/ if (year % 4 != 0)
return false;
else if (year % 100 != 0)
return true;
else if (year % 400 != 0)
return false;
else
return true;
Now let's simplify this a bit:
/**/ if (year % 4 == 0 && year % 100 != 0)
return true;
else if (year % 400 == 0)
return true;
else
return false;
Again:
if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)
return true;
else
return false;
And finally, the whole boolean expression can be directly returned:
return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
Now that your function is correct, let's try an algorithm to your dayNumber function :
If the date provided in parameters is considered correct, then the algorithm is quite simple :
Start sum from 0
Loop from 1 to month excluded
If month is Frebruary then add 29 if isLeapYear returns true, else 28
If month is January, March, May, July, August, October or December add 31
Else add 30
Add day to sum.
I know that giving detailed pseudocode helps more than a solution, but I'm not that good of a teacher yet :/ Good luck with this answer, and get some sleep when you're done :)
I'm assuming that dayNumber(8, 3, 1914) is supposed to return the ISO day number of 1914, March 8th.
What you want to do is the following:
procedure dayNumber(theDay, theMonth, theYear):
set answer to 1 // the first day of the year is day 1
for every month 'thisMonth', up to and excluding theMonth:
increment answer by the number of days in thisMonth
increment answer by (theDay - 1) // the first day of the month is monthday 1
return answer
In the iteration statement (or 'loop body'), there are three cases:
February: if it's a leap year, increment thisMonth by 29, otherwise increment by 28.
Short months (April, June, September, November): increment by 30 days.
Otherwise, long months (January, March, May, July, August, October, December): increment by 31 days.
This translates to:
if (thisMonth == 2) {
// February
if (isLeapYear(theYear))
thisMonth += 29;
else
thisMonth += 28;
} else if (thisMonth == 4 || thisMonth == 6 || thisMonth == 9 || thisMonth == 11) {
// Short month
thisMonth += 30;
} else {
// Long month
thisMonth += 31;
}
(Note: X += Y is, or should be, short for X = X + Y.)
The loop logic you use looks mostly correct to me, except for 1) off-by-one errors because you start with day 0, and 2) adding the monthday inside the loop rather than outside of it:
int dayNumber (int theDay, int theMonth, int theYear) {
int result = 1;
for (int thisMonth = 1; thisMonth < theMonth; thisMonth++) {
/* ... see above ... */
}
return result + theDay - 1;
}
Now, about making the iteration statement prettier, there are basically two ways (and if your course hasn't yet covered them, I of course recommend against using them in an answer). One is using a switch statement, but I'm really not a fan of them, and it doesn't provide much benefit over the code I already gave. The other would be to use arrays:
int dayNumber (int theDay, int theMonth, int theYear) {
int monthDays[12] = { 31, 28, 31, 30, 31, 30, 31
, 31, 30, 31, 30, 31 }
int result = 1;
for (int thisMonth = 1; thisMonth < theMonth; thisMonth++) {
if (thisMonth == 2 && isLeapYear(theYear))
// Special case: February in a leap year.
result += 29;
else
/* Take the month length from the array.
* Because C++'s array indices begin at 0,
* but our first month is month 1,
* we have to subtract one from thisMonth.
*/
result += monthDays[thisMonth - 1];
}
return result + theDay - 1;
}
#include <ctime>
static int GetDaysInMonthOfTheDate(std::tm curDate)
{
std::tm date = curDate;
int i = 0;
for (i = 29; i <= 31; i++)
{
date.tm_mday = i;
mktime(&date);
if (date1->tm_mon != curDate.tm_mon)
{
break;
}
}
return i - 1;
}

C++ error C2106: '=' : left operand must be l-value

Okay, so, ignoring my lazy coding (this is just to get the program to work, I'll clean it up after I get it working). I've set up a couple of if statements that will throw exceptions if I don't get the input I'd like.
#include<string>
#include<iostream>
using namespace std;
int main()
{
bool flag = false;
int month, day, year;
void header();
class monthClassException
{
public:
monthClassException()
{
message = "Invalid Month";
}
monthClassException(string str)
{
message = str;
}
string what()
{
return message;
}
private:
string message;
};
class dayClassException
{
};
class yearClassException
{
};
header();
do
{
try
{
cout << "Please enter your date of birth (MM-DD-YYYY): " << endl;
cin >> month;
cin.ignore(10,'-');
cin >> day;
cin.ignore(10,'-');
cin >> year;
if (month > 12 || month < 1)
throw monthClassException("Invalid Month Entry");
if( ((month == 4 || month == 6 || month == 9 || month == 11) && day > 30) || day < 1)
throw dayClassException();
else if ( ((month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12 ) && day > 31) || day < 1)
throw dayClassException();
else if (month == 2 && year % 4 != 0 && day > 28)
throw dayClassException();
else if((month == 2 && year % 4 = 0) && day > 29)
throw dayClassException();
}
catch(monthClassException mCEO)
{
cout << mCEO.what() << endl;
system("pause");
}
catch(dayClassException)
{
cout << "Invalid Day Entered for Selected Month" << endl;
system("pause");
}
catch(yearClassException yCEO)
{
}
}while(!flag);
return 0;
}
I'm getting my error at that very last exception:
else if((month == 2 && year % 4 = 0) && day > 29)
throw dayClassException();
it's saying that month is an invalid l-value (Why now? At the very end after I've already used it -catastrophically, I will admit.) It could be something really obvious that I fail to see because I'm the one who coded it, or it could be because my really really crazy if statements messed something up somewhere.
Any ideas?
Here is the error:
year % 4 = 0
you probably meant to write ==
= operator as in
year % 4 = 0
means assignment, not comparison. Hence your error.
Fix it to
year % 4 == 0
You have year % 4 = 0.
I think you have a typo: you may want year % 4 == 0.
In addition, I prefer using parentheses to make the code clearer:
...
else if ((month == 2) && (year % 4 == 0) && (day > 29)) {
throw dayClassException();
}
You have a the assignment operator = in your condition instead of the comparison operator ==.
That's pretty clearly a logic error. However, why is it a compiler error? After all, C++ allows assignment inside a condition, and that is something you might legitimately do.
In your case, month == 2 && year % 4 = 0 is processed as ((month == 2) && (year % 4)) = 0 (see C++ Operator Precedence). That expression in the parens evaluates to a temporary. But the left side of an assignment operator must refer to a storage address you can write to (an l-value). So your code is invalid for the same reason that 3 = 3 is invalid. Visual Studio calls this error C2106.
A suggestion in this regard, is to always put the constant on the left hand side of a comparison statement. It helps prevent logical errors. As an example consider the code
if year == 0
and mistakenly you had written:
if year = 0
the result would have been a logical error.
Instead putting the constant 0 on the left side, such that
if 0 = year
would generate a syntax error at compilation, hence preventing you from committing a logical error (that can be harder to debug)

Can't seem to get the correct output from my code block

I was just wondering if anyone noticed i was doing something wrong with my code block. Ths program is supposed to be a test program that compares 2 dates. The function that im working on is supposed to return a 1 if the invoking date is greater, a -1 f the invoking date is less than, and a 0 if the invoking date is equal to the date in the parameter. My test Program :
#include <cstdlib>
#include <iostream>
#include <string>
#include "date.h"
using namespace std;
//date is initialized in a month/day/year format.
int main(int argc, char* argv[])
{
string* d;
date d1(4,1,4);
date d4(4,4,4);
int greaterTest = d4.compareTo(d1);
int lessTest = d1.compareTo(d4);
cout << greaterTest << endl; //i believe these two lines are printing out a
cout << lessTest << endl; //location in memory
cout<<&d <<endl;
system("pause");
return EXIT_SUCCESS;
}
The huge compareTo() function :
int date::compareTo (date another_date)
{
if (this->year == another_date.year && this->month == month && this->day < another_date.day) //if both year and month are the same, test to see if day is less
{
return -1;
}
else if (this->year == another_date.year && this->month == month && this->day > another_date.day) //if both year and month are the same, test to see if day is greater
{
return 1;
}
else if (this->year == another_date.year && this->month > month) //if the years are the same, test to see if the invoking month is greater
{
return 1;
}
else if (this->year == another_date.year && this->month < month) //if the years are the same, test to see if the invoking month is less
{
return -1;
}
else if (this->year > another_date.year) //test to see if the invoking year is greater
{
return 1;
}
else if (this->year < another_date.year) //test to see if the invoking year is less
{
return -1;
}
else if(this-> year == another_date.year && this-> month == another_date.month //test if the dates are exactly the same
&& this-> day == another_date.day)
{
return 0;
}
//else{ return 15;} //if none are true, return 15
}
the only problem im getting is when i try to change the day (the second parameter for date).
I'm not sure if this is the problem, since I can't test it... But, your compareTo function has this line:
this->month == month
Shouldn't it be:
this->month == another_date.month
?
In the first if statement and a few below it as well you have:
this->month == month
This is comparing month to itself, I think you meant:
this->month == another_date.month
Also you don't need to use the 'this' pointer all the time,
month == another_date.month
should suffice.
That might benefit from some early exit:
int date::compareTo (date another_date)
{
if (year > another_date.year) {
//the invoking year is greater
return 1;
}
if (year < another_date.year) {
//the invoking year is less
return -1;
}
// if we reached here, the years are the same. Don't need to compare them for the other cases
if (month > another_date.month) {
return 1;
}
if (month < another_date.month) {
return -1;
}
// if we reached here, the year and month are the same
if (day > another_date.day) {
return 1;
}
if (day < another_date.day) {
return -1;
}
// if we reached here, the year and month and day are the same
return 0;
}
Along the way, the cut+paste error just... disappeared, because that test became redundant.
Unless you're really set on doing an element-by-element comparison, I'd put each set of inputs into a struct tm, then use mktime to convert those to a time_t, and the compare the two time_ts directly. In a typical case, those will be a 32- or 64-bit integer of the number of seconds since midnight Jan 1, 1970, so after the conversion, comparison becomes trivial.
I didn't find your bug in your original code because it was too hard for me to read. I suppose that's why you didn't find it either.
This alternative might be easier to read, and easier to prove correct:
// untested
int date::compareTo (date another_date)
{
if (year < another_date.year) return -1;
if (year > another_date.year) return 1;
if (month < another_date.month) return -1;
if (month > another_date.month) return 1;
if (day < another_date.day) return -1;
if (day > another_date.day) return 1;
return 0;
}