3

I am currently creating a personal diary in C and I'd like to have the possibility to print the post sorted by date. I can extract the date using the struct tm but I don't know to sort the dates so that the most recent is on top. This is my whole function here :

void dateOrder() {
    FILE *postfile = fopen("post.txt", "r");

    int numofpost = getNumOfPost(postfile);
    int dates[numofpost];

    struct tm ptime;

    char *elt = malloc(5 * sizeof(char));
    char *dref = "Date";
    char *href = "Heure";
    char c = 'c';

    char *pseudo = malloc(20 * sizeof(char));
    int pseudolen = 0;

    rewind(postfile);

    while (!feof(postfile)) {

        fscanf(postfile, "%s", elt);

        if (strcmp(elt, dref) == 0) {
            fseek(postfile, 3, SEEK_CUR);
            fscanf(postfile, "%d/%d/%d", (int)&(ptime.tm_mday), (int)&(ptime.tm_mon), (int)&(ptime.tm_year));
        }

        if (strcmp(elt, href) == 0) {
            fseek(postfile, 3, SEEK_CUR);
            fscanf(postfile, "%d:%d", (int)&(ptime.tm_hour), (int)&(ptime.tm_min));
        }

        ptime.tm_year -= 1900;
        ptime.tm_mon -= 1;
        ptime.tm_sec = 0;
        ptime.tm_isdst = -1;
        int rep = mktime(&ptime);

        if (rep != -1) {
            dates[i++] = rep;
        }
    }

    insertsort(dates, sizeof(dates)/sizeof(dates[0]));

    for (int i = 0; i < numofpost; i++) {
        c = 'c';
        rewind(postfile);

        while (!feof(postfile) && c != 24) {

            fscanf(postfile, "%s", elt);

            if (strcmp(elt, "Pseudo") == 0) {
                fseek(postfile, 3, SEEK_CUR);
                fscanf(postfile, "%s", pseudo);
                pseudolen = strlen(pseudo);
            }

            if (strcmp(elt, dref) == 0) {

                fseek(postfile, 3, SEEK_CUR);
                fscanf(postfile, "%d/%d/%d", (int)&(ptime.tm_mday), (int)&(ptime.tm_mon), (int)&(ptime.tm_year));
            }

            if (strcmp(elt, href) == 0) {
                fseek(postfile, 3, SEEK_CUR);
                fscanf(postfile, "%d:%d", (int)&(ptime.tm_hour), (int)&(ptime.tm_min));
            }

            ptime.tm_year -= 1900;
            ptime.tm_mon -= 1;
            ptime.tm_sec = 0;
            ptime.tm_isdst = -1;
            int mkt = mktime(&ptime);

            if (mkt == dates[i]) {
                fseek(postfile, -39, SEEK_CUR);
                fseek(postfile, -pseudolen, SEEK_CUR);

                while (c != 24) {
                    c = fgetc(postfile);

                    if (c == 24)
                        continue;

                    printf("%c", c);
                }
            }
        }
    }

    fclose(postfile);   
}

And this is struct tm :

struct tm {
   int tm_sec;         /* seconds,  range 0 to 59          */
   int tm_min;         /* minutes, range 0 to 59           */
   int tm_hour;        /* hours, range 0 to 23             */
   int tm_mday;        /* day of the month, range 1 to 31  */
   int tm_mon;         /* month, range 0 to 11             */
   int tm_year;        /* The number of years since 1900   */
   int tm_wday;        /* day of the week, range 0 to 6    */
   int tm_yday;        /* day in the year, range 0 to 365  */
   int tm_isdst;       /* daylight saving time             */
 };
