mrflash818's C programming stuff

I dabble in C-language programming now-and-then.

Timediff

2010-12-31 I created a program, which was designed to be a command line program to use in things like bash scripts, to calculate the differences between two dates. It uses the combination of a oldest date restriction and the Gregorian Calendar algorithm to provide accurate counts of days between two dates.

2011-01-02 right now the program does not take any other parameters other than two dates. In the future I will code it up to accept the output modifiers and spew the adjusted output.

The program uses straight C language program constructs and headers.

Here is the C header file, C source code file, and the make file to create it.

/*
 * Copyright (c) 2011
 *      Robert Leyva
 * 
 * timediff.h 
 *
 * Simple, but useful way to quickly determine the difference 
 * between two A.D. dates as a command line program:
 * 
 * timediff [options] date1 date2
 * 
 * where
 * 
 * dates are in ISO 8601 format ( http://en.wikipedia.org/wiki/ISO_8601 )
 * (basically yyyy-mm-dd)
 *
 * Leap year calculated by constraining date to after 1600AD and 
 * the algorithm for the Gregorian Calendar from:
 * http://en.wikipedia.org/wiki/Leap_year of
 *
 * if year modulo 400 is 0
 *       then is_leap_year
 * else if year modulo 100 is 0
 *       then not_leap_year
 * else if year modulo 4 is 0
 *     then is_leap_year
 * else
 *     not_leap_year
 *
 *
 * 
 * options
 * -------------
 * specify the units of time between system time and date1, or 
 * between date1 and date2:
 * 
 * -ms milliseconds
 * -s seconds
 * -m minutes
 * -h hours
 * -d days **default**
 * 
 */

void usage();

typedef struct {
  int year;
  int month;
  int dayOfMonth;
} date_struct;
date_struct ISO8601dateStringConv(char*,int);

void printErrorAndExit(char *);

