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, m2);
std::swap(S1, S2);
}
static const int month_days_sum[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365};
const uint Y1_days = month_days_sum[M1 - 1];
const uint Y2_days = month_days_sum[M2 - 1];
int years_days = (Y2 - Y1) * 365;
// Leap Years
for (uint i = Y1 + 1; i < Y2;) {
if (is_leap_year(i)) {
++years_days;
i += 4;
} else {
++i;
}
}
const bool lY1 = is_leap_year(Y1) && (M1 < 2 || (M1 == 2 && D1 < 29));
const bool lY2 = is_leap_year(Y2) && (M2 > 2 || (M2 == 2 && D2 > 28));
if (Y1 == Y2) {
if (lY1 && lY2) ++years_days;
} else {
if (lY1) ++years_days;
if (lY2) ++years_days;
}
// Convert years to seconds
const long years_seconds = years_days * 86400;
// Time difference in seconds
const long S1s = ((Y1_days + D1) * 86400) + (H1 * 3600) + (m1 * 60) + S1;
const long S2s = ((Y2_days + D2) * 86400) + (H2 * 3600) + (m2 * 60) + S2;
const long total = years_seconds + (S2s - S1s);
if (invert) return -total;
else return total;
}
Standard C++ Alternative Note: very slow, up to (8000 / 35) 228x slower than the above.
time_t calculate_seconds_between2(
const uint Y1, const uint M1, const uint D1, const uint H1, const uint m1, const uint S1, // YY/MM/DD HH:mm:SS
const uint Y2, const uint M2, const uint D2, const uint H2, const uint m2, const uint S2
)
{
time_t raw;
time(&raw);
struct tm t1, t2;
gmtime_r(&raw, &t1);
t2 = t1;
t1.tm_year = Y1 - 1900;
t1.tm_mon = M1 - 1;
t1.tm_mday = D1;
t1.tm_hour = H1;
t1.tm_min = m1;
t1.tm_sec = S1;
t2.tm_year = Y2 - 1900;
t2.tm_mon = M2 - 1;
t2.tm_mday = D2;
t2.tm_hour = H2;
t2.tm_min = m2;
t2.tm_sec = S2;
time_t tt1, tt2;
tt1 = mktime(&t1);
tt2 = mktime(&t2);
return (tt2 - tt1);
}
As shown in the Unit Testing, every single date (excluding tests on time) from 1990 to 2020 has been tested against every date from 1990 to 2020 (n^2) without failure, so the algorithm appears to be correct in terms of accuracy against the GNU implementation on my platform.
Unit Testing Code: http://pastie.org/2933904
Benchmark Code: http://pastie.org/2933893
Tagged with C as this is barely a far cry from being completely transferable.