// -------------------------------------------------------------------------------
// DATES ver 3.5 <mig_f1@hotmail.com>
// a sample program in ANSI C performing calculations between two Gregorian dates:
// difference in days and in (d,m,y) form, weekday & leap years indicators
// jdn conversions, +/- days arithmetic, monthly calendars display
// it can also print yearly calendars in text files, naming them: "calYEAR.txt"
// by passing the year as a command line argument ( 1752-9999 )
// -------------------------------------------------------------------------------
// migf1, Athens 2011 * use at your own risk * free for whatever use * give credit
// -------------------------------------------------------------------------------
// external alogorithms:
// http://w...content-available-to-author-only...t.com/KB/datetime/DateDurationCalculation1.aspx
// http://w...content-available-to-author-only...c.ch/cal_stud/jdn.htm#comp
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <stdlib.h>
#define MAX_INBUF 255+1 // for our input buffer
#define INVALID -1 // just a flag for invalid d,m,y
#define validday(d) ( (d) > 0 && (d) < 32 ) // 1st validity check for days
#define validmonth(m) ( (m) > 0 && (m) < 13 ) // 1st validity check for months
#define validyear(y) ( (y) > 1751 && (y) < 10000 ) // 1st validity check for years
#define isleap(y) ( !((y) % 4) && ( (y) % 100 || !((y) % 400) ) )
#define myabs(x) ( (x) < 0 ? -(x) : (x) ) // absolute value
typedef enum bool { FALSE=0, TRUE } bool; // our custom boolean type
enum { JAN=1, FEB }; // just two month constants
enum { ID1=1, ID2 }; // to identify 1st and 2nd dates
typedef struct Date { // our date structure
long int d; // 1-31
long int m; // 1-12
long int y; // 1752-9999
} Date;
// ------------------------------------------------------------------------------------
// Read s from stdin until either len chars have been typed or ENTER has been hit,
// and null-terminate s (if ENTER was there, it is replaced).
// Return the null-terminated s
//
char *s_get(char *s, size_t len)
{
char *cp;
for (cp
=s
; (*cp
=getc(stdin
)) != '\n' && (cp
-s
) < len
-1; cp
++ ) ; // for-loop with empty body
*cp = '\0'; // null-terminate last character
return s;
}
// ------------------------------------------------------------------------------------
// Trim leading & trailing blanks from string s, pad it with '\0's and return it
// (or NULL on error)
//
char *s_trim(char *s)
{
if ( !s || !*s ) // error, early exit
return NULL;
char *cp1; // for parsing the whole s
char *cp2; // for shifting & padding
// trim leading & shift left remaining
for (cp1
=s
; isblank((int)*cp1
); cp1
++ ) // skip leading blanks, via cp1 ;
for (cp2=s; *cp1; cp1++, cp2++) // shift-left remaining chars, via cp2
*cp2 = *cp1;
*cp2-- = '\0'; // mark end of left trimmed s
// replace trailing blanks with '\0's
while ( cp2
> s
&& isblank((int)*cp2
) ) *cp2-- = '\0'; // pad with '\0'
return s;
}
// ------------------------------------------------------------------------------------
// remove from string s any char contained in string del (return the modified string s)
char *s_strip(char *s, const char *del)
{
if ( !s || !*s)
return NULL;
char *cp1; // for parsing the whole s
char *cp2; // for keeping desired *cp1's
for (cp1=s, cp2=s; *cp1; cp1++ )
if ( !strchr(del
, *cp1
) ) // *cp1 is NOT contained in del *cp2++ = *cp1; // copy it to start of s, via cp2
*cp2 = 0; // null terminate the trimmed s
return s;
}
// ------------------------------------------------------------------------------------
char *s_ncopy( char *dst, const char *src, int n )
{
char *ret = dst;
while ( (dst-ret) < n-1 && (*dst=*src) != '\0' )
dst++, src++;
if ( *dst )
*dst = 0;
return ret;
}
// ------------------------------------------------------------------------------------
// Convert s to lowercase and return it (or NULL on error)
//
char *s_tolower( char *s )
{
if ( !s ) // error, early exit
return NULL;
char *ret;
for ( ret
=s
; (*s
=tolower(*s
)); s
++ ) ;
return ret;
}
// ------------------------------------------------------------------------------------
// break a string up to maxtokens tokens and store them in *tokens[]
// (uses " " as the delimeter string)
// returns the number of tokens, or 0 on failure
// ------------------------------------------------------------------------------------
int s_tokenize(char *s, char *tokens[], int maxtokens, char *delimiters)
{
if ( !s || !*s || !tokens || !maxtokens || !delimiters || !*delimiters )
return 0;
register int i=0;
tokens
[0] = strtok(s
, delimiters
); if (tokens[0] == NULL)
return 0;
for (i
=1; i
< maxtokens
&& (tokens
[i
]=strtok(NULL
, delimiters
)) != NULL
; i
++);
return i;
}
// ------------------------------------------------------------------------------------
// Convert a Gregorian date to a Julian Day count
// IMPORTANT: accurate ONLY for dates after Oct 15, 1582 (Gregorian Calendar)
// Algorithm by Henry F. Fliegel & Thomas C. Van Flandern:
// http://w...content-available-to-author-only...c.ch/cal_stud/jdn.htm#comp
//
long date_2jdn( Date date )
{
return
( 1461 * ( date.y + 4800 + ( date.m - 14 ) / 12 ) ) / 4 +
( 367 * ( date.m - 2 - 12 * ( ( date.m - 14 ) / 12 ) ) ) / 12 -
( 3 * ( ( date.y + 4900 + ( date.m - 14 ) / 12 ) / 100 ) ) / 4 +
date.d - 32075;
}
// ----------------------------------------------------------------------------------
// Convert a Julian Day count to a Gregorian date (d,m,y)
// IMPORTANT: accurate ONLY for dates after Oct 15, 1582 (Gregorian Calendar)
// Algorithm by Henry F. Fliegel & Thomas C. Van Flandern:
// http://w...content-available-to-author-only...c.ch/cal_stud/jdn.htm#comp
//
Date *jdn_2date( Date *date, long jd )
{
long l = jd + 68569;
long n = ( 4 * l ) / 146097;
l = l - ( 146097 * n + 3 ) / 4;
long i = ( 4000 * ( l + 1 ) ) / 1461001;
l = l - ( 1461 * i ) / 4 + 31;
long j = ( 80 * l ) / 2447;
date->d = l - ( 2447 * j ) / 80;
l = j / 11;
date->m = j + 2 - ( 12 * l );
date->y = 100 * ( n - 49 ) + i + l;
return date;
}
// ------------------------------------------------------------------------------------
// Compare date1 against date2, chronologically.
// Return: 1 if date1 > date2, 0 if date1 == date2, -1 if date1 < date2
int date_compare( Date date1, Date date2 )
{
long int jdn1 = date_2jdn( date1 ); // convert date1 to julian day number
long int jdn2 = date_2jdn( date2 ); // convert date2 to julian day number
if (jdn1 > jdn2)
return 1;
if (jdn1 == jdn2)
return 0;
return -1;
}
// ------------------------------------------------------------------------------------
// Check if date.d exceeds the max allowed days in month date.m of year date.y
// Return TRUE if does not, FALSE otherwise
// for example: 29/2/2011, 31/4/anyyear are both FALSE
//
bool date_dayinbounds( Date date, int mdays[] )
{
if ( date.d > mdays[ date.m - 1 ] )
{
if ( date.d == 29 && date.m == FEB && isleap( date.y ) )
return TRUE;
return FALSE;
}
return TRUE;
}
// ------------------------------------------------------------------------------------
// Check if date lyes between calstart and calend (inclusive)
// Return TRUE if it does, FALSE otherwise
//
bool date_inbounds( Date date, Date calstart, Date calend)
{
return date_compare(date, calstart) >= 0 && date_compare(date, calend) <= 0;
}
// ------------------------------------------------------------------------------------
// Convert Gregorian date to weekday (valid from Sep 14, 1752 to Dec 31, 9999)
// Return 0 to 6 (Mon to Sun)
//
int date_2weekday( Date date )
{
return date_2jdn(date) % 7; // return jdn % 7
}
// ------------------------------------------------------------------------------------
// Calc the difference between date1 and date2 and RETURN it expressed as # of days.
// IMPORTANT:,
// the difference is also calc'ed as days, months, years and passed into datediff
// Algorithm by Mohammed Ali Babu
// http://w...content-available-to-author-only...t.com/KB/datetime/DateDurationCalculation1.aspx
//
long date_diff( Date *datediff, Date date1, Date date2, int mdays[] )
{
if ( !datediff )
return -1;
long int jdn1 = date_2jdn( date1 ); // calc jd of date1
long int jdn2 = date_2jdn( date2 ); // calc jd of date2
Date *dp2 = (jdn2 > jdn1) ? &date2 : &date1; // dp2 points to latest date
Date *dp1 = (dp2 == &date1) ? &date2 : &date1; // dp1 points to earliest date
/*
* the following alogorithm is published by Mohammed Ali Babu at:
* http://w...content-available-to-author-only...t.com/KB/datetime/DateDurationCalculation1.aspx
*/
// first calc the difference of the day part
int increment = 0;
if ( dp1->d > dp2->d )
increment = mdays[ dp1->m - 1 ];
if (increment == -1)
{
if ( isleap( dp1->y ) )
increment = 29;
else
increment = 28;
}
if (increment != 0)
{
datediff->d = (dp2->d + increment) - dp1->d;
increment = 1;
}
else
datediff->d = dp2->d - dp1->d;
// then calc the difference of the month part
if ( (dp1->m + increment) > dp2->m )
{
datediff->m = (dp2->m + 12) - (dp1->m + increment);
increment = 1;
}
else {
datediff->m = dp2->m - (dp1->m + increment);
increment = 0;
}
// and last calculate the difference of the year part
datediff->y = dp2->y - (dp1->y + increment);
return myabs( jdn2-jdn1 );
}
// ------------------------------------------------------------------------------------
// Add ndays to basedate and Return the result into date (ndays can be negative)
// (calstart and calend are used for boundary checking)
//
Date *date_plusdays( Date *date, Date basedate, long ndays, Date calstart, Date calend )
{
long jstart = date_2jdn( calstart ); // julian day of our calendar start date
long jend = date_2jdn( calend ); // julian day of our calendar end date
long jd = date_2jdn( basedate ); // julian day of basedate
if ( jd+ndays > jend ) // fix overflow (calend)
return jdn_2date( date, jend );
if ( jd+ndays < jstart ) // fix underflow (calstart)
return jdn_2date( date, jstart );
return jdn_2date( date, jd+ndays );
}
// ------------------------------------------------------------------------------------
// Convert string inbuf into a valid signed long int, store it in date->d
// Return FALSE on failure
//
bool date_getoperation( Date *date, char *inbuf )
{
if ( !inbuf || !*inbuf || (*inbuf != '+' && *inbuf != '-') )
return FALSE;
char *tail; // in strtol() for err checking
errno = 0;
date->m = date->y = INVALID;
s_strip(inbuf, " \t\v"); // strip off blanks from inbuf
date
->d
= strtol(inbuf
, &tail
, 10); // convert inbuf to long if ( *tail != '\0' || errno == ERANGE )
{
puts("\t*** error: invalid operation"); date->d = INVALID;
return FALSE;
}
return TRUE;
}
// ------------------------------------------------------------------------------------
// If string inbuf contains any of our recognized mnemonics, date is set accordingly
// and the function returns TRUE (otherwise it returns FALSE).
//
bool date_getmnemonic( Date *date, char *inbuf, const Date calstart, const Date calend )
{
if ( !inbuf || !*inbuf ) // inbuf is either non-existant or empty
return FALSE; // early exit
time_t today
= time( NULL
); // get system time struct tm
*tmtoday
= localtime( &today
);// convert it to a tm structure long jstart = date_2jdn( calstart ); // julian day of our calendar start date
long jend = date_2jdn( calend ); // julian day of our calendar end date
long jtoday;
Date caltoday;
caltoday.d = (long) tmtoday->tm_mday;
caltoday.m = (long) tmtoday->tm_mon + 1;
caltoday.y = (long) tmtoday->tm_year + 1900;
jtoday = date_2jdn( caltoday );
if ( !strcmp( inbuf
, "start") ) { jdn_2date( date, jstart );
return TRUE;
}
if ( !strcmp( inbuf
, "yesterday") ) { // jdn_2date( date, jtoday-1 ); // faster but no boundary checking
date_plusdays( date, caltoday, -1, calstart, calend);
return TRUE;
}
if ( !strcmp( inbuf
, "today") ) { jdn_2date( date, jtoday );
return TRUE;
}
if ( !strcmp( inbuf
, "tomorrow") ) { // jdn_2date( date, jtoday+1 ); // faster but no boundary checking
date_plusdays( date, caltoday, +1, calstart, calend);
return TRUE;
}
if ( !strcmp( inbuf
, "end") ) { jdn_2date( date, jend );
return TRUE;
}
return FALSE;
}
// ------------------------------------------------------------------------------------
// Read a Gregorian date as a string form stdin, validate it & return it inside *date
// IMPORTANT:
//
// The function returns TRUE if the input string is a valid operation (i.e "-200" )
// instead of a valid date string (i.e. "1/1/2000" ). In that case, date->d
// is assigned the numeric value of the operation ( -200 for the above example )
// while date->m and date->y are assined the value -1 ( INVALID ).
//
// Only the second date is allowed to accept an operation string, so we use
// the parameter 'id' in order to know whether we're dealing with date1 or
// date2 in here.
//
// Since in case of a valid operation in the input, the returned date does NOT
// hold a valid date, further processing needs to be done in the main() function,
// to ensure that date2 is properly converted to a valid date, equalling to:
// date1 + date2.d, before attempting to do anything else with it (e.g. printing
// it)
//
//
bool date_askuser( int id, char *prompt, Date *date,
int max_inbuf, int mdays[], Date calstart, Date calend )
{
if ( !prompt || !date) // inbuf does not exist or it is empty
return FALSE;
char inbuf[ max_inbuf ]; // for reading the user input
char *stokens[3]; // to read d, m, y as strings
char *tail; // in strtol() for err checking
bool stop = TRUE; // for controlling the main loop
bool operated = FALSE; // got a date or an operation?
do
{
stop = TRUE; // reset boolean flag to TRUE
operated = FALSE; // reset boolean flag to FALSE
// prompt and get user input
printf( prompt
); // ask for input fflush(stdin
); // clear input buffer (stdin) s_get(inbuf, max_inbuf); // read input as string in inbuf
// check if user typed just an ENTER
if ( !*inbuf )
continue; // stop = TRUE
s_trim( s_tolower(inbuf) ); // trim leading & trailng blanks
// check if user typed an operation (+/-) followed by a number
if ( id == ID1 && (*inbuf =='+' || *inbuf == '-') ) {
puts("\t*** error: operations only allowed in the 2nd date"); stop = FALSE;
continue;
}
if ( date_getoperation(date, inbuf) ) {
operated = TRUE;
continue; // stop = TRUE
}
// check if user typed any of our mnemonic strings
if ( date_getmnemonic( date, inbuf, calstart, calend ) ) {
stop = TRUE;
continue;
}
// split inbuf in up to 3 strings (tokens) assuming d, m, y
if ( s_tokenize( inbuf, stokens, 3, " /,.;:\t") != 3 ) {
puts("\t*** error: invalid date"); stop = FALSE;
continue;
}
// demand from user to type a day between 1-31
errno = 0; // reset golbal var errno
date
->d
= strtol(stokens
[0], &tail
, 10);// convert str day to long if ( !validday(date->d) || *tail != '\0' || errno == ERANGE )
{
puts("\t*** error: valid days are 1-31"); date->d = INVALID;
stop = FALSE;
continue;
}
// demand from user to type a month between 1-12
errno = 0; // reset golbal var errno
date
->m
= strtol(stokens
[1], &tail
, 10);// convert str month to long if ( *tail != '\0' || errno == ERANGE || !validmonth(date->m) )
{
puts("\t*** error: valid months are 1-12"); date->m = INVALID;
stop = FALSE;
continue;
}
// demand from user to type a year between 1752-9999
errno = 0; // reset golbal errno
date
->y
= strtol(stokens
[2], &tail
, 10);// convert str year to long if ( *tail != '\0' || errno == ERANGE || !validyear(date->y) )
{
puts("\t*** error: valid years are 1752-9999"); date->y = INVALID;
stop = FALSE;
continue;
}
/* now we have a complete date (d,m,y) but this
* does not gurantee us that it is a valid one
* (e.g. 29/2/2000 is valid, but 29/2/2001 is not)
* (also, e.g. 31/4 is invalid, an so on)
*/
// ensure day lyes inside the month boundary of the typed date
if ( !date_dayinbounds(*date, mdays) )
{
date->d = date->m = date->y = INVALID;
puts("\t*** error: invalid day"); stop = FALSE;
continue;
}
// ensure date is between 14/9/1752 and 31/12/9999
if ( !date_inbounds(*date, calstart, calend) )
{
date->d = date->m = date->y = INVALID;
puts("\t*** error: valid dates are 14/9/1752 - 31/12/9999"); stop = FALSE;
continue;
}
} while( !stop || !*inbuf );
return operated;
}
// ------------------------------------------------------------------------------------
void print_help( void )
{
//printf("\n\n%70s\n", "<mig_f1@hotmail.com>\n");
puts("A sample program performing various calculations between two dates in \nthe time interval: 14/9/1752 to 31/12/9999 (results never exceed it).\n\nEach date input is checked in real time against both syntax & logical\nerrors. It can be in one of the following formats...\n-\td/m/y\t\t: other separators: :,.; and tab\n-\ta signed number\t: e.g. -200 (subtract 200 days from 1st Date)\n-\tstart\t\t: equals to 14/9/1752\n-\tyesterday\t: equals to yesterday's date\n-\ttoday\t\t: equals to today's date\n-\ttomorrow\t: equals to tomorrow's date\n-\tend\t\t: equals to 31/12/9999\n\nThe calculated results include...\n-\tdifference expressed in days\n-\tdifference expressed in days, months, years\n-\tweekday indication\n-\tJulian Day Number indication\n-\tleap year indication\n-\tmonthly calendar of both dates (or 1st only, if same with 2nd)"); puts("\nBONUS:\tPass a year (1752 - 9999) in the command line, to create a text\n\tfile called \"calYEAR.txt\", containing the yearly calendar.");
return;
}
// ------------------------------------------------------------------------------------
// Print date in the form: MONTH DAY YEAR, Weekday (jdn) (is leap year ?)
//
void print_date(Date date, int id, char *mnames[], char *dnames[] )
{
if ( !validday( date.d ) || !validmonth( date.m ) || !validyear( date.y ) )
return;
printf( "\t%d. %s %02ld %04ld, %-9s (jdn: %ld) %s\n", id,
mnames[ (int)(date.m)-1 ],
date.d, date.y, dnames[ date_2weekday(date) ],
date_2jdn( date ),
isleap( date.y ) ? "\t(leap year)" : ""
);
return;
}
// ------------------------------------------------------------------------------------
// Print the difference between date1 and date 2 expressed both in number of days
// and in the form: days, months, years
//
void print_diffs(long int ndays, Date date, char *mnames[], char *dnames[] )
{
printf("\tDiff:\t%ld day(s)\n", ndays
); printf( "\t\t%ld years(s), %02ld months(s) and %02ld days(s)\n", date.y, date.m, date.d );
return;
}
// ------------------------------------------------------------------------------------
// Print the monthly calendar of date
//
void fprint_calmonth(Date date, int mdays[], char *mnames[], char *dnames[], FILE *fp )
{
register int i=0, j=0;
Date mon1date = { // temp date for 1/date.m/date.y
.d = 1, .m = date.m, .y = date.y
};
int monthdays = mdays[date.m - 1]; // calc # days of date.m
if ( isleap(date.y) && date.m == FEB ) // +1 if leap February
monthdays++;
// print month & year
fprintf(fp
, "\n%s %ld\t", mnames
[date.
m - 1], date.
y );
// print header (day names)
for (i=0; i<7; i++) {
}
// now print the day numbers
// start with blanks, when needed
for (i=0; i < date_2weekday( mon1date ); i++)
// start printing the day numbers (i is carried from previous loop)
for ( j=0; j < monthdays; j++, i++ )
{
if ( i % 7 == 0)
}
return;
}
// ------------------------------------------------------------------------------------
// Print the yearly calendar of date
//
void fprint_calyear( Date date, int mdays[], char *mnames[], char *dnames[], FILE *fp )
{
register int i;
for (i=0; i<12; i++) {
date.m = i+1;
fprint_calmonth( date, mdays, mnames, dnames, fp );
}
return;
}
// ------------------------------------------------------------------------------------
// Check for any command line arguments, if any and handle them accordingly.
// Currently we are only accepting 1 argument, representing a year for which we
// create a text file, holding the yearly calendar. Years earlier than 1752 or
// later than 9999 are automatically fixed to be equal with the lower or upper
// bound, respectively.
//
void do_cmlargs(char *argv[], Date calstart, Date calend,
int mdays[], char *mnames[], char *dnames[] )
{
Date ydate; // for 1/1/argv[1]
Date *pdate = NULL; // temp pointer
FILE *fp; // file pointer
char fname[12+1] = "";
s_strip( argv[1], " \t\v"); // strip off blanks
// construct a temporary date corresponding to: 1/1/argv[1]
ydate.
d=1; ydate.
m=1; ydate.
y = strtol( argv
[1], NULL
, 10 );
// fix year if it either underflows or overflows (1752 - 9999)
pdate = &ydate;
if ( date_compare(ydate, calstart) < 0 )
pdate = &calstart;
else if ( date_compare(ydate, calend) > 0 )
pdate = &calend;
// construct filename
sprintf(fname
, "cal%ld.txt", pdate
->y
);
// open file and write the yearly calendar
if ( !fp ) { // file open failed
printf("\t*** write error in file: %s\n", fname
); }
else {
fprint_calyear(*pdate, mdays, mnames, dnames, fp);
printf( "\t*** yearly calendar of %ld printed in %s\n", pdate->y, fname );
}
printf("press ENTER to continue...");
return;
}
// ------------------------------------------------------------------------------------
int main( int argc, char *argv[] )
{
char *dnames[7] = { // day names
"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"
};
char *mnames[12] = { // month names
"JAN","FEB","MAR","APR","MAY","JUN","JUL","AUG","SEP","OCT","NOV","DEC"
};
// month lengths in days
int mdays[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
Date calstart = { .d=14, .m=9, .y=1752 }; // our calendar starting date
Date calend = { .d=31, .m=12, .y=9999 }; // our calendar ending date
Date date1, date2; // dates to be read from user
Date diffdate; // for holding diff as (d,m,y)
long ndays; // difference in days
bool operated = FALSE; // input was date or operation?
char inbuf[MAX_INBUF] = ""; // our own input buffer
// check & execute any command line arguments
if ( argv[1] )
do_cmlargs( argv, calstart, calend, mdays, mnames, dnames );
// start the main loop of the program
do
{
print_help();
// read date1 and date2
date_askuser( ID1, "\nFirst date (d/m/y): ", &date1,
MAX_INBUF, mdays, calstart, calend );
operated = date_askuser(
ID2, "Second date (d/m/y): ", &date2,
MAX_INBUF, mdays, calstart, calend );
// if date2 was an operation, store the resulting date into date2
if ( operated )
date_plusdays( &date2, date1, date2.d, calstart, calend);
// calc difference between date1 and date2
ndays = date_diff( &diffdate, date1, date2, mdays );
// print dates and their difference
puts("\t--------------------------------------------------"); print_date( date1, ID1, mnames, dnames );
print_date( date2, ID2, mnames, dnames );
puts("\t--------------------------------------------------"); print_diffs( ndays, diffdate, mnames, dnames );
puts("\t--------------------------------------------------");
// display month calendars
fprint_calmonth( date1, mdays, mnames, dnames, stdout );
if ( date1.y != date2.y || (date1.y == date2.y && date1.m != date2.m) )
fprint_calmonth(date2, mdays, mnames, dnames, stdout);
} while ( *s_tolower( s_get(inbuf, MAX_INBUF) ) != 'n' );
}