How do I construct an ISO 8601 datetime in C++?
Asked Answered
P

13

62

I'm working with the Azure REST API and they are using this to create the request body for table storage:

DateTime.UtcNow.ToString("o")

Which produces:

2012-03-02T04:07:34.0218628Z

It is called "round-trip" and apparently it's an ISO standard (see http://en.wikipedia.org/wiki/ISO_8601) but I have no idea how to replicate it after reading the wiki article.

Does anyone know if Boost has support for this, or possibly Qt?

Pinion answered 2/3, 2012 at 4:25 Comment(0)
S
102

If the time to the nearest second is precise enough, you can use strftime:

#include <ctime>
#include <iostream>

int main() {
    time_t now;
    time(&now);
    char buf[sizeof "2011-10-08T07:07:09Z"];
    strftime(buf, sizeof buf, "%FT%TZ", gmtime(&now));
    // this will work too, if your compiler doesn't support %F or %T:
    //strftime(buf, sizeof buf, "%Y-%m-%dT%H:%M:%SZ", gmtime(&now));
    std::cout << buf << "\n";
}

If you need more precision, you can use Boost:

#include <iostream>
#include <boost/date_time/posix_time/posix_time.hpp>

int main() {
    using namespace boost::posix_time;
    ptime t = microsec_clock::universal_time();
    std::cout << to_iso_extended_string(t) << "Z\n";
}
Sanorasans answered 2/3, 2012 at 4:51 Comment(1)
%Y-%m-%dT%H:%M:%SZ is equivalent to %FT%TZ for those who still have to cope with a C++03 compilerHorizontal
R
59

Using the date library (C++11):

template <class Precision>
string getISOCurrentTimestamp()
{
    auto now = chrono::system_clock::now();
    return date::format("%FT%TZ", date::floor<Precision>(now));
}

Example usage:

cout << getISOCurrentTimestamp<chrono::seconds>();
cout << getISOCurrentTimestamp<chrono::milliseconds>();
cout << getISOCurrentTimestamp<chrono::microseconds>();

Output:

2017-04-28T15:07:37Z
2017-04-28T15:07:37.035Z
2017-04-28T15:07:37.035332Z
Redheaded answered 29/4, 2017 at 0:43 Comment(1)
This only works for C++11 and C++14Zosema
D
28

With C++20, time point formatting (to string) is available in the (chrono) standard library. https://en.cppreference.com/w/cpp/chrono/system_clock/formatter

#include <chrono>
#include <format>
#include <iostream>

int main()
{
   const auto now = std::chrono::system_clock::now();
   std::cout << std::format("{:%FT%TZ}", now) << '\n';
}

Output

2021-11-02T15:12:46.0173346Z

It works in Visual Studio 2019 with the latest C++ language version (/std:c++latest).

Dichotomous answered 2/11, 2021 at 15:20 Comment(0)
L
14

I should point out I am a C++ newb.

I needed string with a UTC ISO 8601 formatted date and time that included milliseconds. I did not have access to boost.

This is more of a hack than a solution, but it worked well enough for me.

std::string getTime()
{
    timeval curTime;
    gettimeofday(&curTime, NULL);

    int milli = curTime.tv_usec / 1000;
    char buf[sizeof "2011-10-08T07:07:09.000Z"];
    char *p = buf + strftime(buf, sizeof buf, "%FT%T", gmtime(&curTime.tv_sec));
    sprintf(p, ".%dZ", milli);

    return buf;
}

The output looks like: 2016-04-13T06:53:15.485Z

Legging answered 13/4, 2016 at 6:55 Comment(1)
I think you want to use sprintf(p, ".%03dZ", milli); to print the milliseconds correctly.Ferryboat
C
5

In Qt, that would be:

QDateTime dt = QDateTime::currentDateTime();
dt.setTimeSpec(Qt::UTC);  // or Qt::OffsetFromUTC for offset from UTC
qDebug() << QDateTime::currentDateTime().toString(Qt::ISODate);
Crookes answered 2/3, 2012 at 9:54 Comment(7)
The numbers in the end is the issue, not the format of the stringPinion
You mean the fractional part of the seconds (i.e. .0218628)? They are optional...Crookes
how? could not see anything in the docPinion
at en.wikipedia.org/wiki/ISO_8601#Times : "Decimal fractions may also be added..."Crookes
Yeah, I fully aware of that. I just wonder how to do it using the Qt approach.Pinion
Ah ok. In most c++ toolkits, time is defined using epoch (time_t, QTime, ...). It is only precise up to the second, so it doesn't make sense (see en.wikipedia.org/wiki/Unix_time)Crookes
Why does your last line call QDateTime::currentDateTime() instead of using dt: qDebug() << dt.toString(Qt::ISODate);Baskett
M
4

