C
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions
-
Section 4: Arrays and Strings
-
Section 5: Pointers
-
Section 6: Memory Management
-
Section 7: Structures and Unions
-
Section 8: The Preprocessor and Build Process
-
Section 9: Standard Library: stdio.h
-
Section 10: Standard Library: stdlib.h
-
Section 11: Standard Library: string.h
-
Section 12: Standard Library: ctype.h and wctype.h
-
Section 13: Standard Library: math.h, complex.h, fenv.h, tgmath.h
-
Section 14: Standard Library: Type and Limit Headers
-
Section 15: Standard Library: Error Handling and Debugging
-
Section 16: Standard Library: Localization and Encoding
-
Section 17: Standard Library: time.h
-
Section 18: Standard Library: Concurrency (C11)
-
Section 19: POSIX and System Programming (unistd.h)
-
Section 20: More Data Structures
-
Section 21: Algorithms in C
-
Section 22: Bitwise Operations
-
Section 23: Command-Line Programs
-
Section 24: Debugging and Best Practices
-
Section 25: Compiler and Language Internals
-
Section 26: Embedded and Cross-Platform Considerations
-
Section 27: Networking Basics
-
Section 28: Practical Projects
-
Section 29: Interview Practice
-
Section 30: C23 Modern Features
-
Section 31: More Practice and Review
131: Converting Between time_t and struct tm
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 tmvariables. - Convert both
struct tmobjects intotime_tusingmktime(). - 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).
There are no comments for now.