PostgreSQL二进制Timestamp解析异常:最小值转换错误求助
我尝试解析PQGetValue返回的PostgreSQL二进制格式timestamp值,通过以下C函数将其转换为struct tm:
#define PG_EPOCH_TIME_T 946681200 // pg epoch in unix epoch static inline int _convert_pg_timestamp_to_tm( struct tm* _tm, uint64_t pg_timestamp ) { int64_t i; struct tm tm; struct tm* p; uint64_t u; u = be64toh(pg_timestamp); memcpy( &i, &u, sizeof(uint64_t) ); i /= 1000000; i += PG_EPOCH_TIME_T; // p = gmtime_r( &i, &tm ); p = localtime_r( &i, &tm ); if( p != &tm ) return -1; dump_tm( &tm ); memset( _tm, 0, sizeof(struct tm) ); _tm->tm_year = tm.tm_year; _tm->tm_mon = tm.tm_mon; _tm->tm_mday = tm.tm_mday; _tm->tm_hour = tm.tm_hour; _tm->tm_min = tm.tm_min; _tm->tm_sec = tm.tm_sec; dump_tm( _tm ); return 0; }
公元(AD)时间转换正常,但转换timestamp最小值4713-01-01 00:00:00 BC时,得到错误结果:Wed Dec 31 23:09:21 -4713。
我怀疑这个8字节值不只是相对于PostgreSQL纪元的微秒数,可能存在用于其他用途的位或字节。另外带时区的timestamp类型拥有相同范围和数据长度,说明有预留空间,数据并非单纯偏移量。我未找到说明其底层结构的文档,特此求助:这个8字节数据的底层结构是什么?
PostgreSQL的timestamp(不带时区)和timestamptz(带时区)二进制存储格式本质是带符号64位整数,单位为微秒,基准是PostgreSQL内部纪元:2000-01-01 00:00:00 UTC。
错误原因分析
你遇到的BC时间转换错误,并非存储格式有额外位,核心原因有两点:
time_t范围限制:标准time_t即便为64位,其可表示的最早时间也远晚于4713 BC,localtime_r/gmtime_r无法正确解析超出范围的时间值,导致出现异常偏移。- 纪元转换后的溢出问题:PostgreSQL timestamp最小值
4713-01-01 BC对应的微秒数为-63082281600000000(相对于2000-01-01 UTC),转换为time_t后完全超出其有效范围,触发标准时间函数的解析错误。
正确处理方向
若要支持PostgreSQL timestamp的完整范围(4713 BC 至 294276 AD),不能依赖time_t和标准C时间函数,需自行实现日期时间的加减与解析逻辑;或在客户端通过调用PostgreSQL的to_char函数将timestamp转换为字符串后再解析(此方法效率较低)。
补充说明
timestamptz与timestamp存储长度相同,是因为两者都采用64位带符号微秒数的结构:timestamptz存储的是转换为UTC后的时间微秒数,timestamp存储的是本地时间微秒数,并无预留空间,只是语义不同。
内容的提问来源于stack exchange,提问作者mystackoverflowpseudo