Boost has a library for this.

I.e. posix_time has the from_iso_string() and to_iso_string() functions.

Maitilde answered 2/3, 2012 at 5:0 Comment(0)
F
2

OK so I've modified a few solutions that I've found as came up with the following :

static QString getTimeZoneOffset()
{
    QDateTime dt1 = QDateTime::currentDateTime();
    QDateTime dt2 = dt1.toUTC();
    dt1.setTimeSpec(Qt::UTC);

int offset = dt2.secsTo(dt1) / 3600;
if (offset >= 0)
    return QString("%1").arg(offset).rightJustified(2, '0',true).prepend("+");
return QString("%1").arg(offset).rightJustified(2, '0',true);
}

Then to easily format a date ( yyyy-MM-dd'T'HH:mm:ss.SSSZ ) :

static QString toISO8601ExtendedFormat(QDateTime date)
{
    QString dateAsString = date.toString(Qt::ISODate);
    QString timeOffset =  Define::getTimeZoneOffset();
    qDebug() << "dateAsString :" << dateAsString;
    qDebug() << "timeOffset :" << timeOffset;
    timeOffset = QString(".000%1%2").arg(timeOffset).arg("00");
    qDebug() << "timeOffset replaced :" << timeOffset;
    if(dateAsString.contains("Z",Qt::CaseInsensitive))
        dateAsString = dateAsString.replace("Z",timeOffset);
    else
        dateAsString = dateAsString.append(timeOffset);
        qDebug() << "dateAsString :" << dateAsString;
    return dateAsString;
}

For example GMT +2 would look like this : 2013-10-14T00:00:00.000+0200

Fons answered 21/10, 2013 at 13:22 Comment(0)
C
2

You can use this function which uses std::put_time with a std::ostringstream to generate the resulting std::string.

#include <iostream>
#include <chrono>
#include <iomanip>
#include <sstream>
/**
 * Generate a UTC ISO8601-formatted timestamp
 * and return as std::string
 */
std::string currentISO8601TimeUTC() {
  auto now = std::chrono::system_clock::now();
  auto itt = std::chrono::system_clock::to_time_t(now);
  std::ostringstream ss;
  ss << std::put_time(gmtime(&itt), "%FT%TZ");
  return ss.str();
}
// Usage example
int main() {
    std::cout << currentISO8601TimeUTC() << std::endl;
}

Reference: https://techoverflow.net/2018/03/30/iso8601-utc-time-as-stdstring-using-c11-chrono/

Cortege answered 12/9, 2020 at 20:38 Comment(0)
S
1

Tested in Visual C++, GNU C++, Emscripten

#include <ctime>
#include <chrono>
#include <iostream> 
#include <locale>  

#if defined (_WIN32) 
#define WINDOWSLIB 1
#elif defined (__APPLE__)//iOS, Mac OS
#define MACOSLIB 1
#elif defined (__LINUX__) || defined(__gnu_linux__) || defined(__linux__) || defined(__linux) || defined(linux)//_Ubuntu - Fedora - Centos - RedHat
#define LINUXLIB 1
#elif defined (__EMSCRIPTEN__)
#define EMSCRIPTENLIB 1
#endif

#define WriteLine(data)std::cout<< data <<std::endl;
typedef std::string String;

