Skip to content
C++
Library
since C++98
Basic

Time Ctime

C-style date and time facilities in <ctime>: time_t, tm, clock, localtime, gmtime, strftime, and safe interop with std::chrono.

<ctime>since C++98

The <ctime> header exposes the C library's date and time facilities (std::time_t, std::tm, std::clock_t, std::time, std::localtime, std::strftime, and related functions) in namespace std.

Overview

<ctime> is the C++ wrapper for C's <time.h>. It defines two kinds of time representation:

  • Calendar time (std::time_t): an arithmetic type holding a point in time. Its epoch and resolution are implementation-defined, but on all mainstream platforms it is a signed integer (64-bit on modern systems) counting seconds since 1970-01-01 00:00:00 UTC. Since C++11 the standard does not guarantee this, though POSIX does.
  • Broken-down time (std::tm): a struct with tm_sec, tm_min, tm_hour, tm_mday, tm_mon (0–11), tm_year (years since 1900), tm_wday (0 = Sunday), tm_yday, and tm_isdst.
  • Processor time (std::clock_t): ticks returned by std::clock(), scaled by CLOCKS_PER_SEC.

Also available: std::timespec and std::timespec_get (since C++17), giving nanosecond-granularity wall-clock readings.

Since C++11, <chrono> is the preferred API for durations, clocks, and timing. <ctime> remains necessary for calendar conversion (until C++20 calendars/time zones, available only in newer library implementations) and for legacy interop. std::chrono::system_clock is guaranteed to bridge to it via to_time_t / from_time_t (C++11).

Syntax

cpp
#include <ctime>

std::time_t time(std::time_t* out);                    // C++98: current calendar time
double      difftime(std::time_t end, std::time_t beg);// C++98: seconds as double
std::time_t mktime(std::tm* t);                        // C++98: local tm -> time_t, normalizes fields
std::clock_t clock();                                  // C++98: CPU time used by process

std::tm* localtime(const std::time_t* t);              // C++98: time_t -> local tm (static buffer)
std::tm* gmtime(const std::time_t* t);                 // C++98: time_t -> UTC tm (static buffer)

std::size_t strftime(char* buf, std::size_t max,
                     const char* fmt, const std::tm* t); // C++98: formatted output

char* asctime(const std::tm* t);                       // C++98: fixed "Www Mmm dd hh:mm:ss yyyy\n"
char* ctime(const std::time_t* t);                     // C++98: asctime(localtime(t))

int std::timespec_get(std::timespec* ts, int base);    // C++17: TIME_UTC

asctime and ctime return pointers to shared static storage and always include a trailing newline. They are deprecated in C (C23 removes asctime/ctime); prefer strftime.

Examples

Formatting the current time safely

localtime returns a pointer to a shared static std::tm, so it is not thread-safe. Use the platform re-entrant variant:

cpp
#include <chrono>
#include <ctime>
#include <string>

std::string iso_local(std::chrono::system_clock::time_point tp) {
    std::time_t t = std::chrono::system_clock::to_time_t(tp);   // C++11
    std::tm tm{};
#if defined(_WIN32)
    localtime_s(&tm, &t);            // MSVC: note reversed argument order
#else
    localtime_r(&t, &tm);            // POSIX
#endif
    char buf[32];
    std::size_t n = std::strftime(buf, sizeof buf, "%Y-%m-%d %H:%M:%S", &tm);
    return std::string(buf, n);      // n == 0 means buffer too small
}

(C11 Annex K's localtime_s has yet another signature, returning tm* with (const time_t*, tm*); it is optional and rarely available outside MSVC.)

UTC timestamps for logs

cpp
#include <ctime>
#include <string>

std::string utc_timestamp(std::time_t t) {
    std::tm tm{};
#if defined(_WIN32)
    gmtime_s(&tm, &t);
#else
    gmtime_r(&t, &tm);
#endif
    char buf[21];
    std::strftime(buf, sizeof buf, "%Y-%m-%dT%H:%M:%SZ", &tm);
    return buf;
}

Date arithmetic with mktime normalization

mktime normalizes out-of-range fields, which makes "add N days" trivial:

cpp
#include <ctime>

std::time_t add_days(std::time_t t, int days) {
    std::tm tm{};
#if defined(_WIN32)
    localtime_s(&tm, &t);
#else
    localtime_r(&t, &tm);
#endif
    tm.tm_mday += days;      // may exceed month length
    tm.tm_isdst = -1;        // let the library determine DST
    return std::mktime(&tm); // returns time_t(-1) on failure
}

Setting tm_isdst = -1 matters: adding days across a DST boundary otherwise yields a one-hour error.

Parsing with C++11 get_time

cpp
#include <ctime>
#include <iomanip>
#include <sstream>

std::time_t parse(const std::string& s) {
    std::tm tm{};
    std::istringstream in(s);
    in >> std::get_time(&tm, "%Y-%m-%d %H:%M:%S");   // C++11, <iomanip>
    if (in.fail()) return std::time_t(-1);
    tm.tm_isdst = -1;
    return std::mktime(&tm);
}

Measuring CPU time vs wall time

cpp
#include <chrono>
#include <ctime>
#include <iostream>

template <class F>
void profile(F&& f) {
    std::clock_t c0 = std::clock();
    auto w0 = std::chrono::steady_clock::now();      // C++11
    f();
    auto w1 = std::chrono::steady_clock::now();
    std::clock_t c1 = std::clock();

    double cpu  = double(c1 - c0) / CLOCKS_PER_SEC;
    double wall = std::chrono::duration<double>(w1 - w0).count();
    std::cout << "cpu=" << cpu << "s wall=" << wall << "s\n";
}

clock() measures CPU time of the process: on POSIX it sums all threads, so a multithreaded run can report more CPU time than wall time. On Windows MSVC it historically returns wall-clock time. Do not use it for portable benchmarking.

Formatting in C++20

cpp
#include <chrono>
#include <format>

auto now = std::chrono::system_clock::now();
auto s = std::format("{:%Y-%m-%d %H:%M:%S}", std::chrono::floor<std::chrono::seconds>(now)); // C++20

std::format can also accept a std::tm (e.g. *std::localtime(&t)) with chrono format specifiers. This still inherits localtime's static-buffer hazard.

Best Practices

  • Use <chrono> clocks (steady_clock for intervals, system_clock for wall time) and convert to time_t only at the boundary where a calendar breakdown is required.
  • Always call re-entrant variants (localtime_r, gmtime_r, localtime_s, gmtime_s) in threaded code.
  • Check return values: localtime can return nullptr, mktime returns -1 on failure, and strftime returns 0 when the buffer is too small (the buffer contents are then unspecified).
  • Initialize std::tm with {} before filling it; unset fields such as tm_isdst or tm_gmtoff cause nondeterministic mktime results.
  • Store and transmit timestamps in UTC (gmtime, ISO 8601) and convert to local time only for display.
  • Use difftime rather than subtracting time_t values when portability to non-integer representations matters; in practice, on mainstream platforms, subtraction works.
  • On 32-bit time_t platforms, overflow occurs in January 2038. Verify sizeof(std::time_t) when targeting embedded or legacy systems.
  • Prefer std::random_device or <random> (C++11) over srand(time(nullptr)) for seeding. Second-resolution seeds collide when processes start within the same second.

Common Pitfalls

Shared static buffers. localtime, gmtime, ctime, and asctime each return pointers to storage that later calls overwrite. This is a bug in this code:

cpp
std::tm* a = std::localtime(&t1);
std::tm* b = std::localtime(&t2);   // a and b now point to the same object

Also, localtime and gmtime typically share one buffer, so interleaving them corrupts results.

Trailing newline. ctime and asctime append '\n'. Strip it before logging.

Off-by-offset fields. tm_year is years since 1900, and tm_mon is 0-based. tm_mday is 1-based. Mixing these is the most common calendar bug.

Assuming time_t is seconds since 1970. True on POSIX, but not guaranteed by ISO C++. Arithmetic like t + 86400 to mean "tomorrow" ignores DST. Use mktime normalization instead.

Treating system_clock as monotonic. system_clock can jump when NTP or an administrator adjusts the clock. For timeouts and elapsed time use steady_clock.

mktime interprets tm as local time. There is no standard timegm. Use timegm (glibc/BSD), _mkgmtime (MSVC), or, since C++20, std::chrono::sys_days and year_month_day conversions on library implementations that support them.

Uninitialized time_t members. std::time_t has no constructor, so a class member of that type is indeterminate unless explicitly initialized (std::time_t expiry{};). Combined with member-initialization-order mistakes, this causes subtle bugs.

clock() wrap-around and units. clock_t may be 32 bits on some systems (wrapping after about 72 minutes with CLOCKS_PER_SEC == 1000000). POSIX requires CLOCKS_PER_SEC to be 1,000,000 regardless of actual resolution.

See Also

  • <chrono>: durations, steady_clock, system_clock (C++11); calendar and time-zone types (C++20)
  • std::put_time / std::get_time in <iomanip> (C++11) for stream-based formatting and parsing
  • <random> (C++11) for seeding and generation instead of srand/rand
  • std::format with chrono specifiers (C++20)
  • std::filesystem::file_time_type (C++17), whose conversion to time_t was not portable until std::chrono::clock_cast (C++20)