4

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?

The Light
  • 26,341
  • 62
  • 176
  • 258
  • A single `TryParseExact` can't validate all of those values you've listed. The key here is *exact*. The format of the input must match the specified format *exactly* (including precision on the decimal portion of the seconds). – yoozer8 May 23 '13 at 16:02
  • Use Format as "yyyy-MM-dd"; YYYY and DD wont work. Check here for the custom format strings http://msdn.microsoft.com/en-us/library/8kb3ddd4.aspx – arunlalam May 23 '13 at 16:03
  • Shouldn't the format be yyyy-MM-ddThh:mm:ssZ? – vc 74 May 23 '13 at 16:05
  • yes, you're right @vc 74 – The Light May 23 '13 at 16:28

4 Answers4

10

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.

Matt Johnson-Pint
  • 230,703
  • 74
  • 448
  • 575
  • Good answer, thanks. What's 'RoundtripKind' doing here though? Also, does InvariantCulture definitely get us ISO 8601? I was surprised to see that it doesn't always seem to: https://stackoverflow.com/a/23801542/ – Max Barraclough Jun 26 '18 at 11:50
2

TryParseExact, as its name implies, will only successfully parse strings that precisely match the specified format. From the documentation:

The format of the string representation must match the specified format exactly.

None of the examples you've given match the format string you've specified. If you wish to accept some set of formats - such as those defined by the UTC standard - and reject all others, use the overload of TryParseExact that accepts an array of possible formats.

That overload is ideal for a situation in which you have a small set of acceptable formats, such as those specified by the UTC standard - as mentioned in the question.

Dan J
  • 16,319
  • 7
  • 50
  • 82
  • How to know whether a datetime string is a valid UTC datetime? – The Light May 23 '13 at 16:15
  • Well, you could take the set of valid formats specified in the standard - which you have included at the top of your question - and provide those format strings (or their .NET equivalents, as described [here](http://msdn.microsoft.com/en-us/library/8kb3ddd4.aspx)) in an array to the overload of `TryParseExact` that I've mentioned. – Dan J May 23 '13 at 16:21
  • Oh, dear I wish there was an easier way than checking them manually for those 8-9 combinations. – The Light May 23 '13 at 16:24
  • 1
    You wish there was an easier way to test if a string is a valid format than to test if the string is a valid format? – Preston Guillot May 23 '13 at 16:27
  • As far as I know, the acceptable format strings shown in your question are not built-in to the .NET framework, unfortunately. At least there are only six of them. It's worth clarifying: Why do you need to determine if a string conforms to the valid UTC timestamp formats? – Dan J May 23 '13 at 16:28
1

Actually what you need is simply achieved by the other "less famous" constructor of TryParseExact(string input, string[] formats, ....).

  • You define an array of acceptable format as follows and then pass it to the TryParseExact above

string[] acceptableFormats =
{
"M/d/yyyy h:mm:ss tt", "M/d/yyyy h:mm tt",
"MM/dd/yyyy hh:mm:ss", "M/d/yyyy h:mm:ss",
"M/d/yyyy hh:mm tt", "M/d/yyyy hh tt",
"M/d/yyyy h:mm", "M/d/yyyy h:mm",
"MM/dd/yyyy hh:mm", "M/dd/yyyy hh:mm"
};

if (DateTime.TryParseExact(dateString, acceptableFormats,  
new CultureInfo("en-US"),   
DateTimeStyles.None,   
out dateValue))  
{  
// Do something useful  
}

Here is the MSDN link for that constructor.

Ashraf ElSwify
  • 192
  • 1
  • 8
0

Try this;

bool validDate = false;
DateTime dt = new DateTime();
try
{
    dt = Convert.ToDateTime(modifiedSince);
    validDate = true;
}
catch(FormatException) { string message = "Not a valid date..."; }

if (validDate)
{
    //Do whatever else you need to do with the validated date.
}

Whether or not this encompasses every known format of a date is unclear, but this is the method I use, and hopefully it works for you.

Mike Varosky
  • 390
  • 1
  • 10
  • As far as the format of your output, that's on you. Once you have the valid DateTime, you can render it back to a string like dt.ToString("YYYY-MM-DDT hh:mm:ssZ"); Although I'm not sure how the Z will fly, never tried it myself. – Mike Varosky May 23 '13 at 20:56