String CurrentISO8601DateTime(bool toUTC=true)
{
    using namespace std::chrono;
    system_clock::time_point now = system_clock::now();
    time_t timet = system_clock::to_time_t(now);
    std::tm tm{};
    String localeStr = setlocale(LC_ALL, nullptr);
    setlocale(LC_ALL, u8"");
    String format = String(u8"%FT%T.").append(std::to_string(duration_cast<milliseconds>(now.time_since_epoch()).count() % static_cast<long long>(1000)));
    if (toUTC)
    {
#ifdef WINDOWSLIB
        gmtime_s(&tm, &timet);
#elif LINUXLIB
        gmtime_r(&timet, &tm);
#elif EMSCRIPTENLIB
        gmtime_r(&timet, &tm);
#endif
        format = format.append(u8"Z");
    }
    else
    {
#ifdef WINDOWSLIB
        localtime_s(&tm, &timet);
#elif LINUXLIB
        localtime_r(&timet, &tm);
#elif EMSCRIPTENLIB
        localtime_r(&timet, &tm);
#endif
        format.append(u8"%z");
    }
    String result = String(255, 0);
    const size_t length = std::strftime(&result[0], result.size(), format.c_str(), &tm);
    result.resize(length);
    setlocale(LC_ALL, localeStr.c_str());
    return result;
}

#define ConsoleWriteLn(data) std::cout<< data <<std::endl;

int main()
{
    ConsoleWriteLn(u8"UTC  : " + CurrentISO8601DateTime());
    ConsoleWriteLn(u8"LOCAL: " + CurrentISO8601DateTime(false));
}


Results

UTC : 2020-04-12T17:00:18.632Z
LOCAL: 2020-04-12T12:00:18.633-0500

You can deserialize normally with Json.NET

Sc answered 12/4, 2020 at 17:5 Comment(0)
A
1

With C++20, to control fraction part of second, we can use time_point_cast.

For .NET user, there is no "fff" equivalent in C++20. Using "fff" in format specification is the .NET style.

std::string strTime;
using namespace std::chrono;
system_clock::time_point tp_100ns = system_clock::from_time_t(SecondsSince1970);
auto tp_s = time_point_cast<seconds>(tp_100ns);

strTime = std::format("{:%FT%TZ}", tp_s);
// 2023-01-24T12:52:26T

strTime = std::format("{:%FT%TZ}", tp_100ns);
// 2023-01-24T12:52:26.0000000T
Amulet answered 11/2, 2023 at 19:24 Comment(0)
P
0

Did it like this:

using namespace boost::posix_time;
ptime t = microsec_clock::universal_time();
qDebug() << QString::fromStdString(to_iso_extended_string(t) + "0Z"); // need 7 digits
Pinion answered 4/3, 2012 at 23:26 Comment(0)
V
0

You can get local or UTC time:

#include <ctime>
#include <iostream>

int main(){
    std::time_t time = std::time(0); // Get current time

    // Construct local time
    char loc[25];
    strftime(loc, sizeof(loc), "%FT%T%z", localtime(&time));

    // Construct UTC time 
    char utc[25];
    strftime(utc, sizeof(utc), "%FT%T%z", gmtime(&time));

    // Print local and UTC time
    std::cout << "Local time: " << loc << std::endl;
    std::cout << "UTC time: " << utc << std::endl;
    return 0;
}
Viaticum answered 2/3, 2021 at 12:6 Comment(1)
You shouldn't suppend Z if it's not Zulu (UTC)Arboreous
B
0

I did this function:

  • Compatible ISO 8601
  • Having milliseconds (other solution does not have this)
  • Having time zone
  • C++11 only
  • Without external lib such as date

(Amazing how it is hard to do simple stuffs)

Result example

2023-03-30T19:49:53.005+0200

Code

#include <iostream>
#include <chrono>
#include <iomanip>
#include <sstream>

std::string getISODateTime(const std::chrono::system_clock::time_point& time)
{
    time_t tt = std::chrono::system_clock::to_time_t(time);
    tm tt2;

    localtime_s(&tt2, &tt); // If not recognized try: localtime_r(&tt, &tt2); !swapped params!

    // Get milliseconds hack
    auto timeTruncated = std::chrono::system_clock::from_time_t(tt);
    int ms = std::chrono::duration_cast<std::chrono::milliseconds>(time - timeTruncated).count();

    return (std::stringstream()
        << std::put_time(&tt2, "%FT%T") // "2023-03-30T19:49:53"
        << "." << std::setw(3) << std::setfill('0') << ms // ".005"
        << std::put_time(&tt2, "%z") // "+0200" (time zone offset, optional)
        ).str();
}


// Usage example
int main()
{
    std::cout << getISODateTime(std::chrono::system_clock::now()) << std::endl;
}
Baklava answered 30/3, 2023 at 18:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.