2016-03-25 41 views
1

我編寫了一個表示Date的C++類,並使用strptime/strftime來從字符串中編寫和實例化日期。使用strptime/strftime的結果不一致

The full code is here

當我使用的是bash的選項卡「樣本輸出」我的linux上運行了好幾次,有時我創建的同一日期和解析回來,有時我得到的最新一個的轉變小時(我的時區UTC + 1)。

所以,這裏發生了什麼,我不知道!

#ifndef DOLIPRANE_TIMEUNIT_HPP 
#define DOLIPRANE_TIMEUNIT_HPP 

enum TimeUnit { 
    DAY, 
    HOUR, 
    MINUTE, 
    SECOND 
    }; 
#endif 
#ifndef DOLIPRANE_DATE_HPP 
#define DOLIPRANE_DATE_HPP 

#include <ctime> 
#include <string> 


class Date 
{ 
public: 

    Date(); 
    Date(time_t epoch); 

    /** 
    * Expected format: dd/MM/YYYY HH:mm:[ss] 
    */ 
    Date(const std::string &date); 

    ~Date(); 

    void 
    add(long val, TimeUnit u = SECOND); 


    bool 
    operator==(const Date &other) const; 

    bool 
    operator!=(const Date &other) const; 

    bool 
    operator<(const Date &other) const; 

    bool 
    operator<=(const Date &other) const; 

    bool 
    operator>(const Date &other) const; 

    bool 
    operator>=(const Date &other) const; 

    friend std::ostream& 
    operator<<(std::ostream &, const Date&); 

    friend std::istream& 
    operator>>(std::istream &, Date&); 

private: 

    static const std::string FORMAT; 

    time_t m_time; 


}; 
#endif 


#include <iostream> 
#include <stdexcept> 
#include <ctime> 

const char SEPARATOR=';'; 

const std::string Date::FORMAT="%d/%m/%Y %H:%M:%S"; 

Date::Date() 
{ 
    m_time = time(NULL); 
} 

Date::Date(time_t epoch) 
    : m_time(epoch) 
{} 

Date::Date(const std::string &date) 
{ 
    struct tm t; 
    const char* ptr = strptime(date.c_str(), FORMAT.c_str(), &t); 
    if (!ptr) { 
    std::string cause = "Cannot parse date "; 
    cause += date; 
    throw std::invalid_argument(cause); 
    } 
    m_time = mktime(&t); 
    if (m_time == -1) { 
    std::string cause = "Cannot compute epoch from " + date; 
    throw std::range_error(cause); 
    } 
} 

Date::~Date() 
{ 
} 

void 
Date::add(long val, TimeUnit u) { 

    switch(u){ 
    case DAY: 
    m_time += 86400*val; 
    break; 
    case HOUR: 
    m_time += 3600*val; 
    break; 
    case MINUTE: 
    m_time += 60*val; 
    break; 
    case SECOND: 
    m_time += val; 
    break; 
    default: 
    throw std::invalid_argument("Unknown TimeUnit specified"); 
    } 
} 

bool 
Date::operator==(const Date& o) const 
{ 
    return m_time == o.m_time; 
} 

bool 
Date::operator!=(const Date& o) const 
{ 
    return ! (*this==o); 
} 

bool 
Date::operator<(const Date &other) const 
{ 
    return m_time < other.m_time; 
} 

bool 
Date::operator<=(const Date &other) const 
{ 
    return m_time <= other.m_time; 
} 

bool 
Date::operator>(const Date &other) const 
{ 
    return m_time > other.m_time; 
} 

bool 
Date::operator>=(const Date &other) const 
{ 
    return m_time >= other.m_time; 
} 

std::ostream& 
operator<<(std::ostream& out, const Date &d) 
{ 
    struct tm* tm = localtime(&d.m_time); 
    char buffer[20]; 
    strftime(buffer, 20, Date::FORMAT.c_str(), tm); 
    out << buffer << SEPARATOR; 
    return out; 
} 

std::istream& 
operator>>(std::istream &in, Date &d) 
{ 
    std::string buf; 
    std::getline(in, buf, SEPARATOR); 
    Date o(buf); 
    d = o; 
    return in; 
} 

#include <iostream> 
#include <fstream> 

int 
main(void) 
{ 
    Date d; 
    std::cout << d << std::endl; 
    std::ofstream out("tmp.txt"); 
    out << d; 
    out.close(); 

    std::ifstream in("tmp.txt"); 
    Date d2; 
    in >> d2; 
    in.close(); 
    std::cout << d2 << std::endl; 

} 

最後,怎麼我測試它:

$ for i in `seq 1 10`; do echo "test $i:"; ./test; rm tmp.txt; done 
test 1: 
26/03/2016 00:30:31; 
26/03/2016 00:30:31; 
test 2: 
26/03/2016 00:30:31; 
26/03/2016 00:30:31; 
test 3: 
26/03/2016 00:30:31; 
25/03/2016 23:30:31; 
test 4: 
26/03/2016 00:30:31; 
26/03/2016 00:30:31; 
test 5: 
26/03/2016 00:30:31; 
25/03/2016 23:30:31; 
test 6: 
26/03/2016 00:30:31; 
26/03/2016 00:30:31; 
test 7: 
26/03/2016 00:30:31; 
25/03/2016 23:30:31; 
test 8: 
26/03/2016 00:30:31; 
25/03/2016 23:30:31; 
test 9: 
26/03/2016 00:30:31; 
25/03/2016 23:30:31; 
test 10: 
26/03/2016 00:30:31; 
26/03/2016 00:30:31; 
+0

提供一個展示行爲並將其包含在此處的最小工作示例,不要鏈接到某處的代碼(一旦鏈接失效,問題就變得對其他人無用)。 – Borgleader

+0

我無法在OS X上重現它。他說他在Ubuntu上運行,但我沒有得到什麼版本。 – xaxxon

+2

我只是把鏈接中的所有選項卡放在一起(編譯它以確保它能正常工作),並用完整的代碼(你欠我remi)編輯問題 – xaxxon

回答

3

問題是
struct tm t;
在日期構造函數的字符串, 從未初始化對象,從而它的tm_isdst字段有一個未指定的值。正確設置tm_isdst字段,您將在多次運行中得到一致的 結果。

+1

[手冊頁](http://man7.org/linux/man-pages/man3/strptime.3.html)中的相關引用:「原則上,此功能確實不是初始化'tm',而是隻存儲指定的值,這意味着'tm'應該在調用之前初始化。「 – Cornstalks

+1

簡單地做struct tm t {};將解決它。 :P –

+0

這與rici的答案是一樣的,但是標記爲這個,因爲我使用了struct tm {}初始化,這是我不知道的!非常感謝 – remi

3

最有可能的問題是夏令時(也稱爲夏令時)。

struct tm有一個叫做tm_isdst的字段。該字段中的正值表示分解時間在DST中。 0表示它不是DST。

strftime會正確設置字段,但strptime不會觸及它(至少在glibc實現中)。 mktime預計它被正確設置,但幸運的是它允許它被設置爲負值,意思是「我不知道」。在這種情況下,mktime將試圖找出答案。 (這可能是不可能的,因爲每年有一個小時重複,一次是夏令時,一次是冬令時改變後)。

離開tm_isdst字段未初始化,因爲在你的代碼中未定義(和不可預知的)行爲。通常正確的策略是在撥打strptime之後以及致電mktime之前將其設置爲-1。

在調用strptime之前,通常最佳做法是在清除或以其他方式初始化struct tm之前,因爲該函數只設置對應于格式的字段。