Recently, I had a project which needed to hold a date with the following properties:
- Hold the year, month, and day
- Take a C string (char*) in the format “YYYYMMDD”
- Do some basic validation and assumption fixing (disallow blank & empty input)
- Produce a string in a specified format for JSON output
My extensive experience in C and relative unfamiliarity the C++ libraries prompted me to do some research to see if there’s a commonly accepted way to handle dates that meets my requirements. Given that I’m trying to introduce C++ to this code base any clean and common ways to do things in C++ are a priority for me.
What I found was disheartening.
EDIT — See section below regarding the possible use of the date library from Howard Hinnant.
Future possible silver bullet from std::chrono
One of the first choices I found is std::chrono::year_month_day. My heart rejoiced with a feeling of hope when I saw this because it looked to hold exactly what I need. Given that it’s in the standard library I figured that it would have relatively easy ways of loading and manipulating the date. That rejoicing stopped when I realized that:
- It’s coming in C++20. The project is just getting to C++14 so that’s not going to happen any time soon given the difficulty of updating compilers / standards for long running projects.
- It’s specification as listed by cppreference.com only shows that it holds the values and provides ways to create objects. While helpful, it’s not as complete as I’d like for something in the standard library. This can be overcome easily, though.
- It’s new enough to not even listed on cplusplus.com.
This type and the other date types that are coming along with it leave me with hope that there will, eventually, be easy and clear ways to handle dates in the C++ standard library. For the moment, however, this doesn’t help me out on my project.
Back to the old way of doing things … maybe
From here, I decided to take a look at good old struct tm. It is usable in C (of which we have plenty) if I needed to use it there. Additionally, I figured that there might be some good C++ functionality that would make it simple and clear to handle string manipulation with a date. Sure enough, I found std::get_time which plays nice with std::stringstream for loading the struct and takes a format for specifying what fields to fill from the input. From there, printing the fields into the appropriate JSON format is easy. WIN!
Well, not so fast! Of course I ran into problems.
The first problem I ran into was on Windows and Visual Studio 2017 (which is new to us; as mentioned earlier, upgrading compilers on long running projects is a pain). It turns out that, even as of VS2017, the Microsoft implementation of get_time requires there to be separators in the input between the fields you’re trying to pull out into the date. This caused problems for me because the input data doesn’t have separators which means more work to get it into a state that will properly be interpreted by std::get_time(). As a side note, I remember finding a description in Microsoft’s documentation that specifically says it requires separators/delimiters but I’m not finding it again. Here is a StackOverflow question with an answer describing what I was seeing.
That StackOverflow question and answer actually leads me into my second problem using struct tm. The GNU version of std::get_time() (linked above) doesn’t mention that it needs separators but, as the SO answer points out, you need at least GCC 5.0. Which, because updating compilers on long running projects is hard (yes, this argument again), we don’t have on our linux boxes. So, std::get_time() goes out the window as an option.
Last possible option before writing it ourselves
The last option that I came across for manipulating dates is the Boost.Date_Time library. You’ll notice that I only linked the top level page for this part of Boost and don’t list any specific possible types or functions to use. The main reason for this is that we’re not presently using Boost. I have heard a great many good things over the years about Boost. The fact that they’re ahead of the curve when compared to the standard libraries and typically have things implemented before the standards are decided upon is awesome. That being said, I couldn’t justify the time to learn the Boost way of doing things and include it JUST for what is a relatively simple date need.
EDIT — CPPLang Slack Community and Howard Hinnant to the possible rescue!
Thanks to the CPPLang slack community, I was pointed toward the date library from Howard Hinnant. The main reason that I was pointed toward this library is that it is, to my understanding, the reference implementation on which the C++20 standard types in std::chrono will be based. Howard even reached out to me personally on Slack to show me an example of what I needed. Talk about customer service!
I spent some time today looking over the library and Howard’s example and the mechanics look very similar to what the std::chrono types will be like. Between that and support that goes back to C++11 this should be a solution that will work. Unfortunately, I was told that, while this looks awesome, our need right now is simple, solved by what we wrote, and we’ve got other things to focus on. So, in the end, my end result below still stands and I hope I’ll have the chance come back to this soon.
End result … We’ll just do it ourselves
In the end I, after discussing things with an architect on our team, decided to implement a simple date type myself. I decided to do this at this time because our current needs are simple and it didn’t take that long to implement, unit test, and test manually. I am a big fan of doing things by convention BUT I am also a fan of not over engineering something for the sake of convention when there is no good convention.
Thank you for taking the time to read through this! If you know of a better way to handle simple things with dates in C++ in a standard way, PLEASE let me know. I would love to hear it!
One Response
Thanks for publicising the shortcoming of the Windows solution; i.e. that separators are needed. I wasted an hour or two wondering what was going on.