lvndry
  • 421
  • 6
  • 13
  • 5
    [`while(!feof(file))` is always wrong](https://stackoverflow.com/questions/5431941/why-is-while-feof-file-always-wrong) – pmg Jul 14 '17 at 09:58
  • 1
    Please post the complete code to a functional example. structure definitions are needed to verify and understand your code. – chqrlie Jul 14 '17 at 09:58
  • how can you sort anything when you only have one date - there's no array or linked list in your code...or maybe you should include a [mcve] – Chris Turner Jul 14 '17 at 10:37
  • Why is `while(!feof(file))` wrong ? – lvndry Jul 14 '17 at 11:23
  • @LVNDRY-- read the link to learn why `while(!feof(file))` is wrong. In short, `feof()` returns a true value if the end-of-file indicator has been set _by a previous I/O operation._ When you use this idiom, followed by an I/O operation that fails (and sets the end-of-file indicator), then going on to use the result of that I/O operation as if it did not fail (and before the end of the loop body), you can expect trouble. – ad absurdum Jul 14 '17 at 11:32
  • I'll read it thank you! – lvndry Jul 14 '17 at 11:52

2 Answers2

4

You can write a comparison function using mktime() and difftime(), and then use qsort() to sort an array of tm structs. The comparison function cmp_dates_descend() below can be used by qsort() to sort an array of dates in descending order:

#include <stdlib.h>
#include <time.h>

#define NUM_DATES  10  /* for example */

int cmp_dates_descend(const void *d1, const void *d2);

int main(void)
{
    /* ... */

    struct tm arr_dates[NUM_DATES];

    /* ... */

    size_t num_dates = sizeof arr_dates / sizeof *arr_dates;

    qsort(arr_dates, num_dates, sizeof *arr_dates, cmp_dates_descend);

    /* ... */

    return 0;
}

int cmp_dates_descend(const void *d1, const void *d2)
{
    struct tm *date_1 = (struct tm *) d1;
    struct tm *date_2 = (struct tm *) d2;

    return double d = -difftime(mktime(date_1), mktime(date_2));
}

Note that this method may encounter problems for large date differences. Since difftime() returns a double (representing a time difference in seconds), the return value may not be representable in an int, which is the value returned by comparison functions used by qsort(). Where INT_MAX == 2147483647, typical of 4 byte ints, date differences of more than about 68 years will lead to overflow in the conversion from double to int, and thus undefined behavior. If such large date differences are to be handled, perhaps a custom sort function should be written.

Edit

@chqrlie has pointed out in the comments that this method could also lead to erroneous comparisons for extremely close dates (fractions of a second), since if difftime(mktime(date_1), mktime(date_2)) is less than 1 in magnitude, the value will be converted to 0 upon return, thus comparing as equal. To avoid this complication, the result of difftime() can be stored in a double and compared with 0 to determine a return value. This is a common trick with comparison functions; this also removes the previous issue with large date differences. Here is an improved comparison function:

int cmp_dates_descend(const void *d1, const void *d2)
{
    struct tm *date_1 = (struct tm *) d1;
    struct tm *date_2 = (struct tm *) d2;

    double d = difftime(mktime(date_1), mktime(date_2));

    return (d < 0) - (d > 0);
}

Edit 2

To leave the array unchanged, which the comparison function is supposed to do as it gets const pointers to the array elements, we can make copies of the tm structures in the comparison function and invoke mktime() on the copies at a small cost in performance:

int cmp_dates_descend(const void *d1, const void *d2)
{
    struct tm date_1 = *(const struct tm *)d1;
    struct tm date_2 = *(const struct tm *)d2;

    double d = difftime(mktime(&date_1), mktime(&date_2));

    return (d < 0) - (d > 0);
}
chqrlie
  • 131,814
  • 10
  • 121
  • 189
ad absurdum
  • 19,498
  • 5
  • 37
  • 60
  • 1
    In addition to the potential overflow, when the `double` value is converted to `int` in `cmp_dates`, small differences convert to `0`, making `qsort` erroneously consider them identical. `mktime()` is supposed to return an integral value, but the method is still approximate. There is an easy fix: store the difference in a `double d` and return `(d < 0) - (d > 0)` for descending order. – chqrlie Jul 14 '17 at 12:53
  • @chqrlie-- good catch. I have left the original code and discussion, and added an edit with an improved comparison function and discussion of why this is better. I'd appreciate it if you would give it a look to make sure all is well ;) – ad absurdum Jul 14 '17 at 13:10
  • I would only advise a small change: making the pointers constant would avoid a warning in some environments `const struct tm *date_1 = (const struct tm *)d1;`. Note that in C, you do not need these local variables, you could just write: `double d = difftime(mktime(d1), mktime(d2));`. – chqrlie Jul 14 '17 at 13:14
  • @chqrlie-- won't `(const struct tm *) d1`, e.g, lead to warnings about discarding `const` in the calls to `mktime()`, since `mktime()` expects non-`const` `struct tm` arguments? Also, when I try `double d = difftime(mktime(d1), mktime(d2))` I get the warning: `expected ‘struct tm *’ but argument is of type ‘const void *’`, along with some other warnings about discarding `const`. – ad absurdum Jul 14 '17 at 13:32
  • 1
    You are correct, `mktime()` does modify the `tm` structure. Using `const` pointers does not work and `double d = difftime(mktime((void*)d1), mktime((void*)d2));` would be quite ugly. Do you compile in C or C++ mode? – chqrlie Jul 14 '17 at 13:41
  • 1
    To enforce const correctness, which is not required here as the array can be modified, one would need to make copies of the `tm` structures and call `mktime()` on the copies. It would be slightly more inefficient. – chqrlie Jul 14 '17 at 13:42
  • @chqrlie-- I compiled in C mode: `gcc -std=c99 -Wall -Wextra -Wpedantic -Werror`. Nice idea about making a copy of the `tm` `struct`s.... – ad absurdum Jul 14 '17 at 14:03
0

Thanks to the answers and comments here is how I print the posts sorted by date

int check(int i, struct tm *dates, struct tm ptime){
   if(dates[i].tm_year == ptime.tm_year && dates[i].tm_mon == ptime.tm_mon && 
      dates[i].tm_mday == ptime.tm_mday && dates[i].tm_hour == ptime.tm_hour && 
      dates[i].tm_min == ptime.tm_min)

      return 1;
 return 0;
}
int compareDates(const void *d1, const void *d2){
struct tm date_1 = *(const struct tm *)d1;
struct tm date_2 = *(const struct tm *)d2;

double d = difftime(mktime(&date_1), mktime(&date_2));

return (d < 0) - (d > 0);
}

void dateOrder(){ //print the post sorted by date

FILE *postfile = fopen("post.txt", "r");

int numofpost = getNumOfPost(postfile);
struct tm dates[numofpost];
int pseudolen = 0, i = 0;

struct tm ptime;

char *elt = malloc(5*sizeof(char));
char *pseudo = malloc(20*sizeof(char));
char *dref = "Date"; //Word to find to get the date
char *href = "Heure"; //Word to find to get hour
char c = 'c';

rewind(postfile);

while(!feof(postfile)){

    fscanf(postfile, "%s", elt);

    if(strcmp(elt, dref) == 0){
        fseek(postfile, 3, SEEK_CUR);
        fscanf(postfile, "%d/%d/%d", (int)&(ptime.tm_mday), (int)&(ptime.tm_mon), (int)&(ptime.tm_year));

        dates[i].tm_year = ptime.tm_year;
        dates[i].tm_mon = ptime.tm_mon;
        dates[i].tm_mday = ptime.tm_mday;
    }

    if(strcmp(elt, href) == 0){
        fseek(postfile, 3, SEEK_CUR);
        fscanf(postfile, "%d:%d", (int)&(ptime.tm_hour), (int)&(ptime.tm_min));

        dates[i].tm_hour = ptime.tm_hour;
        dates[i++].tm_min = ptime.tm_min;
    }
}

size_t num_dates = sizeof(dates)/sizeof(*dates);
qsort(dates, num_dates, sizeof(*dates), compareDates);

for(int i = 0; i < numofpost; i++){
    c = 'c';
    rewind(postfile);

    while(!feof(postfile) && c != 24){ //We read the file until the end c is equal to 24 only if a already founded the wanted post

        fscanf(postfile, "%s", elt);

        if(strcmp(elt, "Pseudo") == 0){
            fseek(postfile, 3, SEEK_CUR);
            fscanf(postfile, "%s", pseudo);
            pseudolen = strlen(pseudo);
        }

        if(strcmp(elt, dref) == 0){
            fseek(postfile, 3, SEEK_CUR);
            fscanf(postfile, "%d/%d/%d", (time_t)&(ptime.tm_mday), (time_t)&(ptime.tm_mon), (time_t)&(ptime.tm_year));
        }

        if(strcmp(elt, href) == 0){
            fseek(postfile, 3, SEEK_CUR);
            fscanf(postfile, "%d:%d", (time_t)&(ptime.tm_hour), (time_t)&(ptime.tm_min));
        }

        if(check(i, dates, ptime)){ //check look if the member of struct are the same in dates ans ptime
            fseek(postfile, -40, SEEK_CUR);
            fseek(postfile, -pseudolen, SEEK_CUR);

            while(c != 24){ //while c != 24 is because user has stop typing a post when typing Ctrl + X == 24 in ascii code
                c = fgetc(postfile);

                if(c == 24)
                    continue;
                printf("%c", c);
            }
        }

        if(ftell(postfile)+15 < feof(postfile)) //If it is not the last post
            fseek(postfile, 15, SEEK_CUR); //I go to next post*
    }
    printf("\n\n\n\n");
}

  fclose(postfile);
  printf("\n\n");   
}
lvndry
  • 421
  • 6
  • 13