๐Ÿ“” Personal Diary โ€” File I/O Mini Project
0%
Mini Project  ยท  File I/O  ยท  C Programming

๐Ÿ“” Personal Diary
File I/O Mini Project in C

Write entries with date and time. Read them back. Search by keyword. Count total entries. Delete the diary. Six focused steps โ€” one real, working program that reads and writes a persistent file.

fopen / fclose fprintf / fscanf fgets / fputs append mode "a" time.h timestamp strstr search
S1
File Modes
S2
Write Entry
S3
Read All
S4
Search
S5
Count & Stats
S6
Full Program

๐Ÿ“”  Project Overview โ€” What We Are Building

A command-line personal diary that saves entries permanently to a file called diary.txt. Every entry is automatically stamped with the current date and time. You can write new entries, read all past entries, search by keyword, count how many entries exist, and clear the diary. Every feature teaches a different File I/O function.

diary.txt โ€” persistent storage fopen "a" โ€” append entries fgets โ€” read line by line strstr โ€” keyword search time.h โ€” auto timestamp remove() โ€” delete file
how data flows through the diary program
User types entry
โ†’
time() timestamp
โ†’
fprintf โ†’ diary.txt
โ†’
fgets reads back
โ†’
strstr searches

The file is the database. Every function opens โ†’ operates โ†’ closes. The file persists between program runs โ€” entries accumulate across sessions.

step 1 โ€” file modes explained
S1
๐Ÿ“‚ File Modes โ€” fopen, fclose and the Six Modes
Every file operation starts with fopen โ€” the mode string controls everything
File Modes
fopen(filename, mode) returns a FILE * pointer โ€” a handle to the open file. If it returns NULL, the file could not be opened (wrong path, no permission, disk full). Always check for NULL before using the pointer. After every operation, fclose(fp) flushes the buffer and releases the file โ€” skipping it can cause data loss. The mode string is the key decision โ€” it controls whether you read, write, or append, and whether the file is text or binary.
ModeMeaningFile exists?File missing?Used for
"r"Read textOpens from startReturns NULLReading existing file
"w"Write textErases contents!Creates new fileFresh write โ€” overwrites
"a"Append textAdds to endCreates new fileAdding entries โ€” diary โœ“
"r+"Read+WriteOpens from startReturns NULLUpdate existing file
"rb"Read binaryOpens bytesReturns NULLImages, binary data
"wb"Write binaryErases!Creates newBinary output
s1_file_modes.c
C
#include <stdio.h>

int main() {
    /* "a" โ€” append: creates if missing, adds to end if exists */
    FILE *fp = fopen("diary.txt", "a");

    if (!fp) {                          /* ALWAYS check NULL */
        printf("Error: cannot open diary.txt\n");
        return 1;
    }

    fprintf(fp, "Test line written to diary.\n");
    fclose(fp);                         /* flush + release */

    printf("Written. diary.txt now exists.\n");

    /* Open same file for reading */
    fp = fopen("diary.txt", "r");
    if (!fp) { printf("Read error\n"); return 1; }

    char line[256];
    while (fgets(line, sizeof(line), fp))
        printf("%s", line);

    fclose(fp);
    return 0;
}
output
Written. diary.txt now exists.
Test line written to diary.
Why "a" for a diary and not "w"? Mode "w" truncates โ€” it deletes everything in the file before writing. One wrong call and your entire diary history is gone. Mode "a" only adds to the end โ€” existing entries are always safe.
step 2 โ€” write a diary entry
S2
โœ๏ธ writeEntry() โ€” Timestamp + fprintf to File
time.h gives current date/time โ€” fprintf writes the formatted entry โ€” fgets reads user input
Write + Timestamp
Writing a diary entry does three things: (1) get the current date and time from time.h, (2) read the user's text with fgets, and (3) write both to the file with fprintf. The entry is wrapped in a visual separator so entries are easy to distinguish when reading back. time(NULL) returns the current Unix timestamp. localtime() converts it to a readable struct. strftime() formats it as a human-readable string.
s2_write_entry.c
C
#include <stdio.h>
#include <time.h>
#include <string.h>

#define DIARY   "diary.txt"
#define MAXLINE 512

