One of my university assignments asked us to create a program using `struct` in order to create a simple event scheduler. This program is for a single day only, not multiple days. ![][1] **Sample Input / Output** For a few reasons, I've opted to create a .GIF to display the sample input / output: ![][2] An example save file: <!-- language-all: lang-none --> > 8 30 dentist_appointment > 14 0 pickup_friend_from_airport > 17 0 buisness_meeting_at_the_office > 20 30 dinner_reservation --- > Disclaimer / Notice: The following code is *not to be copied* without proper credit. It is licensed under cc by-sa 3.0 with **attribution required**. <!-- language-all: lang-cpp --> #include <stdio.h> #include <string.h> #include <stdbool.h> #include <stdlib.h> #include <ctype.h> #define _MAX_EVENTS 10 // 10 Events Max #define _MAX_DESCRIPTION 101 // 100 Character Description Max typedef struct { // typedef a struct called event int hour; // Store the hour / HH int minute; // Store the minute / MM char description[_MAX_DESCRIPTION]; // Store the event description } event; // Print the menu selection void printMenu() { puts("+------ SCHEDULER ------+\n" "| 1. New Event |\n" "| 2. Delete Event |\n" "| 3. Display Schedule |\n" "| 4. Save Schedule |\n" "| 5. Load Schedule |\n" "| 6. Exit |\n" "+-----------------------+\n"); } // Return true if an event is NULL, false otherwise bool isNull(const event *e) { return e == NULL; } // Allocate memory for and initialize an event event *initEvent() { event *e = (event*)malloc(sizeof(event)); e->hour = 0; e->minute = 0; strcpy(e->description, ""); return e; } // Take user input until value is between min and max inclusive, return the input int inputRange(const int min, const int max) { int input = 0; char temp[21]; char *prompt = "| Enter a number between %d and %d: "; printf(prompt, min, max); fgets(temp, 21, stdin); input = atoi(temp); while (input > max || input < min) { // Data validation printf(prompt, min, max); fgets(temp, 21, stdin); input = atoi(temp); } return input; } // Setup a new event with user input and return a pointer to the same event event* newEvent(event *e) { if (isNull(e)) { // If e is NULL e = initEvent(); // Initialize it } char *seperator = "+--------------------------------+"; printf("\n%s\n| NEW EVENT |\n%s\n\n", seperator, seperator); puts("+---------- EVENT TIME ----------+"); e->hour = inputRange(0, 23); e->minute = inputRange(0, 59); puts(seperator); puts("\n+--- EVENT DESCRIPTION ---+"); printf("%s", "| Enter a description: "); fgets(e->description, _MAX_DESCRIPTION, stdin); puts("+-------------------------+\n"); puts("| Event successfully added.\n"); return e; } // Add an event to an event list at a specified index void addEventAtIndex(event list[], const event e, const int i) { if (isNull(&e)) { // if our event is NULL, return return; } list[i].hour = e.hour; list[i].minute = e.minute; strcpy(list[i].description, e.description); } // Insertion sort by swapping struct members void sort(event list[], const int size) { for (int i = 1; i < size; i++) { for (int j = i; j > 0 && (list[j - 1].hour > list[j].hour || (list[j - 1].hour == list[j].hour && list[j - 1].minute > list[j].minute)); j--) { int hourJ = list[j].hour; int minuteJ = list[j].minute; char descriptionJ[_MAX_DESCRIPTION]; strcpy(descriptionJ, list[j].description); int hourJMinus1 = list[j - 1].hour; int minuteJMinus1 = list[j - 1].minute; char descriptionJMinus1[_MAX_DESCRIPTION]; strcpy(descriptionJMinus1, list[j - 1].description); list[j].hour = hourJMinus1; list[j].minute = minuteJMinus1; strcpy(list[j].description, descriptionJMinus1); list[j - 1].hour = hourJ; list[j - 1].minute = minuteJ; strcpy(list[j - 1].description, descriptionJ); } } } // Add an event to an event list by sorting it into position void sortInsert(event list[], int *size, event e) { addEventAtIndex(list, e, *size); // Add event to the end of the list (*size)++; // Increment size // Insertion Sort sort(list, *size); } // Display an event in a readable format: [ID] HH:MM - DESCRIPTION void printEvent(const event e) { char h1 = { (e.hour / 10) + '0' }; // Extract the first digit and convert to char (if any, else 0) char h2 = { (e.hour - (e.hour / 10) * 10) + '0' }; // Extract the second digit and convert to char char m1 = { (e.minute / 10) + '0' }; char m2 = { (e.minute - (e.minute / 10) * 10) + '0' }; printf("%c%c:%c%c - %s", h1, h2, m1, m2, e.description); } // Display all events in an event list void printEventList(const event list[], const int size) { if (size == 0) { puts("\n| You have no events scheduled!\n"); return; } char *seperator = "+--------------------------------+"; printf("\n%s\n| MY SCHEDULE |\n%s\n\n", seperator, seperator); for (int i = 0; i < size; i++) { printf("| [%d] ", i); printEvent(list[i]); } putchar('\n'); } // Delete an event from an event list void deleteEvent(event list[], int *size) { if (*size == 0) { // If list is empty puts("\n| Event list already empty.\n"); return; } char temp[21]; int id; char *seperator = "\n+--------------------------------+"; printf("%s\n| DELETE EVENT |%s\n\n", seperator, seperator); for (int i = 0; i < *size; i++) { // Display the event list so the user can see which event to delete printf("| [%d] ", i); printEvent(list[i]); } printf("%s", "\n| Enter the ID of an event to delete: "); fgets(temp, 21, stdin); id = atoi(temp); if (id > *size - 1) { printf("\n| No event located at %d\n", id); return; } printf("| Event [%d] deleted successfully.\n\n", id); // Set hour and minute to some trivially large value for sorting purposes list[id].hour = 99; list[id].minute = 99; strcpy(list[id].description, ""); if (id != (*size - 1)) { // If the event to remove is already last, there's no need to sort it to last sort(list, *size); } (*size)--; // Decrement the size of the list } // Replace all spaces in a string with an underscore char *encode(char *s) { for (int i = 0; i < strlen(s); i++) { if (s[i] == ' ') { s[i] = '_'; } } return s; } // Replace all underscores in a string with an spaces char *decode(char *s) { for (int i = 0; i < strlen(s); i++) { if (s[i] == '_') { s[i] = ' '; } } return s; } // Save an event list to file void saveEventList(char *filename, event list[], int size) { FILE *f = fopen(filename, "w"); if (f == NULL) { // If our file is NULL, return return; } for (int i = 0; i < size; i++) { fprintf(f, "%d %d %s", list[i].hour, list[i].minute, encode(list[i].description)); // Encode the description (replace spaces with underscores) before saving it into the file } printf("\n| %d %s successfully saved into \"%s\".\n\n", size, (size == 1) ? "event" : "events", filename); // Tenary expression to make sure we're grammatically correct fclose(f); } // Load an event list from file void loadEventList(char *filename, event list[], int *size) { FILE *f = fopen(filename, "r"); char temp[6 + _MAX_DESCRIPTION]; // ## ## MAX_DESCRIPTION_LENGTH if (f == NULL) { printf("\n| File \"%s\" not found.\n\n", filename); return; } *size = 0; // Set size to 0 while (fgets(temp, sizeof(temp), f)) { char *word = strtok(temp, " "); // Use space as the token delimiter, get the first token (hour) list[*size].hour = atoi(word); // Store the token into the list word = strtok(NULL, " "); // Get the second token (minute) list[*size].minute = atoi(word); word = strtok(NULL, " "); // Get the third token (description) strcpy(list[*size].description, decode(word)); // Decode our word before copying it (remove underscores) (*size)++; // Increment size with each line (event) added } printf("\n| %d %s successfully loaded from \"%s\".\n", *size, (*size == 1) ? "event" : "events", filename); printEventList(list, *size); // Display the event list when finished, show the user what's been loaded } int main() { event list[_MAX_EVENTS]; int index = 0; // Number of elements in list int selection = 0; char file[FILENAME_MAX]; char response = 'Y'; char temp[21]; while (selection != 6) { printMenu(); // Print the menu printf("%s", "| Please select an option: "); // Prompt for input fgets(temp, 21, stdin); selection = atoi(temp); // Convert string input to int switch (selection) { case 1: // New Event if (index + 1 > _MAX_EVENTS) { printf("| You can only have %d active events at one time!\n\n", index); break; } sortInsert(list, &index, *newEvent(&list[index])); break; case 2: // Delete Event deleteEvent(list, &index); break; case 3: // Display Schedule printEventList(list, index); break; case 4: // Save Schedule if (index == 0) { // No events, don't save anything puts("| You have no events in your schedule!\n"); } else { printf("%s", "| Please enter a \"filename.txt\": "); fgets(file, FILENAME_MAX, stdin); strtok(file, "\n"); // Strip newline from filename saveEventList(file, list, index); } break; case 5: // Load Schedule if (index > 0) { printf("%s", "| Are you sure you want to discard your current schedule? (Y/N): "); response = toupper(getc(stdin)); char c; while (((c = getchar()) != '\n') && (c != EOF)); // Clear buffer, from getc(); } if (response == 'Y') { printf("%s", "| Please enter a \"filename.txt\": "); fgets(file, FILENAME_MAX, stdin); strtok(file, "\n"); // Strip newline from filename loadEventList(file, list, &index); } break; case 6: // Exit Program puts("\n| Thank you!\n"); break; default: // Error puts("\n| Error in selection\n"); break; } } } **Questions / Concerns** Hopefully I've improved since my last major [code review][3]. I've tried to keep the comments useful, so tell me if I'm still being redundant or need to tone that down. There's a few things I have kept in mind while writing this, including [not repeating myself][4] as best as possible. Was the ASCII formatting when printing a good idea? Or should that just be left when showing the menu? In what ways can I improve, what shouldn't I be doing, and what should I be doing? [1]: https://i.sstatic.net/Nmt4p.png [2]: https://i.sstatic.net/xTQgH.gif [3]: https://codereview.stackexchange.com/questions/110509/scoring-and-grading-answers-against-an-answer-key [4]: https://en.wikipedia.org/wiki/Don%27t_repeat_yourself