3

I am reading from a file in a program using C. in the file I have some dates each in a separate line exactly like this:

20190101
20190304
20180922

Now I want the program to read these as dates and find the difference between the current date and these dates. is it possible to convert these dates into a format which is readable by C? something like this: 2019.01.01 Currently, I read it using fgets and I am not able to convert it to the format above. This is my code:

#include <stdio.h>
#include <stdlib.h>
void FileRead(FILE *pr)
{
    char date [15];
    fgets(date,15,pr);
    printf("date : %s", date);

}
int main()
{
   char x;
   FILE *pr;
   pr=fopen("mytextfile.txt","r");
   if(pr==NULL)
   {
       printf("File can not be opened.");
       return 0;
   }
  while(scanf("%c", &x))
    {
        switch(x)
        {

        case 'v' :
            FileRead(pr);
        break;

        case 'k' :
            return 0;

        }
    }
fclose(pr);
    return 0;
}
  • C has a built-in date-processing library: https://en.wikipedia.org/wiki/C_date_and_time_functions – Dai Mar 15 '19 at 02:55
  • Is there any way to get this date as input and find the difference between current and input? –  Mar 15 '19 at 03:00
  • 4
    @TomasAnda Yes, but without an attempt from you in your question to do this yourself (see how to create a [mcve]) not many people will be inclined to help you. – Govind Parmar Mar 15 '19 at 03:02
  • 2
    " Currently, I read it using fgets and I am not able to convert it to the format above" --> post your code, else this is just a write the code for me post. – chux - Reinstate Monica Mar 15 '19 at 03:03
  • 1
    Yes, you read each line, separate the `year, mo, day` and use that to populate a `struct tm` (which will then represent broken-down time), which you pass to `mktime` to convert to `time_t` which you can use to get the difference in seconds from current time (generally obtained with `localtime()`). – David C. Rankin Mar 15 '19 at 03:03
  • @GovindParmar I added the code as well. –  Mar 15 '19 at 03:33
  • `printf("File can not be opened.")` is the canonical example of a bad error message. It doesn't say which file, it doesn't say why it couldn't be opened, and it prints the message to the wrong stream. `if( fopen(path,mode) == NULL) { perror(path); ...` – William Pursell Mar 15 '19 at 04:08

3 Answers3

1

Once you have char* via fgets, use atoi to convert to integer. Then you have the date in YYYYMMDD format. Then you can do

const int day = dateYYYYMMDD % 100;
const int month = (dateYYYYMMDD % 10000)/100;
const int year = dateYYYYMMDD / 10000;

You can use C Date and time functions to get current date.

To get different between current date and these dates, one option is to convert all dates to Julian date and then do a number difference on julian dates.

If getting julian date is difficult, normalize to same year (adding 365 or 366 based on year is leap or not as you go from lower to higher year). Then get to the same month and then do day difference.

Chintan
  • 374
  • 2
  • 9
  • 1
    Avoid using `atoi` is provides *zero* ability to validate the conversion. Instead use `strtol`. Or while not great, but better would be to read with `fgets` and the parse with `sscanf`. At least then you have a measure of success/failure of the conversion. – David C. Rankin Mar 15 '19 at 03:38
1