static int days_in_month[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_struct min(date_struct,date_struct);
date_struct max(date_struct,date_struct);
int isLeftDateSmaller(date_struct,date_struct);
long daysBetween(date_struct, date_struct);
date_struct addOneDay(date_struct,int);
int isLeapYear(int year);

const char* PROGRAM_NAME = "timediff";


/*
 * Copyright (c) 2011
 *      Robert Leyva
 * 
 * timediff.c 
 *
 * Simple, but useful way to quickly determine the difference 
 * between two A.D. dates as a command line program.
 *
 * Tracks leap year using Gregorian Calendar.
 * 
 * timediff [options] date1 date2
 * 
 * where
 * 
 * dates are in ISO 8601 format ( http://en.wikipedia.org/wiki/ISO_8601 )
 * (basically yyyy-mm-dd)
 * 
 * options
 * -------------
 * specify the units of time between system time and date1, or 
 * between date1 and date2:
 * 
 * -ms milliseconds
 * -s seconds
 * -m minutes
 * -h hours
 * -d days **default**
 * 
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *
 */ 

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "timediff.h"


int main(int argc, char* argv[]) {
  // The program needs to be invoked as timediff yyyy-mm-dd yyyy-mm-dd
  if(argc != 3) usage();

  //1. fetch 1st date
  //2. fetch 2nd date
  //3. determine the difference, adjust for February when it is a leap year
  //4. return the difference in value defaulted or
  //   specified by modifier

  //Determine the lenth of a valid date string, including the required string termination character
  const int VALID_DATE_STRING_LENGTH = strlen("yyyy-mm-dd ");

  date_struct ds1, ds2;
  ds1 = ISO8601dateStringConv(argv[1],VALID_DATE_STRING_LENGTH);
  ds2 = ISO8601dateStringConv(argv[2],VALID_DATE_STRING_LENGTH);

  long result;
  result = daysBetween(ds1,ds2);
  printf("%ld",result);

  exit(0);
}
// ***   end of main   ***
void printErrorAndExit(char * message) {
  fprintf(stderr, message);
  exit(1);
}
date_struct min(date_struct date1, date_struct date2) {
  if(date1.year < date2.year) return date1;
  if(date2.year < date1.year) return date2;

  if(date1.month < date2.month) return date1;
  if(date2.month < date1.month) return date2;

  if(date1.dayOfMonth < date2.dayOfMonth) return date1;
  if(date2.dayOfMonth < date1.dayOfMonth) return date2;

  return date2;
}
date_struct max(date_struct date1, date_struct date2) {
  if(date1.year > date2.year) return date1;
  if(date2.year > date1.year) return date2;

  if(date1.month > date2.month) return date1;
  if(date2.month > date1.month) return date2;

  if(date1.dayOfMonth > date2.dayOfMonth) return date1;
  if(date2.dayOfMonth > date1.dayOfMonth) return date2;

  return date2;
}
int isLeftDateSmaller(date_struct date1, date_struct date2) {
  if(date1.year < date2.year) return 1;
  if(date2.year < date1.year) return 0;

  if(date1.month < date2.month) return 1;
  if(date2.month < date1.month) return 0;

  if(date1.dayOfMonth < date2.dayOfMonth) return 1;
  if(date2.dayOfMonth > date2.dayOfMonth) return 0;

  return 0;
}
int isLeapYear(int year) {
  int value;
  value = year;

  //if year modulo 400 is 0
  //   then is_leap_year
  value = year % 400;
  if(value == 0) return 1;

  //else if year modulo 100 is 0
  //   then not_leap_year
  value = year % 100;
  if(value == 0) return 0;

  //else if year modulo 4 is 0
  //   then is_leap_year
  value = year % 4;
  if(value == 0) return 1;

  //else
  //   not_leap_year
  return 0;
}
date_struct addOneDay(date_struct ds, int isLeapYear){
  int daysInMonth;

  ds.dayOfMonth++;

  //If the month is February test for leap year and adjust daysInMonth
  if(ds.month == 2) {
    daysInMonth = days_in_month[isLeapYear][ds.month];
  } else {
    daysInMonth = days_in_month[0][ds.month];
  }

  if(ds.dayOfMonth > daysInMonth) {
    ds.month++;
    ds.dayOfMonth = 1;
    if(ds.month > 12) {
      ds.year += 1;
      ds.month = 1;
    }
  }
  return ds;
}
long daysBetween(date_struct date1, date_struct date2){
  long result = 0l;
  date_struct minDate = min(date1, date2);
  date_struct maxDate = max(date1, date2);

  date_struct countingDate;
  countingDate.year = minDate.year;
  countingDate.month = minDate.month;
  countingDate.dayOfMonth = minDate.dayOfMonth;

  int leapYear = isLeapYear(countingDate.year);
  int countingYear = countingDate.year;

  while(isLeftDateSmaller(countingDate,maxDate)) {
    countingDate = addOneDay(countingDate,leapYear);
    //if the year changes while counting, check to see if
    //it is a new year
    if(countingYear != countingDate.year) {
      countingYear = countingDate.year;
      leapYear = isLeapYear(countingDate.year);
    }

    result++;
  }

  return result;
}

date_struct ISO8601dateStringConv(char* dateString, int VALID_DATE_STRING_LENGTH) {
  char STRING_DATE_DELIMITERS[] = {'-','/'};
  date_struct result;
  char ds[VALID_DATE_STRING_LENGTH];
  char * pDs;
  int i;
  int MIN_YEAR = 1800;
  char ERROR_MSG_MISSING_PART[] = "Value must be yyyy-m[m]-d[d]\n";
  char ERROR_MSG_BAD_VALUE[] = "Invalid value for year, month, or date\n";

  strncpy(ds, dateString,VALID_DATE_STRING_LENGTH);
  if (VALID_DATE_STRING_LENGTH > 0)
    ds[VALID_DATE_STRING_LENGTH - 1]= '\0';

  // get the year
  pDs = strtok(ds,STRING_DATE_DELIMITERS);
  if(pDs == NULL) printErrorAndExit(ERROR_MSG_MISSING_PART);
  i = atoi(pDs);
  if(i == NULL || i < 1 || i < MIN_YEAR) printErrorAndExit(ERROR_MSG_BAD_VALUE);
  result.year = i;

  // get the month
  pDs = strtok(NULL,STRING_DATE_DELIMITERS);
  if(pDs == NULL) printErrorAndExit(ERROR_MSG_MISSING_PART);
  i = atoi(pDs);
  if(i == NULL || i < 1) printErrorAndExit(ERROR_MSG_BAD_VALUE);
  result.month = i;

  // get the day of month
  pDs = strtok(NULL,STRING_DATE_DELIMITERS);
  if(pDs == NULL) printErrorAndExit(ERROR_MSG_MISSING_PART);
  i = atoi(pDs);
  if(i == NULL || i < 1) printErrorAndExit(ERROR_MSG_BAD_VALUE);
  result.dayOfMonth = i;

  return result;
}

/*
 * Tell the user how to invoke the program
 */
void usage() {
  printf("%s [options] date1 date2 \n",PROGRAM_NAME); 
  printf("\n");
  printf("where \n");
  printf("\n");
  printf("dates are in ISO 8601 format and are after 1800 A.D. \n");
  printf("(http://en.wikipedia.org/wiki/ISO_8601) \n");
  printf("(basically yyyy-mm-dd) \n");
  printf("\n");
  printf("options \n");
  printf("------------- \n");
  printf("The units of time between date1 and date2: \n");
  printf(" -ms milliseconds \n");
  printf(" -s seconds \n");
  printf(" -m minutes \n");
  printf(" -h hours \n");
  printf(" -d days **default** \n");
  printf("\n");
  exit(1);
}

#
# timediff Makefile
#
PROGRAM_NAME=timediff

program: ${PROGRAM_NAME}
	gcc -g -Wall timediff.h timediff.c -o ${PROGRAM_NAME} 

clean:
	rm -f *.o ${PROGRAM_NAME}