Menu

Class 6: C vs Unix, Does anybody really know what time it is?


Reading
APUE section 6.10.

Homework
Printout the Homework and answer the questions on that paper.

Unix and time
We're going to talk about our first system call. A system call is a regular ol' function call, except the function is executed by the operating system rather than by the process that makes the call.
Note: You'll often hear references to "the kernel". The kernel refers to the lowest level of the operating system --- the lowest level software buffer between applications and the hardware. So the kernel is just the core of the Unix operating system.
In other words, a system call represents a request, by your program to the kernel, for some action or information. Our first system call is asking the kernel "do you have the time?".

System calls are function calls, and the system call to request the time uses the function time. It's prototype is

time_t time(time_t *tloc);
	  
You can try man -s2 time for more info. If you do, you'll notice that, right up at the top, you're told that this is a system call! So it's not part of the standard C library, it is a function that is implemented as part of the Unix operating system. What time does with a non-zero argument is outisde of the scope of this discussion. What's important is that time(0) returns the time ... as a great big number. What does that number mean? It is the time in seconds since "the Epoch": 00:00:00 UTC, January 1, 1970. The "UTC" is "Coordinated Universal Time". The type used to store this value is time_t. So what's that? You can actually figure that out by searching for it in the header file that defines time_t.
bash$ grep time_t /usr/include/sys/time.h | head -1
typedef long    time_t;         /* time of day in seconds */
	  
What this tells you, is that type "time_t" is just another name for "long", which on our machines is just another name for int.
p1.cCompiling & running
#include <stdio.h>
#include <time.h>

int main()
{
  int t = time(0);
  printf("%i\n",t);
  return 0;
}
bash$ gcc -o p1 p1.c
bash$ ./p1
1265134163

So why is time a system call? Why is the kernel involved? Because to get the time we need to consult one of the system resources, the clock, and we never use system resources directly; only through the kernel.

The time is 1265134163 ... am I late?
Technically, when time returns a number like 1265134163, it has indeed given us the time. That's just not such useful information. We'd like to convert it to something intelligeble. There are routines for converting time representations, but they are not system calls, since conversion does not require accessing any system resources. They are handled by standard C library calls. The functions we'll look at are:
     #include <time.h>

     char *ctime(const time_t *clock);

     struct tm *localtime(const time_t *clock);

     char *asctime(const struct tm *tm);
	
The first of these takes the time as a time_t and returns the time as a nicely formatted, human-readable string. Note, however, that it asks for a pointer to the time_t object. That's going to impact how we call this function!

p2.cCompiling & running
#include <stdio.h>
#include <time.h>

int main()
{
  time_t t = time(0);
  printf("%i\n",t);
  char *s = ctime(&t);
  printf("%s",s);
  return 0;
}
bash$ gcc -o p2 p2.c
bash$ ./p2
1265135684
Tue Feb  2 13:34:44 2010

When you're using system calls and standard library calls that return anything more complicated that in/float/char, you need to make sure you understand who is responsible for the memory that results are stored in. If you don't, bad things happen. Consider the following program, which reports how long it takes you to type the alphabet.

p3.c (this version is bad!)Compiling & running
#include <stdio.h>
#include <time.h>

int main()
{
  time_t t0 = time(0);

  printf("Type the alphabet: ");
  char alpha[32];
  int success = 0;
  while(!success && scanf("%s",alpha) == 1)
    success = (0 == strcmp(alpha,"abcdefghijklmnopqrstuvwxyz"));

  time_t t1 = time(0);

  printf("\nStarted at  %s\nFinished at %s\n",ctime(&t0),ctime(&t1));
  printf("It took you %i seconds\n",t1-t0);

  return 0;
}
bash$ gcc -o p3 p3.c
bash$ ./p3
Type the alphabet: abcdefghijklmnopqrstuvwxyz

Started at  Tue Feb  2 13:44:43 2010

Finished at Tue Feb  2 13:44:43 2010

It took you 9 seconds

So, if it took me 9 seconds, why are the two date/times identical? Well, we need to understand who's responsible for the char array returned by ctime. It turns out that there's a single static array that ctime always writes its results in ... in fact the char* returned by ctime is always the same. So the second call overwrites the first call! Thus, if we want to save the string returned by ctime for later use, we must copy it into another buffer, where it won't get overwritten. There are two ways to do this: allocate our own buffer and use strcpy to copy the string returned by ctime into it, or use the strdup function, which allocates the buffer for us. Compare these three functions:

