Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to know whether a given string is a valid UTC DateTime format?

I'd need to allow accepting UTC datetimes: http://www.w3.org/TR/NOTE-datetime

Such as:

 - Year:
         YYYY (eg 1997)    Year and month:
         YYYY-MM (eg 1997-07)    Complete date:
         YYYY-MM-DD (eg 1997-07-16)    Complete date plus hours and minutes:
         YYYY-MM-DDThh:mmTZD (eg 1997-07-16T19:20+01:00)    Complete date plus hours, minutes and seconds:
         YYYY-MM-DDThh:mm:ssTZD (eg 1997-07-16T19:20:30+01:00)    Complete date plus hours, minutes, seconds and a decimal fraction of
   a second
         YYYY-MM-DDThh:mm:ss.sTZD (eg 1997-07-16T19:20:30.45+01:00) where:

        YYYY = four-digit year
        MM   = two-digit month (01=January, etc.)
        DD   = two-digit day of month (01 through 31)
        hh   = two digits of hour (00 through 23) (am/pm NOT allowed)
        mm   = two digits of minute (00 through 59)
        ss   = two digits of second (00 through 59)
        s    = one or more digits representing a decimal fraction of a second
        TZD  = time zone designator (Z or +hh:mm or -hh:mm)

How to Convert a string to a DateTime with a specific format?

I wrote the below code:

 private const string Format = "YYYY-MM-DDThh:mm:ssZ";

        public DateTime? Parse(string modifiedSince)
        {
            // how to know whether it's a valid DateTime in UTC format?
        }

But it always returns null which means that it fails to parse to DateTime.

It should successfully validate below UTC values but it doesn't:

2013-05-10
2013-05-10T05:04:10
2013-05-10T05:04:10.40
013-05-10T05:04:10.4Z

(Z is optional usually)

How to use DateTime.Parse or .Parsexact so that it successfully returns a date for the above format?

All other date formats should fail e.g. 20130510

I could write a Regex instead?

like image 968
The Light Avatar asked Dec 12 '22 14:12

The Light


1 Answers

You are missing something important. UTC is not a format.

UTC refers to "Coordinated Universal Time" (yes, the abbreviation is out of sequence intentionally). This is a fixed clock at the prime meridian, equivalent to GMT for all practical purposes. It does not change for "daylight savings time" or "summer time", and it is where we place the zero when talking about offsets of other time zones.

The format you are describing is known as ISO8601. Technically, the ISO8601 standard defines several formats, but the one most commonly used is also defined in RFC3339 and covers only the last two of the formats you listed. In other words, ISO8601 allows multiple representations of time at different precisions, but RFC3339 requires values up to the second.

Now when it comes to .Net, you are missing another couple of important concepts. Specifically, you need to be aware of DateTime.Kind and DateTimeOffset.

When you have a string value that includes a Z or +00:00 at the end, you know that it represents time at UTC, and can be stored in a DateTime that has DateTime.Kind == DateTimeKind.Utc.

If you have some other offset, such as +01:00 or -04:00, then you should be storing these in a DateTimeOffset object instead. If you try to store it in a DateTime, then you have to choose whether you are going to ignore the offset, or apply it and store the time as UTC.

If you don't have anything at the end, then you have ambiguity. You can store this in a DateTime where Datetime.Kind == DateTimeKind.Unspecified but you must be very careful with what you do with it. It doesn't have any information about what time zone or offset that value originated from - so any math operations might be incorrect. For example, you should never obtain a duration by subtracting two DateTime valules with unspecified (or local) kinds. For additional details, see this blog post.

One last thing, understand that .Net has no built-in class that represents a date without a time. For that, we commonly use a DateTime at midnight - but you would not be able to distinguish this from a value where you were actually given the time at midnight. In other worse, after parsing 2013-05-10 and 2013-05-10T00:00:00 - they are equivalent.

Here is some code to get you started.

public DateTime? Parse(string s)
{
    // you may want to add a few more formats here
    var formats = new[] { "yyyy-MM-dd",
                          "yyyy-MM-ddThh:mm:ss",
                          "yyyy-MM-ddThh:mm:ssZ" };

    DateTime dt;
    if (DateTime.TryParseExact(s, formats,
                               CultureInfo.InvariantCulture, // ISO is invariant
                               DateTimeStyles.RoundtripKind, // this is important
                               out dt))
        return dt;

    return null;
}

You should also look into DateTimeOffset.TryParseExact if you plan to go down that route.

like image 169
Matt Johnson-Pint Avatar answered Dec 14 '22 23:12

Matt Johnson-Pint