Требуется: DateTime.TryNew (год, месяц, день) или DateTime.IsValidDate (год, месяц, день) - PullRequest
10 голосов
/ 27 февраля 2012

Название в основном говорит обо всем. Я получаю три пользовательских целых числа (year, month, day) 1 из устаревшей базы данных (которую я не могу изменить). В настоящее время я использую следующий код для разбора этих целых чисел в структуру DateTime:

try {
    return new DateTime(year, month, day);
} catch (ArgumentException ex) {
    return DateTime.MinValue;
}

Иногда значения не представляют действительную дату (да, пользователи вводят такие вещи, как 1999-06-31, и нет, унаследованное приложение не проверяло это). Поскольку генерирование исключения при сбое проверки данных считается плохой практикой , я бы предпочел заменить его на код без исключений. Однако единственное решение, которое я смог найти, - это преобразовать целые числа в одну строку и TryParseExact эту строку, что мне кажется еще более уродливым. Я пропустил какое-то очевидное лучшее решение?


1 На самом деле это одно целое число в формате ГГГГММДД, но преобразование его в год, месяц и день тривиально ...

Ответы [ 2 ]

20 голосов
/ 27 февраля 2012

Нет статической функции IsValidDate(), поэтому вы должны написать ее самостоятельно, первая наивная реализация может быть:

public static bool IsValidDate(int year, int month, int day)
{
    if (year < DateTime.MinValue.Year || year > DateTime.MaxValue.Year)
        return false;

    if (month < 1 || month > 12)
        return false;

    return day > 0 && day <= DateTime.DaysInMonth(year, month);
}

Я сказал, что это наивная реализация, потому что (помимо диапазона аргументов) единственная проверка, чтобы увидеть, существует ли дата для високосного года. На практике это может не сработать из-за проблем с календарем, если вы работаете с негригорианскими календарями (и пропускаете дни даже в григорианском календаре, который использовался для выравнивания даты по юлианскому календарю).

Работа с календарями

Эти предположения могут быть нарушены для негригорианских календарей:

  • 1 января 01 - наименьшая действительная дата. Это неправда. Разные календари имеют разные наименьшие даты. Этот лимит составляет всего лишь DateTime технический лимит, но может существовать календарь (или Эра в календаре) с другой минимальной (и максимальной) датой.
  • Количество месяцев в одном году меньше или равно 12. Это неправда, в некоторых календарях верхняя граница равна 13 и не всегда одинакова для каждого года.
  • Если дата действительна (согласно всем другим правилам), то это действительная дата. Это неправда, календарь может иметь более одной эпохи, и не все даты действительны (возможно, даже в пределах диапазона дат эпохи).

Правила для управления этим довольно сложны, и слишком легко что-то забыть, поэтому в данном случае перехват исключения может быть не такой уж плохой идеей. Лучшая версия предыдущей функции проверки может просто обеспечить базовую проверку и полагаться на DateTime для проверки других правил:

public static DateTime? TryNew(int year,
                               int month,
                               int day,
                               Calendar calendar)
{
    if (calendar == null)
        calendar = new GregorianCalendar();

    if (year < calendar.MinSupportedDateTime.Year)
        return null;

    if (year > calendar.MaxSupportedDateTime.Year)
        return null;

    // Note that even with this check we can't assert this is a valid
    // month because one year may be "shared" for two eras moreover here
    // we're assuming current era.
    if (month < 1 || month > calendar.GetMonthsInYear(year))
        return null;

    if (day <= 0 || day > DateTime.DaysInMonth(year, month))
        return null;

    // Now, probably, date is valid but there may still be issues
    // about era and missing days because of calendar changes.
    // For all this checks we rely on DateTime implementation.        
    try
    {
        return new DateTime(year, month, day, calendar);
    }
    catch (ArgumentOutOfRangeException)
    {
        return null;
    }
}

Тогда, учитывая эту новую функцию, ваш оригинальный код должен быть:

return TryNew(year, month, day) ?? DateTime.MinValue;
9 голосов
/ 27 февраля 2012

Вы можете использовать DateTime.DaysInMonth для проверки правильности даты. Очевидно, месяц должен быть в диапазоне (1; 12)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...