33

I have created an API end-point. The caller may call the API with POST method passing the relevant parameters. In the parameters there is one parameter that is of datetime format.

The problem is that when calling this API the caller may passes datetime in 3 different formats:

  1. long int - e.g. 1374755180
  2. US format - e.g. "7/25/2013 6:37:31 PM" (as string)
  3. Timestamp format - e.g. "2013-07-25 14:26:00" (as string)

I have to parse the datetime value and convert it to a DateTime or string in Timestamp format.

I have tried using DateTime.TryParse(), DateTime.Parse(), Convert.ToDateTime() and Convert.ToDouble() but none of them are working in certainty for me.

The required output has to be in en-GB format.

Edit:

I had thought to have an if-else if-else block to use with TryParse 3 times with one else to say the string could not be parsed. Is this the best solution? Or are there solutions better than this?

Please help!

Vivek Jain
  • 3,811
  • 6
  • 30
  • 47
  • Maybe you can use DateTime.TryParseExact ? – Kamil Budziewski Jul 25 '13 at 13:29
  • How will this help if I don't know the format of the input string? – Vivek Jain Jul 25 '13 at 13:29
  • 1
    @theghostofc, but you do know, you have 3 possible input Strings, one contains no spaces nor any special characters, the other has "/" separators, and the other has "-". You could use 3 parsers to prevalidate the exact format, but it would just be enough to test if it's numeric, or if it contains the separators you're expecting and then try to parse with the proper format. If it doesn't parse, the input field is not valid... – Martin Jul 25 '13 at 13:34
  • It will help because it will fail on one, then the next, then hopefully not. DateTime.Parse() (or TryParse) are likely doing something similar internally. Trying multiple formats. Let me see if I can put an answer together. – Randy James Jul 25 '13 at 13:34

7 Answers7

35

You should consider requiring a timezone. 1 doesn't need it, but #2 and #3 do.

public DateTime ParseRequestDate()
{
    // https://stackoverflow.com/questions/2883576/how-do-you-convert-epoch-time-in-c

    CultureInfo enUS = new CultureInfo("en-US");

    var dt = "1374755180";
    //var dt = "7/25/2013 6:37:31 PM";
    //var dt = "2013-07-25 14:26:00";

    DateTime dateValue;
    long dtLong;

    // Scenario #1
    if (long.TryParse(dt, out dtLong))
        return dtLong.FromUnixTime();

    // Scenario #2
    if (DateTime.TryParseExact(dt, "MM/dd/yyyy hh:mm:ss tt", enUS, DateTimeStyles.None, out dateValue))
        return dateValue;

    // Scenario #3
    if (DateTime.TryParseExact(dt, "yyyy-MM-dd hh:mm:ss", enUS, DateTimeStyles.None, out dateValue))
        return dateValue;

    throw new SomeException("Don't know how to parse...");
}

EDIT As Matt Johnson points out, DateTime.TryParseExact accepts an array of format strings. 2 & 3 could be condensed.

public DateTime ParseRequestDate()
{
    // https://stackoverflow.com/questions/2883576/how-do-you-convert-epoch-time-in-c

    CultureInfo enUS = new CultureInfo("en-US");

    var dt = "1374755180";
    //var dt = "7/25/2013 6:37:31 PM";
    //var dt = "2013-07-25 14:26:00";

    DateTime dateValue;
    long dtLong;

    // Scenario #1
    if (long.TryParse(dt, out dtLong))
        return dtLong.FromUnixTime();

    // Scenario #2 & #3
    var formatStrings = new string[] { "MM/dd/yyyy hh:mm:ss tt", "yyyy-MM-dd hh:mm:ss" };
    if (DateTime.TryParseExact(dt, formatStrings, enUS, DateTimeStyles.None, out dateValue))
        return dateValue;

    throw new SomeException("Don't know how to parse...");
}

The epoch conversion I borrowed from another question. (An extension method)

public static class MyExtensions
{
    public static DateTime FromUnixTime(this long unixTime)
    {
        var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
        return epoch.AddSeconds(unixTime);
    }
}
Community
  • 1
  • 1
Randy James
  • 1,420
  • 13
  • 12
  • 7
    You can probably merge #2 and #3 since `TryParseExact` can accept an array of formats. And a time zone isn't *required*, its just that those formats will have `.Kind == DateTimeKinds.Unspecified`. – Matt Johnson-Pint Jul 25 '13 at 13:58
  • 1
    @RandyJames, I am not sure why parsing was not working for timestamp format but, your answer helped me to decide to parse `long int` differently and other two differently. Thanks! – Vivek Jain Jul 25 '13 at 14:30
  • @RandyJames, I chose your answer as it kicked my mind in the right direction and I could find the solution. Thanks again! – Vivek Jain Jul 27 '13 at 15:16
32

You are looking for the DateTime.ParseExact (MSDN Article)

Which you would use in a situation like this:

string[] formats= { "MM/dd/yyyy hh:mm:ss tt", "yyyy-MM-dd hh:mm:ss" }
var dateTime = DateTime.ParseExact("07/25/2013 6:37:31 PM", formats, new CultureInfo("en-GB"), DateTimeStyles.None);

This allows you to add as many DateTime formats to the array as you need and the method will do the conversion without the if...else statements.

