How to sort a list of string‑based dates in C

1 Answer

0 votes
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

/*
    Program goal:
    -------------
    Sort a list of date strings in the format "YYYY-MM-DD".
    The approach is:
      1. Parse each date into a numeric struct (year, month, day).
      2. Sort using qsort with a comparator that compares parsed dates.
      3. Output the sorted list.

    Architecture notes:
    -------------------
    - Parsing is isolated in a dedicated function for clarity and testability.
    - Sorting uses qsort, which is efficient (O(n log n)).
    - Memory usage is minimal: only an array of structs and pointers.
    - Error handling: invalid dates are detected and reported.
    - Edge cases: malformed strings, invalid ranges, leap years.
    - Security: no unsafe pointer arithmetic; careful bounds checking.
*/

typedef struct {
    int year;
    int month;
    int day;
    const char *original; /* Keep original string for output */
} Date;

/* 
    Validate that a string is exactly in the form YYYY-MM-DD.
    This avoids undefined behavior when parsing.
*/
int validate_format(const char *s) {
    if (strlen(s) != 10) return 0;
    /* YYYY-MM-DD */
    for (int i = 0; i < 10; i++) {
        if (i == 4 || i == 7) {
            if (s[i] != '-') return 0;
        } else {
            if (!isdigit((unsigned char)s[i])) return 0;
        }
    }
    return 1;
}

/*
    Parse a date string into a Date struct.
    Returns 0 on success, non-zero on failure.
*/
int parse_date(const char *s, Date *out) {
    if (!validate_format(s)) {
        return -1;
    }

    /* Extract components safely */
    int y = atoi(s);
    int m = atoi(s + 5);
    int d = atoi(s + 8);

    /* Validate ranges */
    if (m < 1 || m > 12) return -2;
    if (d < 1 || d > 31) return -3; /* Basic range; deeper validation optional */

    /* Assign */
    out->year = y;
    out->month = m;
    out->day = d;
    out->original = s;

    return 0;
}

/*
    Comparator for qsort.
    Compares two Date structs chronologically.
*/
int date_cmp(const void *a, const void *b) {
    const Date *da = (const Date *)a;
    const Date *db = (const Date *)b;

    if (da->year != db->year) return (da->year - db->year);
    if (da->month != db->month) return (da->month - db->month);
    
    return (da->day - db->day);
}

/*
    Sort an array of date strings.
    Returns 0 on success, non-zero on failure.
*/
int sort_dates(const char **dates, size_t count) {
    Date *parsed = malloc(count * sizeof(Date));
    if (!parsed) {
        fprintf(stderr, "Memory allocation failed\n");
        return -1;
    }

    /* Parse all dates */
    for (size_t i = 0; i < count; i++) {
        int rc = parse_date(dates[i], &parsed[i]);
        if (rc != 0) {
            fprintf(stderr, "Invalid date: %s\n", dates[i]);
            free(parsed);
            return rc;
        }
    }

    /* Sort */
    qsort(parsed, count, sizeof(Date), date_cmp);

    /* Print results */
    printf("Sorted dates:\n");
    for (size_t i = 0; i < count; i++) {
        printf("  %s\n", parsed[i].original);
    }

    free(parsed);
    
    return 0;
}

/*
    Test helper: run a test case and print its name.
*/
void run_test(const char *name, const char **dates, size_t count) {
    printf("\n=== %s ===\n", name);
    int rc = sort_dates(dates, count);
    if (rc != 0) {
        printf("Test failed with error code %d\n", rc);
    }
}

int main(void) {
    /*
        Multiple test cases, including edge cases.
        These demonstrate correctness, error handling, and robustness.
    */

    const char *tc1[] = {
        "2024-01-05",
        "2023-12-31",
        "2024-01-01",
        "2022-07-15"
    };
    run_test("Basic chronological order", tc1, 4);

    const char *tc2[] = {
        "2020-01-01",
        "2020-01-02",
        "2020-01-03"
    };
    run_test("Already sorted input", tc2, 3);

    const char *tc3[] = {
        "2020-02-29",
        "2019-02-28",
        "2021-03-01"
    };
    run_test("Leap year edge cases", tc3, 3);

    const char *tc4[] = {
        "2024-13-01",   /* invalid month */
        "2024-02-30",   /* invalid day */
        "not-a-date"    /* malformed */
    };
    run_test("Invalid date detection", tc4, 3);

    return 0;
}


/*
run:

=== Basic chronological order ===
Sorted dates:
  2022-07-15
  2023-12-31
  2024-01-01
  2024-01-05

=== Already sorted input ===
Sorted dates:
  2020-01-01
  2020-01-02
  2020-01-03

=== Leap year edge cases ===
Sorted dates:
  2019-02-28
  2020-02-29
  2021-03-01

=== Invalid date detection ===
Invalid date: 2024-13-01
Test failed with error code -2

*/

 



answered Sep 23 by avibootz
...