CS 3723
 Programming Languages 
Spring 2013
  Time in Unix  


Representing and using calendar time in Unix: Unix measures calendar time internally as the number of seconds after 0:00am on 1 January 1970. This number has been stored in a long variable, a signed 32-bit number. This number will overflow, producing an error on many Unix systems, on Mon Jan 18 19:14:07 2038. This is the Unix version of the Y2K problem. I'm personally really worried about this, since it's only 26 years from now.

Unix doesn't allow an unsigned 32-bit number here, but if it did, then overflow wouldn't occur until the year 2106. If Unix switches to 64-bit numbers, overflow won't occur for 292 billion years. (See Unix Systems Programming, page 302, by K. Robbins and S. Robbins.)


A simple first example: For our purposes all we need is a way to get the integer for the current time, and a way to convert to a readable time format. The following program shows this:

Time in C/Unix
#include <stdio.h>
#include <time.h>    /* for various time functions */
#include <string.h>  /* for various string functions */
#include <stdlib.h>  /* for malloc */
char *display_time(time_t now);

int main() {
   char *t;
   time_t now = time(NULL);
   printf("Seconds in a year (approx): %.0f\n", 3600*24*365.25);
   printf("Seconds in 42 years (approx): %.0f\n", 3600*24*365.25*42);
   printf("Current date and time: %s", ctime(&now));
   t = display_time(now);
   printf("Seconds from 1 Jan 1970 to now: %u\n", now);
   printf("Current date and time: \"%s\"\n", t);
}

char *display_time(time_t now) {
   char *buf = (char *)malloc(40);
   strcpy(buf, ctime(&now));
   /* zap '\n' just before end */
   buf[strlen(buf)-1] = '\0';
   return buf;
}
Output
% cc -o time time.c
% ./time
Seconds in a year (approx): 31557600
Seconds in 42 years (approx): 1325419200
Current date and time: Sun Jan  8 13:30:36 2012
Seconds from 1 Jan 1970 to now: 1326051036
Current date and time: "Sun Jan  8 13:30:36 2012"

In the program above,time_t is just another designation for long. On our systems, a long is the same as an int, but using an int produces error messages. Notice that the display string returned by ctime(&now) has a newline just before the null character. The function display_time overwrites the newline with a null character, zapping the newline.


A second example: The example below shows how we create the date: 42 seconds after 2:31 PM, 24 November 2005 (which is Thanksgiving afternoon in that year). The program below also gives this as the number of seconds past 1 January 1970.

Time in C/Unix
#include <stdio.h>
#include <time.h>
#include <string.h>

time_t create_time(int year,   /* years since 1900 */
                   int mon,    /* monthe, 0 - 11 */
                   int mday,   /* day of month, 1-31 */
                   int hour,   /* hour of day, 0-23 */
                   int min,    /* minute of hour, 0-59 */
                   int sec,    /* second of minute, 0-59 */
                   int isdst); /* 1 means daylight savings time */

int main() {
   time_t created_time_secs;
   int year, mon, mday, hour, min, sec;
   scanf("%i %i %i %i %i %i", &year, &mon, &mday, &hour, &min, &sec);
   created_time_secs = create_time(year, mon, mday, hour, min, sec, 0);
   printf("Seconds past 1 Jan 1970: %i\n", created_time_secs);
   printf("Display time: %s", ctime(&created_time_secs));
}

time_t create_time(int year, int mon, int mday, int hour, int min, int sec, int isdst) {

   time_t create_time_secs;
   struct tm create_time = {0};  /* struct to hold time */
   /* insert magic numbers for create_time */
   create_time.tm_year = year;   /* years since 1900 */
   create_time.tm_mon = mon;     /* 0 - 11 */
   /* beware below: can't have leading 0 on day */
   create_time.tm_mday = mday;   /* 1-31 */
   create_time.tm_hour = hour;   /* 0-23 */
   create_time.tm_min = min;     /* 0-59 */
   create_time.tm_sec = sec;     /* 0-59 */
   create_time.tm_isdst = isdst; /* 1 means daylight savings time */

   create_time_secs = mktime(&create_time);
   printf("%s", ctime(&create_time_secs));
   return create_time_secs;
}
Output
% cc -o timetest timetest.c
% ./timetest
105 10 24 14 31 42
Thu Nov 24 14:31:42 2005
Seconds past 1 Jan 1970: 1132864302
Display time: Thu Nov 24 14:31:42 2005


