Sample Output #1
Sample Output #2
Sample Output #3
έγραψε:
Βελτιώσεις στη V3.65 (το πρόγραμμα έχει πλέον δική του σελίδα: http://x-karagiannis.gr/prog/libs/conte ... /dates.php)
- διεύρυνση του κάτω ορίου στο ημερολόγιο, από 14 Σεπ 1752 σε 15 Οκτ 1582
- δυνατότητα τυπώματος ετήσιου ημερολογίου είτε στην οθόνη είτε σε αρχείο κειμένου που καθορίζει ο χρήστης
- αλλαγή όλων των σχολίων του κώδικα σε μορφή αναγνωρίσιμη από το Doxygen
- δημιουργία πλήρους τεκμηρίωσης στα Αγγλικά, τόσο για τον κώδικα όσο και για το πρόγραμμα
http://forum.ubuntu-gr.org/viewtopic.ph ... 05#p226905
έγραψε:
Βελτιώσεις στη V3.6 (το πρόγραμμα έχει πλέον δική του σελίδα: http://x-karagiannis.gr/prog/libs/conte ... /dates.php)
- αναθεώρηση της αρχιτεκτονικής του κώδικα
- πλήρης συμβατότητα του κώδικα με το πρότυπο ANSI/C89
- ένδειξη σημερινής ημέρας στα ημερολόγια
- δυνατότητα εξόδου μετά την παραγωγή ετήσιου ημερολογίου στη γραμμή εντολών
έγραψε:
Βελτιώσεις στη V3.5 (κώδικας και σύνδεσμος κατεβάσματος στο τέλος της δημοσίευσης)Σημειώσεις Υλοποίησης:
- αποθήκευση ετήσιων ημερολογίων σε αρχεία κειμένου: "calΕΤΟΣ.txt", περνώντας το έτος ως command line argument.
Αν το εκτελέσιμο ονομάζεται "dates", τότε πληκτρολογώντας στη γραμμή εντολών:θα δημιουργηθεί το αρχείο: cal2011.txt που θα περιέχει το ετήσιο ημερολόγιο του 2011
- Κώδικας: Επιλογή όλων
dates 2011
Έτη μικρότερα του 1752 προσαρμόζονται αυτόματα στο έτος 1752, ομοίως έτη μεγαλύτερα του 9999 προσαρμόζονται αυτόματα στο έτος 9999.
Η συνάρτηση που ασχολείται με τα command line arguments είναι η...
- Κώδικας: Επιλογή όλων
void do_cmlargs(char *argv[], Date calstart, Date calend, int mdays[], char *mnames[], char *dnames[] )
η οποία καλείται στη αρχή της main(). Μετατρέπει το string argv[1] σε long, το ελέγχει να μην ξεπερνάει τα όρια του ημερολογίου, χρησιμοποιώντας την συνάρτηση:
- Κώδικας: Επιλογή όλων
int date_compare( Date date1, Date date2 )
Κατόπιν δημιουργεί το αρχείο κειμένου και τυπώνει μέσα του το ετήσιο ημερολόγιο, μέσω της συνάρτησης:
- Κώδικας: Επιλογή όλων
void fprint_calyear( Date date, int mdays[], char *mnames[], char *dnames[], FILE *fp )
Έφτιαξα επίσης τις παρακάτω συναρτήσεις για να γίνουν νοηματικά πιο κατανοητοί κάποιοι έλεγχοι σε διάφορα σημεία του προγράμματος (δείτε τα σχόλιά τους στον κώδικα για περισσότερες λεπτομέρειες):
- Κώδικας: Επιλογή όλων
bool date_dayinbounds( Date date, int mdays[] );
bool date_inbounds( Date date, Date calstart, Date calend);
Βελτιώσεις στη V3 (κώδικας και σύνδεσμος κατεβάσματος στο τέλος της δημοσίευσης)Σημειώσεις Υλοποίησης:
- εμφάνιση μηνιαίων ημερολογίων για τις 2 ημερομηνίες (ή ενός αν αναφέρονται στον ίδιο μήνα)
Προστέθηκε η συνάρτηση:που εμφανίζει στην οθόνη το μηνιαίο ημερολόγιο του ορίσματος date. Χρησιμοποιείται στην main() μαζί με έλεγχο ώστε να μην καλείται δυο φορές όταν οι 2 ημερομηνίες αναφέρονται στον ίδιο μήνα.
- Κώδικας: Επιλογή όλων
void print_calmonth(Date date, int mdays[], char *mnames[], char *dnames[] );
Προστέθηκε επίσης η γενική συνάρτηση διαχείρισης string:η οποία αφαιρεί από το string s οποιονδήποτε χαρακτήρα βρίσκεται μέσα στο string del. Χρησιμοποιείται στη συνάρτηση date_getoperation() με κενούς χαρακτήρες στο del (space και tab) πριν γίνει η μετατροπή της εισόδου από string σε long int, ώστε ακόμα κι αν ο χρήστης γράψει π.χ. "+ 2 1 65 9" θα εκληφθεί ως: "+21659". Σημειώστε πως η s_strip() διαφέρει από την s_trim() (π.χ. η πρώτη αφαιρεί κι ενδιάμεσα κενά, ενώ η δεύτερη μόνο όσα βρίσκονται στην αρχή και στο τέλος).
- Κώδικας: Επιλογή όλων
char *s_strip(char *s, const char *del);
Τέλος, απλώς αλλάχτηκε το όνομα της συνάρτησης print_results() σε print_diffs()
Βελτιώσεις στη V2 (κώδικας και σύνδεσμος κατεβάσματος στο τέλος της δημοσίευσης)Σημειώσεις Υλοποίησης:
- προσθαφαίρεση οποιουδήποτε αριθμού ημερών στην 1η ημερομηνία (αντί για 2η ημερομηνία, γράψτε π.χ. +200 ή -3000)
- εμφάνιση του Ιουλιανού αριθμού (Julian Day Number) για την κάθε ημερομηνία στα αποτελέσματα
- εμφάνιση των οδηγιών όταν ο χρήστης επιλέξει να δώσει νέες ημερομηνίες)
Επειδή η εισαγωγή στοιχείων δεν σχεδιάστηκε εξαρχής με δυνατότητα αριθμητικής, κατέφυγα σε μια γρήγορη μεν, ολίγον... μπακάλικη (και ελαφρώς πολύπλοκη) δε, λύση για την προσθαφαίρεση ημερών στην 1η ημερομηνία.
Εν συντομία, προστέθηκε η συνάρτηση:
- Κώδικας: Επιλογή όλων
bool date_getoperation( Date *date, char *inbuf );
η οποία μετατρέπει το string εισόδου σε long int, και χρησιμοποιείται μέσα στην συνάρτηση:που με τη σειρά της έχει αλλαχτεί αφενός να επιστρέφει boolean κι αφετέρου να δέχεται ένα επιπλέον όρισμα, το id, ώστε να ξέρουμε αν διαβάζουμε την 1η ή την 2η ημερομηνία. Αν διαβάζουμε την 2η, τότε της "επιτρέπουμε" αντί για νορμάλ ημερομηνία να διαβάσει έναν signed long int, τον οποίον τον αποθηκεύουμε στο πεδίο: d της μεταβλητής date, προτού την επιστρέψουμε στην main(), παράλληλα με την τιμή TRUE στην επιστροφή της συνάρτησης αυτής κάθε αυτής.
- Κώδικας: Επιλογή όλων
bool date_askuser( int id, char *prompt, Date *date, int max_inbuf, int mdays[], Date calstart, Date calend )
H main() με τη σειρά της εξετάζει την τιμή επιστροφής της date_askuser() για την 2η ημερομηνία και αν είναι TRUE σημαίνει πως η date2 αντί για κανονική ημερομηνία περιέχει πράξη. Σε αυτή την περίπτωση ο αριθμός της πράξης είναι ήδη αποθηκευμένος στο πεδίο: date2.d οπότε καλεί την συνάρτηση: date_plusdays() για να προσθέσει τις ημέρες που βρίσκονται στο date2.d (+ ή -) στην date1 και να αποθηκεύσει το αποτέλεσμα σε μορφή κανονικής πια ημερομηνίας μέσα στη μεταβλητή date2.
Αν το αποτέλεσμα της πράξης βγαίνει εκτός ορίων του ημερολογίου, τότε στη date2 εκχωρείται το όριο (είτε το πάνω, είτε το κάτω, ανάλογα με το ποιο πήγε να... παραβιαστεί.
Υπάρχουν και σχετικά σχόλια μέσα στον κώδικα (στα Αγγλικά).
Το πρόγραμμα διαβάζει από τον χρήστη 2 ημερομηνίες και κατόπιν υπολογίζει:
- την ημέρα της εβδομάδας για την καθεμιά τους
- το αν κάποια από αυτές βρίσκεται σε δίσεκτο έτος
- ποια είναι η διαφορά τους σε ημέρες
- ποια είναι η διαφορά τους σε μορφή: μέρες, μήνες, χρόνια.
- EDIT: V2
- σε ποιον Ιουλιανό αριθμό αντιστοιχούν (Julian Day Number)
- EDIT: V3
- εμφάνιση μηνιαίων ημερολογίων για τις 2 ημερομηνίες (ή ενός αν αναφέρονται στον ίδιο μήνα)
- EDIT: V3.5
- δημιουργία αρχείων κειμένου με ετήσια ημερολόγια, περνώντας το έτος ως command line argument στη γραμμή εντολών
- start
- end
- today
- yesterday
- tomorrow
- EDIT: V2
- οποιονδήποτε αριθμό με πρόσημο
EDIT: V3.5: πληκτρολογώντας στη γραμμή εντολών ένα έτος μετά το όνομα του προγράμματος (command line argument) δημιουργείται αρχείο κειμένου που περιέχει το ετήσιο ημερολόγιο του έτους αυτού.
Κατά την εισαγωγή της κάθε ημερομηνίας κάνει real-time ελέγχους εγκυρότητας. Σε περίπτωση σφάλματος, αφού πρώτα ενημερώσει τον χρήστη, τον προτρέπει συνεχώς για νέο input μέχρι να εισαγάγει έγκυρη ημερομηνία ή κάποιο από τα παραπάνω mnemonics.
Η συνάρτηση που κάνει όλη τη... βρώμικη δουλειά με τους ελέγχους εγκυρότητας στο διάβασμα των ημερομηνιών είναι η:
- Κώδικας: Επιλογή όλων
void date_askuser( char *prompt, Date *date, int max_inbuf, int mdays[] );
η οποία μεταξύ άλλων χρησιμοποιεί την:
- Κώδικας: Επιλογή όλων
bool date_getmnemonic( Date *date, char *inbuf, const Date calstart, const Date calend );
Για τον υπολογισμό της διαφοράς των 2 ημερομηνιών εκφρασμένης σε ημέρες, όπως και για τον υπολογισμό της ημέρας της εβδομάδας για την καθεμιά τους, οι ημερομηνίες μετατρέπονται σε Ιουλιανούς αριθμούς (Julian Day Numbers) με τη συνάρτηση:
- Κώδικας: Επιλογή όλων
long date_2jdn( Date date );
η οποία έχει και το... αντίστροφο ταίρι της:
- Κώδικας: Επιλογή όλων
Date *jdn_2date( Date *date, long jd )
Η διαφορά εκφρασμένη σε μορφή: μέρες, μήνες, χρόνια υπολογίζεται στη συνάρτηση:
- Κώδικας: Επιλογή όλων
long date_diff( Date *datediff, Date date1, Date date2, int mdays[] );
Επίσης, υπάρχουν οι παρακάτω βοηθητικές συναρτήσεις διαχείρισης strings, οι οποίες ουσιαστικά είναι αυτόνομες και μπορούν να χρησιμοποιηθούν όπως είναι σε οποιοδήποτε πρόγραμμα χρησιμοποιεί 8-μπιτες κωδικοσελίδες χαρακτηρων ASCII...
- Κώδικας: Επιλογή όλων
char *s_get(char *s, size_t len);
char *s_trim(char *s);
char *s_tolower( char *s );
int s_tokenize(char *s, char *tokens[], int maxtokens, char *delimiters);
Ακόμα υπάρχει η συνάρτηση:
- Κώδικας: Επιλογή όλων
Date *date_plusdays( Date *date, Date basedate, long ndays, Date calstart, Date calend );
Και τέλος, τα παρακάτω macros χρησιμοποιούνται για διάφορους ελέγχους εγκυρότητας...
- Κώδικας: Επιλογή όλων
#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) ) )
Όλα τα σχόλια όπως και το πρόγραμμα τα έχω γράψει στα Αγγλικά, ελπίζω να μην υπάρχει πρόβλημα! Ο κώδικας είναι γραμμένος σε στάνταρ C οπότε λογικά γίνεται compile σε οποιαδήποτε πλατφόρμα. Να σημειώσω επίσης πως δεν χρησιμοποιώ πρότυπα συναρτήσεων, γιατί φροντίζω όλες μου οι συναρτήσεις να ορίζονται πριν χρησιμοποιηθούν (για αυτό είναι και τελευταία η main() ).
Σχόλια, απορίες, προτάσεις, επισημάνσεις, παρατηρήσεις πάντα ευπρόσδεκτα, και κυρίως bug reports
Παραθέτω τον κώδικα, αλλά επειδή το code-tag τον κάνει μαντάρα στη στοίχιση των σχολίων, μπορείτε επίσης να τον δείτε με syntax-highlighting και να τον κατεβάσετε κι από το ideone.com, στα ακόλουθα links: v3.5 ( v3, v2, v1 ).
- Κώδικας: Επιλογή όλων
// -------------------------------------------------------------------------------
// 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://www.codeproject.com/KB/datetime/DateDurationCalculation1.aspx
// http://www.hermetic.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://www.hermetic.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://www.hermetic.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://www.codeproject.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://www.codeproject.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++) {
fprintf(fp, "%3s ", dnames[i]);
}
fputc('\n', fp);
// now print the day numbers
// start with blanks, when needed
fprintf(fp, "\t\t");
for (i=0; i < date_2weekday( mon1date ); i++)
fprintf(fp, "%3s ", "");
// start printing the day numbers (i is carried from previous loop)
for ( j=0; j < monthdays; j++, i++ )
{
if ( i % 7 == 0)
fprintf(fp, "\n\t\t");
fprintf(fp, "%3d ", j+1);
}
fputc('\n', fp);
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
fp = fopen(fname, "w");
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 );
fclose( fp );
}
printf("press ENTER to continue...");
fflush(stdin); getchar();
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
putchar('\n');
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);
printf("\ntry again (/n): ");
fflush(stdin);
} while ( *s_tolower( s_get(inbuf, MAX_INBUF) ) != 'n' );
exit(0);
}




