Hur kan jag hitta DATETIME från MySQL igenom MySQL/Connector C++ 8.0?

Permalänk

Hur kan jag hitta DATETIME från MySQL igenom MySQL/Connector C++ 8.0?

Detta är ett litet svårt problem.
MySQL/Connector C++ 8.0 är den senaste MySQL versionen där MySQL/Connector C är utgående.

För att läsa en tabell i en MySQL Databas så kan man göra så här:

std::vector<std::vector<std::string>> getDatabaseValues(const char tableName[]) { std::vector<std::string> values; std::vector<std::vector<std::string>> table; if (isConnectedToDatabase()) { // Select only the first row std::string query = "SELECT * FROM " + std::string(tableName); mysqlx::SqlResult result = connection->sql(query).execute(); int columnCount = result.getColumnCount(); if (result.hasData()) { mysqlx::Row row; while (row = result.fetchOne()) { for (int i = 0; i < columnCount; i++) { switch (row[i].getType()) { case mysqlx::common::Value::UINT64: values.push_back(std::to_string(row[i].get<uint64_t>())); break; case mysqlx::common::Value::INT64: values.push_back(std::to_string(row[i].get<int64_t>())); break; case mysqlx::common::Value::FLOAT: values.push_back(std::to_string(row[i].get<float>())); break; case mysqlx::common::Value::STRING: values.push_back(row[i].get<std::string>()); break; case mysqlx::common::Value::RAW: mysqlx::bytes bytes = row[i].getRawBytes(); ??? Nu då? break; } } table.push_back(values); } } } return table; }

Problemet som jag har är att tillfället case mysqlx::common::Value::RAW: körs när jag läser datatypen DATETIME i MySQL. Detta har med att bytes är alltså

https://dev.mysql.com/doc/dev/connector-cpp/8.0/classmysqlx_1...

Citat:

JSON data is represented as a JSON string. ENUM values are represented as strings with enum constant names. Values of type DATE and TIMESTAMP use the same representation as DATETIME, with time part empty in case of DATE values. GEOMETRY values use the internal geometry storage format described here.

Note that raw representation of BYTES and STRING values has an extra 0x00 byte added at the end, which is not part of the originial data. It is used to distinguish null values from empty byte sequences.

Så detta betyder att bytes har null-terminering samt att bytes kan både vara DATE och TIMESTAMP.

Men då är frågan. Hur får jag bytes till std::string i C++?

Datat i bytes ser ut så här under körning

Där riktiga värdet skall vara: '2022-12-04 12:31:29.285'
Bytevärdet är: 0x20c4aa83da0

Permalänk
Medlem

det verkar vara ihopsatt av 2 int - den första 4 byten är antal dagar från '1900-01-01 00:00:00' och den andra 4 byte är antal tick från 00:00 i 1/300-dels sekund.

med andra ord får du inte ut datum och tid direkt utan ytterligare funktionerna som tolkar om antal dagar sedan 1900-01-01 00:00:00 till ett datum och sedan antal 1/300-dels sekunder till tid på dagen - och på det skall också sättas vilken tidzon, sommar och vintertid etc.

det bör finnas färdiga funktioner för detta.

detta tolkat ifrån

https://stackoverflow.com/questions/4946292/how-to-cast-the-h...

Permalänk
Hedersmedlem
Skrivet av heretic16:

Bytevärdet är: 0x20c4aa83da0

Bara det (eller är det starten)?
Ibland kan man spara tid och frustration genom att representera datum som unix-tidstämpel istället…

Permalänk
Skrivet av xxargs:

det verkar vara ihopsatt av 2 int - den första 4 byten är antal dagar från '1900-01-01 00:00:00' och den andra 4 byte är antal tick från 00:00 i 1/300-dels sekund.

med andra ord får du inte ut datum och tid direkt utan ytterligare funktionerna som tolkar om antal dagar sedan 1900-01-01 00:00:00 till ett datum och sedan antal 1/300-dels sekunder till tid på dagen - och på det skall också sättas vilken tidzon, sommar och vintertid etc.

det bör finnas färdiga funktioner för detta.

detta tolkat ifrån

https://stackoverflow.com/questions/4946292/how-to-cast-the-h...

Ja? Vart då? Jag har sökt i MySQL's egna dokument, men hittar absolut inget vettigt.

Skrivet av Elgot:

Bara det (eller är det starten)?
Ibland kan man spara tid och frustration genom att representera datum som unix-tidstämpel istället…

Jo, unixtid är riktigt bra. Men dagens unixtid är väll bara skapat för sekunder sedan 1970?

Permalänk
Medlem

@heretic16 unxtimestamp klarar också datum före 1970 om du har stöd för negativa tal .

Permalänk
Medlem
Skrivet av heretic16:

Jo, unixtid är riktigt bra. Men dagens unixtid är väll bara skapat för sekunder sedan 1970?

Unixtid är signed.

$ date --date="1960-02-28 16:51:32" +%s -310550908

Edit: Too slow.

Visa signatur

Desktop: AMD 3950X, 64 GB RAM, Nvidia 4070 ... (Windows 11)
Serverdesktop: AMD 5600G, 64 GB RAM (Proxmox)
Labbmiljö: Supermicro SC825 X9DRi-F 2xE5-2667v2 64GB RAM
Kamera: Canon R5, Canon RF 100-500, Laowa 100mm f/2.8, Canon RF 24-70 f/2,8

Permalänk

Men om jag ska använda MySQL's egna interna C++ kommando för att konvertera om RAW till DATETIME. Hur gör man då?

Det är inte bara jag som har problem med detta, även StackOverFlow har problem med detta.

Permalänk
Medlem
Skrivet av heretic16:

Detta är ett litet svårt problem.
MySQL/Connector C++ 8.0 är den senaste MySQL versionen där MySQL/Connector C är utgående.

För att läsa en tabell i en MySQL Databas så kan man göra så här:

std::vector<std::vector<std::string>> getDatabaseValues(const char tableName[]) { std::vector<std::string> values; std::vector<std::vector<std::string>> table; if (isConnectedToDatabase()) { // Select only the first row std::string query = "SELECT * FROM " + std::string(tableName); mysqlx::SqlResult result = connection->sql(query).execute(); int columnCount = result.getColumnCount(); if (result.hasData()) { mysqlx::Row row; while (row = result.fetchOne()) { for (int i = 0; i < columnCount; i++) { switch (row[i].getType()) { case mysqlx::common::Value::UINT64: values.push_back(std::to_string(row[i].get<uint64_t>())); break; case mysqlx::common::Value::INT64: values.push_back(std::to_string(row[i].get<int64_t>())); break; case mysqlx::common::Value::FLOAT: values.push_back(std::to_string(row[i].get<float>())); break; case mysqlx::common::Value::STRING: values.push_back(row[i].get<std::string>()); break; case mysqlx::common::Value::RAW: mysqlx::bytes bytes = row[i].getRawBytes(); ??? Nu då? break; } } table.push_back(values); } } } return table; }

Problemet som jag har är att tillfället case mysqlx::common::Value::RAW: körs när jag läser datatypen DATETIME i MySQL. Detta har med att bytes är alltså

https://dev.mysql.com/doc/dev/connector-cpp/8.0/classmysqlx_1...

Så detta betyder att bytes har null-terminering samt att bytes kan både vara DATE och TIMESTAMP.

Men då är frågan. Hur får jag bytes till std::string i C++?

Datat i bytes ser ut så här under körning
https://iili.io/HC24g5P.png

Där riktiga värdet skall vara: '2022-12-04 12:31:29.285'
Bytevärdet är: 0x20c4aa83da0

Har inte själv jobbat med Mysql i C++ men tycker att det verkar rätt konstigt att deras s.k. Connector inte inkluderar stöd för att tolka DATETIME-värden från databasen till en lämplig typ. (Om det verkligen kan vara så??)

Såg dock detta, känns som ett litet tillägg som verkar riktigt hjälpsamt: https://stackoverflow.com/a/74126419/2069787

Då kan du ju antingen använda std::chrono::nånting-instanserna rakt av (lämpligt vid jämförelser osv) eller formatera dem i valfritt strängformat för presentation.

Visa signatur

Desktop: Ryzen 5800X3D || MSI X570S Edge Max Wifi || Sapphire Pulse RX 7900 XTX || Gskill Trident Z 3600 64GB || Kingston KC3000 2TB || Samsung 970 EVO Plus 2TB || Samsung 960 Pro 1TB || Fractal Torrent || Asus PG42UQ 4K OLED
Proxmox server: Ryzen 5900X || Asrock Rack X570D4I-2T || Kingston 64GB ECC || WD Red SN700 1TB || Blandning av WD Red / Seagate Ironwolf för lagring || Fractal Node 304

Permalänk
Skrivet av evil penguin:

Har inte själv jobbat med Mysql i C++ men tycker att det verkar rätt konstigt att deras s.k. Connector inte inkluderar stöd för att tolka DATETIME-värden från databasen till en lämplig typ. (Om det verkligen kan vara så??)

Såg dock detta, känns som ett litet tillägg som verkar riktigt hjälpsamt: https://stackoverflow.com/a/74126419/2069787

Då kan du ju antingen använda std::chrono::nånting-instanserna rakt av (lämpligt vid jämförelser osv) eller formatera dem i valfritt strängformat för presentation.

Jag har testat detta. Det verkar som den bara läser sekundvis

#include "boost/date_time/posix_time/posix_time.hpp" #include <vector> #include <cstddef> #include <chrono> 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::system_clock::time_point mySQLDateTimeToTimePoint(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; } std::string readMySQLDateTimeToString(const mysqlx::Value& value) { std::chrono::system_clock::time_point timePoint = mySQLDateTimeToTimePoint(value); std::time_t unixTime = std::chrono::system_clock::to_time_t(timePoint); return "något..."; }

Detta är bara i sekunder: 1670157089

Men här är det i nanosekunder: 16701570892850000
Ta bort en nolla och det blir mikrosekunder.

Så det är fel på denna kodrad

std::time_t unixTime = std::chrono::system_clock::to_time_t(timePoint);

Permalänk

Nu har jag hittat ett sätt att få mikrosekunder.

std::chrono::system_clock::time_point timePoint = mySQLDateTimeToTimePoint(value); std::chrono::system_clock::duration duration = timePoint.time_since_epoch(); int64_t microseconds = duration.count()/10;

Nu är det bara få mikrosecunder till date-time. Någon som vet ett bra och propert sätt? Visst jag kan ju ful-C-koda till datument, men det måste finnas något standard C++ sätt?

Permalänk

Nu har jag hittat ett sätt.
Men skulle behöva lite hjälp från er.

Jag läste denna vector a

case mysqlx::common::Value::RAW: mysqlx::bytes data = row[i].getRawBytes(); const mysqlx::byte* first = data.begin(); std::vector<int> a; for (int i = 0; i < data.length(); i++) a.push_back(first[i]); a; int month = first[2]; int date = first[3]; int hour = first[4]; int minute = first[5]; int second = first[6]; values.push_back("YYYY-MM-DDTHH:MM:SS:sss"); break; }

Och jag läste cellen

Citat:

2022-12-08 20:43:45.787

Men jag fick bara detta

Som jag misstänker så är byte 2 måste vara månad, byte 3 måste vara datum, byte 4 måste vara timme i UTC tidzon, byte 5 måste vara minut and byte 6 måste vara sekunder.

Men vad kan byte 0 och byte 1 vara då? Jo, det är årtal.
Men det verkar som att byte 0 och byte 1 skapar ett 12-bit värde för årtal med tanke på att byte 1 är 0xf = 15, vilket är 4 bitar.
För att skicka 12-bitar så måste man ta 12 >> 8 och därmed så blir det 4 bitar kvar, vilket är 0xf om alla bitar är 1:or.
Detta är dock bara en teori. Jag har inte hittat årtalet 2022.

Men hur kan jag hitta millisekunder från byte 7 till byte 10?

Permalänk
Hedersmedlem

@heretic16 Ett värde till att jämföra med skulle nog underlätta. Det kan ju vara någon godtycklig faktor inblandad till exempel.

Permalänk
Medlem

Vilken version av MySQL connector använder du?
8.0.20 fixade en bugg där sista byten saknades.

https://dev.mysql.com/doc/relnotes/connector-cpp/en/news-8-0-...

Visa signatur

Intel Core i7 6700K | Gigabyte Z170X-UD3 | Corsair Vengeance LPX 16GB DDR4 2400Mhz | EVGA GTX 980Ti Hybrid | Samsung 950 PRO 256GB | Noctua NH-D15 | EVGA G2 750 | Fractal Design Define R5

Permalänk
Medlem
Permalänk
Skrivet av Elgot:

@heretic16 Ett värde till att jämföra med skulle nog underlätta. Det kan ju vara någon godtycklig faktor inblandad till exempel.

Vilket värde vill du ha?
Du vet tidskoden som jag skrev ovan och den råa datan. Vad mer vill du veta?

Skrivet av Zajin:

Vilken version av MySQL connector använder du?
8.0.20 fixade en bugg där sista byten saknades.

https://dev.mysql.com/doc/relnotes/connector-cpp/en/news-8-0-...

Jag har version

Citat:

mysql-connector-cpp:x64-windows 8.0.30#1

Där har vi svaret.
Så jag har 11 bytes, men här visas det 8 bytes... Så hur ska jag tolka detta?

Permalänk
Hedersmedlem
Skrivet av heretic16:

Vilket värde vill du ha?
Du vet tidskoden som jag skrev ovan och den råa datan. Vad mer vill du veta?

Ett annat millisekund-värde bara, så man kan testa sina gissningar mot något.

Permalänk
Skrivet av Elgot:

Ett annat millisekund-värde bara, så man kan testa sina gissningar mot något.

Här får du.

Citat:

2022-12-04 12:31:29.285

Permalänk
Medlem
Skrivet av heretic16:

Vilket värde vill du ha?
Du vet tidskoden som jag skrev ovan och den råa datan. Vad mer vill du veta?

Jag har version
Där har vi svaret.
Så jag har 11 bytes, men här visas det 8 bytes... Så hur ska jag tolka detta?

Hittade antingen på stack overflow eller i dokumentationen att precisionen på sekunderna kan variera. "Fractional seconds" så det kanske är en konfigurationssak.

Visa signatur

flippy @ Quakenet

Permalänk
Hedersmedlem
Skrivet av heretic16:

Tack.
En annan strategi är ju att hitta värden som varierar på något enkelt sätt (till exempel ökar med 1). Går det att hitta 286 och 788 också? Och är det där alla decimaler (eller avrundar den till tre)?

Permalänk

Här är lite mer information.

Datum:
2000-00-00 00:00:00.000

Datum:
0000-00-00 00:00:00.000

Datum:
0000-12-31 23:59:59.999

Datum:
0255-12-31 23:59:59.999

Datum:
4095-12-31 23:59:59.999

Datum:
0000-00-00 00:00:00.255

Då är det helt uppenbart att första biten i första elementet är 1, dvs 0b10000000 = 128.
Så då blir år:

(byte[1] << 7 ) | (byte[0] >> 1)

Om jag har 4095, så får jag byte[0] = 255 = 0b11111111 och byte[1] = 31 = 0b00011111

Så om jag förskjuter byte[0] >> 1 och sedan lägger jag på byte[1] på byte[0].
Detta fungerar.

int year = (first[1] << 7) | (first[0] & 0x7f); int microseconds = (first[9] << 14) | (first[8] << 7) | (first[7] & 0x7f);

,

Permalänk
Hedersmedlem
Skrivet av heretic16:

Detta fungerar.

int year = (first[1] << 7) | (first[0] & 0x7f); int microseconds = (first[9] << 14) | (first[8] << 7) | (first[7] & 0x7f);

,

Aha. Lömskt med 7-bitarsrepresentation.

Permalänk
Skrivet av Elgot:

Aha. Lömskt med 7-bitarsrepresentation.

Ja. Det är riktigt uruselt.
Jag använder senaste C++ connector för MySQL. Tydligen så ska inte C connector utvecklas längre.