Why Not Just Use ctime()?
ctime() and asctime() always produce the same fixed format: Wed May 15 10:30:00 2025\n. But real applications need flexibility. A bank might need 15/05/2025. A log file might need 2025-05-15T10:30:00Z. A mobile app might need Wednesday, 15 May. That’s where strftime() comes in.
Keywords in This Post
strftime() works like printf() for dates. You provide a format string with percent-prefixed conversion specifiers (like %Y for 4-digit year, %m for month), and it fills your buffer with the formatted string.
#include <time.h>
size_t strftime(char *outstr, size_t maxsize,
const char *format, const struct tm *timeptr);
/* Returns number of bytes written (not counting \0), or 0 on error */
#include <time.h>
#include <stdio.h>
int main(void)
{
time_t t = time(NULL);
struct tm *tm_ptr = localtime(&t);
char buf[128];
/* ISO 8601 date format */
strftime(buf, sizeof(buf), "%Y-%m-%d", tm_ptr);
printf("ISO date : %s\n", buf); /* e.g. 2025-05-15 */
/* Indian date format */
strftime(buf, sizeof(buf), "%d/%m/%Y", tm_ptr);
printf("DD/MM/YYYY : %s\n", buf); /* e.g. 15/05/2025 */
/* Full verbose format */
strftime(buf, sizeof(buf), "%A, %d %B %Y, %H:%M:%S %Z", tm_ptr);
printf("Verbose : %s\n", buf);
/* e.g. Wednesday, 15 May 2025, 10:30:00 IST */
return 0;
}
Note: Unlike ctime(), strftime() does not automatically append a newline. You control exactly what goes in the buffer. If the output would overflow maxsize bytes, strftime() returns 0 and the buffer contents are undefined.
| Specifier | What it expands to | Example output |
|---|---|---|
| %Y | 4-digit year | 2025 |
| %y | 2-digit year | 25 |
| %m | Month as 2-digit number (01-12) | 05 |
| %B | Full month name | May |
| %b | Abbreviated month name | May |
| %d | Day of month (01-31) | 15 |
| %A | Full weekday name | Wednesday |
| %a | Abbreviated weekday name | Wed |
| %H | Hour (24-hour clock, 00-23) | 10 |
| %I | Hour (12-hour clock, 01-12) | 10 |
| %p | AM or PM | AM |
| %M | Minute (00-59) | 30 |
| %S | Second (00-60) | 00 |
| %F | ISO date, same as %Y-%m-%d | 2025-05-15 |
| %T | Time, same as %H:%M:%S | 10:30:00 |
| %Z | Timezone abbreviation | IST |
| %j | Day of year (001-366) | 135 |
| %% | Literal percent character | % |
| Use Case | Format String | Example Output |
|---|---|---|
| Log file timestamp | “%Y-%m-%d %H:%M:%S” | 2025-05-15 10:30:00 |
| ISO 8601 UTC | “%FT%TZ” | 2025-05-15T10:30:00Z |
| Indian date format | “%d-%m-%Y” | 15-05-2025 |
| 12-hour AM/PM | “%I:%M %p” | 10:30 AM |
| Day of week + date | “%A, %d %B %Y” | Thursday, 15 May 2025 |
| Filename-safe timestamp | “%Y%m%d_%H%M%S” | 20250515_103000 |
strptime() is the reverse of strftime(). It reads a date/time string and fills a struct tm according to a format. Think of it like scanf() but for dates.
#define _XOPEN_SOURCE /* Required to enable strptime on Linux */
#include <time.h>
char *strptime(const char *str, const char *format, struct tm *timeptr);
/* Returns pointer to first unprocessed char in str, or NULL on parse error */
#define _XOPEN_SOURCE
#include <time.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
struct tm t;
char *remaining;
char buf[64];
/* Parse an Indian-format date string */
memset(&t, 0, sizeof(struct tm));
remaining = strptime("15/05/2025", "%d/%m/%Y", &t);
if (remaining == NULL) {
fprintf(stderr, "Parse failed\n");
return 1;
}
t.tm_isdst = -1;
time_t epoch = mktime(&t);
printf("Parsed time_t : %ld\n", (long)epoch);
/* Now print it back in a different format */
strftime(buf, sizeof(buf), "%A, %d %B %Y", &t);
printf("Formatted : %s\n", buf);
/* Output: Thursday, 15 May 2025 */
return 0;
}
Real-world use case: A server receives a date/time string from a REST API response (e.g., "2025-05-15T10:30:00"). You use strptime() to parse it into a struct tm, convert to time_t with mktime(), then do calculations like “how many days ago was this event?”
Important: strptime() never sets tm_isdst. Always set t.tm_isdst = -1 before passing the struct to mktime(), otherwise DST handling may give you a wrong time_t value. Also, zero-initialise the struct with memset(&t, 0, sizeof(t)) before calling strptime() to avoid garbage values in unset fields.
A common pattern in Linux programs is a small helper that returns a formatted current-time string. Here is how to build one using strftime():
#include <time.h>
#include <stdio.h>
#define BUF_SIZE 128
/* Returns current time as a formatted string.
'fmt' is a strftime format string; pass NULL for a sensible default.
Returns NULL on error. */
const char *curr_time(const char *fmt)
{
static char buf[BUF_SIZE];
time_t t;
struct tm *tm_ptr;
size_t n;
t = time(NULL);
tm_ptr = localtime(&t);
if (tm_ptr == NULL)
return NULL;
n = strftime(buf, BUF_SIZE,
(fmt != NULL) ? fmt : "%Y-%m-%d %H:%M:%S",
tm_ptr);
return (n == 0) ? NULL : buf;
}
int main(void)
{
printf("Now (default) : %s\n", curr_time(NULL));
printf("ISO 8601 : %s\n", curr_time("%FT%T"));
printf("Filename-safe : %s\n", curr_time("%Y%m%d_%H%M%S"));
return 0;
}
Up Next in This Series
Part 6 covers Timezones — the TZ environment variable, tzset(), and how Linux manages timezone data in /usr/share/zoneinfo.