void writeEntry() {
    /* 1. Get current date and time */
    time_t now  = time(NULL);
    struct tm *t = localtime(&now);
    char stamp[64];
    strftime(stamp, sizeof(stamp),
             "%d %b %Y  %H:%M:%S", t);

    /* 2. Read entry from user */
    char entry[MAXLINE];
    printf("Write your entry (one paragraph):\n> ");
    fgets(entry, sizeof(entry), stdin);

    /* Strip trailing newline for clean storage */
    entry[strcspn(entry, "\n")] = '\0';

    /* 3. Open in append mode โ€” never erases old entries */
    FILE *fp = fopen(DIARY, "a");
    if (!fp) { printf("Error opening diary!\n"); return; }

    /* 4. Write formatted entry */
    fprintf(fp, "========================================\n");
    fprintf(fp, "DATE : %s\n", stamp);
    fprintf(fp, "----------------------------------------\n");
    fprintf(fp, "%s\n", entry);
    fprintf(fp, "========================================\n\n");

    fclose(fp);   /* flush to disk โ€” critical! */
    printf("Entry saved on %s\n", stamp);
}

int main() {
    writeEntry();
    writeEntry();   /* second entry accumulates โ€” "a" mode */
    return 0;
}
diary.txt after two entries
========================================
DATE : 04 Jul 2026  09:15:32
----------------------------------------
Started learning File I/O in C today. Finally makes sense!
========================================

========================================
DATE : 04 Jul 2026  09:16:10
----------------------------------------
Wrote my first diary entry using fprintf. Feels great.
========================================
strcspn(entry, "\n") returns the index of the first newline character. Setting that position to '\0' strips the trailing newline that fgets always includes. Without this, every entry would have an extra blank line when read back.
step 3 โ€” read all entries
S3
๐Ÿ“– readAll() โ€” fgets Line by Line Until EOF
Open in "r" mode โ€” fgets reads one line per call โ€” loop until it returns NULL
Read + fgets
Reading uses mode "r" and fgets(line, size, fp). fgets reads up to size-1 characters or until a newline โ€” whichever comes first โ€” and always null-terminates the result. When it reaches the end of file it returns NULL. The loop while(fgets(line, sizeof(line), fp)) reads every line until EOF โ€” the most idiomatic C file-reading pattern. We also number the entries as we display them by counting the separator lines.
s3_read_all.c
C
#include <stdio.h>
#include <string.h>

#define DIARY   "diary.txt"
#define MAXLINE 512

void readAll() {
    FILE *fp = fopen(DIARY, "r");
    if (!fp) {
        printf("Diary is empty โ€” no entries yet.\n");
        return;
    }

    char  line[MAXLINE];
    int   entryNum = 0;

    printf("\n===== YOUR DIARY =====\n\n");

    /* fgets returns NULL at EOF โ€” standard read loop */
    while (fgets(line, sizeof(line), fp)) {

        /* Count entry start by detecting separator */
        if (strncmp(line, "========", 8) == 0) {
            entryNum++;
            printf("--- Entry #%d ---\n", entryNum);
            continue;   /* skip the raw separator line */
        }

        printf("%s", line);   /* print each line as-is */
    }

    fclose(fp);
    printf("\nTotal entries found: %d\n", entryNum / 2);
    /* divide by 2: each entry has opening + closing separator */
}

int main() { readAll(); return 0; }
output
===== YOUR DIARY =====

--- Entry #1 ---
DATE : 04 Jul 2026  09:15:32
----------------------------------------
Started learning File I/O in C today. Finally makes sense!

--- Entry #2 ---
DATE : 04 Jul 2026  09:16:10
----------------------------------------
Wrote my first diary entry using fprintf. Feels great.

Total entries found: 2
fgets vs fscanf for reading: fscanf stops at whitespace โ€” useless for sentences. fgets reads the entire line including spaces, making it the right tool for any text that might contain spaces. Use fscanf only for structured numeric data like CSV files.
step 4 โ€” search entries
S4
๐Ÿ” searchDiary() โ€” strstr Keyword Match While Reading
Read line by line โ€” strstr checks each line โ€” print matching entries in full
Search + strstr
Search combines file reading with string matching. We read the diary line by line and use strstr(line, keyword) to check whether each line contains the search term. strstr returns a pointer to the first occurrence in the string, or NULL if not found. The trick is tracking which entry we are in โ€” we buffer the current entry's lines so we can print the entire entry when a match is found inside it, not just the matching line.
s4_search.c
C
#include <stdio.h>
#include <string.h>

