Skip to content

Commit

Permalink
Improve convertion of Timestamp to string
Browse files Browse the repository at this point in the history
Use Fliegel & Van Flandern algorithm
  • Loading branch information
jedelbo committed Aug 7, 2023
1 parent 0c1777c commit 96b1fd7
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 24 deletions.
29 changes: 5 additions & 24 deletions src/realm/timestamp.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@

#include <cstdint>
#include <ostream>
#include <iomanip>
#include <chrono>
#include <ctime>
#include <realm/util/assert.hpp>
Expand Down Expand Up @@ -183,6 +182,9 @@ class Timestamp {
return size_t(m_seconds) ^ size_t(m_nanoseconds);
}

// Buffer must be at least 32 bytes long
const char* to_string(char* buffer) const;

template <class Ch, class Tr>
friend std::basic_ostream<Ch, Tr>& operator<<(std::basic_ostream<Ch, Tr>& out, const Timestamp&);
static constexpr int32_t nanoseconds_per_second = 1000000000;
Expand All @@ -197,29 +199,8 @@ class Timestamp {
template <class C, class T>
inline std::basic_ostream<C, T>& operator<<(std::basic_ostream<C, T>& out, const Timestamp& d)
{
if (d.is_null()) {
out << "null";
return out;
}
auto seconds = time_t(d.get_seconds());
struct tm buf;
#ifdef _MSC_VER
bool success = gmtime_s(&buf, &seconds) == 0;
#else
bool success = gmtime_r(&seconds, &buf) != nullptr;
#endif
if (success) {
// We need a buffer for formatting dates.
// Max size is 20 bytes (incl terminating zero) "YYYY-MM-DD HH:MM:SS"\0
char buffer[30];
if (strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &buf)) {
out << buffer;
if (auto nano = d.get_nanoseconds()) {
out << '.' << std::setfill('0') << std::setw(9) << nano;
}
}
}

char buffer[32];
out << d.to_string(buffer);
return out;
}
// LCOV_EXCL_STOP
Expand Down
97 changes: 97 additions & 0 deletions src/realm/util/serializer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,104 @@
#include <cctype>
#include <cmath>


