Whats the way to compare dates in c++ - c++

Whats the best way to compare dates in c++ which are made into a structure
that has a day,month and year variable i.e how to check that 30 days have passed since a event and so on. I personally found that when comparing 2 dates in the same year I make both dates into "days-dates" using a array that holds the day count of all the months of the year and then convert the dates into days and do the calculations I need. Is there a easier way to compare the dates ?

Here is a very good C++11/14 library for handling dates1. It allows you to have {year, month, day} structures (called date::year_month_day), and {count-of-days} structures (called sys_days). It also allows easy and efficient conversions between these two structures. And naturally there are comparison operators.
The entire library is essentially an extension of <chrono> into calendar types.
Video introduction found here:
https://www.youtube.com/watch?v=tzyGjOm8AKo
Lots of example code here.
Here is a detailed explanation of the underlying algorithms to convert between the {year, month, day} structure and the {count-of-days} structure:
http://howardhinnant.github.io/date_algorithms.html
Here are some date creation and comparison examples for both field (year_month_day) and serial (sys_days) data structures:
#include "date.h"
int
main()
{
using namespace date::literals;
// create and compare {year, month, day} structures
constexpr auto ymd1 = 2017_y/jan/21;
constexpr auto ymd2 = ymd1 + date::months{15};
static_assert(ymd2 > ymd1, "ymd2 is 15 months after ymd1");
static_assert(ymd2 == 2018_y/apr/21, "ymd2 is 2018-04-21");
// create and compare {count_of_days} structures
constexpr date::sys_days sd2 = ymd2;
static_assert(sd2 == ymd2, "sd2 is the same day as ymd2");
static_assert(sd2.time_since_epoch().count() == 17642, "sd2 is day 17642");
constexpr date::sys_days sd1 = sd2 - date::days{465};
static_assert(sd1 < sd2, "sd1 is 465 days before sd2");
static_assert(sd1.time_since_epoch().count() == 17177, "sd1 is day 17177");
static_assert(sd1 == 2017_y/jan/11, "sd1 is 2017-01-11");
}
The constexpr / static_assert requires a fully conforming C++14 compiler. For C++11, remove the constexpr and change static_assert to assert (and eliminate the static_assert message).
date::sys_days is a typedef for the chrono::time_point:
time_point<system_clock, duration<int, ratio<86400>>
The above example code requires only "date.h", and no other C++ source files (no installation). There is also available a timezone library at this same github location, but that does require some installation.
1 I am the principal author of this library.

For what you describe, the standard C style routines are probably going to work best: http://en.cppreference.com/w/cpp/chrono/c
Here is a rough example using C style coding
#include <ctime>
const int SECONDS_PER_DAY = 24 * 60 * 60;
tm day1;
memset( &day1, 0, sizeof(day1) );
day1.tm_year = 2016 - 1900; // tm_year is the years since 1900
// so to represent 2016, the value is 116.
day1.tm_mon = 0;
day1.tm_mday = 20;
time_t day1a = mktime( &day1 );
tm day2;
memset( &day2, 0, sizeof(day2) );
day1.tm_year = 2008 - 1900;
day1.tm_mon = 0;
day1.tm_mday = 20;
time_t day2a = mktime( &day2 );
double day_delta = difftime( day1, day2 ); // returns number of seconds
double days_past = (day_delta / SECONDS_PER_DAY)
This is standard and will work on everywhere.
N.B. the POSIX standard defines time_t as an integral type

If you need to implement it yourself then simplest way is to keep date as a single integer - Julian day As you can see on the article calculation from month/day/year into Julian day and back is pretty trivial (from computer perspective of course). When you keep dates as a single number calculate difference in days or compare them is trivial. Or if you say that it is already implemented as a structure you can convert your dates into Julian day on the fly (or keep mutable field to cache that value).

Related

C++ std::tm returns wrong value after converting it from std::chrono::time_point

