patterncppMinor
Date Time - Seconds Difference
Viewed 0 times
differencesecondstimedate
Problem
For trivial reasons, I decided to have a go at differentiating dates. Low and behold having no idea how much of a non-trivial task it was to become.
It was originally a small sidetrack from a project I'm doing.
And, whilst performance isn't a huge concern here, the code I've posted below performs highly optimally in comparison to its alternative (shown below it). This is preferred, as originally this was used in a real-time program, and without other changes to the high-level algorithm, the cost of re-calculating the date difference every frame (up to 60FPS) was creating a significant run-time penalty.
But what I'm looking for in my solution, is algorithmic improvements, not optimizations (it runs more than fast enough). Such as removing the for loop for calculating which years are leap years (perhaps using 365.242199 constant?).
And especially techniques on how to get rid of that huge tree of comparisons for the initial swap; that just doesn't look like good practice... ever. I'm sure it can be done in the algorithm, but my attempts failed and I ran out of time.
```
long calculate_seconds_between(
uint Y1, uint M1, uint D1, uint H1, uint m1, uint S1,
uint Y2, uint M2, uint D2, uint H2, uint m2, uint S2
)
{
bool invert = false;
if (Y1 > Y2) {
invert = true;
} else if (Y1 == Y2) {
if (M1 > M2) {
invert = true;
} else if (M1 == M2) {
if (D1 > D2) {
invert = true;
} else if (D1 == D2) {
if (H1 > H2) {
invert = true;
} else if (H1 == H2) {
if (m1 > m2) {
invert = true;
} else if (m1 == m2 && S1 > S2) {
invert = true;
}
}
}
}
}
if (invert) {
std::swap(Y1, Y2);
std::swap(M1, M2);
std::swap(D1, D2);
std::swap(H1, H2);
std::swap(m1, m
It was originally a small sidetrack from a project I'm doing.
And, whilst performance isn't a huge concern here, the code I've posted below performs highly optimally in comparison to its alternative (shown below it). This is preferred, as originally this was used in a real-time program, and without other changes to the high-level algorithm, the cost of re-calculating the date difference every frame (up to 60FPS) was creating a significant run-time penalty.
But what I'm looking for in my solution, is algorithmic improvements, not optimizations (it runs more than fast enough). Such as removing the for loop for calculating which years are leap years (perhaps using 365.242199 constant?).
And especially techniques on how to get rid of that huge tree of comparisons for the initial swap; that just doesn't look like good practice... ever. I'm sure it can be done in the algorithm, but my attempts failed and I ran out of time.
```
long calculate_seconds_between(
uint Y1, uint M1, uint D1, uint H1, uint m1, uint S1,
uint Y2, uint M2, uint D2, uint H2, uint m2, uint S2
)
{
bool invert = false;
if (Y1 > Y2) {
invert = true;
} else if (Y1 == Y2) {
if (M1 > M2) {
invert = true;
} else if (M1 == M2) {
if (D1 > D2) {
invert = true;
} else if (D1 == D2) {
if (H1 > H2) {
invert = true;
} else if (H1 == H2) {
if (m1 > m2) {
invert = true;
} else if (m1 == m2 && S1 > S2) {
invert = true;
}
}
}
}
}
if (invert) {
std::swap(Y1, Y2);
std::swap(M1, M2);
std::swap(D1, D2);
std::swap(H1, H2);
std::swap(m1, m
Solution
I think if i was doing it, I'd try to structure it more like the standard code: turn each Y/M/D/H/m/S into seconds since some epoch, then use fairly straightforward subtraction to compute the difference.
For the seconds since epoch, I'd probably use something like a normal Julian Day Number, but with a more recent epoch (to reduce magnitudes, and with them the possibility of overflow), then calculate seconds into the day, something like this:
That leaves only calculating the modified JDN. It's not exactly transparent, but:
This formula is from a 1991 Usenet post by Tom Van Flandern, with an even more modified JDN (i.e., an even more recent epoch).
Another way to help avoid overflow would be to model it a bit more closely after your code: compute a difference in days, and a difference in seconds, and only then convert the days to seconds, and add on the difference in seconds within the day:
In particular, this would make it easier to avoid overflow while still using standard Julian day numbers. This would be useful if (for example) you were using standard Julian day numbers for other purposes, so you wanted to re-use those standard routines.
I haven't run full regression tests for accuracy (since the point is more about the overall structure than the actual code implementing it), but I'm reasonably certain the approach can/will produce accurate results. A quick test for speed indicates that it should be reasonably competitive in that regard as well -- at least with the compilers I have handy, it's fairly consistently somewhat faster. Even if (for example) I've messed something up in transcribing Tom's formula into C++, I doubt that fixing it will have any major effect on speed.
Readability is open to a bit more question. Most of this code is very simple and straightforward, with one line of nearly impenetrable "magic math". Yours "distributes" the complexity, so there's no one part that's terribly difficult, but also no part that's really easy, obvious, or reusable either.
Edit: As written this produces the absolute value of the difference. Eliminating that simplifies the code to something like this:
unsigned calculate_seconds_between2(unsigned Y1, unsigned M1, unsigned D1, unsigned H1, unsigned m1, unsigned S1,
unsigned Y2, unsigned M2, unsigned D2, unsigned H2, unsigned m2, unsigned S2)
{
// JSN = seconds since some epoch:
unsigned T1 = JSN(Y1, M1, D1, H1, m1, S1);
unsigned T2 = JSN(Y2, M2, D2, H2, m2, S2);
return T1>T2 ? T1-T2 : T2-T1;
}For the seconds since epoch, I'd probably use something like a normal Julian Day Number, but with a more recent epoch (to reduce magnitudes, and with them the possibility of overflow), then calculate seconds into the day, something like this:
unsigned JSN(unsigned Y, unsigned M, unsigned D, unsigned H, unsigned m, unsigned S) {
static const int unsigned secs_per_day = 24 * 60 * 60;
return mJDN(Y-1900, M, D) * secs_per_day + H * 3600 + m * 60 + S;
}That leaves only calculating the modified JDN. It's not exactly transparent, but:
unsigned mJDN(unsigned Y, unsigned M, unsigned D) {
return 367*Y - 7*(Y+(M+9)/12)/4 + 275*M/9 + D;
}This formula is from a 1991 Usenet post by Tom Van Flandern, with an even more modified JDN (i.e., an even more recent epoch).
Another way to help avoid overflow would be to model it a bit more closely after your code: compute a difference in days, and a difference in seconds, and only then convert the days to seconds, and add on the difference in seconds within the day:
unsigned time_diff(/* ...*/) {
unsigned D1 = JDN(Y1, M1, D1);
unsigned D2 = JDN(Y2, M2, D2);
unsigned T1 = H1 * 3600 + m1 * 60 + S1;
unsigned T2 = H2 * 3600 + m2 * 60 + S1;
if (D1 == D2)
return T1>T2 ? T1-T2 : T2-T1;
return D1>D2 ? (D1-D2)*secs_per_day + T1-T2 : (D2-D1)*secs_per_day + T2-T1;
}In particular, this would make it easier to avoid overflow while still using standard Julian day numbers. This would be useful if (for example) you were using standard Julian day numbers for other purposes, so you wanted to re-use those standard routines.
I haven't run full regression tests for accuracy (since the point is more about the overall structure than the actual code implementing it), but I'm reasonably certain the approach can/will produce accurate results. A quick test for speed indicates that it should be reasonably competitive in that regard as well -- at least with the compilers I have handy, it's fairly consistently somewhat faster. Even if (for example) I've messed something up in transcribing Tom's formula into C++, I doubt that fixing it will have any major effect on speed.
Readability is open to a bit more question. Most of this code is very simple and straightforward, with one line of nearly impenetrable "magic math". Yours "distributes" the complexity, so there's no one part that's terribly difficult, but also no part that's really easy, obvious, or reusable either.
Edit: As written this produces the absolute value of the difference. Eliminating that simplifies the code to something like this:
int mJDN(int Y, int M, int D) {
return 367*Y - 7*(Y+(M+9)/12)/4 + 275*M/9 + D;
}
int JSN(ull Y, ull M, ull D, ull H, ull m, ull S) {
static const int secs_per_day = 24 * 60 * 60;
return mJDN(Y-1900, M, D) * secs_per_day + H * 3600 + m * 60 + S;
}
int calculate_seconds_between3(int Y1, int M1, int D1, int H1, int m1, int S1,
int Y2, int M2, int D2, int H2, int m2, int S2)
{
int T1 = JSN(Y1, M1, D1, H1, m1, S1);
int T2 = JSN(Y2, M2, D2, H2, m2, S2);
return T2-T1;
}Code Snippets
unsigned calculate_seconds_between2(unsigned Y1, unsigned M1, unsigned D1, unsigned H1, unsigned m1, unsigned S1,
unsigned Y2, unsigned M2, unsigned D2, unsigned H2, unsigned m2, unsigned S2)
{
// JSN = seconds since some epoch:
unsigned T1 = JSN(Y1, M1, D1, H1, m1, S1);
unsigned T2 = JSN(Y2, M2, D2, H2, m2, S2);
return T1>T2 ? T1-T2 : T2-T1;
}unsigned JSN(unsigned Y, unsigned M, unsigned D, unsigned H, unsigned m, unsigned S) {
static const int unsigned secs_per_day = 24 * 60 * 60;
return mJDN(Y-1900, M, D) * secs_per_day + H * 3600 + m * 60 + S;
}unsigned mJDN(unsigned Y, unsigned M, unsigned D) {
return 367*Y - 7*(Y+(M+9)/12)/4 + 275*M/9 + D;
}unsigned time_diff(/* ...*/) {
unsigned D1 = JDN(Y1, M1, D1);
unsigned D2 = JDN(Y2, M2, D2);
unsigned T1 = H1 * 3600 + m1 * 60 + S1;
unsigned T2 = H2 * 3600 + m2 * 60 + S1;
if (D1 == D2)
return T1>T2 ? T1-T2 : T2-T1;
return D1>D2 ? (D1-D2)*secs_per_day + T1-T2 : (D2-D1)*secs_per_day + T2-T1;
}int mJDN(int Y, int M, int D) {
return 367*Y - 7*(Y+(M+9)/12)/4 + 275*M/9 + D;
}
int JSN(ull Y, ull M, ull D, ull H, ull m, ull S) {
static const int secs_per_day = 24 * 60 * 60;
return mJDN(Y-1900, M, D) * secs_per_day + H * 3600 + m * 60 + S;
}
int calculate_seconds_between3(int Y1, int M1, int D1, int H1, int m1, int S1,
int Y2, int M2, int D2, int H2, int m2, int S2)
{
int T1 = JSN(Y1, M1, D1, H1, m1, S1);
int T2 = JSN(Y2, M2, D2, H2, m2, S2);
return T2-T1;
}Context
StackExchange Code Review Q#6364, answer score: 5
Revisions (0)
No revisions yet.