A more complicated program: Finally, here is the program used for the submission of programs for the recitations for this course. After the program code I discuss how it works.

The program for submitting Recitation 13 in CS 3343, Spring 2012: cs3343r13.c
/* cs3343r13.c: submit recitation, Spring 2012
   This resides in ~wagner/bin
   In order to change to recitation 13,
      1. change name to "cs3343r13.c"
      2. change "int rec_num = 12" to "int rec_num = 13"
      3. change full credit submission time to one week later
      4. change part credit submission time to one week later
      4.1. be careful about daylight savings time
      recompile (see below)
      change permissions
   Commands:
      5. % cc -o cs3343r13 -lm cs3343r13.c
      6. % chmod 600 cs3343r13.c
      7. % chmod 755 cs3343r13

   This assumes a directory:
      8. /home/wagner/archive/spr12/CS3343/r13
         ( % chmod 733 r13 ),
      where at each stage there is world "x" permission.
      The final r11 directory has world "xw" permission
      The final file has world "rw" permission.
      It could be read or written by anyone who knew its name.
      9. Needs an "emailback" file with world "rw" permission.
         % chmod 666 emailback
   New semester: change one "Spring 2012" near the end.
 Known problems:
   1. The file emailback may be accessed by multiple processes without
      locking.  This file is only a convenience, however.
   2. Security based on secrecy of filenames is not good.
   3. Some of the emails sent back upon submission were incomplete
      for unknown reasons.
 */
#include <stdio.h>   /* for various I/O functions */
#include <unistd.h>  /* for getuid */
#include <pwd.h>     /* for getpwuid */
#include <time.h>    /* for various time functions */
#include <string.h>  /* for various string functions */
#include <stdlib.h>  /* for malloc */
#include <math.h>    /* for floor */
double seed1;        /* seed for RNG */

double rand1() { /* simple RNG; replace with a better one */
   double a = 16807.0,
          m = 2147483647.0;
   double q;
   seed1 = a*seed1;
   q = floor(seed1/m);
   seed1 = seed1 - q*m;
   return(seed1/m);
}