namespace realm {

/* Uses Fliegel & Van Flandern algorithm */
static constexpr long date_to_julian(int y, int m, int d)
{
return (1461 * (y + 4800 + (m - 14) / 12)) / 4 + (367 * (m - 2 - 12 * ((m - 14) / 12))) / 12 -
(3 * ((y + 4900 + (m - 14) / 12) / 100)) / 4 + d - 32075;
}

static void julian_to_date(int jd, int* y, int* m, int* d)
{
int L = jd + 68569;
int n = (4 * L) / 146097;
int i, j;

L = L - (146097 * n + 3) / 4;
i = (4000 * (L + 1)) / 1461001;
L = L - (1461 * i) / 4 + 31;
j = (80 * L) / 2447;
*d = L - (2447 * j) / 80;
L = j / 11;
*m = j + 2 - (12 * L);
*y = 100 * (n - 49) + i + L;
}

// Confirmed to work for all val < 16389
static void out_dec(char** buffer, unsigned val, int width)
{
int w = width;
char* p = *buffer;
while (width > 0) {
width--;
unsigned div10 = (val * 6554) >> 16;
p[width] = char(val - div10 * 10) + '0';
val = div10;
}
*buffer += w;
}

static constexpr long epoc_time = date_to_julian(1970, 1, 1); // 2440588
static constexpr int seconds_in_a_day = 24 * 60 * 60;

const char* Timestamp::to_string(char* buffer) const
{
if (is_null()) {
return "null";
}
int64_t seconds = get_seconds();
int32_t nano = get_nanoseconds();
if (nano < 0) {
nano += Timestamp::nanoseconds_per_second;
seconds--;
}

int64_t days = seconds / seconds_in_a_day;
int julian_days = int(days + epoc_time);

int seconds_in_day = int(seconds - days * seconds_in_a_day);
if (seconds_in_day < 0) {
seconds_in_day += seconds_in_a_day;
julian_days--;
}

int year;
int month;
int day;
int hours = seconds_in_day / 3600;
int remainingSeconds = seconds_in_day - hours * 3600;
int minutes = remainingSeconds / 60;
int secs = remainingSeconds - minutes * 60;

julian_to_date(julian_days, &year, &month, &day);
// const char* format = (year < 0) ? "%05d-%02d-%02d %02d:%02d:%02d" : "%04d-%02d-%02d %02d:%02d:%02d";
// auto l = sprintf(buffer, format, year, month, day, hours, minutes, secs);
char* p = buffer;
if (year < 0) {
*p++ = '-';
year = -year;
}
out_dec(&p, year, 4);
*p++ = '-';
out_dec(&p, month, 2);
*p++ = '-';
out_dec(&p, day, 2);
*p++ = ' ';
out_dec(&p, hours, 2);
*p++ = ':';
out_dec(&p, minutes, 2);
*p++ = ':';
out_dec(&p, secs, 2);
*p = '\0';
if (nano) {
snprintf(p, 32 - (p - buffer), ".%09d", nano);
}
return buffer;
}

namespace util {
namespace serializer {

Expand Down
58 changes: 58 additions & 0 deletions test/test_json.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include <string>
#include <fstream>
#include <ostream>
#include <chrono>

#include <realm.hpp>
#include <external/json/json.hpp>
Expand Down Expand Up @@ -859,6 +860,63 @@ TEST(Json_Schema)
CHECK_EQUAL(expected, json);
}


using namespace std::chrono;

TEST(Json_Timestamp)
{
char buffer1[31];
char buffer2[31];
Timestamp(-63549305085, 0).to_string(buffer1);
CHECK(strcmp(buffer1, "-0044-03-15 15:15:15") == 0);
Timestamp(0, 0).to_string(buffer1);
CHECK(strcmp(buffer1, "1970-01-01 00:00:00") == 0);
Timestamp(-1, 0).to_string(buffer1);
CHECK(strcmp(buffer1, "1969-12-31 23:59:59") == 0);
Timestamp(-1, -100000000).to_string(buffer1);
CHECK(strcmp(buffer1, "1969-12-31 23:59:58.900000000") == 0);

// Compare our own to_string with standard implementation
// for years 1900 to 2050
struct tm buf {};
buf.tm_year = 1900 - 1900;
buf.tm_mday = 1;
auto start = mktime(&buf);
buf.tm_year = 2050 - 1900;
auto end = mktime(&buf);
constexpr int64_t seconds_in_a_day = 24 * 60 * 60;

for (auto d = start; d < end; d += (seconds_in_a_day - 1)) {
Timestamp t(d, 0);
t.to_string(buffer1);
auto seconds = time_t(t.get_seconds());
#ifdef _MSC_VER
gmtime_s(&buf, &seconds);
#else
gmtime_r(&seconds, &buf);
#endif
strftime(buffer2, sizeof(buffer2), "%Y-%m-%d %H:%M:%S", &buf);
CHECK(strcmp(buffer1, buffer2) == 0);
}
/*
auto t1 = steady_clock::now();
for (auto d = 0; d < 10000; d++) {
Timestamp(d * seconds_in_a_day, 0).to_string(buffer1);
}
auto t2 = steady_clock::now();
std::cout << " time_to_string: " << duration_cast<microseconds>(t2 - t1).count() << " us" << std::endl;
t1 = steady_clock::now();
for (auto d = 0; d < 10000; d++) {
auto seconds = time_t(d);
gmtime_r(&seconds, &buf);
// strftime(buffer2, sizeof(buffer2), "%Y-%m-%d %H:%M:%S", &buf);
}
t2 = steady_clock::now();
std::cout << " gm_time: " << duration_cast<microseconds>(t2 - t1).count() << " us" << std::endl;
*/
}

} // anonymous namespace

#endif // TEST_TABLE

0 comments on commit 96b1fd7

Please sign in to comment.