The easiest way to approach the difference from now to then is to read the dates from the file and parse them into month, day and year (m, d, y). If you are not going to do a complete validation of each conversion, then a simple way to separate the 4-digit year and 2-digit month and day is to use fscanf with the appropriate field-width modifiers to limit the conversion to the required number of digits, e.g.

    while (fscanf (fp, "%4d%2d%2d", &y, &m, &d) == 3) {

Then all that is needed within the loop is to populate the struct tm with the year, month and day values (remembering to subtract 1900 from the year, and set each of the hour, minute and second members zero and the daylight savings time member to -1). A simple function can do this and return time_t after calling mktime, e.g.

time_t fill_broken_down_time (int y, int m, int d)
{                   /* initialize struct members */
    struct tm bdt = { .tm_sec=0, .tm_min=0, .tm_hour=0, .tm_mday=d, 
                    .tm_mon=m>0?m-1:0, .tm_year=y-1900, .tm_isdst=-1 };

    return mktime(&bdt);    /* return mktime conversion to time_t */
}

To finish, all you need is to get the time_t values and call difftime to get the difference in seconds between the current time and time read from the file as a double value. Continuing the loop in main(), e.g.

    while (fscanf (fp, "%4d%2d%2d", &y, &m, &d) == 3) {
        time_t  now = time(NULL),
                then = fill_broken_down_time (y, m, d);
        printf ("date[%d] %d/%d/%d is %.2f seconds from now.\n", 
                n++, m, d, y, difftime (now, then));
    }

Putting it altogether, you could do something like:

#include <stdio.h>
#include <time.h>

time_t fill_broken_down_time (int y, int m, int d)
{                   /* initialize struct members */
    struct tm bdt = { .tm_sec=0, .tm_min=0, .tm_hour=0, .tm_mday=d, 
                    .tm_mon=m>0?m-1:0, .tm_year=y-1900, .tm_isdst=-1 };

    return mktime(&bdt);    /* return mktime conversion to time_t */
}

int main (int argc, char **argv) {
    /* use filename provided as 1st argument (stdin by default) */
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
    int y, m, d, n = 1;

    if (!fp) {  /* validate file open for reading */
        perror ("file open failed");
        return 1;
    }

    while (fscanf (fp, "%4d%2d%2d", &y, &m, &d) == 3) {
        time_t  now = time(NULL),
                then = fill_broken_down_time (y, m, d);
        printf ("date[%d] %d/%d/%d is %.2f seconds from now.\n", 
                n++, m, d, y, difftime (now, then));
    }
    if (fp != stdin) fclose (fp);   /* close file if not stdin */

    return 0;
}

(note: the program expects the filename to read dates from as the first argument to the program (or the program will read from stdin by default if no argument is given))

Example Input File

$ cat dat/3dates.txt
20190101
20190304
20180922

Example Use/Output

$ ./bin/time_from_now dat/3dates.txt
date[1] 1/1/2019 is 6300212.00 seconds from now.
date[2] 3/4/2019 is 943412.00 seconds from now.
date[3] 9/22/2018 is 15030212.00 seconds from now.

Edit Per-Comment Modifying Input File Format

If your data file is actually different than the three-lines of dates you originally posted with your question and it contains heading rows before the date information, then you will need to read, identify and handle those lines before handle the date line. Since you want output in days as opposed to seconds, you simply divide the number of seconds by 86400 to obtain the time difference in days.

To read an handle heading lines, simply adjust your read to read an entire line at a time into an adequately sized buffer. Declared a constant of sufficient size to ensure your buffer will be large enough, e.g.

#define MAXC 1024u  /* if you need a constant, #define one (or more) */
...
int main (int argc, char **argv) {
...
    char buf[MAXC];     /* buffer to hold each line read from file */

Then you will simply use sscanf instead of fscanf to do the exact same parse of information from each line. If the line format does not satisfy the yyyymmdd format, you know it's not a date line - handle those lines any way you want (they are just output in the example below with the prefix "non-date line: ".

Combined with dividing the number of seconds since the time in the file with 86400 seconds per-day, your new read loop would be similar to:

    while (fgets (buf, MAXC, fp)) {     /* read each line in file */
        /* if line isn't a date line, just output line as non-date line */
        if (sscanf (buf, "%4d%2d%2d", &y, &m, &d) != 3) {
            printf ("non-date line: %s", buf);
            continue;
        }
        time_t  now = time(NULL),
                then = fill_broken_down_time (y, m, d);
        double secs = difftime (now, then); /* get seconds between dates */
        printf ("date[%d] %02d/%02d/%04d is %11.2f sec (%g days) from now.\n", 
                n++, m, d, y, secs, secs / 86400.0);
    }

You say:

"I am not able to open the file"

The program expects you to provide the filename to read as the first argument to the program, otherwise the program will read from stdin by default. The means you have to provide the filename to the program, e.g.

./yourprogram your_date_file

or you have to provide that data on stdin by either piping that information to the program from the output of some other program, or simply redirecting the file as input on stdin, e.g.

some_utility_making_dates | ./yourprogram

or

./yourprogram < your_date_file

Incorporating all the changes, your program would look like:

#include <stdio.h>
#include <time.h>

#define MAXC 1024u  /* if you need a constant, #define one (or more) */

time_t fill_broken_down_time (int y, int m, int d)
{                   /* initialize struct members */
    struct tm bdt = { .tm_sec=0, .tm_min=0, .tm_hour=0, .tm_mday=d, 
                    .tm_mon=m>0?m-1:0, .tm_year=y-1900, .tm_isdst=-1 };

    return mktime(&bdt);    /* return mktime conversion to time_t */
}

int main (int argc, char **argv) {
    /* use filename provided as 1st argument (stdin by default) */
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
    int y, m, d, n = 1;
    char buf[MAXC];     /* buffer to hold each line read from file */

    if (!fp) {  /* validate file open for reading */
        perror ("file open failed");
        return 1;
    }

    while (fgets (buf, MAXC, fp)) {     /* read each line in file */
        /* if line isn't a date line, just output line as non-date line */
        if (sscanf (buf, "%4d%2d%2d", &y, &m, &d) != 3) {
            printf ("non-date line: %s", buf);
            continue;
        }
        time_t  now = time(NULL),
                then = fill_broken_down_time (y, m, d);
        double secs = difftime (now, then); /* get seconds between dates */
        printf ("date[%d] %02d/%02d/%04d is %11.2f sec (%g days) from now.\n", 
                n++, m, d, y, secs, secs / 86400.0);
    }
    if (fp != stdin) fclose (fp);   /* close file if not stdin */

    return 0;
}

Example Input File w/Headings

$ cat dat/3dates-w-headers.txt
This file contains dates to read and convert to days.
The file also contains this description and dates in the format:

yyyymmdd
20190101
20190304
20180922

Example Use/Output

$ ./bin/time_from_now2 dat/3dates-w-headers.txt
non-date line: This file contains dates to read and convert to days.
non-date line: The file also contains this description and dates in the format:
non-date line:
non-date line: yyyymmdd
date[1] 01/01/2019 is  6348645.00 sec (73.4797 days) from now.
date[2] 03/04/2019 is   991845.00 sec (11.4797 days) from now.
date[3] 09/22/2018 is 15078645.00 sec (174.521 days) from now.

Look things over and let me know if you have further questions.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
  • I am not able to open the file. plus I want to show the difference in days. and what if the first line is a name the second last name and the third one date? –  Mar 15 '19 at 15:46
  • Thumbs up, David! that looks an amazing solution and should be the answer. something else came to my mind, can we use if statements at the same time while reading from files using `fgets` and `scanf`? if yes, then I would like to add something else to this question. –  Mar 15 '19 at 19:22
  • Sure, that is the beauty of programming you can add whatever additional conditions you like within any loop. The condition I have just checks if the line fails to parse into `y, m d` -- if it does, I print it as explained above and go get the next line. You can change that any way you like. Give it a go and if you have problems let me know. – David C. Rankin Mar 15 '19 at 19:45
  • Works fine now. I owe you a lot. Thank You. –  Mar 15 '19 at 20:01
  • OK. I added some values in the file. the first line is name, second is plate number like `AA333BB`, third is `bool` value `1` or `0`, fourth is cost `100.00` and the final is date. in my case, this is a record for one customer. i have many of them at the same file but separated only by one empty line. Now i want to print the name and plateNumber from each record, if the date is more than one year from now. and of course i want to check if the type is `1` i should print one vlaue and if its `0` i should print another. i am not able to figure out your example in this case, can we do that? –  Mar 15 '19 at 20:24
  • if the type is 1, I want to multiply the cost by `1.5` and if it's 0 I want it to be multiplied by `2.5`. I know this looks weird but let me know if it's possible. –  Mar 15 '19 at 20:27
  • 1
    What you will want to do is use the counter `n` to control what you are reading. You can simply do `while (fgets(..)) { switch (n) { case 1: /*handle 1st line*/ break; case 2: /*handle plate number*/ break, ....` The other option is to test the content of each line -- but if you know it has a fixed-format and what the lines contain, then a simple counter and `switch` or an `if (n == 1)... else if (n == 2)... else if ...` is a simpler way to go. – David C. Rankin Mar 15 '19 at 21:00
  • On your cost multiplier, you can make the filename mandatory as `argv[1]` (1st program argument) and then pass `0` or `1` as `argv[2]` (2nd argument) setting a default of either `'0'` or `'1'` if no 2nd arguments is passed. (e.g. `int multflag = argc > 2 ? argv[2][0] : '1'`; (which just sets `multflag` to the 1st character in `argv[2]` if provided or `'1'` by default using the *ternary* operator. Then you can just check `if (multflag == `'0'`) val * 2.5; else val * 1.5;` Nothing fancy. (the proper way would be to use `getopt` and set up an option taking scheme -- but that's for later....) – David C. Rankin Mar 15 '19 at 21:05
  • And note... the single-quotes around `'0'` and `'1'` (you read arguments as characters from the command line) so when you compare the `multflag` make sure you are comparing against `'0'` or `'1'` `:)` – David C. Rankin Mar 15 '19 at 21:10
  • Sure. I will try and let you know if there was a problem. `:)` –  Mar 15 '19 at 21:26
  • @DavidC.Rankin I asked the question here https://stackoverflow.com/questions/55191612/reading-different-values-from-file-in-c-and-print-based-on-conditions –  Mar 15 '19 at 22:51
0

The output is, you just loop the file lines and set a if condition in it

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

int main(void)
{
    char buff[20];
    time_t now = time(NULL);

    strftime(buff, 20, "%Y%m%d\n", localtime(&now));
    printf("Today : %s \n\n", buff);

    static const char filename[] = "file.txt";
    FILE *file = fopen(filename, "r");

    if (file != NULL)
    {
        char line[128]; /* or other suitable maximum line size */
        while (fgets(line, sizeof line, file) != NULL) /* read a line */
        {
            fputs(line, stdout); /* write the line */

            if (strcmp(line, buff) == 0)
                printf("\nThis line of date equal to current date\n");

        }
        fclose(file);
    }
    else
    {
        perror(filename); /* why didn't the file open? */
    }
    return 0;
}

file.txt

20190315
20190304
20180922
  • undefined reference to getline. –  Mar 15 '19 at 03:48
  • answer is correct ,i just run this in my Ubuntu system ,which is your platform ? Windows ? linux ? mac ? it is the system config error ! change #define _GNU_SOURCE with your source with the help of [link] (https://stackoverflow.com/questions/13112784/undefined-reference-to-getline-in-c) – Anandhukrishna VR Mar 15 '19 at 03:53
  • It prints the current date, but the problem is still there, now imagine there is a word in the first line and a word in the second line and the date in the third line. now how to read only the third line (date) and subtract the number of days from the current date to that? –  Mar 15 '19 at 11:26
  • problem still there means? undefined reference to getline.?? – Anandhukrishna VR Mar 15 '19 at 11:37
  • No, that is fine now. what if there is a word in the first line and a word in the second line and the date in the third line. now how to read only the third line (date) and subtract the number of days from the current date to that? –  Mar 15 '19 at 11:44