If your integer is in seconds since Unix Epoch you add the number of seconds to the DateTime of the Epoch (01/01/1970) (.Net doesn't have an out of the box method for this, but the logic is seconds since 'Epoch'):

new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(seconds);

From this question.

Darbio
  • 11,286
  • 12
  • 60
  • 100
  • I am not sure why parsing was not working for timestamp format. Thanks for your answer! – Vivek Jain Jul 25 '13 at 14:31
  • This code gives String was not recognized as a valid DateTime error. Need to add leading 0's to meet the expected format :) – Leszek P Dec 01 '20 at 16:59
  • Why would you provide a specific cultural format when parsing an exact format? How can this make sense, since the strict format is specified in the string, how can the culture possibly influence the outcome? – PandaWood Apr 14 '22 at 01:10
3

I was facing same problem in a project where my code will run on different environments with various culture formats.

Google showed me this hidden gem. The helper function is indispensable to auto-parse datetime properly regardless of culture formats

Usage Examples:

string str = @"The last round was June 10, 2005; this time the unbroken record was held.";
DateTimeRoutines.ParsedDateTime pdt;
if (DateTimeRoutines.TryParseDate(str, DateTimeRoutines.DateTimeFormat.USA_DATE, out pdt))
    Console.WriteLine("Date was found: " + pdt.DateTime.ToString());

According to the author, the code is capable of parsing various cases:

@"Member since:      10-Feb-2008"
@"Last Update: 18:16 11 Feb '08 "
@"date    Tue, Feb 10, 2008 at 11:06 AM"
@"see at 12/31/2007 14:16:32"
@"sack finish 14:16:32 November 15 2008, 1-144 app"
@"Genesis Message - Wed 04 Feb 08 - 19:40"
@"The day 07/31/07 14:16:32 is "
@"Shipping is on us until December 24, 2008 within the U.S." 
@" 2008 within the U.S. at 14:16:32"
@"5th November, 1994, 8:15:30 pm"
@"7 boxes January 31 , 14:16:32."
@"the blue sky of Sept  30th  2008 14:16:32"
@" e.g. 1997-07-16T19:20:30+01:00"
@"Apr 1st, 2008 14:16:32 tufa 6767"
@"wait for 07/31/07 14:16:32"
@"later 12.31.08 and before 1.01.09"
@"Expires: Sept  30th  2008 14:16:32"
@"Offer expires Apr 1st, 2007, 14:16:32"
@"Expires  14:16:32 January 31."
@"Expires  14:16:32 January 31-st."
@"Expires 23rd January 2010."
@"Expires January 22nd, 2010."
@"Expires DEC 22, 2010."
Korayem
  • 12,108
  • 5
  • 69
  • 56
2

One way to deal with this problem would be setting up a factory method that "understands" different formats, and parses them accordingly.

You can create a chain of if-then-elses to deal with this problem, but you can also make a "table-driven" implementation: what you need is an array of delegates that take a string, and tell you two things:

  • Whether or not this delegate can parse the incoming string, and
  • If yes, what is the result of that parse, expressed as DateTime

Here is a sample implementation:

private static readonly DateParsers = new Func<string,Tuple<DateTime,bool>>[] {
    (s) => {
        long res;
        if (long.TryParse(s, out res)) {
            // The format was correct - make a DateTime,
            // and return true to indicate a successful parse
            return Tuple.Create(new DateTime(res), true);
        } else {
            // It does not matter what you put in the Item1
            // when Item2 of the tuple is set to false
            return Tuple.Create(DateTime.MinValue, false);
        }
    }
    ...
    // Add similar delegates for other formats here
};

Now your factory method could be implemented as follows:

private static bool TryParseMultiformat(string s, out DateTime res) {
    // Check all parsers in turn, looking for one returning success
    foreach (var p in DateParsers) {
        var tmp = p(s);
        if (tmp.Item2) {
            res = tmp.Item1;
            return true;
        }
    }
    res = DateTime.MinValue;
    return false;
}
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
1

If the possible formats are fixed then you can use TryParseExact

A possible solution is to use TryParse, if it fails to get proper date then fallback to known formats and use TryPraseExact

Shekhar_Pro
  • 18,056
  • 9
  • 55
  • 79
0

Thanks for your answers. I tried the options suggested in couple of answers and found out a very simple approach that worked for me.

public static bool ParseDate(string dateString, out DateTime dateValue)
{
    long dtLong = 0L;
    bool result = false;

    if (long.TryParse(dateString, out dtLong))
    {
        // I copied the epoch code here for simplicity
        dateValue = new DateTime(1970, 1, 1, 0, 0, 0).AddSeconds(dtLong);
        result = true;
    }

    // Working for US and Timestamp formats
    else if (DateTime.TryParse(dateString, out dateValue))
        result = true;

    return result;
}

Earlier I was trying to use TryParse for all the 3 formats which was not working.

Somehow, TryParseExact did not work for the timestamp format. It worked for the US format. That is the reason I had to write my own.

Vivek Jain
  • 3,811
  • 6
  • 30
  • 47
0

If you use TryParseExact, only G-O-D and the Microsoft developers know how many possible date time formats it will try to parse before it gives up. Perhaps a better solution is to use a quick regex and then an appropriate parser. I tried to make the regex as simple as possibke, you may have to tweak this a little

    private  static readonly Regex R1
        = new Regex(@"^\d+$", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline);
    private static readonly Regex R2
        = new Regex(@"M$", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline);
    private static readonly Regex R3
        = new Regex(@"^\d{4}-", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline);

    private static void Main(string[] args)
    {
        string[] stringDates = new[]
            {
                "1374755180",
                "2013-07-25 14:26:00",
                "7/25/2013 6:37:31 PM"
            };


        foreach (var s in stringDates)
        {
            DateTime date = default(DateTime);
            if (R1.IsMatch(s))
                date = new DateTime(long.Parse(s));
            else if (R2.IsMatch(s))
                date = DateTime.Parse(s);
            else if (R3.IsMatch(s))
                date = DateTime.Parse(s);

            if (date != default(DateTime))
                Console.WriteLine("{0}", date);
        }

        Console.WriteLine("Press ENTER to continue...");
        Console.ReadLine();
    }
Juan Ayala
  • 3,388
  • 2
  • 19
  • 24