functionexamplewho's in charge of memory
ctime
char* s = ctime(&t);
...
ctime is responsible for both allocating and deallocating the buffer pointed to by s. However, the buffer may get overwritten!
strcpy
char* tmp = ctime(&t);
char* s = (char*)malloc((strlen(tmp)+1)*sizeof(char));
strcpy(s,tmp);
...
free(s);
We are responsible for both allocating and deallocating the buffer pointed to by s.
strdup
char* s = strdup(ctime(&t));
...
free(s);
strdup is responsible for allocating the buffer pointed to by s, but we are responsible for deallocating it.

Letting the program understand the date/time
What if instead of presenting the time to the user, we want the program to be able to pick out aspects of the date/time, like the month or the hour of the day? There is another C standard library function for that: localtime. Here's its prototype:
struct tm *localtime(const time_t *clock);
	
Hopefully we understand the argument: it takes (a pointer to) the time in seconds since the Epoch. But what is the result? It returns a pointer to a struct tm object. What's that? Well if you look at the man page for localtime you'll eventually find the following:
     The tm structure contains the following members:

       int  tm_sec;     /* seconds after the minute [0, 60]  */
       int  tm_min;     /* minutes after the hour [0, 59] */
       int  tm_hour;    /* hour since midnight [0, 23] */
       int  tm_mday;    /* day of the month [1, 31] */
       int  tm_mon;     /* months since January [0, 11] */
       int  tm_year;    /* years since 1900 */
       int  tm_wday;    /* days since Sunday [0, 6] */
       int  tm_yday;    /* days since January 1 [0, 365] */
       int  tm_isdst;   /* flag for daylight savings time */
So let's print out the date as month/day/year.

p4.cCompiling & running
#include <stdio.h>
#include <time.h>

int main()
{
  time_t t0 = time(0);
  struct tm* pt = localtime(&t0);
  printf("%i/%i/%i\n", 1 + pt->tm_mon, pt->tm_mday, 1900 + pt->tm_year);

  return 0;
}
bash$ gcc -o p4 p4.c
bash$ ./p4
2/2/2010

Question: since localtime is returning a pointer to a struct tm, who's responsible for allocating and deallocating that struct? This is actually just like ctime. It's localtime's job to do both, and any information in there is in danger of being overwritten. If you want your own copy, you can do this:

struct tm mytm = *localtime(&t);	    
	  
This says set mytm equal to the object pointed to localtime's result. The semantics of setting structs equal is that you get memberwise assignment. In this case, where all members are ints, that works perfectly.

It's critially important that you understand types of struct's and pointers to structs and accessing struct fields.

How many seconds 'til graduation
Now we have all the pieces in place to write a program that answers a critical question: how many seconds 'til you graduate! If we could get the time of graduations in the seconds-since-Epoch format, we could solve this easily. To do this, we use the mktime function, which is the inverse of the localtime function. It too is a C standard library function:
 time_t mktime(struct tm *timeptr);
	
So we create a struct tm object, fill its fields appropriately, and pass mktime a pointer to it.

p5.cCompiling & running
#include <stdio.h>
#include <time.h>

int main()
{
  time_t t0 = time(0);
  struct tm gd;
  gd.tm_sec = 0;
  gd.tm_min = 0;
  gd.tm_hour = 12;
  gd.tm_mday = 25;
  gd.tm_mon = 4; /* Remember: months since January - [0, 11] */
  gd.tm_year = 2012-1900;
  gd.tm_isdst = 1;

  time_t t1 = mktime(&gd);
  int r = t1 - t0;
  printf("%i seconds 'til graduation!\n",r);
  printf("that's %i hours, %i minutes and %i seconds\n",
	 r/3600,(r%3600)/60,r%60);

  return 0;
}
bash$ gcc -o p5 p5.c
bash$ ./p5
72821752 seconds 'til graduation!
that's 20228 hours, 15 minutes and 52 seconds
		

Now, how could you use this to print out the number of seconds 'til graduation every time you logged in?