Merge pull request #10300 from Icinga/error-timestamp-out-of-range-53323-214

IDO: timestamp out of range errors
This commit is contained in:
Yonas Habteab 2025-01-14 12:06:18 +01:00 committed by GitHub
commit ebac52b830
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 49 additions and 4 deletions

View File

@ -891,9 +891,13 @@ bool IdoMysqlConnection::FieldToEscapedString(const String& key, const Value& va
*result = static_cast<long>(dbrefcol);
} else if (DbValue::IsTimestamp(value)) {
long ts = rawvalue;
// MySQL TIMESTAMP columns have the year-2038 problem, hence the upper limit below.
// Also, they don't accept FROM_UNIXTIME(0): ERROR 1292 (22007): Incorrect datetime value: '1970-01-01 00:00:00'
double ts = rawvalue;
std::ostringstream msgbuf;
msgbuf << "FROM_UNIXTIME(" << ts << ")";
msgbuf << "FROM_UNIXTIME(" << std::fixed << std::setprecision(0)
<< std::fmin(std::fmax(ts, 1.0), 2147483647.0) << ")";
*result = Value(msgbuf.str());
} else if (DbValue::IsObjectInsertID(value)) {
auto id = static_cast<long>(rawvalue);

View File

@ -15,6 +15,7 @@
#include "base/context.hpp"
#include "base/statsfunction.hpp"
#include "base/defer.hpp"
#include <cmath>
#include <utility>
using namespace icinga;
@ -700,9 +701,49 @@ bool IdoPgsqlConnection::FieldToEscapedString(const String& key, const Value& va
*result = static_cast<long>(dbrefcol);
} else if (DbValue::IsTimestamp(value)) {
long ts = rawvalue;
// In addition to the limits of PostgreSQL itself (4713BC - 294276AD),
// years not fitting in YYYY may cause problems, see e.g. https://github.com/golang/go/issues/4556.
// RFC 3339: "All dates and times are assumed to be (...) somewhere between 0000AD and 9999AD."
// The below limits include safety buffers to make sure the timestamps are within 0-9999 AD in all time zones:
//
// postgres=# \x
// Expanded display is on.
// postgres=# SELECT TO_TIMESTAMP(-62135510400) AT TIME ZONE 'UTC' AS utc,
// postgres-# TO_TIMESTAMP(-62135510400) AT TIME ZONE 'Asia/Vladivostok' AS east,
// postgres-# TO_TIMESTAMP(-62135510400) AT TIME ZONE 'America/Juneau' AS west,
// postgres-# TO_TIMESTAMP(-62135510400) AT TIME ZONE 'America/Nuuk' AS north;
// -[ RECORD 1 ]--------------
// utc | 0001-01-02 00:00:00
// east | 0001-01-02 08:47:31
// west | 0001-01-02 15:02:19
// north | 0001-01-01 20:33:04
//
// postgres=# SELECT TO_TIMESTAMP(-62135510400-86400) AT TIME ZONE 'UTC' AS utc,
// postgres-# TO_TIMESTAMP(-62135510400-86400) AT TIME ZONE 'Asia/Vladivostok' AS east,
// postgres-# TO_TIMESTAMP(-62135510400-86400) AT TIME ZONE 'America/Juneau' AS west,
// postgres-# TO_TIMESTAMP(-62135510400-86400) AT TIME ZONE 'America/Nuuk' AS north;
// -[ RECORD 1 ]-----------------
// utc | 0001-01-01 00:00:00
// east | 0001-01-01 08:47:31
// west | 0001-01-01 15:02:19
// north | 0001-12-31 20:33:04 BC
//
// postgres=# SELECT TO_TIMESTAMP(253402214400) AT TIME ZONE 'UTC' AS utc,
// postgres-# TO_TIMESTAMP(253402214400) AT TIME ZONE 'Asia/Vladivostok' AS east,
// postgres-# TO_TIMESTAMP(253402214400) AT TIME ZONE 'America/Juneau' AS west,
// postgres-# TO_TIMESTAMP(253402214400) AT TIME ZONE 'America/Nuuk' AS north;
// -[ RECORD 1 ]-------------
// utc | 9999-12-31 00:00:00
// east | 9999-12-31 10:00:00
// west | 9999-12-30 15:00:00
// north | 9999-12-30 22:00:00
//
// postgres=#
double ts = rawvalue;
std::ostringstream msgbuf;
msgbuf << "TO_TIMESTAMP(" << ts << ") AT TIME ZONE 'UTC'";
msgbuf << "TO_TIMESTAMP(" << std::fixed << std::setprecision(0)
<< std::fmin(std::fmax(ts, -62135510400.0), 253402214400.0) << ") AT TIME ZONE 'UTC'";
*result = Value(msgbuf.str());
} else if (DbValue::IsObjectInsertID(value)) {
auto id = static_cast<long>(rawvalue);