Why is QMultiMap's find operation not working as I expected? - c++

I have a QMultiMap<QDateTime, SomeOwnDataType> from which I'd like to retrieve all values with a certain timestamp. This is what I do:
QMap<QDateTime, Appointment>::iterator it = _reminders.find(now);
where now has a value of di 6. mrt 12:07:00 2012. This is my loop condition:
while (it != _reminders.end() && it.key() == now) {
This was the state of the _reminders object:
Contrary to my expectations, the loop was skipped entirely. How come?

I believe that the problem is that the two timestamps are not equal. If you check the == operator code of QDateTime you will see that the equality holds if both time and date are equal.
bool QDateTime::operator==(const QDateTime &other) const
{
if (d->spec == other.d->spec && d->utcOffset == other.d->utcOffset)
return d->time == other.d->time && d->date == other.d->date;
else {
QDate date1, date2;
QTime time1, time2;
d->getUTC(date1, time1);
other.d->getUTC(date2, time2);
return time1 == time2 && date1 == date2;
}
}
But the time equal operator compares miliseconds:
bool operator==(const QTime &other) const { return mds == other.mds; }
where mds is the time in miliseconds. In the QTime constructor mds is calculated as follows:
mds = (h*SECS_PER_HOUR + m*SECS_PER_MIN + s)*1000 + ms;
It would be safer if you just checked if the difference between two timestamps is within a limit. For example :
while (it != _reminders.end() && abs(now.msecsTo(it.key())) < aLimitInMsecs) {

How do you initialize now?
QDateTime goes up to the millisecond, so a toString() could display the same value while actually the values are different...
Unless at some point the key _reminders[0] is set to the value of now, they will be different.
If you are constructing an calendar app, you might use a QString as the key to your QMultiMap, with the values being the output of QDateTime::toString() (the format depending on the precision you are willing (day, hours, minute, ...)

Related

Checking a date is in an interval given by partial/periodic dates

I can define an interval with start and end in the format YYMMDD, but they can also be partial/periodic - meaning some elements (day, month or year) can be left blank.
For example, start = " 1115" and end = " 0115" the interval is 15th nov to 15th jan every year.
I want to check if a non-partial date is in the interval.
int compareParial(const char* first, const char* second)
{
for (int i = 0; i < 6; ++i)
{
if (first[i] != ' ' && second[i] != ' ' && first[i] != second[i])
return first[i] > second[i] ? 1 : -1;
}
return 0;
}
bool isDateInInterval(const char* start, const char* end, const char* searchDate)
{
int firstCompare = compareParial(start, searchDate);
int endCompare = compareParial(end, searchDate);
if (firstCompare <= 0 && endCompare >= 0)
return true;
// the date can still be in the interval if the start of the interval is in one year, but end in the next year
bool switched = 0 < compareParial(start, end);
if (switched && (firstCompare <= 0) != (endCompare >= 0))
return true;
return false;
}
int main()
{
cout << boolalpha << isDateInInterval(" 1115", " 0115", "251110") << endl;
return 0;
}
Update: If the dates are reversed check again if searchDate is in.
A problem I notice is what if start and end are reversed but the year is provided. For example: isDateInInterval("200105", "190601", "251110") would be true
C++20 contains types which can represent partial dates: year, month, day, year_month, month_day, etc.2
For example:
auto start = November/15;
auto end = January/15;
By using actual calendrical types, as opposed to strings, the logic you have to deal with can be greatly simplified. A complete year_month_day might be compared against an interval defined by a pair of month_day like this:
bool
compare_partial(std::chrono::month_day start, std::chrono::month_day end,
std::chrono::year_month_day searchDate)
{
using namespace std::chrono;
// Guess that both start and end fall in the same year
auto trial_start = start/searchDate.year();
auto trial_end = end/searchDate.year();
if (trial_start <= trial_end)
{
return trial_start <= searchDate && searchDate <= trial_end;
}
// start/y > end/y
// Otherwise guess that searchDate comes after trail_start:
if (trial_start <= searchDate)
{
// trial_end must be in the next year
trial_end += years{1};
return trial_start <= searchDate && searchDate <= trial_end;
}
// Otherwise searchDate < start/y && start/y > end/y
// trial_start must be in the previous year
trial_start -= years{1};
return trial_start <= searchDate && searchDate <= trial_end;
}
Be forewarned that even this answer is somewhat wrong1. However by using actual calendrical types to do things like add/subtract a year, and do the comparisons, one makes the code cleaner, easier to read, and thus less likely to contain errors.
This answer also only addresses the month_day partial date. You might also have a year_month partial date, or a mixture of month_day and year_month.
std::chrono has no type year_day, and I'm not sure what that would mean anyway. If you have an idea of what it would mean, I have no doubt that C++20 chrono could help you model it.
In any event:
cout << boolalpha << compare_partial(November/15, January/15, 2025y/November/10) << endl;
will output:
false
Even if you don't use C++20 chrono (or it's free preview), modeling this using calendrical types (perhaps of your own making), as opposed to strings, is highly recommended for creating a robust, error-free solution.
1 Expressions such as trial_end += years{1}; aren't guaranteed to be valid dates. For example what if trial_end has the value 2020-02-29. Adding a year to that will give you 2021-02-29. To make this correct, you must decide how you want to handle such situations (e.g. map it to 2021-02-28?).
2 There also exists a free, open-source, header-only preview of this part of C++20 which works with C++11/14/17: https://github.com/HowardHinnant/date
If year is set, and the dates are switched, you must return false, since it is an empty interval.
bool switched = 0 < compareParial(start, end);
if (start[0]==' ' && switched && (firstCompare <= 0) != (endCompare >= 0))
return true;
return false;

c++ testing if one file is older than a set of files

I am creating a cache for some data, but of course I want the cache to become invalid if any of the source files from which the cache is made is modified. TO that effect I made this function:
bool CacheIsValid(
const std::string& cache_shader_path,
const std::vector<std::string>& shader_paths)
{
// This is really messy because of the std stuff, the short of it is:
// Find the youngest file in the shader paths, then check it against the
// timestamp of the cached file.
std::time_t youngest_file_ts = std::time(nullptr);
for(auto file : shader_paths)
{
std::time_t current_timestamp =
std::chrono::system_clock::to_time_t(
std::chrono::file_clock::to_sys(fs::last_write_time(file)));
double time_diff = difftime(youngest_file_ts, current_timestamp);
if(time_diff > 0) youngest_file_ts = current_timestamp;
}
// All this is doing is comparing the youngest file time stamp with the cache.
return fs::exists(cache_shader_path)
&& (difftime(youngest_file_ts, std::chrono::system_clock::to_time_t(
std::chrono::file_clock::to_sys(fs::last_write_time(cache_shader_path)))) < 0);
}
I don't know what I did wrong but that is always returning true even when the input files are modified. I am checking the timestamps using stat and the files on disk are objectively younger than the cache file, I also tested that the inputs to this function are correct, and they are.
It seems to me that you're jumping through a lot of hoops that make this tremendously more difficult than it needs to be. filesystem::last_write_time returns a time_point<someclock>1 , which supports comparison. As such, at least as far as I can see, there's no reason to do the long, drawn-out conversion to time_t, then using difftime to do the comparison.
If I understand the idea of what you're doing, you want to ascertain that the one file is at least as new as any of the files named in the vector. To do that a bit more directly, I'd write code something along this general line:
bool isNewer(std::string const &file, std::vector<std::string> const &otherFiles) {
auto newest = std::max_element(otherFiles.begin(), otherFiles.end(),
[&](std::string const &a, std::string const &b) {
return fs::last_write_time(a) > fs::last_write_time(b);
});
return fs::last_write_time(file) >= fs::last_write_time(*newest);
}
I may have misunderstood the direction you want one or both comparisons done, in which case this may not be right--but if so, changing the comparison(s) to match your requirements should be trivial.
where someclock is some arbitrary type that meets a few specified requirements for C++17, or std::chrono::file_clock for C++20.
due to you want to find youngest_file_ts -> find most recently timestamp (greater number) of a changing file however
double time_diff = difftime(youngest_file_ts, current_timestamp);
if(time_diff > 0) youngest_file_ts = current_timestamp; // find greater number one
after the for loop youngest_file_ts is oldest timestamp of a changing file
therefor
(difftime(youngest_file_ts, std::chrono::system_clock::to_time_t(
std::chrono::file_clock::to_sys(fs::last_write_time(cache_shader_path)))) < 0) alway true.
it should be change like
if (shader_paths.empty()) {
return false
} else {
//initialize youngest_file_ts as last_write_time of first element in shader_paths
std::time_t youngest_file_ts = std::chrono::system_clock::to_time_t(
std::chrono::file_clock::to_sys(fs::last_write_time(shader_paths.at(0)));
for (auto file : shader_paths)
{
std::time_t current_timestamp = std::chrono::system_clock::to_time_t(
std::chrono::file_clock::to_sys(fs::last_write_time(file)));
double time_diff = difftime(youngest_file_ts, current_timestamp);
if (time_diff < 0) youngest_file_ts = current_timestamp;
}
// All this is doing is comparing the youngest file time stamp with the cache.
return fs::exists(cache_shader_path)
&& (difftime(youngest_file_ts, std::chrono::system_clock::to_time_t(
std::chrono::file_clock::to_sys(fs::last_write_time(cache_shader_path)))) < 0);
}

QtCustomPlot is plotting different times that what im passing in

I am trying to pass in time values on a range of a single day(9:30 - 4:00), i use an api and libcurl to retrieve a .json for me that gives me "date: 2020-06-04" and "minute: 09:30" and i have them read into a vector like so:
//Reads in data from json(historical data 1 day delayed)
for(Json::Value::ArrayIndex i = 0 ; i != chartData.size(); i++)
{
if(chartData[i].isMember("average"))
{
value.push_back(chartData[i]["average"].asDouble());
time.push_back(chartData[i]["date"].asString());
auto timeDate = QDate::fromString(time[i].c_str(), Qt::ISODate);
minute.push_back(chartData[i]["minute"].asString());
auto minuteDate = QTime::fromString(minute[i].c_str(), "hh:mm");
timeInEpoch.push_back(QDateTime(timeDate, minuteDate).toSecsSinceEpoch());
if((value[i] == 0) && (i != chartData.size() - 1))
{
value[i] = value[i-1];
}
if(value[i] > maxAvg)
{
maxAvg = value[i];
}
else if(value[i] < minAvg)
{
minAvg = value[i];
}
}
}
I then have them converted to a date and time so they should be like "2020-06-04 09:30" and then converted into SecsSinceEpoch().
After that they should be plotted but when they are plotted its like all of the times are shifted hours to the right, like so
Does anyone have any ideas what would be causing this? Thank you in advance!
fromString() will give back you the datetime in local time, while toSecsSinceEpoch() is converting the datetime value to UTC. So either you have to set specifically the timezone of the input string (for example to UTC), or you can use offsetFromUtc() (https://doc.qt.io/qt-5/qdatetime.html#offsetFromUtc) to adjust the result of toSecsSinceEpoch()

boolean operations overloading inside classes in c++ and if statements for dates inside a class

I'm using this code in my previous question:
Adding the year implementation in c++ using a class
I want to use an if statement to check for dates in a way that if the day was 31 it gets back to 0 and the month gets incremented by one. I even tried to write another method and use it inside of the + operation but this failed as well because I'm incrementing the day in the return function inside of the operation declaration. As a result, it will need to be incremented before checking for the conditions first ! but what if the number was initially 31? there is no month that has 32 days !
I tried to use it but because of my implementation it didn't work as it should
My other question is that I'm trying to use a Boolean reference check with the operation == as well
This is what I've done so far :
bool operator==(const Date&) const;
bool Date::operator==(const Date& date) const
{
if (day == date.day && monthnum == date.monthnum && year == date.year)
return true;
else return false;
}
but for some reason when I try to test it in the main by saying for example, date1==date2, it doesn't compile !
am I writing it wrong ?
"no operation == matches these operands"
this is the error I get when I try to compile the code
I want to use an if statement to check for dates in a way that if the day was 31 it gets back to 0 and the month gets incremented by one.
This is as simple to implement as:
if (day == 31) {
day = 0;
monthnum++;
}
I try to test it in the main by saying for example, date1==date2, it doesn't compile ! am I writing it wrong ?
Yeah well, you are declaring a free function operator==, while what you want is a member function. Inside Date do:
class Date {
public:
// ...
bool operator==(const Date&) const;
// ...
};
You can also use a free function, to be honest, but that would require more changes and it generally is the same. Just in case you want to use it here's how:
bool operator==(const Date& lhs, const Date& rhs) {
return (lhs.day == rhs.day && lhs.monthnum == rhs.monthnum && lhs.year == rhs.year);
}
(I've removed the redundant if-else pair).
The compiler states that "no operation == matches these operands". I simply have this code in my main: cout << date1 == date2;
Yes, you should do this instead:
cout << (date1 == date2);
otherwise what the compiler reads is this:
(cout << date1) == date2;

C++ Equals Operator

I have the following equals operator:
bool operator==(const Duration& x, const Duration& y){
return ( x.hrs == y.hrs, x.mins == y.mins, x.secs == y.secs );
}
I have also tried:
bool operator==(const Duration& x, const Duration& y){
return ( (x.hrs == y.hrs) && (x.mins == y.mins) && (x.secs == y.secs) );
}
In my main method I have:
//Arbitrary Durations - Testing
Duration dTest0 (01,45,12);
Duration dTest1 (01,35,45);
Duration dTest2 (01,35,45);
Duration dTest3 (01,25,05);
if ( dTest0 == dTest1 ){
cout<< "broken" << endl;
}
else{
cout<< "working" << endl;
}
My program keeps outputting "broken" which suggests that dTest0 and dTest1 are infact equal... Where am I going wrong?
Additional: If I use x.getHours == y.getHours... It puts a red line under the "." and says: 'Error: a pointer to a bound function may only be used to call the function`.
Any advice would be appreciated. Thanks.
The first implementation will only return true if x.secs == y.secs. The results of the first two comparisons will be discarded. The , operator evaluates to the value of its second operand, which in this case boils down to just x.secs == y.secs.
The second one, however, is correct. If it is not working, then you must be setting the values of hrs, mins, and secs incorrectly in the constructor of Duration.
The problem that you have with getHours is that you need to call it. It is a member function after all. So do x.getHours() instead of x.getHours.
The first one is wrong. , does not work that way.
The second one is correct, assuming Duration is reasonable.
You may have a bug in your Duration constructor. I'd even think it is likely.