int main() {
   int rec_num = 0; /* recitation number, from 0 to 14 or so */
   /* get the username, tricky (Robbins&Robbins 2nd Ed., page 176) */
   long uid = (long)getuid();
   struct passwd *pw = getpwuid(uid);
   char *username = pw -> pw_name;

   /* now work on time */
   struct tm now_time = {0}; /* current time */
   time_t now = time(NULL);
   char *now_disp = ctime(&now);
   char *now_time_disp = (char *) (malloc(40));
   int now_final = now;

   struct tm full_credit_time = {0}; /* full credit time */
   time_t full_credit;
   char *full_credit_disp;
   char *full_credit_time_disp = (char *) (malloc(40));
   int full_credit_final;

   struct tm part_credit_time = {0}; /* part credit time */
   time_t part_credit;
   char *part_credit_disp;
   char *part_credit_time_disp = (char *) (malloc(40));
   int part_credit_final;

   /* file handling */
   char *filename = (char *)(malloc(100));
   FILE *outfile;
   FILE *emailfile; /****** extra to email back files ***************/
   int days_till_due; /* number of days till it is (full) due +3 */
   char days_till_due_str[2]; /* so can strcat */
   char work_str[10]; /* for strcat */
   char rec_num_str[4]; /* for strcat, may be two digits */
   char zero_str[] = "0"; /* to put a '0' at the start, strcat */
   int rand_part; /* 6 random digits at end of filename, not starting with 0 */
   int i; /* to exercise RNG before using */
   char *init_part = (char *)(malloc(300)); /* initial part of recitation file */
   int ch; /* buffer for I/O */
   int chars = 0; /* number of chars read */
   int lines = 0; /* number of lines read */
   char *system_call = (char *)(malloc(150)); /* string for system call */
   char *emailstuff = (char *)(malloc(150)); /* string for emailfile */
   char *emailstuff2 = (char *)(malloc(200)); /* string for emailfile */
   char *emailfilename = (char *)(malloc(150)); /* string for emailfilename */
   char *emailname = (char *)(malloc(50)); /* string for email name */

   /* finish up the times */
   now_time = *localtime(&now);
   strcpy(now_time_disp, now_disp);
   /* insert magic numbers for full credit time */
   /* below is years since 1900 */
   full_credit_time.tm_year = 112;
   /* beware below: month number starts with 0 */
   full_credit_time.tm_mon = 0;
   /* beware below: can't have leading 0 on day */
   full_credit_time.tm_mday = 16; /* must increment this by 7, overflow to mon */
   full_credit_time.tm_hour = 23;
   full_credit_time.tm_min = 59;
   full_credit_time.tm_sec = 59;
   full_credit_time.tm_isdst = 0; /* 1 means daylight savings time is in effect */
   
   full_credit = mktime(&full_credit_time);
   full_credit_final = full_credit;
   full_credit_disp = ctime(&full_credit);
   strcpy(full_credit_time_disp, full_credit_disp);

   /* insert magic numbers for part credit time */
   part_credit_time.tm_year = 112;
   part_credit_time.tm_mon = 0;
   part_credit_time.tm_mday = 20; /* must increment this by 7, overflow to mon */
   part_credit_time.tm_hour = 23;
   part_credit_time.tm_min = 59;
   part_credit_time.tm_sec = 59;
   part_credit_time.tm_isdst = 0; /* 1 means daylight savings time is in effect */

   part_credit = mktime(&part_credit_time);
   part_credit_final = part_credit;
   part_credit_disp = ctime(&part_credit);
   strcpy(part_credit_time_disp, part_credit_disp);

   /* start work on file-name */
   filename[0] = '\0';
   emailname[0] = '\0';
   strcat(filename, "/home/wagner/archive/spr12/CS3343/r");
   sprintf(rec_num_str, "%i", rec_num);
   strcat(filename, rec_num_str);
   strcat(filename, "/");
   strcat(filename, username);
   strcat(emailname, username);
   /* first a single digit related to days before due date:
      4 = ontime, < 4 early, 8 = part, 9 = no credit */
   days_till_due = now_time.tm_yday - full_credit_time.tm_yday + 3;
   if (days_till_due < 0) days_till_due = 0;
   if (days_till_due > 9) days_till_due = 9;
   days_till_due_str[0] = days_till_due + '0';
   days_till_due_str[1] = '\0';
   strcat(filename, days_till_due_str);
   strcat(emailname, days_till_due_str);

   /* now tack on hours and minutes from current time */
   sprintf(work_str, "%i", now_time.tm_hour);
   if (now_time.tm_hour < 10) {
      work_str[2] = '\0'; work_str[1] = work_str[0]; work_str[0] = '0';
   }
   strcat(filename, work_str);
   strcat(emailname, work_str);
   sprintf(work_str, "%i", now_time.tm_min);
   if (now_time.tm_min < 10) {
      work_str[2] = '\0'; work_str[1] = work_str[0]; work_str[0] = '0';
   }
   strcat(filename, work_str);
   strcat(emailname, work_str);
   seed1 = now_time.tm_min + now_time.tm_sec +
      filename[0] + filename[1] + filename[2];  /* weak initialization for RNG */
   for (i = 0; i < 10; i++) rand1();  /* run the RNG a little */
   rand_part = (int)(rand1()*900000 + 100000); /* 6 more random digits */
   sprintf(work_str, "%i", rand_part);
   strcat(filename, work_str);
   strcat(emailname, work_str);
   strcat(filename, ".txt");
   strcat(emailname, ".txt");

   /* handle initial part of file */
   init_part[0] = '\0';
   strcat(init_part, "Submitted by: ");
   strcat(init_part, username);
   strcat(init_part, "@cs.utsa.edu\n");
   strcat(init_part, "Course: CS 3343, Spring 2012, Recitation ");
   strcat(init_part, rec_num_str);
   strcat(init_part, "\nTime Received: ");
   strcat(init_part, now_time_disp);
   strcat(init_part, "Credit: ");
   /* NOTE: MUST CHANGE WITH NEW SEMESTER */
   printf("CS 3343, Analysis of Algorithms, Spring 2012\n");
   printf("Processing Recitation Number %i\n", rec_num);
   printf("Submission  Date/Time: %s", now_time_disp);
   printf("Full Credit Date/Time: %s", full_credit_time_disp);
   printf("Part Credit Date/Time: %s", part_credit_time_disp);
   /* decide about due dates */
   if (now_final <= full_credit_final) {  /* full-credit */
      printf("Received and archived for FULL CREDIT\n");
      strcat(init_part, "FULL\n\n");
   }
   else if (now_final <= part_credit_final) { /* part-credit */
      printf("Received and archived for PART (75%%) CREDIT\n");
      strcat(init_part, "PART\n\n");
   }
   else {  /* no-credit */
      printf("Received and archived, but for NO CREDIT\n");
      strcat(init_part, "NONE\n\n");
   }

   /* now file stuff */
   if ((outfile = fopen(filename, "w")) == NULL) {;
      fprintf(stderr, "Couldn't open file: %s\n", filename);
      exit(1);
   }
   fputs(init_part, outfile);
   while ((ch = getchar()) != EOF) {
      chars++;
      if (ch == '\n') lines++;
      fputc(ch, outfile);
   }
   fflush(outfile);
   fclose(outfile);
   printf("%i bytes and %i lines received.\n", chars, lines);

   system_call[0] = '\0';
   strcat(system_call, "chmod 666 ");
   strcat(system_call, filename);
   system(system_call);

   /************ extra to email files back *******************/
   /* first the emailfilename */
   emailfilename[0] = '\0';
   strcat(emailfilename, "/home/wagner/archive/spr12/CS3343/r");
   sprintf(rec_num_str, "%i", rec_num);
   strcat(emailfilename, rec_num_str);
   strcat(emailfilename, "/");
   strcat(emailfilename, "emailback");

   if ((emailfile = fopen(emailfilename, "a")) == NULL) {;
      fprintf(stderr, "Couldn't open file: %s\n", "emailback");
      exit(1);
   }
   emailstuff[0] = '\0';
   strcat(emailstuff, "mailx ");
   strcat(emailstuff, username);
   strcat(emailstuff, "@cs.utsa.edu");
   strcat(emailstuff, " < ");
   strcpy(emailstuff2, emailstuff);
   strcat(emailstuff, emailname);
   strcat(emailstuff2, filename);
   strcat(emailstuff, "\n");
   fputs(emailstuff, emailfile);
   system(emailstuff2);
   printf("The received file has been emailed to you for confirmation.\n");
}


How the recitation submission program works: I wanted a system for submitting and retaining recitations that would be simple, secure, under my own complete control, and not requiring root access on any machine. After awhile I came to realize that if a student was going to write a file into a directory of mine so that I could later read it, the directory would have to be world writeable and the file would have to be world read/writeable. Then anyone would be able to read this file, not just me, so how would I get any security? The only method I could think of was to make the file name include a random string. The directory it's in is not world readable, so to read the file one would need to know the file name. The directory was (of course) readable by me, so I could get the file name directly, but it would be secret from others. Thus security depends on the secrecy of (long, random, secret) file names. Dr. Hugh Maynard has said that this method stinks, because information like the names of files often leaks out from an OS. He's right, but still this method may be "good enough" for my low-security demands.

I use the extra file "emailback" to store the command used to email the assignment back to a student as confirmation of its receipt. This way, after I grade the assignment (adding comments and the grade), I can use the same command to send the graded assignment back. As I mention in the comments at the head of the program, the file "emailback" is not protected against simultaneous writes by multiple users.


Revision date: 2012-12-31. (Please use ISO 8601, the International Standard.)