簡體   English   中英

如何在 C++ 中將 Pascal TDateTime(double) 時間轉換為 Unix 紀元

[英]How to convert a Pascal TDateTime(double) time to a Unix epoch in C++

我需要使用 c++ 從一個雙精度值的 Pascal TDateTime object 轉換為 Unix 紀元。

提出了一種可能的解決方案( https://forextester.com/forum/viewtopic.php?f=8&t=1000 ):

unsigned int UnixStartDate = 25569;

unsigned int DateTimeToUnix(double ConvDate)
{
  return((unsigned int)((ConvDate - UnixStartDate) * 86400.0));
}

但是,此轉換代碼會產生錯誤,例如:

TDateTime 時間值 = 37838.001388888886 (05.08.2003 02:00)

它轉換為 Unix 紀元 1060041719 (05.08.2003 00:01:59),這顯然是不正確的。

如何准確轉換此 TDateTime 值?

Delphi/C++Builder RTL 有一個DateTimeToUnix() function 用於這個確切的目的。

TDateTime中,整數部分是從December 30 1899開始的天數,小數部分是從00:00:00.000開始的時間。 使用涉及超過一整天的原始數學可能有點棘手,因為浮點數學是不准確的

例如, 0.001388888886不完全是00:02:00 ,它更接近00:01:59.999 所以你遇到了一個四舍五入的問題,這正是你必須注意的。 TDateTime具有毫秒精度,一天有86400000毫秒,因此.00138888888600:00:00.000之后的119999.9997504毫秒,如果這些毫秒被截斷為11999900:02:00 00:01:59 ,如果它們被截斷為 00:02:00四舍五入到120000

由於細微的精度損失,RTL 幾年前停止在TDateTime上使用浮點運算。 現代TDateTime操作現在通過TTimeStamp進行往返以避免這種情況。

由於您嘗試從 RTL 外部執行此操作,因此您需要在代碼中實現相關算法。 您展示的算法是多年前 RTL 如何用於TDateTime轉換為 Unix 時間戳,但現在已經不是這樣了。 當前的算法現在看起來更像這樣(從原始 Pascal 轉換為 C++):

#include <cmath>

#define HoursPerDay   24
#define MinsPerHour   60
#define SecsPerMin    60
#define MSecsPerSec   1000
#define MinsPerDay    (HoursPerDay * MinsPerHour)
#define SecsPerDay    (MinsPerDay * SecsPerMin)
#define SecsPerHour   (SecsPerMin * MinsPerHour)
#define MSecsPerDay   (SecsPerDay * MSecsPerSec)

#define UnixDateDelta 25569 // Days between TDateTime basis (12/31/1899) and Unix time_t basis (1/1/1970)
#define DateDelta 693594    // Days between 1/1/0001 and 12/31/1899

const float FMSecsPerDay = MSecsPerDay;
const int IMSecsPerDay = MSecsPerDay;

struct TTimeStamp
{
    int Time; // Number of milliseconds since midnight
    int Date; // One plus number of days since 1/1/0001
};

typedef double TDateTime;

TTimeStamp DateTimeToTimeStamp(TDateTime DateTime)
{
    __int64 LTemp = std::round(DateTime * FMSecsPerDay); // <-- this might require tweaking!
    __int64 LTemp2 = LTemp / IMSecsPerDay;
    TTimeStamp Result;
    Result.Date = DateDelta + LTemp2;
    Result.Time = std::abs(LTemp) % IMSecsPerDay;
    return Result;
}

__int64 DateTimeToMilliseconds(const TDateTime ADateTime)
{
    TTimeStamp LTimeStamp = DateTimeToTimeStamp(ADateTime);
    return (__int64(LTimeStamp.Date) * MSecsPerDay) + LTimeStamp.Time;
}

__int64 SecondsBetween(const TDateTime ANow, const TDateTime AThen)
{
    return std::abs(DateTimeToMilliseconds(ANow) - DateTimeToMilliseconds(AThen)) / MSecsPerSec;
}

__int64 DateTimeToUnix(const TDateTime AValue)
{
    __int64 Result = SecondsBetween(UnixDateDelta, AValue);
    if (AValue < UnixDateDelta)
        Result = -Result;
    return Result;
}

請注意我在DateTimeToTimeStamp()中的評論。 我不確定std::round() ) 是否對所有值產生與 Delphi 的System::Round()完全相同的結果。 你將不得不嘗試它。

這是使用我的免費、開源、僅標頭的 C++20 計時預覽庫(適用於 C++11/14/17)的一種非常簡單的方法:

#include "date/date.h"
#include <iostream>

date::sys_seconds
convert(double d)
{
    using namespace date;
    using namespace std::chrono;
    using ddays = duration<double, days::period>;

    return round<seconds>(sys_days{1899_y/12/30} + ddays{d});
}

int
main()
{
    using namespace date;
    using namespace std;
    using namespace std::chrono;

    auto tp = convert(37838.001388888886);
    cout << tp << " = " << (tp-sys_seconds{})/1s << '\n';
}

Output:

2003-08-05 00:02:00 = 1060041720

IEEE 64 位雙精度數具有足夠的精度,可以在此范圍內四舍五入到最接近的秒數。 直到 2079 年的某個時候,精度都小於 1µs。而且精度將保持在 10µs 以下一千年。

Fwiw,這是反向轉換:

double
convert(date::sys_seconds tp)
{
    using namespace date;
    using namespace std::chrono;
    using ddays = duration<double, days::period>;

    return (tp - sys_days{1899_y/12/30})/ddays{1};
}

因此,這樣:

cout << setprecision(17) << convert(convert(37838.001388888886)) << '\n';

輸出:

37838.001388888886

好往返。

更新 C++20

這現在可以在 C++20 中完成(假設您的 std::lib 供應商已將<chrono>更新為 C++20)。

#include <chrono>
#include <iostream>

std::chrono::sys_seconds
convert(double d)
{
    using namespace std::chrono;
    using ddays = duration<double, days::period>;

    return round<seconds>(sys_days{1899y/12/30} + ddays{d});
}

double
convert(std::chrono::sys_seconds tp)
{
    using namespace std::chrono;
    using ddays = duration<double, days::period>;

    return (tp - sys_days{1899y/12/30})/ddays{1};
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM