ctime(3)
๐ ctime(3) ๐ โ Library Functions Manual โจ
okay so welcome to the ctime(3) glow-up, bestie ๐ซถ this page lives in the Library Functions Manual and it's literally your one-stop shop for turning date-and-time values into broken-down time or into ASCII strings. it's giving timezone chaos, it's giving static buffers, it's giving nostalgia. let's get into it ๐
๐ธ NAME ๐ธ
asctime, ctime, gmtime, localtime, mktime, asctime_r, ctime_r, gmtime_r, localtime_r โ transform date and time to broken-down time or ASCII, no cap โฐโจ
๐ LIBRARY ๐
Standard C library (libc, -lc) โ the classic, the icon, the one you're already linking against ๐
โจ SYNOPSIS โจ
#include <time.h>
char *asctime(const struct tm *tm);
char *asctime_r(const struct tm *restrict tm,
char buf[restrict 26]);
char *ctime(const time_t *timep);
char *ctime_r(const time_t *restrict timep,
char buf[restrict 26]);
struct tm *gmtime(const time_t *timep);
struct tm *gmtime_r(const time_t *restrict timep,
struct tm *restrict result);
struct tm *localtime(const time_t *timep);
struct tm *localtime_r(const time_t *restrict timep,
struct tm *restrict result);
time_t mktime(struct tm *tm);
๐ง Feature Test Macro Requirements for glibc ๐ง
(see <pink>feature_test_macros(7)</pink> if you're like "what is a feature test macro" โ it's fine, we've all been there ๐)
asctime_r(), ctime_r(), gmtime_r(), localtime_r():
_POSIX_C_SOURCE
|| /* glibc <= 2.19: */ _BSD_SOURCE || _SVID_SOURCE
๐ DESCRIPTION ๐
okay so first things first, there are two whole flavours of time on this page and you gotta know which one you're holding, fr fr ๐ฅ
ctime(), gmtime(), and localtime() all take an argument of data type time_t, which represents calendar time. when you read it as an absolute time value, it's the number of seconds that have elapsed since the Epoch, 1970-01-01 00:00:00 +0000 (UTC). that's it. that's the whole vibe. ๐ฐ๏ธ
asctime() and mktime() both take an argument representing broken-down time โ which is that chopped-up representation split into year, month, day, and so on. very "let's break the timeline into little pieces and label them" energy ๐งฉ
broken-down time is stored in the structure tm, which is described over in tm(3type). go say hi to it ๐
ctime(t) is literally equivalent to asctime(localtime(t)). it converts the calendar time t into a null-terminated string that looks exactly like this:
"Wed Jun 30 21:49:08 1993\n"
the abbreviations for the days of the week are "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", and "Sat". the abbreviations for the months are "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", and "Dec". ๐
โ ๏ธ bestie, listen: the return value points to a statically allocated string that might get overwritten by subsequent calls to any of the date and time functions. it's not yours. it never was. do not hold onto it ๐ญ
the function also sets the external variables tzname, timezone, and daylight as if it called tzset(3). the reentrant version ctime_r() does the same, but it stores the string in a user-supplied buffer that should have room for at least 26 bytes. it need not set tzname, timezone, and daylight. ๐
gmtime() converts the calendar time timep to broken-down time representation, expressed in Coordinated Universal Time (UTC). it may return NULL when the year does not fit into an integer โ that's your sign to slow down ๐ญ. the return value points to a statically allocated struct which might be overwritten by subsequent calls to any of the date and time functions. gmtime_r() does the same, but stores the data in a user-supplied struct. ๐ฆ
localtime() converts the calendar time timep to broken-down time representation, expressed relative to the user's specified timezone. the function also sets the external variables tzname, timezone, and daylight as if it called tzset(3). the return value points to a statically allocated struct which might be overwritten by subsequent calls to any of the date and time functions. localtime_r() does the same, but stores the data in a user-supplied struct. it need not set tzname, timezone, and daylight. ๐
asctime() converts the broken-down time value tm into a null-terminated string with the same format as ctime(). the return value points to a statically allocated string which might be overwritten by subsequent calls to any of the date and time functions (yes, again, it's that kind of page ๐ญ). asctime_r() does the same, but stores the string in a user-supplied buffer which should have room for at least 26 bytes. ๐
mktime() converts a broken-down time structure, expressed as local time, to calendar time representation. here's the fine print, bestie, pay attention ๐
- it ignores the values supplied by the caller in the
tm_wdayandtm_ydayfields. literally does not care. - the value in the
tm_isdstfield tellsmktime()whether daylight saving time (DST) is in effect for the time supplied in thetmstructure:- a positive value means DST is in effect โ
- zero means DST is not in effect โ
- a negative value means
mktime()should (use timezone information and system databases to) attempt to determine whether DST is in effect at the specified time. it's gonna investigate ๐
- see
timegm(3)for a UTC equivalent of this function. ๐
mktime() modifies the fields of the tm structure as follows: tm_wday and tm_yday get set to values determined from the contents of the other fields; if structure members are outside their valid interval, they get normalized (so that, for example, 40 October is changed into 9 November โ glow-up math ๐); and tm_isdst is set (regardless of its initial value) to a positive value or to 0, respectively, to indicate whether DST is or is not in effect at the specified time. the function also sets the external variables tzname, timezone, and daylight as if it called tzset(3).
๐จ if the specified broken-down time cannot be represented as calendar time (seconds since the Epoch),mktime()returns(time_t) -1and does not alter the members of the broken-down time structure. it just walks away. no changes. periodt. ๐
๐ RETURN VALUE ๐
- on success,
gmtime()andlocaltime()return a pointer to astruct tm. ๐ซถ - on success,
gmtime_r()andlocaltime_r()return the address of the structure pointed to by result. - on success,
asctime()andctime()return a pointer to a string. โจ - on success,
asctime_r()andctime_r()return a pointer to the string pointed to by buf. - on success,
mktime()returns the calendar time (seconds since the Epoch), expressed as a value of typetime_t. โฑ๏ธ - on error,
mktime()returns the value(time_t) -1, and leaves thetm->tm_wdaymember unmodified. the remaining functions returnNULLon error. on error,errnois set to indicate the error. ๐ญ
๐ฅ ERRORS ๐ฅ
EOVERFLOWโ the result cannot be represented. that's it, that's the error. the number is simply too much ๐
๐ ATTRIBUTES ๐
for an explanation of the terms used in this section, see attributes(7).
asctime()โ Thread safety:MT-Unsafe race:asctime localeasctime_r()โ Thread safety:MT-Safe localectime()โ Thread safety:MT-Unsafe race:tmbuf race:asctime env localectime_r(),gmtime_r(),localtime_r(),mktime()โ Thread safety:MT-Safe env localegmtime(),localtime()โ Thread safety:MT-Unsafe race:tmbuf env locale
note the pattern bestie: the _r versions are the MT-Safe girlies, the plain ones are out here racing each other ๐โโ๏ธ๐จ
๐ฆ VERSIONS ๐ฆ
- POSIX doesn't specify the parameters of
ctime_r()to berestrict; that is specific to glibc. so don't go assuming that everywhere, iykyk ๐ - in many implementations, including glibc, a
0intm_mdayis interpreted as meaning the last day of the preceding month. iconic little quirk, honestly. - according to POSIX.1,
localtime()is required to behave as thoughtzset(3)was called, whilelocaltime_r()does not have this requirement. so for portable code,tzset(3)should be called beforelocaltime_r(). write that down ๐
โญ STANDARDS โญ
asctime()ctime()gmtime()localtime()mktime()โ C23, POSIX.1-2024. ๐gmtime_r()localtime_r()โ POSIX.1-2024.asctime_r()ctime_r()โ None. ๐ถ (not a single standard claimed them, we're rooting for them anyway)
๐ธ HISTORY ๐ธ
gmtime()localtime()mktime()โ C89, POSIX.1-1988. the old guard ๐ซกasctime()ctime()โ C89, POSIX.1-1988. Marked obsolescent in C23 and in POSIX.1-2008 (recommendingstrftime(3)).gmtime_r()localtime_r()โ POSIX.1-1996.asctime_r()ctime_r()โ POSIX.1-1996. Marked obsolescent in POSIX.1-2008. Removed in POSIX.1-2024 (recommendingstrftime(3)).
๐ so yeah, theasctime/ctimefamily has been getting the "we recommendstrftime(3)instead" note for a while now. they still work, they're still here, but the standards girlies have moved on.
๐ CAVEATS ๐
okay bestie, sit down, because this is the part where the manpage gets real with us ๐ซถ none of this is scary, it's just... the truth, and you deserve to know it.
๐งต Thread safety ๐งต
The four functions asctime(), ctime(), gmtime(), and localtime() all hand you back a pointer to static data, and that is exactly why they are not thread-safe. ๐ญ The grown-up, thread-safe versions โ asctime_r(), ctime_r(), gmtime_r(), and localtime_r() โ are specified by SUSv2. ๐
And POSIX.1 has โจopinionsโจ, quote:
"Theasctime(),ctime(),gmtime(), andlocaltime()functions shall return values in one of two static objects: a broken-down time structure and an array of typechar. Execution of any of the functions that return a pointer to one of these object types may overwrite the information in any object of the same type pointed to by the value returned from any previous call to any of them."
Translation, bestie: they are all fighting over the same two little memory spots. ๐ And yes โ this can occur in the glibc implementation.
โฐ mktime() โฐ
(time_t) -1 can represent a valid time โ it is literally one second before the Epoch, bestie. So if you're trying to figure out whether mktime() failed, you can NOT just vibe-check the return value ๐ญ you have to look at the tm->tm_wday field. There's a whole example program waiting for you down in EXAMPLES, go look. ๐
The handling of a non-negative tm_isdst in mktime() is poorly specified, and passing a value that is incorrect for the time specified gives you unspecified results. ๐ And here's the tea: mktime() is one of the few functions that actually knows when DST is in effect, so coming up with the correct value yourself can be genuinely difficult.
The workaround, and she's iconic: call mktime() twice โ once with tm_isdst set to zero, and once with tm_isdst set to a positive value โ and discard the results from the call that changes it. ๐
- If neither call changes
tm_isdst, then the time specified probably happens during a fall-back period where DST begins or ends, and both results are valid but represent two different times. ๐ - If both calls change it, that could indicate a fall-forward transition, or some other reason why the time specified does not exist. ๐ซ
And finally, the plot twist: the specification of time zones and daylight saving time is up to regional governments, they change often, and they may include discontinuities beyond mktime's ability to document a result. For example, a change in the timezone definition may cause a clock time to be repeated or skipped without a corresponding DST change. ๐
โจ EXAMPLES โจ
The program below defines a wrapper that lets you detect invalid and ambiguous times, with EINVAL and ENOTUNIQ, respectively. ๐
The following shell session shows sample runs of the program:
$ TZ=UTC ./a.out 1969 12 31 23 59 59 0;
-1
$
$ export TZ=Europe/Madrid;
$
$ ./a.out 2147483647 2147483647 00 00 00 00 -1;
a.out: mktime: Value too large for defined data type
$
$ ./a.out 2024 08 23 00 17 53 -1;
1724365073
$ ./a.out 2024 08 23 00 17 53 0;
a.out: my_mktime: Invalid argument
1724368673
$ ./a.out 2024 08 23 00 17 53 1;
1724365073
$
$ ./a.out 2024 02 23 00 17 53 -1;
1708643873
$ ./a.out 2024 02 23 00 17 53 0;
1708643873
$ ./a.out 2024 02 23 00 17 53 1;
a.out: my_mktime: Invalid argument
1708640273
$
$ ./a.out 2023 03 26 02 17 53 -1;
a.out: my_mktime: Invalid argument
1679793473
$
$ ./a.out 2023 10 29 02 17 53 -1;
a.out: my_mktime: Name not unique on network
1698542273
$ ./a.out 2023 10 29 02 17 53 0;
1698542273
$ ./a.out 2023 10 29 02 17 53 1;
1698538673
$
$ ./a.out 2023 02 29 12 00 00 -1;
a.out: my_mktime: Invalid argument
1677668400
๐ Program source: mktime.c
#include <err.h>
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#define is_signed(T) ((T) -1 < 1)
static time_t my_mktime(struct tm *tp);
int
main(int argc, char *argv[])
{
char **p;
time_t t;
struct tm tm;
if (argc != 8) {
fprintf(stderr, "Usage: %s yyyy mm dd HH MM SS isdst\n", argv[0]);
exit(EXIT_FAILURE);
}
p = &argv[1];
tm.tm_year = atoi(*p++) - 1900;
tm.tm_mon = atoi(*p++) - 1;
tm.tm_mday = atoi(*p++);
tm.tm_hour = atoi(*p++);
tm.tm_min = atoi(*p++);
tm.tm_sec = atoi(*p++);
tm.tm_isdst = atoi(*p++);
errno = 0;
tm.tm_wday = -1;
t = my_mktime(&tm);
if (tm.tm_wday == -1)
err(EXIT_FAILURE, "mktime");
if (errno == EINVAL || errno == ENOTUNIQ)
warn("my_mktime");
if (is_signed(time_t))
printf("%jd\n", (intmax_t) t);
else
printf("%ju\n", (uintmax_t) t);
exit(EXIT_SUCCESS);
}
static time_t
my_mktime(struct tm *tp)
{
int e, isdst;
time_t t;
struct tm tm;
unsigned char wday[sizeof(tp->tm_wday)];
e = errno;
tm = *tp;
isdst = tp->tm_isdst;
memcpy(wday, &tp->tm_wday, sizeof(wday));
tp->tm_wday = -1;
t = mktime(tp);
if (tp->tm_wday == -1) {
memcpy(&tp->tm_wday, wday, sizeof(wday));
return -1;
}
if (isdst == -1)
tm.tm_isdst = tp->tm_isdst;
if ( tm.tm_sec != tp->tm_sec
|| tm.tm_min != tp->tm_min
|| tm.tm_hour != tp->tm_hour
|| tm.tm_mday != tp->tm_mday
|| tm.tm_mon != tp->tm_mon
|| tm.tm_year != tp->tm_year
|| tm.tm_isdst != tp->tm_isdst)
{
errno = EINVAL;
return t;
}
if (isdst != -1)
goto out;
tm = *tp;
tm.tm_isdst = !tm.tm_isdst;
tm.tm_wday = -1;
mktime(&tm);
if (tm.tm_wday == -1)
goto out;
if (tm.tm_isdst != tp->tm_isdst) {
errno = ENOTUNIQ;
return t;
}
out:
errno = e;
return t;
}
๐ SEE ALSO ๐
date(1)gettimeofday(2)time(2)utime(2)clock(3)difftime(3)strftime(3)strptime(3)timegm(3)tzset(3)time(7)
and that's a wrap, bestie!! ๐ this whole thing came to you from Linux man-pages 6.20, dated 2026-02-08 โ go forth, format your timestamps, and remember: always check tm_wday, never trust -1. ๐โจ๐ฆ
๐ฆ where this page comes from
- arch rolling: man-pages 6.20-1 ยท BSD-2-Clause AND BSD-3-Clause AND BSD-4.3TAHOE AND BSD-4-Clause-UC AND GPL-1.0-or-later AND GPL-2.0-only AND GPL-2.0-or-later AND GPL-3.0-or-later AND LGPL-3.0-linking-exception AND LGPL-3.0-or-later AND LicenseRef-Public-Domain AND LicenseRef-UltraPermissive AND Linux-man-pages-1-para AND Linux-man-pages-copyleft AND Linux-man-pages-copyleft-2-para AND Linux-man-pages-copyleft-var AND MIT AND Spencer-94
- nixpkgs unstable: man-pages 6.20
- ๐ the page's license: Linux-man-pages-copyleft
- Copyright 1993, David Metcalfe <david@prism.demon.co.uk>
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms