Skip to content

Instantly share code, notes, and snippets.

@skull-squadron
Last active August 28, 2025 21:52
Show Gist options
  • Save skull-squadron/861d33ddb19b1781b0bd24864888d922 to your computer and use it in GitHub Desktop.
Save skull-squadron/861d33ddb19b1781b0bd24864888d922 to your computer and use it in GitHub Desktop.
C++ O(1) epoch <-> datetime (requires floating point math)
/testcxx
/testc
// FEATURES
//
// - Valid C++-14 and C99
// - Only uses integer arithmetic
// - Essentially O(1) performance for all functions
// - (C++ only) constexpr functions for compile-time evaluation when compiling as C++
// - Exhaustive testing of all functions
//
// BUGS
//
// - Doesn't handle leap seconds
// - Doesn't handle dates before 1970-01-01 00:00:00
// - uint32_t epoch doesn't handle dates after 2106-02-07 06:28:15
// - Doesn't check input dates are valid
//
#ifndef EPOCH_H
#define EPOCH_H
#ifdef __cplusplus
#define EPOCH_CONSTEXPR constexpr
#else
#define EPOCH_CONSTEXPR
#endif
#include <stdbool.h>
#include <stdint.h>
typedef enum DayOfWeek {
SUNDAY = 1,
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY
} DayOfWeek;
// Result is not in half-day form
// y 1970..2106
// m 1..12
// d 1..31
EPOCH_CONSTEXPR uint32_t
julianDate(uint16_t y, uint8_t m, uint8_t d) {
if (m < 3) --y;
m += (m < 3) ? 13 : 1;
return (36525*y)/100 - y/100 + y/400 + (306001*m)/10000 + d + 1720997;
}
// y 1970..2106
// m 1..12
// d 1..31
// h 0..23
// min 0..59
// s 0..59
EPOCH_CONSTEXPR uint32_t
dateTimeToEpoch(uint16_t y, uint8_t m, uint8_t d, uint8_t h, uint8_t min, uint8_t s) {
return 60 * (60 * (24 * (julianDate(y, m, d) - 2440588) + h) + min) + s;
}
// jd is the Julian Date
// y is the **year** part (NULL to not store)
// m is the **month** part (NULL to not store)
// d is the **day** part (NULL to not store)
EPOCH_CONSTEXPR void
dateFromJulian(uint32_t jd, uint16_t *y, uint8_t *m, uint8_t *d) {
uint8_t q = (4*jd - 17918)/146097;
uint32_t r = jd + q + (-q+3)/4 + 1486;
uint16_t s = (20*r - 2442)/7305;
uint16_t t = r - 365*s - s/4;
uint8_t u = (10000*t)/306001;
if (d) *d = t - (306001*u)/10000;
uint8_t mm = u - ((u > 13) ? 13 : 1);
if (m) *m = mm;
if (y) *y = s - ((mm > 2) ? 4716 : 4715);
}
// epoch is seconds since 1970-01-01 00:00:00 TA1/UTC
// y is the **year** part (NULL to not store)
// m is the **month** part (NULL to not store)
// d is the **day** part (NULL to not store)
// h is the **hour** part (NULL to not store)
// min is the **minute** part (NULL to not store)
// s is the **second** part (NULL to not store)
EPOCH_CONSTEXPR void
dateTimeFromEpoch(uint32_t epoch, uint16_t *y, uint8_t *m, uint8_t *d, uint8_t *h, uint8_t *min, uint8_t *s) {
if (s) *s = epoch % 60; epoch /= 60;
if (min) *min = epoch % 60; epoch /= 60;
if (h) *h = epoch % 24; epoch /= 24;
dateFromJulian(epoch + 2440588, y, m, d);
}
EPOCH_CONSTEXPR bool
isLeapYear(uint16_t y) { return ((y & 3) == 0 && y % 100 != 0) || (y % 400 == 0); }
// m 1..12
// d 1..31
EPOCH_CONSTEXPR DayOfWeek
toDayOfWeek(uint16_t y, uint8_t m, uint16_t d) {
const uint8_t t[12] = { 0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4 };
if (m < 3) --y;
return (DayOfWeek)((y + (y>>2) - y/100 + y/400 + t[m-1] + d) % 7 + 1);
}
// m 1..12
EPOCH_CONSTEXPR uint8_t
daysInMonth(uint16_t m, bool leap_y) {
const uint8_t dm[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
return dm[m-1] + ((m == 2 && leap_y) ? 1 : 0);
}
#endif
CXX ?= c++
CC ?= cc
TARGETS = testcxx testc
all: $(TARGETS)
testcxx: test.cpp epoch.h Makefile
$(CXX) -O3 -std=c++14 $(CXXFLAGS) -o $@ $<
./$@
testc: test.cpp epoch.h Makefile
$(CC) -O3 -x c -std=c99 $(CFLAGS) -o $@ $<
./$@
clean:
rm -f $(TARGETS)
#include "epoch.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define EXPECT(cond, message, ...) \
if (!(cond)) { \
fprintf(stderr, "ERROR: (%s == false) " message, #cond, ##__VA_ARGS__); \
exit(1); \
}
#define EXPECT_EQ(actual, expected, message, ...) \
do { \
int _a = (actual); \
int _e = (expected); \
if (_a != _e) { \
fprintf(stderr, "ERROR: (%s (%d) != %s (%d))" message, #actual, _a, #expected, _e, ##__VA_ARGS__); \
exit(1); \
} \
} while(0)
EPOCH_CONSTEXPR DayOfWeek nextDay(DayOfWeek d) { return (d == SATURDAY) ? SUNDAY : (DayOfWeek)(d+1); }
void testToDayOfWeek() {
printf("testToDayOfWeek\n");
DayOfWeek expected = THURSDAY; // 1970-01-01 12:00:00 UTC
bool leap = isLeapYear(1970);
for (uint16_t y = 1970; y < 2400; ++y) {
leap = leap ? false : isLeapYear(y);
for (uint8_t m = 1; m <= 12; ++m)
for (uint8_t d = 1; d <= daysInMonth(m, leap); expected = nextDay(expected), ++d)
EXPECT_EQ(toDayOfWeek(y, m, d), expected, "%04u-%02u-%02u", y, m, d);
}
}
void testIsLeapYear() {
printf("testIsLeapYear\n");
for (uint16_t y = 1970; y < 2400; ++y) {
bool expected = (y % 4 == 0 && (y % 100 != 0 || y % 400 == 0));
EXPECT_EQ(isLeapYear(y), expected, "y: %u", y);
}
}
void testJulianDate() {
printf("testJulianDate\n");
uint32_t expected = 2440588; // 1970-01-01 12:00:00 UTC
bool leap = isLeapYear(1970);
for (uint16_t y = 1970; y < 2400; ++y) {
leap = leap ? false : isLeapYear(y);
for (uint8_t m = 1; m <= 12; ++m)
for (uint8_t d = 1; d <= daysInMonth(m, leap); ++d, ++expected)
EXPECT_EQ(julianDate(y, m, d), expected, "%04u-%02u-%02u", y, m, d);
}
}
void testDateFromJulian() {
printf("testDateFromJulian\n");
uint16_t y;
uint8_t m, d;
for (uint32_t expected = 2440588; expected <= 2490625; ++expected) {
dateFromJulian(expected, &y, &m, &d);
uint32_t actual = julianDate(y, m, d);
EXPECT_EQ(actual, expected, "jd: %u -> %04u-%02u-%02u -> %u", expected, y, m, d, actual);
}
}
void testDateTimeToEpoch() {
printf("testDateTimeToEpoch\n");
uint32_t expected = 0;
bool leap = isLeapYear(1970);
for (uint16_t y = 1970; y < 2400; ++y) {
leap = leap ? false : isLeapYear(y);
for (uint8_t m = 1; m <= 12; ++m)
for (uint8_t d = 1; d <= daysInMonth(m, leap); ++d)
for (uint8_t h = 0; h < 24; ++h)
for (uint8_t min = 0; min < 60; ++min)
for (uint8_t s = 0; s < 60; ++s, ++expected)
EXPECT_EQ(dateTimeToEpoch(y, m, d, h, min, s), expected, "");
}
}
void testDateTimeFromEpoch() {
printf("testDateTimeFromEpoch");
uint32_t epoch = 0;
struct {
uint16_t y;
uint8_t m, d, h, min, s;
} a, e;
bool leap = isLeapYear(1970);
for (e.y = 1970; e.y <= 2105; ++e.y) {
printf("\rtestDateTimeFromEpoch: year %u", e.y);
leap = leap ? false : isLeapYear(e.y);
for (e.m = 1; e.m <= 12; ++e.m)
for (e.d = 1; e.d <= daysInMonth(e.m, leap); ++e.d)
for (e.h = 0; e.h < 24; ++e.h)
for (e.min = 0; e.min < 60; ++e.min)
for (e.s = 0; e.s < 60; ++e.s, ++epoch) {
dateTimeFromEpoch(epoch, &a.y, &a.m, &a.d, &a.h, &a.min, &a.s);
EXPECT(memcmp(&a, &e, sizeof(a)) == 0, "epoch %u: expected: %04u-%02u-%02u %02u:%02u:%02u , actual: %04u-%02u-%02u %02u:%02u:%02u", epoch,
e.y, e.m, e.d, e.h, e.min, e.s,
a.y, a.m, a.d, a.h, a.min, a.s);
}
}
printf("\n");
}
void testDaysInMonth() {
printf("testDaysInMonth\n");
struct {
uint8_t month;
bool leap;
uint8_t expected;
} tests[] = {
{1, false, 31}, {1, true, 31},
{2, false, 28}, {2, true, 29},
{3, false, 31}, {3, true, 31},
{4, false, 30}, {4, true, 30},
{5, false, 31}, {5, true, 31},
{6, false, 30}, {6, true, 30},
{7, false, 31}, {7, true, 31},
{8, false, 31}, {8, true, 31},
{9, false, 30}, {9, true, 30},
{10, false, 31}, {10, true, 31},
{11, false, 30}, {11, true, 30},
{12, false, 31}, {12, true, 31},
};
for (size_t i = 0; i < sizeof(tests)/sizeof(*tests); ++i)
EXPECT_EQ(daysInMonth(tests[i].month, tests[i].leap), tests[i].expected, "month: %u leap: %d", tests[i].month, tests[i].leap);
}
int main() {
setbuf(stdout, NULL); // stop buffering stdout
testJulianDate();
testDateTimeToEpoch();
testDateFromJulian();
testDateTimeFromEpoch();
testIsLeapYear();
testToDayOfWeek();
testDaysInMonth();
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment