25

I need to know if a given string is a valid DateTime format string because the string may represent other things. I tried DateTime.ParseExact(somedate.ToString(format), format) thinking it would barf on an invalid format, but it doesn't.

So I'm good with simply testing if the string contains only "yYmMdDsShH" characters. Something like std::string.find_first_not_of would work, but System.String doesn't have this.

I thought that RegEx might do the trick, but I'm very weak with regular expressions.

Note that Linq is not available for this one (.NET 2.0 only).

Updated

To clarify, I need to know if a given string represents a date time format and not something else like this:

if (input == "some special value")
... // it's a special case value
else if (Environment.GetEnvironmentVariable(input))
... // it's an environment variable name
else if (IsDateTimeFormatString(input))
... // it's a date time format string
else if (input.IndexOfAny(Path.GetInvalidPathChars()) < 0)
... // it's a file path
else
   throw new Exception(); // Not a valid input

I can restrict a DateTime format string to only "yYmMdDsShH", or I can add a few separator characters into it as well, it's up to me what to allow or not allow.

Tergiver
  • 14,171
  • 3
  • 41
  • 68
  • 1
    Are you looking for string characters (yYmM.. etc) or the numeric values that go there? (100720 (for today)? – AllenG Jul 20 '10 at 19:02

7 Answers7

45

With .NET2, you need to roll your own check for this. For example, the following method uses a foreach to check:

bool FormatValid(string format)
{
    string allowableLetters = "yYmMdDsShH";

    foreach(char c in format)
    {
         // This is using String.Contains for .NET 2 compat.,
         //   hence the requirement for ToString()
         if (!allowableLetters.Contains(c.ToString()))
              return false;
    }

    return true;
}

If you had the option of using .NET 3.5 and LINQ, you could use Enumerable.Contains to work with characters directly, and Enumerable.All. This would simplify the above to:

bool valid = format.All(c => "yYmMdDsShH".Contains(c));
Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373
  • LINQ doesn't exist in .NET 2.0 – MikeD Jul 20 '10 at 19:05
  • 2
    @MikeD: That's why my implementation doesn't use it ;) It's a pure .NET 2 implementation. I just mentioned that LINQ makes this easy... – Reed Copsey Jul 20 '10 at 19:17
  • @MikeD: It's using String.Contains, which DOES exist in .NET 2 (hence the "ToString" on the character: http://msdn.microsoft.com/en-us/library/dy85x1sa.aspx – Reed Copsey Jul 20 '10 at 19:17
  • @Reed: I thought of a Linq solution too, but unfortunetely the 2.0 requirement is not flexible. – Tergiver Jul 20 '10 at 19:24
  • @Tergiver: This is a .NET 2 solution - not a LINQ solution. This will work perfectly in .NET 2. (I'm going to edit the wording, since everyone's getting confused...) – Reed Copsey Jul 20 '10 at 19:25
  • @Tergiver: There - reworded it so it's more clear that I wasn't suggesting using LINQ (I was just pointing it out, so anyone without that restriction would know there's an easier way to go...) – Reed Copsey Jul 20 '10 at 19:28
  • @Reed: I understood Reed, thank you. I went with this solution only modified to avoid conversion of a char to a string (c.ToString()). – Tergiver Jul 20 '10 at 19:38
  • The various pains involved in doing things like this char-by-char over a string are the reason regexes were invented. – jwg Jul 11 '13 at 08:24
28

Like this:

static readonly Regex Validator = new Regex(@"^[yYmMdDsShH]+$");

public static bool IsValid(string str) {
    return Validator.IsMatch(str);
}

The regex works like this:

  • ^ matches the beginning of the string
  • [...] matches any of the characters that appear in the brackets
  • + matches one or more characters that match the previous item
  • $ matches the end of the string

Without the ^ and $ anchors, the regex will match any string that contains at least one valid character, because a regex can match any substring of the string use pass it. The ^ and $ anchors force it to match the entire string.

SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
4

I'd just do this:

public static class DateTimeFormatHelper
{
    // using a Dictionary<char, byte> instead of a HashSet<char>
    // since you said you're using .NET 2.0
    private static Dictionary<char, byte> _legalChars;

    static DateTimeFormatHelper()
    {
        _legalChars = new Dictionary<char, byte>();
        foreach (char legalChar in "yYmMdDsShH")
        {
            _legalChars.Add(legalChar, 0);
        }
    }

    public static bool IsPossibleDateTimeFormat(string format)
    {
        if (string.IsNullOrEmpty(format))
            return false; // or whatever makes sense to you

        foreach (char c in format)
        {
            if (!_legalChars.ContainsKey(c))
                return false;
        }

        return true;
    }
}

Of course, this might be an excessively strict definition, as it rules out what most people would consider valid formats such as "yyyy-MM-dd" (since that includes "-" characters).

Determining exactly what characters you wish to allow is your judgment call.

Dan Tao
  • 125,917
  • 54
  • 300
  • 447
  • @Reed: I just realized that as you were posting that comment -- updated to use a `Dictionary` instead. – Dan Tao Jul 20 '10 at 18:59
  • The brute force method occurred to me too. I was hoping maybe I missed something in System.String or a simple RegEx. Oh well, as long as it works right? – Tergiver Jul 20 '10 at 19:07
  • @Tergiver: Looks like a RegEx option has been supplied by SLaks. But often brute force wins out in terms of both performance and maintainability. At least in a case such as this one, where the logic is not particularly complex and the brute force approach doesn't really require that much code, I personally prefer it. Up to you, though. – Dan Tao Jul 20 '10 at 19:09
  • @DanTao I very much doubt that this outperforms the correct regex approach, and it's not clear to me why it's more maintainable. – jwg Sep 06 '14 at 20:35
4

Something like

Regex regex = new Regex("^(y|Y|m|M|d|D|s|S|h|H)+$");
if (regex.IsMatch('DateTime String'))
{
    // 'valid' 
}

if you're literally searching for those characters and not the numerical representation for a given date and time

Russ Cam
  • 124,184
  • 33
  • 204
  • 266
2

Thank you everyone. I 'upped' all of you and settled on a brute force implementation that doesn't use a Dictionary/HashSet and doesn't convert chars to strings:

private const string DateTimeFormatCharacters = "yYmMdDhHsS";
private static bool IsDateTimeFormatString(string input)
{
    foreach (char c in input)
        if (DateTimeFormatCharacters.IndexOf(c) < 0)
            return false;
    return true;
}
Tergiver
  • 14,171
  • 3
  • 41
  • 68
1

Slightly shorted Dan Tao's version since string represents an implementation of IEnumerable&lt&char>

   [TestClass]
   public class UnitTest1 {
      private HashSet<char> _legalChars = new HashSet<char>("yYmMdDsShH".ToCharArray());

      public bool IsPossibleDateTimeFormat(string format) {
         if (string.IsNullOrEmpty(format))
            return false; // or whatever makes sense to you
         return !format.Except(_legalChars).Any();
      }

      [TestMethod]
      public void TestMethod1() {
         bool result = IsPossibleDateTimeFormat("yydD");
         result = IsPossibleDateTimeFormat("abc");
      }
   }
Florian Reischl
  • 3,788
  • 1
  • 24
  • 19
  • One of the OP's requirements was not to use any LINQ extension methods (like `Except` and `Any`). – Dan Tao Jul 20 '10 at 19:07
0

There's a new project, NLib, which can do this much faster:

if (input.IndexOfNotAny(new char[] { 'y', 'm', 'd', 's', 'h' }, StringComparison.OrdinalIgnoreCase) < 0)
{
    // Valid
}
drifter
  • 611
  • 5
  • 17