如何在C语言中从文件中读取日期

3
我正在使用C编写的程序中从文件中读取信息。在文件中,每行都有一个日期,就像这样:

20190101
20190304
20180922

现在我想让程序读取这些日期,并找到当前日期与这些日期之间的差异。是否可以将这些日期转换为C语言可读取的格式?例如:2019.01.01。 目前,我使用fgets读取它们,但无法将其转换为上述格式。 这是我的代码:

#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语言内置了一个日期处理库:https://en.wikipedia.org/wiki/C_date_and_time_functions - Dai
有没有办法以输入的方式获取日期并找出当前日期与输入日期之间的差异? - user10592994
4
@TomasAnda 是的,但是如果您在问题中没有尝试自己完成此操作(请参见如何创建[mcve]),那么很少有人会倾向于帮助您。 - Govind Parmar
2
目前,我使用fgets读取它,但我无法将其转换为上述格式。如果没有发布您的代码,那么这只是一个让我写代码的帖子。 - chux - Reinstate Monica
1
是的,你需要读取每一行,把“年,月,日”分开,并使用它们来填充一个struct tm结构体(表示拆分的时间),然后将其传递给mktime函数转换为time_t类型。你可以用这个time_t类型的数据与当前时间相减得到秒数差(通常使用localtime()函数获取当前时间)。 - David C. Rankin
显示剩余2条评论
3个回答

1
一旦你通过fgets获得了char*,使用atoi将其转换为整数。然后你就有了以YYYYMMDD格式表示的日期。然后你可以进行操作。
const int day = dateYYYYMMDD % 100;
const int month = (dateYYYYMMDD % 10000)/100;
const int year = dateYYYYMMDD / 10000;

你可以使用C日期和时间函数来获取当前日期。
要获取当前日期与这些日期之间的差异,一种选项是将所有日期转换为儒略日,然后在儒略日上进行数字差异。
如果获取儒略日很困难,请将其归一化到同一年(从低年到高年添加365或366),然后到达相同的月份,然后进行天数差异。

1
避免使用 atoi,因为它提供了验证转换的能力。相反,请使用 strtol。或者虽然不是很好,但更好的方法是使用 fgets 读取,然后使用 sscanf 解析。至少这样你就有了一定程度上的转换成功/失败的措施。 - David C. Rankin

1
从现在到过去最简单的方法是读取文件中的日期并将其解析为月份、日期和年份(m,d,y)。如果您不打算对每个转换进行完整的验证,则可以使用fscanf和适当的字段宽度修饰符来分离4位数的年份和2位数的月份和日期,例如:
    while (fscanf (fp, "%4d%2d%2d", &y, &m, &d) == 3) {

然后,在循环内部所需的一切就是用年、月和日值填充struct tm(记得从年份中减去1900,并将每个小时、分钟和秒成员都设置为零,将夏令时成员设置为-1)。一个简单的函数可以做到这一点,并在调用mktime之后返回time_t,例如:

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 */
}

为了完成操作,你只需要获取time_t值并调用difftime函数,以获取当前时间和从文件中读取的时间之间的差值(以double值表示)。在main()函数中继续循环。
    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));
    }

把所有东西都放在一起,你可以这样做:
#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;
}

注意:程序期望从第一个参数中读取要读取日期的文件名(如果没有给出参数,则程序将默认从stdin中读取)
示例输入文件
$ cat dat/3dates.txt
20190101
20190304
20180922

示例用法/输出
$ ./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.

编辑每个评论修改输入文件格式

如果您的数据文件实际上与您最初发布问题时所发布的三行日期不同,并且其中包含日期信息之前的标题行,则需要在处理日期行之前读取、识别和处理这些行。由于您希望以天为单位而不是秒为单位输出,因此只需将秒数除以86400即可得到时间差。

要读取和处理标题行,只需调整您的读取操作,将整个行一次性读入足够大的缓冲区中。声明一个足够大的常量来确保您的缓冲区足够大,例如:

#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 */

然后,您只需使用sscanf而不是fscanf来执行每行中信息的精确解析。如果行格式不满足yyyymmdd格式,则知道它不是日期行 - 以任何方式处理这些行(在下面的示例中仅输出前缀为"non-date line: ")。
结合将自文件时间以来的秒数除以每天的86400秒,您的新读取循环将类似于:
    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);
    }

你说:

"I am not able to open the file"
该程序期望您提供要作为程序第一个参数读取的文件名,否则程序将默认从标准输入(stdin)读取。这意味着您必须向程序提供文件名,例如:
./yourprogram your_date_file

或者你需要通过将该信息从其他程序的输出中传输到程序中,或者简单地将文件重定向为stdin的输入来提供该数据,例如:

some_utility_making_dates | ./yourprogram

或者

./yourprogram < your_date_file

将所有更改合并后,您的程序将如下所示:
#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;
}

带标题的示例输入文件

$ 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

示例用法/输出

$ ./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.

请查看并告诉我是否有进一步的问题。

当然,这就是编程的美妙之处,你可以在任何循环中添加任何额外的条件。我刚刚检查的条件是如果该行无法解析为“y,m d”,则将其打印为上面所述并获取下一行。您可以按任何方式更改它。试试看,如果有问题请告诉我。 - David C. Rankin
好的。我已经在文件中添加了一些值。第一行是名称,第二行是车牌号,如AA333BB,第三行是布尔值10,第四行是成本100.00,最后是日期。在我的情况下,这是一个客户的记录。我有很多记录在同一个文件中,但是只用一个空行分隔。现在,如果日期距离现在超过一年,我想打印出每个记录的名称和车牌号。当然,如果类型为“1”,我应该打印一个值,如果它是“0”,我应该打印另一个值。我无法在这种情况下弄清楚您的示例,我们可以做到吗? - user6710403
如果类型为1,我想将成本乘以1.5;如果类型为0,则将其乘以2.5。我知道这看起来很奇怪,但请告诉我是否可能。 - user6710403
1
你需要做的是使用计数器 n 来控制你正在读取的内容。你可以简单地使用 while (fgets(..)) { switch (n) { case 1: /*处理第一行*/ break; case 2: /*处理车牌号*/ break, .... 另一个选择是测试每行的内容——但如果你知道它有固定的格式和包含什么内容,那么一个简单的计数器和 switch 或者 if (n == 1)... else if (n == 2)... else if ... 是更简单的方法。 - David C. Rankin
@DavidC.Rankin 我在这里提出了问题 https://stackoverflow.com/questions/55191612/reading-different-values-from-file-in-c-and-print-based-on-conditions - user6710403
显示剩余6条评论

0

输出是,你只需循环文件行并在其中设置 if 条件

#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

未定义 getline 引用。 - user10592994
答案是正确的,我在我的Ubuntu系统上运行了它,你的平台是什么?Windows?Linux?Mac?这是系统配置错误!使用链接的帮助将#define _GNU_SOURCE更改为您的源代码。 - Anandhukrishna VR
它打印出当前日期,但问题仍然存在,现在想象一下第一行有一个单词,第二行有一个单词,第三行有日期。现在如何只读取第三行(日期),并从当前日期减去天数呢? - user10592994
问题仍然存在是什么意思?未定义对getline的引用。? - Anandhukrishna VR
不,现在没问题了。如果第一行有一个单词,第二行有一个单词,第三行有日期,那么如何仅读取第三行(日期),并从当前日期减去天数呢? - user10592994

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接