TL;DR: How to use a std::chrono::system_clock::time_point to compare based on only certain parameters (e.g. I just want hours, minutes and seconds, but not day, month, etc.).
Also: After converting the std::chrono::system_clock::time_point to a std::tm, the std::tm.tm_hours contains a value one higher than originally input to the std::chrono::system_clock::time_point.
My theoretical approach on getting a std::chrono::system_clock::time_point to work:
typedef std::chrono::system_clock::time_point TimePoint;
TimePoint MainWindow::createTimePoint(int h, int m)
{
TimePoint createdTime = std::chrono::system_clock::time_point{std::chrono::hours(h) + std::chrono::minutes(m)};
time_t tt = std::chrono::system_clock::to_time_t(createdTime);
tm timeExtracted = *localtime(&tt);
std::cout << "input:\t\t" << "H = " << h << ", M = " << m << std::endl;
std::cout << "timeExtracted:\t" << "H = " << timeExtracted.tm_hour << ", M = " << timeExtracted.tm_min << std::endl;
return createdTime;
}
If I run this, the hours of timeExtracted are always +1 from the input h.
Why is that so? And how to fix this? I went over a few other posts that showed this, but they couldnt help me. Probably also because of this:
I think that when I create a TimePoint, the day, month, etc. is also set to a random value or initiated to a certain value. The point is: I want them to always be the same value, so that my TimePoint (after converting) basically shows this:
timeExtracted.tm_sec = 0
timeExtracted.tm_min = m
timeExtracted.tm_hour = h
timeExtracted.tm_mon = 0
timeExtracted.tm_wday = 0
timeExtracted.tm_mday = 0
timeExtracted.tm_yday = 0
timeExtracted.tm_year = 0
timeExtracted.tm_isdst = 0
How can I compare two of these TimePoint utilising using the compare operations of std::chrono on them, but only compare the hour and minute.
If my question is unclear, I'm sorry, it's late in the evening. I'll check again next morning. Thank you.
I'm going to start an answer, but this isn't going to be a complete answer because I'm not yet sure of the complete question. However, I can help.
TimePoint createdTime = system_clock::time_point{hours(h) + minutes(m)};
(I've clipped the std::chrono:: qualifiers so that this is easier to read and discuss)
This creates a time stamp that is 1970-01-01 hh:mm:00 UTC. In a nutshell, system_clock::time_point is measuring the duration of time (in some units like microseconds or nanoseconds) since New Years 1970, UTC. Technically the above is an approximation, system_clock doesn't count leap seconds, but we can (and should) ignore that detail for now.
This:
tm timeExtracted = *localtime(&tt);
is going to introduce UTC offset corrections based on your computer's setting for the local time zone. The time zone adjustment rules are (hopefully) going to be based on what was in effect in 1970 in your area.
There exist techniques and libraries for taking a system_clock::time_point and breaking it up into fields such as {year, month, day, hours, minutes, seconds, microseconds}. But that conversion also depends on if you want these fields in UTC, local time, or some other arbitrary time zone.
And the very first step is to apply the UTC offset associated with some time zone if desired. It may be that your {h, m} input needs a UTC offset adjustment prior to putting them into system_clock::time_point if the intent is that {h, m} represent local time instead of UTC.
Update: Store hours example
This example will use my free, open-source time zone library, because I feel it is much easier to work with and allows for more readable and expressive code.
This example takes as input a system_clock::time_point and compares it to a list of open/close times for each day of the week and determines if the input time is inside or outside of those time-of-day ranges for the weekday associated with the input time t. The store hours are presumed to be stated with respect to the store's local time zone, which is also the current time zone set for the computer running this code.
#include "date/tz.h"
#include <algorithm>
#include <cassert>
#include <chrono>
bool
is_store_open_at(std::chrono::system_clock::time_point tp)
{
using namespace date;
using namespace std::chrono;
struct day_schedule
{
weekday wd;
minutes open;
minutes close;
};
// hours are expressed in terms of local time
static constexpr day_schedule store_hours[]
{
// week day open-time close-time
{Monday, 0h, 0h}, // closed all day
{Tuesday, 8h, 18h},
{Wednesday, 8h, 18h},
{Thursday, 8h, 18h},
{Friday, 8h, 18h},
{Saturday, 8h, 15h+30min},
{Sunday, 9h+30min, 15h}
};
auto local_tp = current_zone()->to_local(tp);
auto local_day = floor<days>(local_tp);
auto local_time_of_day = local_tp - local_day;
weekday local_weekday{local_day};
auto ds = std::find_if(std::begin(store_hours), std::end(store_hours),
[local_weekday](day_schedule const& x)
{
return x.wd == local_weekday;
});
assert(ds != std::end(store_hours));
return ds->open <= local_time_of_day && local_time_of_day < ds->close;
}
#include <iostream>
int
main()
{
std::cout << is_store_open_at(std::chrono::system_clock::now()) << '\n';
}
The function begins by defining some handy data structures to store the open and close times for each day of the week. The open and close members of day_schedule measure "minutes since midnight" in local time.
The input time tp is in terms of UTC, since its type is system_clock::time_point. This is not currently specified by the C++ standard, but will be for next year's C++20.
zoned_seconds is used to convert the UTC time t into local time according to the computers time zone setting obtained by calling current_zone(). I've truncated t to seconds to simplify some of the syntax. This isn't strictly necessary. I've edited to use slightly simpler syntax to eliminate the zoned_seconds. zoned_seconds can be really useful in other examples, but in this one was more trouble than it was worth. auto local_tp = current_zone()->to_local(tp) is a simpler way to translate UTC to a local time point.
local_tp is a chrono::time_point that is considered "local time", and is distinct from the family of chrono::time_points associated with system_clock. The advantage of doing this is so that if local time and UTC time are accidentally mixed, it is a compile-time error.
local_days is simply local_tp truncated to days precision. It is still a chrono::time_point, just a coarse one that points to the beginning of the day as described by the local time zone.
The time duration since the local midnight is simply local_tp - local_day.
The day of the week (as defined by the local time zone) can be obtained by converting local_day to type weekday. This is the local day of the week associated with tp.
Now it is a simple matter to search store_hours for the entry that matches local_weekday.
The store is open if local_time_of_day is at or past the open time and has not yet reached the close time.
If the "store hours" are specified in UTC instead of local time, then this program simplifies somewhat, but is still similar.

Generate a date vector using <ctime>

How can I build a vector containing all dates (daily period) between two given dates using <ctime> library? For example, given January 1, 2019 and January 10, 2019, a vector containing the 10 dates in between (inclusive)?
I don’t really mind about the representation of the dates, could be a string or any other structure but I would like to understand how to manipulate <ctime> objects.
If there is a better library for time representation in C++, I’d be glad to hear about it.
With the C++20 date library (a.k.a. Howard Hinnant's date library):
#include "date.h"
auto get_dates(date::sys_days first, date::sys_days last)
{
std::vector<date::sys_days> dates;
for (; first <= last; first += date::days{1})
dates.push_back(first);
return dates;
}
(live demo)
Here's a small, quick demo program - it makes a vector of struct tm, and then prints the time out. Each new entry to the vector is one day ahead of the old one, seamlessly wrapping around months and years as it rolls past them.
Time is often stored in a time_t, which is the count of seconds since y2k. This data type seems easier to manipulate than a struct tm - we'll use it, coupled with how many seconds are in a day, to create a vector of struct tm as we go. We'll start at time0, and we'll continue 20 days, to time19, adding a new struct tm for every day we count.
#include <iostream>
#include <ctime>
#include <vector>
int main(void) {
double secs_in_a_day = 86400;
time_t time0; //your start time here
time(&time0); //i'm using the current time
//20 days later
time_t time19 = time0 + (20 * secs_in_a_day); //your end time here
std::vector<struct tm > timevec;
for(time_t i = time0; i <= time19; i += secs_in_a_day) {
struct tm t = *(gmtime(&i));
std::cout << i << "\n";
timevec.push_back(t);
}
char buffer[80];
for(struct tm tim : timevec) {
strftime(buffer, 80, "Time: %d / %m / %y\n", &tim);
puts(buffer);
}
return 0;
}
Note that the for loop increments by the number of seconds in a day. It's likely possible to do this directly with struct tm or struct tm * variables, but then there would be a good deal of chasing pointers around. Given that it's so easy to convert to time_t, it's probably worth saving the headache to use it instead.
Hope this helps - C++ admittedly leaves a bit to be desired when dealing with time.

Creating a `std::chrono::time_point` from a calendar date known at compile time

This answer shows how to parse a string to a std::chrono::time_point, as follows:
std::tm tm = {};
std::stringstream ss("Jan 9 2014 12:35:34");
ss >> std::get_time(&tm, "%b %d %Y %H:%M:%S");
auto tp = std::chrono::system_clock::from_time_t(std::mktime(&tm));
If I want to create a std::chrono::time_point from a (Gregorian) calendar date whose year, month, and day-of-month are known at compile time, is there any simpler way than parsing it from a string as suggested above?
Yes, you can do the entire computation at compile time, creating a constexpr system_clock::time_point using Howard Hinnant's date/time library.
#include "date/date.h"
#include <chrono>
int
main()
{
using namespace date;
using namespace std::chrono;
constexpr system_clock::time_point tp = sys_days{January/9/2014} + 12h + 35min + 34s;
static_assert(tp == system_clock::time_point{1389270934s}, "");
}
This is assuming that the date/time is UTC. If it isn't, you will have to manually add/subtract the UTC offset to make it so. As time zone rules are changed at the whim of politicians all the time, there is little hope in making them constexpr. Even historical time zone rules are updated when misunderstandings come to light.
Also this program will port to C++20 by dropping #include "date/date.h" and using namespace date;. Also using Howard Hinnant's date/time library requires C++14 constexpr muscle. C++11 constexpr is not sufficient (but you can do it at run-time, dropping the constexpr and static_assert).
If you have c++20, or will use Howard Hinnant date/time library, then Howard Hannant's answer is better, as it gives you a constexpr time_point.
However, if one doesn't yet have c++20 and wants to avoid adding more external libraries, then this answer is still useful.
You can set the members of the std::tm individually in the initializer, to avoid parsing a string.
// 9th January, 2014
#define DAY 9
#define MONTH 1
#define YEAR 2014
std::tm tm = { /* .tm_sec = */ 0,
/* .tm_min = */ 0,
/* .tm_hour = */ 0,
/* .tm_mday = */ (DAY),
/* .tm_mon = */ (MONTH) - 1,
/* .tm_year = */ (YEAR) - 1900,
};
tm.tm_isdst = -1; // Use DST value from local time zone
auto tp = std::chrono::system_clock::from_time_t(std::mktime(&tm));
The designated initializers are commented out since they are only available in C++20 (though gcc has supported trivial designated initializers as an extension for some time and would work with this case). The fields initialized to zero could be omitted if one had full C++20 designated initializers and wanted midnight on the target date.
It is important to note that mktime will interpret the tm as local time, not GMT nor UTC. If tm_isdst is not set to -1, it will be local standard time, even if daylight savings (summer time) would be in use in the local time zone for the time specified.
Producing a UTC time point from a std::tm, a problem shared with your example, is addressed in other questions, such as Easy way to convert a struct tm (expressed in UTC) to time_t type
This works with C++11 and above:
#include <chrono>
std::chrono::system_clock::time_point
createDateTime(int year,
int month,
int day,
int hour,
int minute,
int second) // these are UTC values
{
tm timeinfo1 = tm();
timeinfo1.tm_year = year - 1900;
timeinfo1.tm_mon = month - 1;
timeinfo1.tm_mday = day;
timeinfo1.tm_hour = hour;
timeinfo1.tm_min = minute;
timeinfo1.tm_sec = second;
tm timeinfo = timeinfo1;
time_t tt = toUTC(timeinfo);
return std::chrono::system_clock::from_time_t(tt);
}
time_t toUTC(std::tm& timeinfo)
{
#ifdef _WIN32
std::time_t tt = _mkgmtime(&timeinfo);
#else
time_t tt = timegm(&timeinfo);
#endif
return tt;
}
Taken from ApprovalTests/utilities/DateUtils.cpp

How to convert unsigned int 64 into time in C++?

I am converting CLI C++ code to standard C++, and i have a piece of code that gets a UINT64 number (from a remote server - so i can't change to format/precision of the time i get) and converts it into DateTime object and later outputs the following value: myDatetime.ToString("dd/MM/yyyy hh:mm:ss.fffffff tt").
I haven't found a way to convert unsigned int 64 into time in C++.
The following code does nothing for numbers so big (that's the 64bit number i get from the server).
time_t rawtime=131274907755873979
localtime_s(&timeinfo, &rawtime);
I need some help :)
My question wan't answered in the thread Convert Epoch Time string to Time since it doesn't work for numbers as large as i need. For example the number 131274907755873979 which is what i get from the server. The function ctime for that value simply returns NULL.
I need a way to convert between the time i get as a unsigned int64 into standard C++ time object.
std::string LongToString(int64_t longDate) {
char buff[128];
std::chrono::duration<int64_t, std::milli> dur(longDate);
auto tp = std::chrono::system_clock::time_point(
std::chrono::duration_cast<std::chrono::system_clock::duration>(dur));
std::time_t in_time_t = std::chrono::system_clock::to_time_t(tp);
strftime(buff, 128, "%Y-%m-%d %H:%M:%S", localtime(&in_time_t));
std::string resDate(buff);
return resDate;
}
This is a case with bsoncxx::types::b_date get_date().to_int64() MongoDB.
The DateTime saved with int64_t.
You have not told us how the existing code converts that number into a DateTime. Let us suppose that it does so by invoking this constructor: DateTime( long long ticks ).
According to the documentation of that constructor of DateTime,
long long ticks A date and time expressed in the number of 100-nanosecond intervals that have elapsed since January 1, 0001 at 00:00:00.000 in the Gregorian calendar.
On the other hand, according to the documentation of localtime_s and the documentation of time_t, localtime_s() requires
the number of seconds (not counting leap seconds) since 00:00, Jan 1 1970 UTC.
So, you first need to convert 100-nanosecond intervals to seconds, and then convert from January 1, 0001 to January 1, 1970.
Using Howard Hinnant's datetime library this computation can be done quite easily. It works with VS 2013 and later.
#include "tz.h"
#include <cstdint>
#include <string>
#include <iostream>
std::string
FILETIME_to_string(std::uint64_t i)
{
using namespace std;
using namespace std::chrono;
using namespace date;
using FileTime = duration<int64_t, ratio<1, 10000000>>;
auto const offset = sys_days{jan/1/1970} - sys_days{jan/1/1601};
auto tp = sys_days{jan/1/1970} + (FileTime{static_cast<int64_t>(i)} - offset);
return format("%d/%m/%Y %I:%M:%S %p", make_zoned("Etc/GMT-2", tp));
}
int
main()
{
std::cout << FILETIME_to_string(131274907755873979) << '\n';
}
This skips DateTime and goes straight to the string. I wasn't sure what you are wanting with tt in the format. But whatever it is, it can be handled.
This library builds on the C++11 <chrono> library. So the first thing to do is to create a duration to represent the windows tick size (100 ns). Then just compute the offset between the two epochs and subtract it from the input, and form a std::chrono::time_point. Now you can format that time_point however you want.
The program above outputs:
29/12/2016 03:12:55.5873979 PM
If you use VS 2017 you'll be able to make offset constexpr, making the conversion more efficient.

Date handling before 1970 c++

I was wondering what is the best thing to do here. I was given an assignment and I have to update a Date class that uses Ctime.
The class must now work for date before 1970. I was looking around and I didn't find much...
So I guess my question is what would be the best way to achieve that?
Date Range from [1970-2037] to [1900-2037]
Assuming you mean CTime from MFC, if you enable OLE you can use COleDateTime as a drop-in replacement. Per the documentation, it supports dates "from January 1, 100, through December 31, 9999".
You could store the date as a signed integer number of days since 1970-01-01 or any other epoch. Negative dates would be the date before 1970.
Or you could use Boost.Date_Time library.
How about a date class that stores an internal std::tm datetime object and the total seconds since (or before) Jan 1, 1970 as a time_t. The members of tm are all ints and time_t should be 64bit everywhere (I hope), so in theory that should cover all the times you could consider.
In the constructor of your class you'd need to compute those total seconds and the only standard library function that seems to do that is mktime. Unfortunately this one only works for dates after Jan 1, 1970.
One possible workaround... add a really big number to the year and work with that internally.
#include <ctime>
#include <iostream>
class CustomDateTime {
const int MKTIME_DELTA = 100000;
std::tm _datetime;
std::time_t _total_seconds;
public:
CustomDateTime(int year, int mon, int day, int hour, int min, int sec) {
_datetime.tm_year = year - 1900 + MKTIME_DELTA;
_datetime.tm_mon = mon - 1;
// copy day, hour, min, sec
_total_seconds = std::mktime(&_datetime);
}
bool operator==(const CustomDateTime& rhs) {
return _total_seconds == rhs._total_seconds;
}
void print() {
std::cout << _datetime.tm_year + 1900 - MKTIME_DELTA << ':'
<< _datetime.tm_mon + 1 << _datetime.tm_mday << '\n';
}
};
That should cover all years between 1970 - MKTIME_DELTA = -98030 and the far far future.