MySQL中的日期是如何编码/存储的



我必须从数据库中为C++上的应用程序获取的原始字节中解析日期。我发现MySQL中的日期是4个字节,最后两个分别是月和日。但前两个字节奇怪地编码年份,所以如果日期是2002-08-30,内容将是210、15、8、31。如果日期是1996-12-22,则该日期将被存储为204、15、12、22。显然,第一个字节不能大于255,所以我检查了2047年——它是255、15和2048——它是128、16。

起初我认为关键是二进制运算,但我不太理解其中的逻辑:

2047: 0111 1111 1111
255:  0000 1111 1111
15:   0000 0000 1111
2048: 1000 0000 0000
128:  0000 1000 0000
16:   0000 0001 0000

知道吗?

似乎编码的逻辑是擦除第一个数字的最高有效位,并从这个擦除的位中写入第二个数字,如下所示:

2002 from 210 and 15:
1101 0010 -> _101 0010;
0000 1111 + _101 0010 -> 0111 1101 0010
2048 from 128 and 16:
1000 0000 -> _000 0000
0001 0000 + _000 0000 -> 1000 0000 0000

我们也遇到了同样的问题,并开发了以下C++20辅助方法,用于mysqlx(MySQL连接器/C++8.0 X DevAPI)的生产,以正确读取DATE、DATETIME和TIMESTAMP字段:

#pragma once
#include <vector>
#include <cstddef>
#include <chrono>
#include <mysqlx/xdevapi.h>
namespace mysqlx {
static inline std::vector<uint64_t>
mysqlx_raw_as_u64_vector(const mysqlx::Value& in_value)
{
std::vector<uint64_t> out;
const auto bytes = in_value.getRawBytes();
auto ptr = reinterpret_cast<const std::byte*>(bytes.first);
auto end = reinterpret_cast<const std::byte*>(bytes.first) + bytes.second;
while (ptr != end) {
static constexpr std::byte carry_flag{0b1000'0000};
static constexpr std::byte value_mask{0b0111'1111};
uint64_t v = 0;
uint64_t shift = 0;
bool is_carry;
do {
auto byte = *ptr;
is_carry = (byte & carry_flag) == carry_flag;
v |= std::to_integer<uint64_t>(byte & value_mask) << shift;
++ptr;
shift += 7;
} while (is_carry && ptr != end && shift <= 63);
out.push_back(v);
}
return out;
}
static inline std::chrono::year_month_day
read_date(const mysqlx::Value& value)
{
const auto vector = mysqlx_raw_as_u64_vector(value);
if (vector.size() < 3)
throw std::out_of_range{"Value is not a valid DATE"};
return std::chrono::year{static_cast<int>(vector.at(0))} / static_cast<int>(vector.at(1)) / static_cast<int>(vector.at(2));
}
static inline std::chrono::system_clock::time_point
read_date_time(const mysqlx::Value& value)
{
const auto vector = mysqlx_raw_as_u64_vector(value);
if (vector.size() < 3)
throw std::out_of_range{"Value is not a valid DATETIME"};
auto ymd = std::chrono::year{static_cast<int>(vector.at(0))} / static_cast<int>(vector.at(1)) / static_cast<int>(vector.at(2));
auto sys_days = std::chrono::sys_days{ymd};
auto out = std::chrono::system_clock::time_point(sys_days);
auto it = vector.begin() + 2;
auto end = vector.end();
if (++it == end)
return out;
out += std::chrono::hours{*it};
if (++it == end)
return out;
out += std::chrono::minutes{*it};
if (++it == end)
return out;
out += std::chrono::seconds{*it};
if (++it == end)
return out;
out += std::chrono::microseconds{*it};
return out;
}
} //namespace

然后可以按如下方式使用:

auto row = table.select("datetime", "date").execute().fetchOne();
auto time_point = read_date_time(row[0]);
auto year_month_day = read_date(row[1]);

getBytes文档链接到ColumnMetaData文档url。

ColumnMetaData文档链接到protobuf编码url。

protobuf编码url/协议缓冲区文档说明:

基本128变体

可变宽度整数或变量位于导线的核心总体安排它们允许使用任意位置对无符号64位整数进行编码介于1到10个字节之间,较小的值使用较少的字节。

变量中的每个字节都有一个连续位,指示它后面的字节是变量的一部分。这是最多的字节的有效位(MSB)(有时也称为符号位)。较低的7个比特是有效载荷;生成的整数通过将其组成字节的7比特有效载荷附加在一起。

例如,这里是数字1,编码为01——它是一个字节,因此MSB未设置:

0000 0001
^ msb

这里是150,编码为9601——这有点复杂:

10010110 00000001
^ msb    ^ msb

你怎么知道这是150?首先从中删除MSB每个字节,因为这只是用来告诉我们是否已经到达数字的末尾(如您所见,它设置在第一个字节中在变量中超过一个字节)。然后我们连接7位有效载荷,并将其解释为一个小endian,64位无符号integer:

10010110 00000001        // Original inputs.
0010110  0000001        // Drop continuation bits.
0000001  0010110        // Put into little-endian order.
10010110                // Concatenate.
128 + 16 + 4 + 2 = 150  // Interpret as integer.

因为变量对协议缓冲区至关重要,所以在protoscope中语法中,我们将它们称为纯整数。150与CCD_ 4相同。

根据您提供的内容,它似乎是N1 - 128 + N2 * 128

哪个版本???

DATETIME过去是以压缩十进制(8字节)进行编码的。但是,当添加分数秒时,格式被更改为类似的格式

  • 长度指示(1字节)
  • 自1970年以来的秒INT UNSIGNED(4字节)
  • 小数秒(0-3字节)

DATEMEDIUMINT UNSIGNED(3字节)一样存储为0000-00-00(或类似的东西)之后的天数。

你是怎么得到";原始字节";?没有函数可以让你这么做。选择HEX(某个日期)首先转换为字符串(如"2022-03-22"),然后取其十六进制。这将得到323032322D30332D3232

关于代码引用答案。

关于文档内容检查以下单词:

getBytes文档链接到ColumnMetaData文档url。

ColumnMetaData文档链接到protobuf编码url。

protobuf编码url/协议缓冲区文档说明:

基本128变体

可变宽度整数或变量位于导线的核心总体安排它们允许使用任意位置对无符号64位整数进行编码介于1到10个字节之间,较小的值使用较少的字节。

变量中的每个字节都有一个连续位,指示它后面的字节是变量的一部分这是最多的字节的有效位(MSB)(有时也称为符号位)。较低的7个比特是有效载荷;生成的整数通过将其组成字节的7比特有效载荷附加在一起。

例如,这里是数字1,编码为01——它是一个字节,因此MSB未设置:

0000 0001
^ msb

这里是150,编码为9601——这有点复杂:

10010110 00000001
^ msb    ^ msb

你怎么知道这是150?首先从中删除MSB每个字节,因为这只是用来告诉我们是否已经到达数字的末尾(如您所见,它设置在第一个字节中在变量中超过一个字节)。然后我们连接7位有效载荷,并将其解释为一个小endian,64位无符号integer:

10010110 00000001        // Original inputs.
0010110  0000001        // Drop continuation bits.
0000001  0010110        // Put into little-endian order.
10010110                // Concatenate.
128 + 16 + 4 + 2 = 150  // Interpret as integer.

因为变量对协议缓冲区至关重要,所以在protoscope中语法中,我们将它们称为纯整数。150与CCD_ 14相同。

变量中的每个字节都有一个连续位,指示它后面的字节是否是变量的一部分

相关内容

  • 没有找到相关文章

最新更新