Time and Date Processing in C
Chuck Allison
--------------------------------------------------------------------------------
Chuck Allison is a software architect for the Family History Department of the Church of Jesus Christ of Latter Day Saints Church Headquarters in Salt Lake City. He has a B.S. and M.S. in mathematics, has been programming since 1975, and has been teaching and developing in C since 1984. His current interest is object-oriented technology and education. He is a member of X3J16, the ANSI C++ Standards Committee. Chuck can be reached on the Internet at allison@decus.org, or at (801)240-4510.
Most operating systems have some way of keeping track of the current date and time. ANSI C makes this information available in various formats through the library functions defined in time.h. The time function returns a value of type time_t (usually a long), which is an implementation-dependent encoding of the current date and time. You in turn pass this value to other functions which decode and format it.
The program in Listing 1 uses the functions time, localtime and strftime to print the current date and time in various formats. The localtime function breaks the encoded time down into
struct tm
{
int tm_sec; /* (0 - 61) */
int tm_min; /* (0 - 59) */
int tm_hour; /* (0 - 23) */
int tm_mday; /* (1 - 31) */
int tm_mon; /* (0 - 11) */
int tm_year; /* past 1900 */
int tm_wday; /* (0 - 6) */
int tm_yday; /* (0 - 365) */
int tm_isdst; /* daylight savings flag */
};
local time overwrites a static structure each time you call it, and returns its address (therefore only one such structure is available at a time in a program without making an explicit copy). The ctime function returns a pointer to a static string which contains the full time and date in a standard format (including a terminating newline). strftime formats a string according to user specifications (e.g., %A represents the name of the day of the week). See Table 1 for the complete list of format descriptors.
Time/Date Arithmetic
You can do time/date arithmetic by changing the values in a tm structure. The program in Listing 2 shows how to compute a date a given number of days in the future, as well as the elapsed execution time in seconds. Note the optional alternate syntax for the time function (the time_t parameter is passed by reference instead of returned as a value). The mktime function alters a tm structure so that the date and time values are within the proper ranges, after which the day-of-week (tm_wday) and day-of-year (tm_yday) fields are updated accordingly. mktime brings the date and time values in the tm structure into their proper ranges, and updates the day of week (tm-wday) and day of year (tm-yday) values accordingly. This occurs when a date falls outside the range that your implementation supports. My MS-DOS-based compiler, for example, cannot encode dates before January 1, 1970, but VAXC can process dates as early as the mid-1800s. The asctime function returns the standard string for the time represented in tm parameter (so ctime (&tval) is equivalent to asctime (localtime(&tval)). The function difftime returns the difference in seconds between two time_t encodings as a double.
If you need to process dates outside your system's range or calculate the interval between two dates in units other than seconds, you need to roll your own date encoding. The application in Listing 3 through Listing 5 shows a technique for determining the number of years, months and days between two dates, using a simple month-day-year structure. It subtracts one date from another, much as you might have done in elementary school (i.e., it subtracts the days first, borrowing from the month's place if necessary, and so on). Note that leap years are taken into account. For brevity, the date_interval function assumes that the dates are valid and that the first date entered precedes the second. Following the lead of the functions in time.h, It returns a pointer to a static Date structure which holds the answer.
File Time/Date Stamps
Most operating systems maintain a time/date stamp for files. At the very least, you can find out when a file was last modified. (The common make facility uses this information to determine if a file needs to be recompiled, or if an application needs to be relinked). Since file systems vary across platforms, there can be no universal function to retrieve a file's time/date stamp, so the ANSI standard doesn't define one. However, most popular operating systems (including MS-DOS and VAX/VMS) provide the UNIX function stat, which returns pertinent file information, including the time last modified expressed as a time_t. The program in Listing 6 uses stat and difftime to see if the file time1.c is newer than (i.e., was modified more recently than) time2.c.
If you need to update the time/date stamp of a file to the current time, simply overwrite the first byte of a file with itself. Although the contents haven't changed, your file system will think it has, and will update the time/date stamp accordingly. (Know your file system! Under VAX/VMS, you get a newer version of the file, while the older version is retained). This is sometimes called "touching" a file. The implementation of touch in Listing 7 creates the file if it doesn't already exist. Note that the file is opened in "binary" mode (indicated by the character b in the open mode string — I'll discuss file processing in detail in a future capsule).
Table 1 Format descriptors for strftime
Code Sample Output
---------------------------------------------
%a Wed
%A Wednesday
%b Oct
%B October
%c Wed Oct 07 13:24:27 1992
%d 07 (day of month [01-31])
%H 13 (hour in [00-23])
%I 01 (hour in [01-12])
%j 281 (day of year [001-366])
%m 10 (month [01-12])
%M 24 (minute [00-59])
%p PM
%S 27 (second [00-59] )
%U 40 (Sunday week of year [00-52])
%w 3 (day of week [0-6])
%W 40 (Monday week of year [00-52])
%x Wed Oct 7, 1992
%X 13:24:27
%y 92
%Y 1992
%Z EDT (daylight savings indicator)
Listing 1 time1.c — prints the current date and time in various formats
#include <stdio.h>
#include <time.h>
#define BUFSIZE 128
main()
{
time_t tval;
struct tm *now;
char buf[BUFSIZE];
char *fancy_format =
"Or getting really fancy:/n"
"%A, %B %d, day %j of %Y./n"
"The time is %I:%M %p.";
/* Get current date and time */
tval = time(NULL);
now = localtime(&tval);
printf("The current date and time:/n"
"%d/%02d/%02d %d:%02d:%02d/n/n",
now->tm_mon+1, now->tm_mday, now->tm_year,
now->tm_hour, now->tm_min, now->tm_sec);
printf("Or in default system format:/n%s/n",
ctime(&tval));
strftime(buf,sizeof buf,fancy_format,now);
puts(buf);
return 0;
}
/* Output
The current date and time:
10/06/92 12:58:00
Or in default system format:
Tue Oct 06 12:58:00 1992
Or getting really fancy:
Tuesday, October 06, day 280 of 1992.
The time is 12:58 PM.
*/
/* End of File */
Listing 2 time2.c — shows how to compute a date a given number of days in the future, as well as the elapsed execution time in seconds
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
main()
{
time_t start, stop;
struct tm *now;
int ndays;
/* Get current date and time */
time(&start);
now = localtime(&start);
/* Enter an interval in days */
fputs("How many days from now? ",stderr);
if (scanf("%d",&ndays) !=1)
return EXIT_FAILURE;
now->tm_mday += ndays;
if (mktime(now) != -1)
printf("New date: %s",asctime(now));
else
puts("Sorry. Can't encode your date.");
/* Calculate elapsed time */
time(&stop);
printf("Elapsed program time in seconds: %f/n",
difftime(stop,start));
return EXIT_SUCCESS;
}
/* Output
How many days from now? 45
New date: Fri Nov 20 12:40:32 1992
Elapsed program time in seconds: 1.000000
*/
/* End of File */
Listing 3 date.h — a simple date structure
struct Date
{
int day;
int month;
int year;
};
typedef struct Date Date;
Date* date_interval(const Date *, const Date *);
/* End of File */
Listing 4 date_int.c — computes time interval between two dates
/* date_int.c: Compute duration between two dates */
#include "date.h"
#define isleap(y) /
((y)%4 == 0 && (y)%100 != 0 || (y)%400 == 0)
static int Dtab [2][13] =
{
{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
{0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
};
Date *date_interval(const Date *d1, const Date *d2)
{
static Date result;
int months, days, years, prev_month;
/* Compute the interval - assume d1 precedes d2 */
years = d2->year - d1->year;
months = d2->month - d1->month;
days = d2->day - d1->day;
/* Do obvious corrections (days before months!)
*
* This is a loop in case the previous month is
* February, and days < -28.
*/
prev_month = d2->month - 1;
while (days < 0)
{
/* Borrow from the previous month */
if (prev_month == 0)
prev_month = 12;
--months;
days += Dtab[isleap(d2->year)][prev_month--];
}
if (months < 0)
{
/* Borrow from the previous year */
--years;
months += 12;
}
/* Prepare output */
result.month = months;
result.day = days;
result.year = years;
return &result;
}
/* End of File */
Listing 5 tdate.c — illustrates the date_interval function
/* tdate.c: Test date_interval() */
#include <stdio.h>
#include <stdlib.h>
#include "date.h"
main()
{
Date d1, d2, *result;
int nargs;
/* Read in two dates - assume 1st precedes 2nd */
fputs("Enter a date, MM/DD/YY> ",stderr);
nargs = scanf("%d/%d/%d%*c", &d1.month,
&d1.day, &d1.year);
if (nargs != 3)
return EXIT_FAILURE;
fputs("Enter a later date, MM/DD/YY> ",stderr);
nargs = scanf("%d/%d/%d%*c", &d2.month,
&d2.day, &d2.year);
if (nargs != 3)
return EXIT_FAILURE;
/* Compute interval in years, months, and days */
result = date_interval(&d1, &d2);
printf("years: %d, months: %d, days: %d/n",
result->year, result->month, result->day);
return EXIT_SUCCESS;
}
/* Sample Execution:
Enter a date, MM/DD/YY> 10/1/51
Enter a later date, MM/DD/YY> 10/6/92
years: 41, months: 0, days: 5 */
/* End of File */
Listing 6 ftime.c — determines if time1.c is newer than time2.c
/* ftime.c: Compare file time stamps */
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <time.h>
main()
{
struct stat fs1, fs2;
if (stat("time1.c",&fs1) == 0 &&
stat("time2.c",&fs2) == 0)
{
double interval =
difftime(fs2.st_mtime,fs1.st_mtime);
printf("time1.c %s newer than time2.c/n",
(interval < 0.0) ? "is" : "is not");
return EXIT_SUCCESS;
}
else
return EXIT_FAILURE;
}
/* Output
time1.c is not newer than time2.c */
/* End of File */
Listing 7 touch.c — updates time stamp by overwriting old file or creating the file if it doesn't exist
/* touch.c: Update a file's time stamp */
#include <stdio.h>
void touch(char *fname)
{
FILE *f = fopen(fname,"r+b");
if (f != NULL)
{
char c = getc(f);
rewind(f);
putc(c,f);
}
else
fopen(fname,"wb");
fclose(f);
}
/* End of File */