2

How would I parse the following string date to a DateTime object in C#:

"Thursday, 1st January 1970"

This is coming from an XML feed and DateTime.Parse doesnt seem to like it in en-GB locale. The feed will only ever come from a british server so I'm not to worried about globalization issues

My initial brute force approach would be to:

  • Delete everything up to and including the comma, and the trailing space to leave "1st January 1970"
  • Then remove "st", "nd", "rd" or "th" as appropriate to leave "1 January 1970"
  • Then convert month to its numeric equivalent leaving "1 1 1970"
  • Then replace spaces with "/" to get "1/1/1970"

Im sure there must be a far more elegant way though? I couldnt get DateTime.Prse or Datetime.ParseExact to work

Ben Fidge
  • 203
  • 3
  • 10
  • careful when removing "st" if you're expecting August. Try Regex.Replace("August 1st 2009", "([0-9])(st|nd|rd|th)","$1"); obviously using your date instead of the string I've used as an example. – Steve Nov 18 '15 at 13:50

3 Answers3

6

I don't believe that DateTime parsing knows anything about ordinals, but it should be able to handle everything else. So you could use:

public static string RemoveOrdinals(string input)
{
    // Ugly but oh so simple.
    return input.Replace("0th", "0")
                .Replace("1st", "1")
                .Replace("2nd", "2")
                .Replace("3rd", "3")
                .Replace("11th", "11") // Need to handle these separately...
                .Replace("12th", "12")
                .Replace("13th", "13")
                .Replace("4th", "4")
                .Replace("5th", "5")
                .Replace("6th", "6")
                .Replace("7th", "7")
                .Replace("8th", "8")
                .Replace("9th", "9");
}

Then:

string text = RemoveOrdinals(text);
DateTime date = DateTime.ParseExact(text, "dddd, d MMMM yyyy",
                                    CultureInfo.GetCulture("en-GB"));

(As a quick plug, of course you want just a date rather than a date/time. Unfortunately .NET doesn't have a type to represent that - but you could use LocalDate in my Noda Time library. We don't handle ordinals either - yet, anyway - so you'd still need the extra method. Let me know if you'd like to see the relevant code though.)

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Just a note about ordinals; the forms "2d" and "3d" are generally considered dictionary-acceptable (if infrequently used) in place of "2nd" and "3rd". Be sure those are parsed properly as well. – James Cronen Jul 17 '13 at 21:47
  • @Tenner: I would be *astonished* if those came up in this context. There are only so many corner cases it's worth worrying about, IMO. – Jon Skeet Jul 17 '13 at 21:48
  • I don't presume to know anywhere near as much as you Jon, but wouldn't it be simpler to just `Replace("th", "").Replace("nd", "")` etc? That would take care of cases like 21st, 12th, 22nd as well. Or is there some other reason the developer would want to code `Replace()` for each potential date? – Evan L Jul 17 '13 at 21:58
  • @Evanlewis: You wouldn't want "Monday" to be replaced with "Moay" would you? There's method to my madeness :) The above already takes care of "21st" as that contains "1st"... it's only the last digit which is important. – Jon Skeet Jul 17 '13 at 21:59
  • Of course! Ha, didn't even think about the day of the week issue. Thursday would also become ursday. Try to parse that! You are right about the last digit as well. Thanks for the lesson ;) – Evan L Jul 17 '13 at 22:01
  • @Evanlewis: No, Thursday would be okay because "Th" != "th". But it did take me a while to think through the various days of the week (and months) to check that one of them *did* have an "nd", "st" or "th" :) – Jon Skeet Jul 17 '13 at 22:02
5

Just to provide a slightly different take on this, and give you an idea of some other options you have; you can specify the format to DateTime.Parse (or TryParse as in my example) to account for circumstances like this, without trying to 'pre format' the string into something else with String.Replace calls and the like;

public DateTime ParseOrdinalDateTime(string dt)
{
    string[] expectedFormats = 

    DateTime d;
    if (DateTime.TryParseExact(dt, "dddd, d\"st\" MMMM yyyy", null, DateTimeStyles.None, out d))
        return d;
    if (DateTime.TryParseExact(dt, "dddd, d\"nd\" MMMM yyyy", null, DateTimeStyles.None, out d))
        return d;
    if (DateTime.TryParseExact(dt, "dddd, d\"rd\" MMMM yyyy", null, DateTimeStyles.None, out d))
        return d;
    if (DateTime.TryParseExact(dt, "dddd, d\"th\" MMMM yyyy", null, DateTimeStyles.None, out d))
        return d;

    throw new InvalidOperationException("Not a valid DateTime string");
}

The reason I'd propose this approach is that it sets out your input expectations very clearly, and contains the behaviour to a single method. If the format changes, you can just specify a different format string in here and account for a new date time string structure.

Or, a slight variation on the above, taking into account below comments;

private static DateTime ParseOrdinalDateTime(string dt)
{
    string[] expectedFormats = new[]
    {
        "dddd, d'st' MMMM yyyy",
        "dddd, d'nd' MMMM yyyy",
        "dddd, d'rd' MMMM yyyy",
        "dddd, d'th' MMMM yyyy"
    };

    try
    {
        return DateTime.ParseExact(dt, expectedFormats, null, DateTimeStyles.None);
    }
    catch (Exception e)
    {
        throw new InvalidOperationException("Not a valid DateTime string", e);
    }
}

NOTE: The only reason I catch and throw an InvalidOperationException above is to protect the caller from having to catch Exception to handle whatever possible exceptions DateTime.ParseExact may throw. You could easily modify this API.

RJ Lohan
  • 6,497
  • 3
  • 34
  • 54
  • Why not use the overload which takes multiple patterns? Also, you can use `'` instead of `"` to quote a piece of the pattern, which would make it simpler. – Jon Skeet Jul 17 '13 at 21:49
  • 2
    I would also suggest specifying the culture explicitly - we know what it's meant to be, so why rely on the thread's current culture? – Jon Skeet Jul 17 '13 at 22:00
  • 1
    Ive used the second version where an array of formats passed in, as per Jon Skeets suggestion. I didnt know you could quote a piece of format specifier. For me this is the clearest and the list of allowable formats could be stored in DB or other data store – Ben Fidge Jul 18 '13 at 07:34
  • Shouldn't it throw a `FormatException` when the format is wrong? – Rowland Shaw Nov 07 '14 at 12:17
0

Use DateTime.Parse with a culture specific formatter:

http://msdn.microsoft.com/en-us/library/kc8s65zs.aspx

First reverse the logic at this answer to strip the "st", "nd" etc. from the day of the month:

https://stackoverflow.com/a/4544313/2420979

Then just use DateTime.Parse normally:

var result = DateTime.Parse("Thursday, 1 January 1970", new CultureInfo("en-GB"));
Community
  • 1
  • 1
Haney
  • 32,775
  • 8
  • 59
  • 68
  • 1
    The answer you've linked to converts *to* an ordinal format - not *from* it. I'd also recommend using a specific format rather than just `DateTime.Parse`, which always feels pretty much like "hit and hope" to me. – Jon Skeet Jul 17 '13 at 21:38
  • True, and sorry, I meant to say REVERSE the logic... Updating. :) – Haney Jul 17 '13 at 21:43