从重定向为输入的文件中验证日期

Validating dates from a file that is redirected as input

首先,这是一道作业题。

具有 mm/dd/yyyy 格式的日期列表的文件将被重定向为来自命令提示符的输入。这是一个例子./main.out < file.txt。对于我找到的每个有效日期,我想将其写入输出文件。


我的教授给出了构成无效日期的标准。

1.) 如果有任何非数字字符,如 12/4A/199A5.

2.) 如果他们缺少正斜杠或有 2 个以上的正斜杠。

3.) 如果它们是浮点数,如 12/4/1995.3

4.) 如果它们是不正确的日期,例如 2/29/1973,因为 29 表示该年份应该是闰年,但 1973 不能被 4 整除或400.

5.) 如果没有日期(空字符串)或部分缺失,如 4/3/.


这里是有效日期的例子。

1.) 带有前导或尾随空格的日期,例如 12 /23/ 1694

2.) 带前导零的日期,例如 004/030/2000

3.) 月份数在 1 到 12 之间的任何日期,正确的日长(这取决于月份)和年份可以是正数、负数或零。


这是我到目前为止所做的,仅使用必要的相关功能。

int writeToFile()
{
    FILE *outputFile;
    char buffer[BUFFERSIZE];
    int month = 0, days = 0, year = 0;
    int isValidFormat = 0, isValidDate = 0;
    size_t strDateLength;

    outputFile = fopen("Output.dat", "w");

    if(outputFile == NULL)
    {
        fprintf(stderr, "Couldn't write to the file.\n");
        return FALSE;
    }

    while(fgets(buffer, BUFFERSIZE, stdin))
    {
        strDateLength = strlen(buffer);

        if(buffer[strDateLength - 1] != '\n' && strDateLength <  BUFFERSIZE - 1)
        {
            appendNewLine(buffer, strDateLength);
        }

        isValidFormat = validateDateFormat(buffer, &month, &days, &year, strDateLength);

        if(isValidFormat)
        {
            isValidDate = validateDate(month, days, year);

            if(isValidDate)
            {
                fprintf(outputFile, "%s", buffer);
            }
        }
    }

    fclose(outputFile);
    return TRUE;
}

void appendNewLine(char *str, size_t strLength)
{
    str[strLength] = '\n';
    str[strLength + 1] = '[=10=]';
}

int validateDateFormat(char *str, int *month, int *days, int *year, size_t strDateLength)
{
    int index, index2 = 0;
    char temp[BUFFERSIZE];
    int numOfForwSlashes = 0;

    for(index = 0; index < strDateLength; index++)
    {
        if(str[index] == '/')
        {
            if(numOfForwSlashes == 0)
            {
                *month = atoi(temp);
            }
            else if(numOfForwSlashes == 1)
            {
                *days = atoi(temp);
            }

            numOfForwSlashes++;
            memset(&temp[0], 0, sizeof(temp));
            index2 = 0;
        }
        else if(str[index] == '\n')
        {
            *year = atoi(temp);
        }
        else if(!isdigit(str[index]) && str[index] != ' ')
        {
            return FALSE;
        }
        else
        {
            temp[index2] = str[index];
            index2++;
        }
    }

    if(numOfForwSlashes != TOTALFORWARDSLASHES) // This define is 2
    {
        return FALSE;
    }
    else
        return TRUE;
}

int validateDate(int month, int days, int year)
{
    if((month < JANUARY || month > DECEMBER) && (days < 1 || days > THIRTYONE))
    {
        return FALSE;
    }

    if(month == FEBURARY)
    {
        if(isLeapYear(year))
        {
            if(days > TWENTYNINE)
            {
                return FALSE;
            }
        }
        else
        {
            if(days > TWENTYEIGHT)
            {
                return FALSE;
            }
        }
    }
    else if(month == APRIL || month == JUNE || month == SEPTEMBER || month == NOVEMBER)
    {
        if(days > THIRTY)
        {
            return FALSE;
        }
    }
    else
    {
        if(days > THIRTYONE)
        {
            return FALSE;
        }
    }

    return TRUE;
}

问题:

除了缺少年份的情况外,它在大多数情况下都有效,坦率地说,我想知道是否有办法在仍然遵循标准的同时使它更简洁。使用 sscanf 或 scanf 并不能解决我遇到的所有问题,strtok 之类的字符串函数也无济于事。

... it works except for cases where the year is missing and to be frank I would like to know if there is way I could make this more concise ...

与其只创建一个函数来解析日期,不如创建一个辅助函数来解析每个整数。

未经测试的代码示例:

#include <ctype.h>
#include <errno.h>
#include <stdlib.h>

// Parse 1 int and optional following delimiter
// Return NULL on error
static const char *Get_int(const char *p, int *i, int endchar) {
  if (p) {
    char *endptr;
    errno = 0;
    long y = strtol(p, &endptr, 10);
    if (errno || p == endptr) return NULL;
    *i = y;  // Test for INT_MIN... INT_MAX if desired

    // skip following white-space
    while (isspace((unsigned char ) *endptr))
      endptr++;

    // Look for delimiter
    if (endchar) {
      if (*endptr != endchar) return NULL;
      endptr++;
    }
    return endptr;
  }
  return NULL;
}

int validateDateFormat(char *str, int *month, int *days, int *year, 
    size_t strDateLength) {
  // form string
  char temp[strDateLength + 1];
  memcpy(temp, str, strDateLength);
  temp[strDateLength] = '[=10=]';
  const char *p = temp;

  p = Get_int(p, month, '/');
  p = Get_int(p, days, '/');
  p = Get_int(p, year, 0);
  return p && *p == '[=10=]';
}

一个sscanf()方法:

使用"%n"检测是否扫描了整个字符串。

int validateDateFormat(char *str, int *month, int *days, int *year, 
    size_t strDateLength) {
  // form string
  char temp[strDateLength + 1];
  memcpy(temp, str, strDateLength);
  temp[strDateLength] = '[=11=]';

  int n = 0;
  sscanf(temp, "%d /%d /%d %n", month, days, year, &n);
  return n > 0 && temp[n] == '[=11=]';
)

黑客利用:请注意,下面的代码假定 strDateLength > 0fgets() 将愉快地读入空字符作为第一个字符并继续阅读,直到遇到 '\n'

由于strDateLength是一个size_t,一个无符号类型,0-1是一个很大的值,buffer[strDateLength - 1]肯定会出问题。

while(fgets(buffer, BUFFERSIZE, stdin)) {
  strDateLength = strlen(buffer);
  if(buffer[strDateLength - 1] != ...

顺便说一句,无论如何都不需要添加 '\n'