Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
291 views
in Technique[技术] by (71.8m points)

c# - Wanted: DateTime.TryNew(year, month, day) or DateTime.IsValidDate(year, month, day)

The title basically says it all. I'm getting three user-supplied integers (year, month, day)1 from a legacy database (which I cannot change). Currently, I use the following code to parse those integers into a DateTime structure:

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

Sometimes, the values don't represent a valid date (yes, users enter stuff like 1999-06-31, and no, the legacy app did not verify this). Since throwing an exception when data validation fails is considered bad practice, I'd prefer to replace this with exception-less code. However, the only solution I could find was to convert the integers into one string and TryParseExact this string, which seems even uglier to me. Did I miss some obvious better solution?


1 Actually, it's one integer in the format YYYYMMDD, but converting that to year, month and day is trivial...

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

There is not a static function IsValidDate() so you have to write it by yourself, first naive implementation may be:

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);
}

I said this is a naive implementation because (besides arguments range) the only check to see if a date exists is for leap year. In practice this may fail because of calendar issues if you're working with non Gregorian calendars (and missing days even in Gregorian calendar that has been used to align date from Julian calendar).

Working With Calendars

These assumptions may be broken for non Gregorian calendars:

  • 1 January 01 is smallest valid date. It's not true. Different calendars have a different smallest date. This limit is just DateTime technical limit but there may be a calendar (or an Era within a calendar) with a different minimum (and maximum) date.
  • Number of months in one year is less or equal than 12. It's not true, in some calendars upper bound is 13 and it's not always the same for every year.
  • If a date is valid (according all other rules) then it's a valid date. It's not true, a calendar may have more than one era and not all dates are valid (possibly even within era date range).

Rules to manage this are pretty complex and it's too easy to forget something so, in this case, catching an exception may not be such bad idea. A better version of previous validation function may just provide basic validation and relying on DateTime to check other rules:

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;
    }
}

Then, given this new function, your original code should be:

return TryNew(year, month, day) ?? DateTime.MinValue;

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...