HiveBrain v1.2.0
Get Started
← Back to all entries
snippetcppMinor

C++ time types and format conversions

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
formatconversionstimetypesand

Problem

I wrote a small header-only C++11 header for converting between various C++ time formats:

// time_utils/time_cast.hpp
#ifndef TIME_CAST_HPP
#define TIME_CAST_HPP

#include 
#include 
#include 
#include 
#include 

namespace time_utils {

using chrono_time = std::chrono::time_point;

// TimeProxy
template 
class TimeProxy {
};

template <>
class TimeProxy {
public:
    TimeProxy(std::time_t time) : _time(time) {}
    std::string operator()(const char* format) {
        std::stringstream ss;
        ss 
class TimeProxy {
public:
    TimeProxy(std::time_t time) : _time(time) {}
    chrono_time operator()() {
        return std::chrono::system_clock::from_time_t(_time);
    }
private:
    std::time_t _time;
};

template <>
class TimeProxy {
public:
    TimeProxy(std::time_t time) : _time(time) {}
    std::time_t operator()() {
        return _time;
    }
private:
    std::time_t _time;
};

// time_cast
template 
TimeProxy time_cast(std::string in, const char* format) {
    // read in to std::time_t
    struct tm bk_time;  
    strptime(in.c_str(), format, &bk_time);
    std::time_t time = mktime(&bk_time);
    return TimeProxy(time);
}

template 
TimeProxy time_cast(std::time_t time) {
    // pass time_t directly
    return TimeProxy(time);
}

template 
TimeProxy time_cast(chrono_time in) {
    // read in to std::time_t
    std::time_t time = std::chrono::system_clock::to_time_t(in);
    return TimeProxy(time);
}

}

#endif


To cast one time to another you have to use time_utils::time_cast template function, passing target type as a template argument. The input type will be deduced:

time_utils::time_cast(input_time)();


The following types are supported:

  • std::string



  • std::time_t



  • std::chrono::time_point



In order to read strings you also have to pass strftime format inside the first pair of parentheses:

time_utils::time_cast(input_string, "%T")();


In order get strings you have to pass format string in a second pair of parenth

Solution

Your template class looks useful, and the usage is made quite concise with the explicit declaration/definition of the time_cast() variants.

That's somehow replicating an interface like the standard cast implementations (static_cast<>(), reinterpret_cast<>(), etc.) do.

So just my 2¢ so far:

DRY

First when looking at your code, I was bothered a bit regarding the repetition of the common class layout in your specializations regarding the _time member variable.

Though, after factoring out your common data member to a common base class like this

class TimeProxyBase {
protected:
    TimeProxyBase(std::time_t time) : _time(time) {}
    std::time_t _time;
};

// TimeProxy
template 
class TimeProxy {
};

template <>
class TimeProxy : private TimeProxyBase {
public:
    TimeProxy(std::time_t time) : TimeProxyBase(time) {}
    std::string operator()(const char* format) {
        std::stringstream ss;
        ss _time)), format);
        return ss.str();
    }
};

template <>
class TimeProxy : private TimeProxyBase {
public:
    TimeProxy(std::time_t time) : TimeProxyBase(time) {}
    chrono_time operator()() {
        return std::chrono::system_clock::from_time_t(this->_time);
    }
};

template <>
class TimeProxy : private TimeProxyBase {
public:
    TimeProxy(std::time_t time) : TimeProxyBase(time) {}
    std::time_t operator()() {
        return this->_time;
    }
};


I have my doubts, if that really improves the code you have already. At least it doesn't regarding less typing.

On the other hand, if there would be more common data members I'd go that way.

The refactored code can be inspected here.

Do not use a prefix _ underscore for your own applications

The prefix underscore is preserved for C++ standard class implementations and/or compiler internals (see more detailed info here please). Rather use a postfix underscore for naming, and distinguishing member variable and parameter names:

std::time_t time_;


or

std::time_t time;


and

TimeProxy(std::time_t time_) : time(time_) {


Consider using a const reference in the constructor

Though it's very probable that time_t might be a simple positive integer number or anyways movable type, it might be better to have an explicit const reference than copy by value

TimeProxy(const std::time_t& time_);


for clarity of semantics and clearly no need for copying (at point of passing the parameter).

I know this is heavily discussed and passing by value seems to be preferred (because of standard moving behaviors should apply). Though I personally prefer the const reference style and the actual type of time_t is unspecified according this reference.

Using a const reference shouldn't do any harm anyways (besides typing a bit more).

Code Snippets

class TimeProxyBase {
protected:
    TimeProxyBase(std::time_t time) : _time(time) {}
    std::time_t _time;
};

// TimeProxy
template <typename T>
class TimeProxy {
};

template <>
class TimeProxy<std::string> : private TimeProxyBase {
public:
    TimeProxy(std::time_t time) : TimeProxyBase(time) {}
    std::string operator()(const char* format) {
        std::stringstream ss;
        ss << std::put_time(std::localtime(&(this->_time)), format);
        return ss.str();
    }
};

template <>
class TimeProxy<chrono_time> : private TimeProxyBase {
public:
    TimeProxy(std::time_t time) : TimeProxyBase(time) {}
    chrono_time operator()() {
        return std::chrono::system_clock::from_time_t(this->_time);
    }
};

template <>
class TimeProxy<std::time_t> : private TimeProxyBase {
public:
    TimeProxy(std::time_t time) : TimeProxyBase(time) {}
    std::time_t operator()() {
        return this->_time;
    }
};
std::time_t time_;
std::time_t time;
TimeProxy(std::time_t time_) : time(time_) {
TimeProxy(const std::time_t& time_);

Context

StackExchange Code Review Q#124395, answer score: 2

Revisions (0)

No revisions yet.