Problem
Given two datetimes, dt0 and dt1 (could be out of order), what is an algorithm which can determine if there is at least 24 hours worth of weekend (SAT, SUN) between the two dates?
Assume that there is a dayofweek() function that returns 0 for SUN, 1 for MON, etc...
Note: the problem is easy to visualize geometrically in terms of line segments, but the calculation eludes me for the moment.
Solution
The solution below will work for UTC, but it will fail for DST.
weekdayno() implementation not included: SUN==0, MON==1, etc...
isWeekday() is also not shown, but is trivial to implement once you have dayofweek()
binary operator-() implementation also not shown, but we simply convert both instances to UNIX-time (no. of secs since Epoch) and take the difference to yield the number of seconds between two DateTimes
hh() mm() ss() are just const accessors for returning hours, minutes, and seconds, respectively
James McNellis is right on the mark concerning DST.
Getting this code to work for the general DST case is non-trivial: need to add tz and anywhere you do any kind of date arithmetic requires careful consideration. Additional unit tests will be needed.
Lessons Learned
Query stackoverflow for various ways to look at a problem.
You can never have too many unit tests: need them to flush out weird edge cases
Use visualization, if possible, to look at a problem
What appears to be a trivial problem can actually be a bit tricky when you look at the details (eg DST).
Keep the solution as simple as possible because your code will very likely change: in order to fix for bugs/new test cases or in order to add new features (eg make it work for DST). Keep it as readable and easy to understand as possible: prefer algorithms over switch/cases.
Be brave and try things out: keep hammering at the solution until something works comes about. Use unit-tests so you can continuously refactor. It takes a lot of work to write simple code, but in the end, it's worth it.
Conclusion
The current solution is sufficient for my purposes (I will use UTC to avoid DST problems). I will select holygeek's Answer for his suggestion that I draw some ASCII art. In this case, doing so has helped me come up with an algorithm that is easy-to-understand and really, as simple as I can possibly make it. Thanks to all for contributing to the analysis of this problem.
static const size_t ONEDAYINSECS = (24 * 60 * 60);
DateTime
DateTime::nextSatMorning() const
{
// 0 is SUN, 6 is SAT
return *this + (6 - weekdayno()) * ONEDAYINSECS -
(((hh() * 60) + mm())*60 + ss());
}
DateTime
DateTime::previousSunNight() const
{
return *this - ((weekdayno() - 1 + 7)%7) * ONEDAYINSECS -
(((hh() * 60) + mm())*60 + ss());
}
bool
DateTime::straddles_24HofWeekend_OrMore( const DateTime& newDt ) const
{
const DateTime& t0 = min( *this, newDt );
const DateTime& t1 = max( *this, newDt );
// ------------------------------------
//
// <--+--F--+--S--+--S--+--M--+-->
// t0 ^ ^ t1
// +---->+ +<----|
// | |
// +<--nSecs-->+
// edge0 edge1
//
// ------------------------------------
DateTime edge0 = t0.isWeekday() ? t0.nextSatMorning() : t0;
DateTime edge1 = t1.isWeekday() ? t1.previousSunNight() : t1;
return (edge1 - edge0) > ONEDAYINSECS;
}
John Leidegren asked for my unit tests so here there are (using googletest)
Note that they pass for the non-DST cases above (running for UTC) - I expect the current implementation to fail for DST cases (haven't added them to the test cases below yet).
TEST( Test_DateTime, tryNextSatMorning )
{
DateTime mon{ 20010108, 81315 };
DateTime exp_sat{ 20010113, 0ul };
EXPECT_EQ( exp_sat, mon.nextSatMorning() );
}
TEST( Test_DateTime, tryPrevSunNight )
{
DateTime tue{ 20010109, 81315 };
DateTime exp_sun1{ 20010108, 0ul };
EXPECT_EQ( exp_sun1, tue.previousSunNight() );
DateTime sun{ 20010107, 81315 };
DateTime exp_sun2{ 20010101, 0ul };
EXPECT_EQ( exp_sun2, sun.previousSunNight() );
}
TEST( Test_DateTime, straddlesWeekend )
{
DateTime fri{ 20010105, 163125 };
DateTime sat{ 20010106, 101515 };
DateTime sun{ 20010107, 201521 };
DateTime mon{ 20010108, 81315 };
DateTime tue{ 20010109, 81315 };
EXPECT_FALSE( fri.straddles_24HofWeekend_OrMore( sat ));
EXPECT_TRUE( fri.straddles_24HofWeekend_OrMore( sun ));
EXPECT_TRUE( fri.straddles_24HofWeekend_OrMore( mon ));
EXPECT_TRUE( sat.straddles_24HofWeekend_OrMore( sun ));
EXPECT_TRUE( sat.straddles_24HofWeekend_OrMore( mon ));
EXPECT_FALSE( sun.straddles_24HofWeekend_OrMore( mon ));
EXPECT_TRUE( fri.straddles_24HofWeekend_OrMore( tue ));
EXPECT_FALSE( sun.straddles_24HofWeekend_OrMore( tue ));
}
This diagram may help:
SAT SUN
|---------|---------|
a---------b
a---------b
...
a---------b
The silliest way I can think of solving this is: copy the smaller datetime value, continuously add to it until it's either larger than the other datetime value (the bigger one) or dayofweek() doesn't equal 0 or 7 any more. Then check if the total value of time you added is less than 24 hours.
A slightly less silly way would be to check its a weekend, add 24 hours of time and then check once to make sure its a weekend and still less than the second datetime.
Daylight savings shouldn't really come into play as long as your function to find what day it is works.
Approach it in a structured manner: What are some of the simple/edge cases? What is the average case?
Simple cases I can think of off the top of my head (note, since you've already forced t0 to be the lower value, I'm assuming that below):
If t1 - t0 is less than 1 day, return false
If t1 - t0 is >= 6 days, return true (there's ALWAYS 24 hours of weekend time in any given 6 day block, even if you start on a Sunday).
Then we take dayofweek() for both t0 and t1, and do some checks (this is the average case). We can be extra cheap here now because we know t0 is only up to 5 days earlier than t1.
Edit: Removed my conditions because there were nasty little edge cases I wasn't considering. Anyway, the solution I recommended is still viable, I just won't do it here.
This site calculates business days in C#, does that help? http://www.infopathdev.com/forums/t/7156.aspx
Related
How can I change just the hour of an existing std::chrono::system_clock::time_point?
For example, say I wanted to implement this function:
void set_hour(std::chrono::system_clock::time_point& tp, int hour) {
// Do something here to set the hour
}
std::chrono::system_clock::time_point midnight_jan_1_2022{std::chrono::seconds{1640995200}};
set_hour(midnight_jan_1_2022, 11);
// midnight_jan_1_2022 is now 11am on Jan 1 2022
....
The answer depends on exactly what you mean. The simplest interpretation is that you want to take whatever date tp points to (say yyyy-mm-dd hh:MM:ss.fff...), and create: yyyy-mm-dd hour:00:00.000....
Another possible interpretation is that yyyy-mm-dd hh:MM:ss.fff... is transformed into yyyy-mm-dd hour:MM:ss.fff....
In either event C++20 makes this easy, and if you don't yet have access to C++20 <chrono>, then there exists a free, open-source header-only library that emulates C++20 <chrono> and works with C++11/14/17.
If you want to zero the minute, second and subsecond fields as described in the first interpretation that is:
void
set_hour(std::chrono::system_clock::time_point& tp, int hour)
{
using namespace std::chrono;
auto day = floor<days>(tp);
tp = day + hours{hour};
}
I.e. you simply floor the time_point to days-precision and then add the desired hours.
The second interpretation is slightly more complicated:
void
set_hour(std::chrono::system_clock::time_point& tp, int hour)
{
using namespace std::chrono;
auto day = floor<days>(tp);
hh_mm_ss hms{tp - day};
tp = day + hours{hour} + hms.minutes() + hms.seconds() + hms.subseconds();
}
Here you have to discover and recover the {minutes, seconds, subseconds} fields to re-apply them to the desired date (along with the desired hour). hh_mm_ss is a C++20 {hours, minutes, seconds, subseconds} data structure that automates the conversion from a duration into a field structure so that you can more easily replace the hours field.
Both of these solutions will give the same answer for your example input:
2022-01-01 11:00:00.000000
since the input has zeroed minutes, seconds and subseconds fields already.
I'm not asking why the following differenceInDays is wrong.
After figuring that out I kind of kicked myself.
But I wonder what's the right fix.
/**
* differenceInDays - return the difference in days between 2 dates.
*
* Since `moment.duration.asDays` can return a non-integral value
* (i.e. 36 hours == 1.5 days) each date is first adjusted to the
* start of day before the difference is determined.
*/
const differenceInDays = (date1, date2, timezone) => {
const localStartOfDay1 = moment(date1)
.tz(timezone)
.startOf('day');
const localStartOfDay2 = moment(date2)
.tz(timezone)
.startOf('day');
return moment.duration(localStartOfDay2.diff(localStartOfDay1)).asDays();
};
expect(differenceInDays('2020-03-08T17:00:00Z',
'2020-03-09T17:00:00Z',
'America/New_York')).toEqual(1);
Expected value to equal:
1
Received:
0.9583333333333334
The result might not be integral if DST transition or a leap adjustment happens between date1 and date2.
One fix could be to just round the result.
Brief background:
I am trying to plot Candlestick charts of Stocks, by using QCustomPlot version 1.3 beta.
I skipped through the library's code, and found out that for the time-series, it uses a type-def (qcustomplot.h:line 3140)
typedef QMap<double, QCPFinancialData> QCPFinancialDataMap;
Where QCPFinancialData is (qcustomplot.h:line 3124)
class QCP_LIB_DECL QCPFinancialData
{
public:
QCPFinancialData();
QCPFinancialData(double key, double open, double high, double low, double close);
double key, open, high, low, close;
};
So, the OHLC data is obviously there, and the class uses a key, which is used in the QMap, to index the Time-series entry.
So, the obvious key, would be the date-time (I am plotting End-Of-Day charts, so each entry is simply a date, no time used). In my Parsing code, I've used
boost::gregorian::date
Since it has quite a lot of advantages (conversion from string, calculation of date-time elapsed, etc).
Question is, should I go ahead and simply convert the boost::gregorian::date to a unix timestamp, and then record that timestamp as a double? I found a small template function on github that converts it to time_t type, but I guess double shouldn't be a problem in this case, or is this a potential bug? AFAIK, Unix time-stamp denotes the seconds since Jan 01 1970, which when represented as a double should be more than enough for a key?
In the examples of QCustomPlot, they use an accumulator/counter since the beginning of the time series sequence (e.g., starting date) rather than the time-stamp.
A timestamp since the epoch can be stored quite conventiently in a double as you have enough space for the seconds since the epoch (ie Jan 1, 1970) and still have enough resolution for a little more than a microsecond.
Eg R does this:
R> now <- Sys.time() # get current date and time
R> now # default format to test
[1] "2014-11-11 20:38:27.307228 CST" # NB I have an option set for subsec.
R> as.integer(now) # as integer: seconds since epoch
[1] 1415759907
R> as.double(now) # as double under default precision
[1] 1415759907
R> print(as.double(now), digits=16) # as double with forced higher prec.
[1] 1415759907.307228
R>
I used these as double at the C/C++ layer all the time. And if I am not mistaken, you can
get Boost to do the conversion for you.
Edit: I knew I had it somewhere:
boost::posix_time::ptime pt;
// ... in what follows dt has subcomponents we can access
pt = boost::posix_time::ptime(boost::gregorian::date(dt.getYear(),
dt.getMonth(),
dt.getDay()),
boost::posix_time::time_duration(dt.getHours(),
dt.getMinutes(),
dt.getSeconds(),
dt.getMicroSeconds()/1000.0));
A conversion to time_t including subseconds:
boost::posix_time::ptime dt = ....
boost::posix_time::ptime epoch(boost::gregorian::date(1970,1,1));
boost::posix_time::time_duration x = dt - epoch; // needs UTC to local corr.,
// but we get the fract. sec.
struct tm t = boost::posix_time::to_tm(dt); // this helps with UTC conve.
time_t tt = mktime(&t) + 1.0 * x.fractional_seconds() / x.ticks_per_second()));
I'm getting radar data as "tracks" and the track data indicates the number of UTC seconds since the last midnight, apparently. This is not the number of seconds since the 1st of jan 1970.
Now I want to convert that to date time, knowing that the clock on the computer could be slightly out of sync with the clock on the radar. I'll assume the radar's seconds are the reference, not the computer's.
I want to convert these seconds to a full date time. Things seem to be a little tricky around
midnight.
Any suggestions? I've got some ideas, but I don't want to miss anything.
I'm working with C++ Qt.
// Function to extend truncated time, given the wall time and period, all
// in units of seconds.
//
// Example: Suppose the truncated period was one hour, and you were
// given a truncated time of 25 minutes after the hour. Then:
//
// o Actual time of 07:40:00 results in 07:25:00 (07:40 + -15)
// o Actual time of 07:10:00 results in 07:25:00 (07:10 + +15)
// o Actual time of 07:56:00 results in 08:25:00 (07:56 + +29)
double extendTruncatedTime(double trunc, double wall, int period) {
return wall + remainder(trunc - wall, period);
}
#define extendTruncatedTime24(t) extendTruncatedTime(t, time(0), 24 * 60 * 60)
Some commentary:
The units of wall are seconds, but its base can be arbitrary. In Unix, it typically starts at 1970.
Leap seconds are not relevant here.
You need #include <math.h> for remainder().
The period in extendTruncatedTime() is almost always twenty-four hours, 24 * 60 * 60, as per the OP's request. That is, given the time of day, it extends it by adding the year, month, and day of month, based on the 'wall' time.
The only exception I know to the previous statement is, since you mention radar, is in the Asterix CAT 1 data item I001/141, where the period is 512 seconds, and for which extendTruncatedTime() as given doesn't quite work.
And there is another important case which extendTruncatedTime() doesn't cover. Suppose you are given a truncated time consisting of the day of month, hour, and minute. How can you fill in the year and the month?
The following code snippet adds the year and month to a time derived from a DDHHMM format:
time_t extendTruncatedTimeDDHHMM(time_t trunc, time_t wall) {
struct tm retval = *gmtime_r(&trunc, &retval);
struct tm now = *gmtime_r(&wall, &now);
retval.tm_year = now.tm_year;
retval.tm_mon = now.tm_mon;
retval.tm_mon += now.tm_mday - retval.tm_mday > 15; // 15 = half-month
retval.tm_mon -= now.tm_mday - retval.tm_mday < -15;
return timegm(&retval);
}
As written, this doesn't handle erroneous inputs. For example, if today is July 4th, then the non-nonsensical 310000 will be quietly converted to July 1st. (This may be a feature, not a bug.)
If you can link against another lib, i'd suggest to use boost::date_time.
It seems you want to take current date in seconds from midnight (epoch) then add the radar time to it, then convert the sum back to a date time, and transform it into a string.
Using boost will help you in:
getting the right local time
calculating the date back
incorporating the drift into the calculation
taking leap seconds into account
since you'll have concept like time intervals and durations at your disposal. You can use something like (from the boost examples):
ptime t4(date(2002,May,31), hours(20)); //4 hours b/f midnight NY time
ptime t5 = us_eastern::local_to_utc(t4);
std::cout << to_simple_string(t4) << " in New York is "
<< to_simple_string(t5) << " UTC time "
<< std::endl;
If you want to calculate the drift by hand you can do time math easily similar to constructs like this:
ptime t2 = t1 - hours(5)- minutes(4)- seconds(2)- millisec(1);
I had the exact same problem but I'm using C#. My implementation is included here if anyone needs the solution in C#. This does not incorporate any clock drift.
DateTime UTCTime = DateTime.UtcNow.Date.AddSeconds(secondSinceMidnightFromRadar);
I have my own C++ DateTime class defined as:
class DateTime
{
public:
int year;
int month;
int day;
int hour;
int min;
int sec;
int millisec;
};
I have 2 DateTime which I need to compare to see which one is greater than (more recent) the other.
Is there any freely available C++ DateTime class that I can use to
Convert my DateTime class to their DateTime class
Their class should provide < , > , <= , >= operators for comparison
If a concrete example could be provided that would be great. Note that I need to compare down to millisecond.
I was thinking about Boost or Qt. Preferred Boost though.
See Boost Date Time library
And your class looks very much like struct tm
EDIT:
You're right that struct tm doesn't support millisecond precision.
Take a look at a Boost example. Does that help?
You may want to check out QDateTime from Qt, wich has the required operators and ms accuracy.
Conversion from your class could be done via
class DateTime
{
public:
int year;
int month;
int day;
int hour;
int min;
int sec;
int millisec;
QDateTime toQDateTime() {
return QDateTime(QDate(year, month, day), QTime(hour, min, sec, millisec));
}
};
The other way around is similar ;-)
I don't know of any off the top of my head. But I'd consider rewriting your date class to hold a single 64-bit integer describing milliseconds since the conventional epoch (1970 is it?). Then you are free to simply divide by 1000 and use the normal CRT functions for formatting as a string, plus you can take the value modulo 1000 to get the millisecond part.
Comparison operators then become easy..
I ditch storing dates in gregorian ages ago.
I store dates as an 32bit integer (sort of like a Julian date).
So the date is composed as (Year * 1000) + DOY (DOY is day of year).
I.e.
- 2009001 Is Jan 1 2009
- 2009365 is Dec 31 2009
My date class of course provides methods for getting the Year, Month and Day, adding, subtracting, incrementing and decrementing, comparing, getting the number of days between dates etc..
For date and time, I use 64bit float where the integer portion of the real number is the same as integer (Julian like) dates described above, and the fraction represents the time in fraction of a day.
I.e.
2009001.04166666666~ is Jan 1,2009 1:00am
2009001.06249999999~ is Jan 1,2009 1:30am
2009001.95833333333~ is Jan 1,2009 11:00pm
If you only need minute accuracy, you can use 32bit float
for date and time but you can't adequately accurately
store seconds and milliseconds.
The advantages of storing dates (and time) in this manner are:
You only need 8bytes to represent the data and time
as compared to 28bytes (assuming 32bit integers)
used by the DateTime class in the question.
Compared with dates stored as seconds from an epoch,
when looking at the number (for example in the debugger)
you can more or less identify from
the number the year and the day of year, and the approximate time of day
(to get the hour, minute, second
after midnight simply mulitply by 24, 1440, 86400 respectively).
Comparing dates is trivial, simply compare the numbers
(A single CPU operation compared to the several it
would take for the example DateTime).
Fewer comparison operations to do date arithmetic.
The disadvange of this (for time time) is a slight loss of accuracy (this is practically a mute point) and you have to do some simple rounding to get nice integer values when convering to integer values of hours minutes and seconds.
Okay, here's the final code snippet that answers my own question. I thought of sharing this in case it might helpful to some other people in the future. Thanks to Fred Larson for pointing the Boost example.
I chose Boost to do the DateTime calculation because my application already makes use of Boost somewhere else. I think I might have been able to use Qt as well, though I cant completely confirm.
Assuming DateTime is defined as:
class DateTime
{
public:
int year;
int month;
int day;
int hour;
int min;
int sec;
int millisec;
};
To do a simple DateTime comparison
bool DateTime::operator < (const DateTime& dt_)
{
using namespace boost::posix_time;
using namespace boost::gregorian;
ptime thisTime( date(this->year,this->month,this->day),
hours(this->hour) +
minutes(this->min) +
seconds(this->sec) +
boost::posix_time::millisec(int(this->millisec)) );
ptime thatTime( date(dt_.year,dt_.month,dt_.day),
hours(dt_.hour) +
minutes(dt_.min) +
seconds(dt_.sec) +
boost::posix_time::millisec(int(dt_.millisec)) );
return thisTime < thatTime;
}
To add 2 DateTime together to return a new DateTime
DateTime DateTime::operator + ( const DateTime& dt_ )
{
using namespace boost::posix_time;
using namespace boost::gregorian;
date thisDate( this->year, this->month, this->day );
date newDate = thisDate + years(dt_.year) + months(dt_.month) + days(dt_.day);
ptime newDateTime( newDate,
hours(this->hour) + hours(dt_.hour) +
minutes(this->min) + minutes(dt_.min) +
seconds(this->sec) + seconds(dt_.sec) +
boost::posix_time::millisec(int(this->millisec)) +
boost::posix_time::millisec(int(dt_.millisec))
);
DateTime dateTime;
date t1_date = newDateTime.date();
dateTime.year = t1_date.year();
dateTime.month = t1_date.month();
dateTime.day = t1_date.day();
time_duration t1_time = newDateTime.time_of_day();
dateTime.hour = t1_time.hours();
dateTime.min = t1_time.minutes();
dateTime.sec = t1_time.seconds();
dateTime.millisec = t1_time.fractional_seconds()/1000.0f;
return dateTime;
}
What's wrong with using the content of <time.h> for implementing your class? It's standard C90.
GNU R uses a struct tm replacement with microsecond precision -- instead of (integer) seconds since the epoch, it now uses a floating point number. That is really really useful. For many of my applications, I just past doubles around and yet get the time conversions.
See R-2.9.1/src/main/datetime.c in the current R sources.
Having that in a standalone C++ class would be handy though.
Look at
MFC datetime classes CTime and COleDateTime classes
More at http://www.codeproject.com/KB/datetime/datetimedisc.aspx