#define DIARY    "diary.txt"
#define MAXLINE  512
#define MAXENTRY 2048

void searchDiary(const char *keyword) {
    FILE *fp = fopen(DIARY, "r");
    if (!fp) { printf("Diary is empty.\n"); return; }

    char line[MAXLINE];
    char buffer[MAXENTRY];    /* accumulate one entry */
    int  matched   = 0;
    int  found     = 0;
    int  inEntry   = 0;
    int  openSep   = 0;

    buffer[0] = '\0';

    printf("\nSearching for: \"%s\"\n", keyword);
    printf("================================\n");

    while (fgets(line, sizeof(line), fp)) {

        if (strncmp(line, "========", 8) == 0) {
            if (inEntry) {
                /* closing separator โ€” entry complete */
                if (matched) {
                    printf("%s\n", buffer);   /* print full entry */
                    found++;
                }
                buffer[0] = '\0';
                matched = 0;
                inEntry = 0;
            } else {
                inEntry = 1;   /* opening separator */
            }
            continue;
        }

        /* Accumulate lines in buffer */
        strncat(buffer, line,
                MAXENTRY - strlen(buffer) - 1);

        /* strstr โ€” check if keyword appears in this line */
        if (strstr(line, keyword))
            matched = 1;
    }

    fclose(fp);

    if (!found)
        printf("No entries found containing \"%s\".\n", keyword);
    else
        printf("Found in %d entr%s.\n",
               found, found == 1 ? "y" : "ies");
}

int main() {
    searchDiary("fprintf");
    searchDiary("weather");   /* not in diary */
    return 0;
}
output
Searching for: "fprintf"
================================
DATE : 04 Jul 2026  09:16:10
----------------------------------------
Wrote my first diary entry using fprintf. Feels great.

Found in 1 entry.

Searching for: "weather"
================================
No entries found containing "weather".
strstr(line, keyword) is case-sensitive โ€” "C" and "c" are different. For case-insensitive search, convert both line and keyword to lowercase copies using tolower() from ctype.h before comparing. The project works correctly as-is for exact matches.
step 5 โ€” count and stats
S5
๐Ÿ“Š diaryStats() โ€” Count Entries, Words, Characters
One pass through the file โ€” count separators, words, chars โ€” print summary
Stats + fgetc
Statistics require one full read pass. We count entries by detecting separator lines with strncmp. We count total characters using fgetc โ€” which reads one character at a time and returns EOF at end of file. We count words by detecting transitions from whitespace to non-whitespace. We also add a clear diary function using remove() โ€” the standard library function that deletes a file by name.
s5_stats.c
C
#include <stdio.h>
#include <string.h>
#include <ctype.h>

#define DIARY   "diary.txt"
#define MAXLINE 512

void diaryStats() {
    FILE *fp = fopen(DIARY, "r");
    if (!fp) { printf("Diary is empty.\n"); return; }

    int  entries = 0, words = 0, chars = 0, lines = 0;
    int  inWord  = 0;
    char line[MAXLINE];

    while (fgets(line, sizeof(line), fp)) {
        lines++;

        /* Count entries via opening separator */
        if (strncmp(line, "========", 8) == 0) {
            entries++;
            continue;    /* skip separator from word/char count */
        }
        if (strncmp(line, "--------", 8) == 0) continue;
        if (strncmp(line, "DATE",      4) == 0) continue;

        /* Walk each character โ€” count words + chars */
        for (int i = 0; line[i] != '\0'; i++) {
            char c = line[i];
            chars++;

            if (isspace((unsigned char)c)) {
                inWord = 0;
            } else {
                if (!inWord) { words++; inWord = 1; }
            }
        }
    }
    fclose(fp);

    printf("\n===== DIARY STATISTICS =====\n");
    printf("Total entries : %d\n", entries / 2);
    printf("Total lines   : %d\n", lines);
    printf("Total words   : %d\n", words);
    printf("Total chars   : %d\n", chars);
    printf("Avg words/entry: %.1f\n",
           entries ? (float)words / (entries/2) : 0);
}

/* Delete the diary file */
void clearDiary() {
    if (remove(DIARY) == 0)
        printf("Diary cleared.\n");
    else
        printf("Could not clear โ€” diary may not exist.\n");
}

int main() {
    diaryStats();
    return 0;
}
output
===== DIARY STATISTICS =====
Total entries : 2
Total lines   : 12
Total words   : 21
Total chars   : 112
Avg words/entry: 10.5
remove(DIARY) is permanent. It deletes the file from disk โ€” there is no undo, no recycle bin. Always confirm with the user before calling it. In the full program we add a "Are you sure? (y/n)" prompt before proceeding.
step 6 โ€” complete program
S6
๐Ÿ Complete Diary Program โ€” Menu-Driven Full App
All five functions + switch menu โ€” persistent across runs โ€” real working diary
Full Program
All five functions unified under a clean switch-case menu loop. The program runs until the user chooses Exit. Because we always use "a" mode for writing and "r" for reading, every entry ever written accumulates in diary.txt โ€” even across separate program runs. The file is the permanent memory.
diary.c โ€” Complete Project
C
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <ctype.h>

#define DIARY    "diary.txt"
#define MAXLINE  512
#define MAXENTRY 4096

/* โ”€โ”€ WRITE โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
void writeEntry() {
    time_t    now   = time(NULL);
    struct tm *t    = localtime(&now);
    char      stamp[64];
    strftime(stamp, sizeof(stamp),
             "%d %b %Y  %H:%M:%S", t);

    char entry[MAXLINE];
    printf("\nWrite your entry:\n> ");
    fgets(entry, sizeof(entry), stdin);
    entry[strcspn(entry, "\n")] = '\0';

    FILE *fp = fopen(DIARY, "a");
    if (!fp) { printf("Error!\n"); return; }
    fprintf(fp,
        "========================================\n"
        "DATE : %s\n"
        "----------------------------------------\n"
        "%s\n"
        "========================================\n\n",
        stamp, entry);
    fclose(fp);
    printf("Entry saved. [%s]\n", stamp);
}

/* โ”€โ”€ READ ALL โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
void readAll() {
    FILE *fp = fopen(DIARY, "r");
    if (!fp) { printf("\nDiary is empty.\n"); return; }
    char line[MAXLINE];
    int  n = 0, open = 0;
    printf("\n===== ALL ENTRIES =====\n");
    while (fgets(line, sizeof(line), fp)) {
        if (strncmp(line, "========", 8) == 0) {
            if (!open) { n++; printf("\n[Entry #%d]\n", n); }
            open = !open; continue;
        }
        printf("%s", line);
    }
    fclose(fp);
    printf("\n--- %d total entr%s ---\n",
           n, n==1?"y":"ies");
}

/* โ”€โ”€ SEARCH โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
void searchDiary() {
    char kw[64];
    printf("\nSearch keyword: "); fgets(kw, sizeof(kw), stdin);
    kw[strcspn(kw, "\n")] = '\0';

    FILE *fp = fopen(DIARY, "r");
    if (!fp) { printf("Diary is empty.\n"); return; }

    char line[MAXLINE], buf[MAXENTRY];
    int  found=0, matched=0, open=0;
    buf[0] = '\0';

    printf("\nResults for \"%s\":\n"
           "================================\n", kw);

    while (fgets(line, sizeof(line), fp)) {
        if (strncmp(line, "========", 8) == 0) {
            if (open && matched) { printf("%s\n", buf); found++; }
            open = !open; buf[0]='\0'; matched=0;
            continue;
        }
        strncat(buf, line, MAXENTRY-strlen(buf)-1);
        if (strstr(line, kw)) matched=1;
    }
    fclose(fp);
    printf(found ? "Found in %d entr%s.\n"
                  : "No matches found.\n",
           found, found==1?"y":"ies");
}

/* โ”€โ”€ STATS โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
void diaryStats() {
    FILE *fp = fopen(DIARY, "r");
    if (!fp) { printf("\nDiary is empty.\n"); return; }
    char line[MAXLINE];
    int  entries=0,words=0,chars=0,inWord=0;
    while (fgets(line, sizeof(line), fp)) {
        if(strncmp(line,"========",8)==0){entries++;continue;}
        if(strncmp(line,"--------",8)==0||strncmp(line,"DATE",4)==0)continue;
        for(int i=0;line[i];i++){
            chars++;
            if(isspace((unsignedchar)line[i])) inWord=0;
            else if(!inWord){words++;inWord=1;}
        }
    }
    fclose(fp);
    printf("\n===== DIARY STATS =====\n"
           "Entries : %d\nWords   : %d\n"
           "Chars   : %d\nAvg wds : %.1f\n",
           entries/2, words, chars,
           entries ? (float)words/(entries/2) : 0);
}

/* โ”€โ”€ CLEAR โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
void clearDiary() {
    char c;
    printf("\nDelete ALL entries? (y/n): ");
    scanf(" %c", &c); getchar();
    if (c == 'y' || c == 'Y')
        printf(remove(DIARY)==0
               ? "Diary cleared.\n"
               : "Error clearing diary.\n");
    else
        printf("Cancelled.\n");
}

/* โ”€โ”€ MENU โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
void showMenu() {
    printf("\nโ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—\n"
           "โ•‘   ๐Ÿ“” PERSONAL DIARY     โ•‘\n"
           "โ• โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ\n"
           "โ•‘  1. Write new entry      โ•‘\n"
           "โ•‘  2. Read all entries     โ•‘\n"
           "โ•‘  3. Search by keyword    โ•‘\n"
           "โ•‘  4. Diary statistics     โ•‘\n"
           "โ•‘  5. Clear diary          โ•‘\n"
           "โ•‘  6. Exit                 โ•‘\n"
           "โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•\n"
           "Choice: ");
}

int main() {
    int choice;
    do {
        showMenu();
        scanf("%d", &choice); getchar();
        switch (choice) {
            case 1: writeEntry();  break;
            case 2: readAll();     break;
            case 3: searchDiary(); break;
            case 4: diaryStats();  break;
            case 5: clearDiary();  break;
            case 6: printf("Goodbye!\n"); break;
            default: printf("Invalid choice.\n");
        }
    } while (choice != 6);
    return 0;
}
sample session output
โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—
โ•‘   ๐Ÿ“” PERSONAL DIARY     โ•‘
โ• โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ
โ•‘  1. Write new entry      โ•‘
โ•‘  2. Read all entries     โ•‘
โ•‘  3. Search by keyword    โ•‘
โ•‘  4. Diary statistics     โ•‘
โ•‘  5. Clear diary          โ•‘
โ•‘  6. Exit                 โ•‘
โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
Choice: 1

Write your entry:
> Completed the File I/O lesson today. Loved the diary project!
Entry saved. [04 Jul 2026  14:22:18]

Choice: 4

===== DIARY STATS =====
Entries : 3
Words   : 47
Chars   : 263
Avg wds : 15.7

Choice: 3

Search keyword: lesson

Results for "lesson":
================================
DATE : 04 Jul 2026  14:22:18
----------------------------------------
Completed the File I/O lesson today. Loved the diary project!

Found in 1 entry.

Choice: 6
Goodbye!
Persistence across runs: close the program, run it again, choose option 2 โ€” all previous entries are still there. The file diary.txt is the permanent memory. This is the core idea of File I/O โ€” data that outlives the program that created it.
checklist โ€” tick each concept when understood
  • S1 โ€” File Modes: fopen(name, mode) returns FILE* or NULL. Always check NULL. "a" appends safely. "w" erases. "r" reads. fclose(fp) flushes to disk โ€” never skip it.
  • S2 โ€” Write Entry: time(NULL) โ†’ localtime() โ†’ strftime() for timestamp. fgets(entry, size, stdin) reads full line with spaces. strcspn(s,"\n") strips newline. fprintf(fp, ...) writes formatted text.
  • S3 โ€” Read All: while(fgets(line, size, fp)) โ€” reads until NULL (EOF). Standard file-read loop. strncmp detects separator lines to count and format entries.
  • S4 โ€” Search: strstr(line, keyword) returns pointer if found, NULL if not. Buffer current entry between separators. Print full entry only when match found inside it.
  • S5 โ€” Stats: Word counting via inWord flag โ€” flip 0โ†’1 on non-space = new word. isspace() from ctype.h. remove(filename) deletes file permanently โ€” always confirm before calling.
  • S6 โ€” Full Program: Menu loop with do-while + switch. getchar() after scanf clears the newline from input buffer โ€” prevents fgets from reading an empty line. Persistent file survives between program runs.