views:

192

answers:

7

Apologies if this has been asked before. I have some data which I need to store as strings, some of that data being dates. The data starts off as strings like "01/02/10" (uk format). Now, later on, this data is parsed and, depending upon what does the parsing, the results are different (01-Feb-10 vs. 02-Jan-10 for example). Given that the data starts off as strings, before I stored it I would like to say, "if this looks like a date, format it as dd-mmm-yy".

The trouble being that many things look like a date to the DateTime.Parse() function.

So, I applied some rules and only accept "reasonable" date formats for my checks, and wrote an IsDate() function. I'm seeking suggestions about how to do this because, while it works, my solution seems very clunky.

The whole reason why I did this rather than go down the usual DateTime.TryParse routine is clear if you have ever started throwing random strings at it (like "3/4" and "6.12").

Here's what I have so far:

class Program
{
  static void Main(string[] args)
  {
     Debug.Assert(IsDate(6.12) == false);
     Debug.Assert(IsDate("3/4") == false);
     Debug.Assert(IsDate(010210) == false);
     Debug.Assert(IsDate("010210") == false);
     Debug.Assert(IsDate("12-jan-2000") == true);
     Debug.Assert(IsDate("12-12-20") == true);
     Debug.Assert(IsDate("1/1/34") == true);
     Debug.Assert(IsDate("09/30/20") == false);
     Debug.Assert(IsDate(DateTime.Now) == true);
  }

  static Boolean IsDate(Object value)
  {
     DateTimeFormatInfo DateTimeFormatGB = new CultureInfo("en-GB").DateTimeFormat; // new CultureInfo("en-US").DateTimeFormat;
     return IsDate(value, DateTimeFormatGB);
  }

  static private List<String> AcceptableDateFormats = new List<String>(72);
  static Boolean IsDate(Object value, DateTimeFormatInfo formatInfo)
  {
     if (AcceptableDateFormats.Count == 0)
     {
        foreach (var dateFormat in new[] { "d", "dd" })
        {
           foreach (var monthFormat in new[] { "M", "MM", "MMM" })
           {
              foreach (var yearFormat in new[] { "yy", "yyyy" })
              {
                 foreach (var separator in new[] { "-", "/" }) // formatInfo.DateSeparator ?
                 {
                    String shortDateFormat;
                    shortDateFormat = dateFormat + separator + monthFormat + separator + yearFormat;
                    AcceptableDateFormats.Add(shortDateFormat);
                    AcceptableDateFormats.Add(shortDateFormat + " " + "HH:mm"); // formatInfo.TimeSeparator
                    AcceptableDateFormats.Add(shortDateFormat + " " + "HH:mm:ss");
                 }
              }
           }
        }
     }

     String sValue = value.ToString().Trim();
     DateTime unused;

     foreach (String format in AcceptableDateFormats)
     {
        if (DateTime.TryParseExact(sValue, format, formatInfo, DateTimeStyles.None, out unused) == true) return true;
     }

     return false;
  }
}

I didn't use the date/time separators from the culture information because I wanted to accept both a "/" and a "-". I guess I could have used the time one though, as that's unlikely to change (for me).

+6  A: 

Did you check out DateTime.TryParse()'s alternative override where it gives you a lot more control over what it considers to be a date?

Dave Markle
It doesn't give you much extra control.
Ross Watson
+1  A: 

Have you checked out trying DateTime.TryParse overload that accepts IFormatProvider and DateTimeStyles arguments? You may be able to use that to be more picky about what you accept as an actual date, while not unnecessarily throwing exception just to test the strings.

Andrew Barber
Yes, I have. I cannot find a way to prevent it from thinking that "3/4" or "6.12" is not a date (and I'm not throwing exceptions).
Ross Watson
+6  A: 

Consider using DateTime.TryParseExact

max
I thought I did. The issue being that I don't control the format of the data, but its a reasonable expectation that its a UK format date which might or might not have a time part.
Ross Watson
+2  A: 

To convert string to date You either specify a culture that uses that specific format : like we want to convert string date "dd/MM/yyyy" to Date..

datetime mydate = Convert.ToDateTime(
txtdate.Text, CultureInfo.GetCulture("en-GB")
);

or use the ParseExact method:

datetime mydate = DateTime.ParseExact(
txtdate.Text, "dd/MM/yyyy", CultureInfo.Invariant
);

The ParseExact method only accepts that specific format, while the Convert.ToDateTime method still allows some variations on the format, and also accepts some other date formats.

To catch illegal input, you can use the TryParseExact method:

DateTime d;
if (DateTime.TryParseExact(txtdate.Text, "dd/MM/yyyy", CultureInfo.Invariant, DateTimeStyles.None, out d)) {
datetime mydate = d;
} else {
// communcate the failure to the user
} 

I hope below links will provide you some help:

http://dotnetacademy.blogspot.com/2010/09/convert-string-to-date.html

http://msdn.microsoft.com/en-us/library/system.datetime.tryparse.aspx

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

http://dotnetacademy.blogspot.com/2009/10/get-current-system-date-format.html

This is a Example for tryParse : http://dotnetperls.com/datetime-tryparse

Rajesh Rolen- DotNet Developer
Smells like spam. You can promote your own stuff, but please include more real content than just a a link to your own blog.
Joel Coehoorn
@Joel: I have posted it on my blog many months back .. i have also provided links of MSDN... so you cant say like that
Rajesh Rolen- DotNet Developer
@Joel: Sorry, i was not know that to provide link of own blog for solution is not accepted on stackoverflow.
Rajesh Rolen- DotNet Developer
Linking to blogs is fine, and linking to your blog is fine. Linking to your blog when most of your answer is behind the link and your blog name looks like a company name smells like spam. Whether or not it really is spam doesn't matter- the smell is there. Your edited response is much nicer, thanks.
Joel Coehoorn
A: 

Alternatively you could have your own regular expression check after you found a potential date if you require a greater degree of control. something like this.

^(0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])[- /.](19|20)\d\d$

covers xx-yy-zz and xx/yy/zz as per your requirement

Terrance
The trouble is, my simple example checks for about 70 valid date formats. Granted a regex could catch more than one format, but you would still have a lot of regex strings. Plus, to most people, they look like line noise. :o)
Ross Watson
It seems that there's no simple answer in this case. Your searching for a greater degree of control but ease of use. This is more of a design decision here. Either consider limiting the number of accepted formats or combine a couple simple Regex checks for stuff like "3/4" or "6.12" and after use DateTime.TryParse once the string has passed the regex expressions.
Terrance
Oh and with regex, comments can go a long way
Terrance
A: 

Try

DateTime result;
DateTime.TryParseExact(value.ToString(), new string[] { "dd/MM/yyyy", "d/M/yyyy" }, null, DateTimeStyles.None, out result)
sh_kamalh
Hi, Thanks. I hadn't spotted I could pass an array of acceptable formats.
Ross Watson
A: 
using System.Globalization;

CultureInfo ukCI = CultureInfo.CreateSpecificCulture("en-GB");
Console.WriteLine(DateTime.Parse("1/2/2010", ukCI).ToString("dd-MMM-yyyy"));

You can use TryParse in place of Parse, if you want to validate that the argument is a date.

shahkalpesh
Thanks, but did you look at the example code ?
Ross Watson