2016-03-25 3 views
1

Я написал класс C++, который представляет Date, и я использую strptime/strftime для записи и создания экземпляров Dates from string.Непоследовательные результаты с использованием strptime/strftime

The full code is here

Когда я запускаю его несколько раз на моем 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;
в конструктор Date, который принимает строку, , который никогда не инициализирует объект, и, таким образом, его поле tm_isdst имеет неопределенное значение. Установите поле tm_isdst правильно, и вы получите согласованные результаты за несколько прогонов.

+1

Соответствующая цитата из [man page] (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. A 0 указывает, что это не DST.

strftime будет правильно установить поле, но strptime не прикасается к нему (по крайней мере, в реализации glibc). mktime ожидает, что он будет правильно установлен, но, к счастью, он позволяет установить отрицательное значение, что означает «я не знаю». В этом случае mktime попытается понять это. (Это может быть невозможно, потому что есть один час каждый год, который повторяется один раз в летнее время и один раз в зимнее время после смены часов.)

Оставляя поле tm_isdst неинициализированным, так как в вашем коде не определено (и непредсказуемым) поведением. Обычно правильная стратегия заключается в том, чтобы установить ее на -1 после вызова strptime и до вызова mktime.

Как правило, лучше всего обнулить или иным образом инициализировать struct tm перед вызовом strptime, поскольку эта функция устанавливает только поля, соответствующие формату.

Смежные вопросы