Inspired by a section from K&R C (exercises 5-8 and 5-9), I decided to write a program that would take a date numerous numeric forms, all based around DD/MM/YYYY, and from the given date determine some information.
Firstly, find out if the date is valid, i.e. a valid day of a valid month and from there figure out some other things. Is it a leap year? What is the day of the week? What is the day of the year? What is the name of the month? Once this is all calculated the program converts the date into a string and everything is printed out to the user.
I spent a lot of time re-factoring my code and trying to make as many optimisations as possible to code size, memory usage, and potential performance chokes, but I feel as if some of these may have been counterproductive and conflicted with each other and may have netted a decrease in performance. These include, but are not limited to, using the smallest possible variable type for a job (i.e. a short instead of an int) and using the ternary operator where ever possible. How can I improve this code to make it faster, more efficient, less redundant, and easier to read?
/*
* Filename: date.c
* Date: 5-9-17
* Licence: GNU GPL V3
*
* Reads a date string in the form DD/MM/YYYY and determines if it is a valid date, the day of the year, if that year is a leap year, and converts the date to a string
*
* Options:
* N/A
*
* Return/exit codes:
* 0 EXEC_SUCCESS - Successful execution
* 1 MEM_ERROR - Memory allocation error
* 2 READ_ERROR - fgets() error
*
* Todo:
* N/A
*
* Possible additions:
* - Add option(s) for taking different date forms (i.e. -a switches to American date formats)
*
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#define EXEC_SUCCESS 0
#define MEM_ERROR 1
#define READ_ERROR 2
typedef struct {
short day;
short month;
short year;
} date_t;
const char *months[] = {
NULL ,
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
};
const char *days[] = {
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
};
const short days_per_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}
};
char *date_to_string(char **string, date_t date); /* Converts the date in decimal form into a string containing the day and name of the month */
short day_of_year(date_t date); /* Determines the day of the year from a given date */
short day_of_week(date_t date); /* Determines the numerical value of the day of the week (taking Sunday as the first day), only works on dates after 14/09/1752 due to the adoption of the Gregorian calendar in the UK */
bool is_valid_date(date_t date);
bool is_leap_year(date_t date);
int main()
{
date_t date;
char *date_string;
char input[128];
char *format[] = {
"%d/%d/%d",
"%d-%d-%d",
"%d.%d.%d",
"%d,%d,%d",
"%d %d %d"
};
printf("Note: day of week only accurate after 14/09/1752\n");
for(;;) {
printf("\nEnter date: ");
date = (const date_t){ 0 };
if(!fgets(input, sizeof(input), stdin)) {
fprintf(stderr, "\nError: fgets() read faliure!\n");
return READ_ERROR;
}
for(int i = 0; i < 5 && sscanf(input, format[i], &date.day, &date.month, &date.year) != 3; i++)
;
if(!is_valid_date(date)) {
fprintf(stderr, "Invalid date!\n");
continue;
}
if(!date_to_string(&date_string, date)) {
fprintf(stderr, "\nError: Memory allocation faliure!\n");
return MEM_ERROR;
}
printf( "The %s is a %s and is day number %d of the year %d, which is %sa leap year\n",
date_string,
days[day_of_week(date)],
day_of_year(date),
date.year,
is_leap_year(date) ? "" : "not "
);
free(date_string);
}
return EXEC_SUCCESS;
}
char *date_to_string(char **string, date_t date)
{
char date_str[48] = { '\0' };
char *suffix[] = { "st", "nd", "rd", "th" };
short date_str_len;
sprintf(date_str,
"%d%s of %s %d",
date.day,
suffix[ (date.day == 1 || date.day == 21 || date.day == 31) ? 0 :
(date.day == 2 || date.day == 22) ? 1 :
(date.day == 3 || date.day == 23) ? 2 : 3],
months[date.month],
date.year
);
date_str_len = strlen(date_str);
if(!(*string = calloc(date_str_len + 1, sizeof(char)))) { /* Remember kids: Always zero your character strings before you use them */
fprintf(stderr, "Error: Cannot allocate memory!\n");
return NULL;
}
strncpy(*string, date_str, date_str_len);
return *string;
}
short day_of_year(date_t date)
{
short day;
short month;
short leap;
leap = is_leap_year(date);
for(day = 0, month = 1; month < date.month; month++)
day += *(*(days_per_month + leap) + month);
day += date.day;
return day;
}
bool is_valid_date(date_t date)
{
return ((date.month > 0 && date.month < 13) && ((date.day > 0 && date.day <= days_per_month[0][date.month]) || (date.day == 29 && date.month == 2 && is_leap_year(date))));
}
bool is_leap_year(date_t date)
{
return ((date.year % 4 == 0 && date.year % 100 != 0) || date.year % 400 == 0);
}
short day_of_week(date_t date)
{
short day = date.day;
short month = date.month;
short year = date.year;
return (day += month < 3 ? year-- : year - 2, 23 * month / 9 + day + 4 + year / 4 - year / 100 + year / 400) % 7;
}