Skip to Content
Course content

131: Converting Between time_t and struct tm

Click on the "Edit" button in the top corner of the screen to edit your slide content.

If you've spent any time debugging C programs that interact with the real world, you know that time is a nightmare. We have time_t, which is basically just a big integer counting seconds since 1970, and we have struct tm, which breaks that giant number down into years, months, and days. To a computer, the integer is great. To a human, the struct is necessary. The trouble starts when you move between them.

The Static Buffer Trap

The most common mistake I see—and one I made myself early in my career—is treating localtime() or gmtime() like standard functions that return a fresh piece of memory. You might be tempted to write something like this when trying to compare two different timestamps in a log file:

time_t start_time = 1672531200; // Jan 1, 2023
time_t end_time = 1672617600;   // Jan 2, 2023

struct tm *start_tm = localtime(&start_time);
struct tm *end_tm = localtime(&end_time);

printf("Started on day %d, ended on day %d\n", start_tm->tm_mday, end_tm->tm_mday);

You'd expect this to print "Started on day 1, ended on day 2". Instead, it will print "Started on day 2, ended on day 2". Why? Because localtime() doesn't allocate new memory for every call. It returns a pointer to a single, static struct tm internal to the C library. Every time you call it, you're overwriting the exact same memory location. By the time you get to your printf, start_tm and end_tm are pointing to the same spot, and that spot holds the data from the most recent call.

Owning Your Own Memory

If you're working in a multi-threaded environment, the static buffer isn't just a logic bug; it's a race condition waiting to happen. Two threads calling localtime() simultaneously will corrupt each other's data. The professional way to handle this is to provide your own storage. On POSIX systems (Linux, macOS), we use localtime_r(). The "r" stands for reentrant.

Instead of the library deciding where the data goes, you declare a struct tm on your own stack and pass its address into the function. This completely bypasses the shared global state:

time_t start_time = 1672531200;
time_t end_time = 1672617600;

struct tm tm_start, tm_end; // Allocated on the stack

localtime_r(&start_time, &tm_start);
localtime_r(&end_time, &tm_end);

printf("Started on day %d, ended on day %d\n", tm_start.tm_mday, tm_end.tm_mday);

This is cleaner, thread-safe, and logically sound. You aren't relying on a hidden global variable; you're explicitly managing your data. If you're on Windows, you'll find localtime_s(), which flips the arguments around but solves the same problem.

Tying it Back to the Epoch

Eventually, you'll need to go the other way: you've let a user input a date via a struct tm, and now you need to convert it back to time_t so you can save it to a database or perform date arithmetic. For this, we use mktime().

One thing to keep in mind is that mktime() is the inverse of localtime(), not gmtime(). It assumes the struct tm you provide is in local time. It also does something surprisingly helpful: it "normalizes" the struct. If you accidentally set tm_mday to 32, mktime() will automatically roll that over to the 1st of the next month and update the rest of the struct accordingly. It's a handy trick, but it can hide bugs in your input validation if you aren't paying attention.




📋 Practical Task

Build a Session Duration Calculator

Write a program that calculates the number of seconds elapsed between two user-provided timestamps. Your program should:

  • Prompt the user to enter a start date and time (Year, Month, Day, Hour, Minute, Second).
  • Prompt the user to enter an end date and time.
  • Store these in two separate struct tm variables.
  • Convert both struct tm objects into time_t using mktime().
  • Calculate the difference in seconds using difftime().
  • Print the total seconds elapsed.

Constraint: Ensure you initialize your struct tm variables to zero (e.g., using memset or {0}) before filling them, to avoid mktime() interpreting garbage memory as the daylight savings flag (tm_isdst).

Rating
0 0

There are no comments for now.

to be the first to leave a comment.