-2

I have a very specific requirement in SQL Server 2012 where I need to match a given string to the specific date format of 'MM/dd/yyyy'. I do not need to CONVERT or CAST the string to a date, just a simple 1 or 0. Is the string in that format or not.

Example:

| ID |    Date    | Result |
|----|------------|--------|
| 1  | 05/31/2022 | 1      |
| 2  | 31/05/2022 | 1      |
| 3  | 2022/05/31 | 0      |
| 4  | 5/31/2022  | 0      |
| 5  | 5/31/22    | 0      |
| 6  | qwerty     | 0      |
| 7  | 5.31.2022  | 0      |
| 8  | 5-31-2022  | 0      |
| 9  | May/31/2022| 0      |

I had assumed a simple regex would work but I have not been able to find one that works in SQL. Currently I attempting to use '^[0-9]{2}/[0-9]{2}/[0-9]{4}' but this is failing for all values. I feel like I am missing something very simple but I cannot figure out what it is, and regex is not my strong suit as well.

Based on the comments i need to clarify something. The validation that is being done is 2 fold.

First, is the string a valid date, I have that covered no problem so I did not think to add that to this question originally.

Second, is the string in the appropriate format "2 digits + '/' + 2 digits + '/' + 4 digits". This second step is where I am requiring assistance.

Any guidance would be appreciated.

Jeff Martinez
  • 83
  • 1
  • 9
  • 1
    Perhaps [this answer](https://stackoverflow.com/questions/46324500/how-to-convert-custom-string-to-date-in-sql-server) might help – Peter Smith May 26 '22 at 18:25
  • Cleaarly did not read the post. I do not need to CONVERT the value to a date I do not even care if it is a valid date, only that it is in MM/dd/yyyy format. – Jeff Martinez May 26 '22 at 18:31
  • 2
    Why are you storing your date (and time) data in something that *isn't* a date and time data type? That is the real flaw here. – Thom A May 26 '22 at 18:33
  • While you may not need the converted date as a result, actually doing the conversion and checking the result could be at least part of the solution. SQL Server does not have regex support. Very (very) frequently the solution to a problem with checking dates is to actually convert to a DATE(TIME) format. – squillman May 26 '22 at 18:33
  • I do not control the data, I am dealing with an existing database with an existing set of business rules. perhaps the 'MM/dd/yyyy' is confusing the issue, all i need to know is if the value is 2 digits '/' 2 digits '/' 4 digits – Jeff Martinez May 26 '22 at 18:36
  • 2
    You can easily check if a value will convert to a date, with a specific style, with `TRY_CONVERT`. Checking if the value is is `[0-9][0-9]/[0-9][0-9]/[0-9][0-9][0-9][0-9]` would mean a value like `'74/13/5194'` is a valid "date". – Thom A May 26 '22 at 18:37
  • 2
    `all i need to know is if the value is 2 digits '/' 2 digits '/' 4 digits` - no, that's not all you need because `31/05/2022` in your requirements gets a result of `0`. This is where the conversion to proper date types comes into play. We're not telling you to store the converted values. We're saying convert them on the fly and check the conversion results to see if they match the format you require. Then return `1` or `0` based on that check. – squillman May 26 '22 at 18:40
  • That is all i need @squillman. I understand how to check if a date is valid. A simple ISDATE() will give me that. The problem is I need to return a 0 if the value is in the wrong format regardless if it is a valid or invalid date. – Jeff Martinez May 26 '22 at 18:44
  • Then you need to explain why `31/05/2022` yields a `0` and `05/31/2022` yields a `1` in your sample results. If your pattern matching / regex idea was correct these would both yield a `1`. – squillman May 26 '22 at 18:45
  • 1
    @squillman fair point that is mistake on my part and will edit the post – Jeff Martinez May 26 '22 at 18:48
  • As a side note, the reason your Regex is failing is because SQL Server does not support Regex; thus it wasn't Regex, but just a pattern. – Thom A May 26 '22 at 18:52
  • Ok, now that that is settled out - do you _truly_ just need `2dig/2dig/4dig` or do you need _actual valid dates_? How about `19/99/2021`? Should that return `1` or `0`? – squillman May 26 '22 at 18:58
  • Yes i truly just need 2d/2d/4d and @Larnu provided a pattern that seems to be working on inital testing. I modfied my question to hopefully be more clear as to the validations being done. I already know if it is a valida date - I just need to know it is the correct pattern – Jeff Martinez May 26 '22 at 19:05
  • I mean, ok, but `31/05/2022` I would argue really doesn't meet your requirement of `MM/dd/yyyy`. – squillman May 26 '22 at 19:12
  • @squillman In hindsight I wish I had not put MM/dd/yyyy on the question. At the time I posted it seemd the best way to express the pattern I was looking for. Most likely since I had been staring at acceptance criteria that was written with that wording for most of the day. Apologies for the confusion it caused – Jeff Martinez May 26 '22 at 19:18
  • Ok, I updated my answer based on that. It should give you what you want now. – squillman May 26 '22 at 19:21
  • Many people have been down this path of trying to validate strings as date when there is no control or consistency of format. Beware the use of the term "valid" and think carefully how you will use this information. "05/02/2022" can be either May 2 or Feb 5 - you have no way of knowing. Both are "valid" dates but only one represents "truth" for that particular row. – SMor May 26 '22 at 19:33
  • One option is to use `TRY_CONVERT` and check for null – Charlieface May 26 '22 at 23:26

3 Answers3

2

This will check for valid dates in the format MM/dd/yyyy or dd/MM/yyyy, which basically satisfies your 2dig/2dig/4dig requirement while still checking for valid dates:

DECLARE @t TABLE (Dt VARCHAR(100));
INSERT @t
VALUES ('05/31/2022'),
       ('31/05/2022'),
       ('2022/05/31'),
       ('5/31/2022'),
       ('5/31/22'),
       ('qwerty'),
       ('5.31.2022'),
       ('5-31-2022'),
       ('May/31/2022'),
       ('19/99/2022');

SELECT Dt,
       IIF((TRY_CONVERT(DATE, Dt, 101) IS NOT NULL OR TRY_CONVERT(DATE, Dt, 103) IS NOT NULL)
       AND Dt LIKE '[0-9][0-9]/[0-9][0-9]/[0-9][0-9][0-9][0-9]',
           1,
           0)
FROM @t;
squillman
  • 13,363
  • 3
  • 41
  • 60
  • when i run the query i get an error for try_Convert not being recognized, I checked the compatibility level on the server and it appears to be set at 100 – Jeff Martinez May 26 '22 at 19:32
  • I would recommend addressing that if you can. 100 is the compatability level for SQL Server 2008 R2. You'd do yourself a huge favor if you could get it to 110 which is SQL Server 2012. `TRY_CONVERT` is available in 110 and above. Even if you are not in control of the db I would seriously push for that. Maybe start with "Hey, you guys need to upgrade to a modern version of SQL Server" and settle for an update to the compatability level. – squillman May 26 '22 at 19:34
  • I can change the server I am working on but I cannot change the production servers it will eventually be deployed to. At least not w/o an Act of Congress to make it happen – Jeff Martinez May 26 '22 at 19:36
  • Ask them if they realize that SQL Server 2012 goes EOL (even past the extended end date) in less than 2 months. – squillman May 26 '22 at 19:38
  • I will have a chat with the DBA in charge. When i look at the database engine it appears to be a SQL Server 2019 build. There must be other legacy dBs I cannot view. Thanks – Jeff Martinez May 26 '22 at 19:44
1

I'm adding a second answer just to show what can also be added based purely on pattern matching. It tests the month and day values. It also allows for single or double digit month or day. I include both a basic 31 max day test on all months plus a test by month. The basic 31 day limit test is redundant if you want to test by month.

I don't test for a max 28 days for non-leap years. (It can be done, but logic is required to test the modulus when dividing the year by 4, 100, and 400.)

[EDIT] I attempted the leap year logic. Seems to work for a simple test.

This is not the original request, but it seems some might be interested in a more complete solution.

DECLARE @testdata as TABLE(ID int, Date varchar(20))

INSERT @testdata VALUES
(1, '05/31/2022'),
(2, '31/05/2022'),
(3, '2022/05/31'),
(4, '5/31/2022'),
(5, '5/31/22'),
(6, 'qwerty'),
(7, '5.31.2022'),
(8, '5-31-2022'),
(9, 'May/31/2022'),
(10, '1//2022'),
(11, '123/123/2022'),
(12, '123/123/123/2022'),
(13, '5/10/2022'),
(14, '2/30/2022'),
(15, '2/29/2000'),
(16, '2/29/2001'),
(17, '2/29/2004'),
(18, '2/29/2100')

SELECT *, 
    CASE WHEN PATINDEX('%[^0-9/]%', Date) = 0 -- only digits and /
        AND PATINDEX('%/%/%/%', Date) = 0 -- no more than 2 /
        AND PATINDEX('[0-9]%/[0-9]%/[0-9][0-9][0-9][0-9]', Date) > 0 -- 1 or more digit month and day with 4 digit year
        AND PARSENAME(REPLACE(Date, '/', '.'), 3) BETWEEN 1 AND 12 -- months are 1 to 12
        AND PARSENAME(REPLACE(Date, '/', '.'), 2) BETWEEN 1 AND 31 -- days are 1 to 31
        AND PARSENAME(REPLACE(Date, '/', '.'), 1) BETWEEN 1900 AND 3000 -- year limits?
        AND PARSENAME(REPLACE(Date, '/', '.'), 2) BETWEEN 1 AND ( -- max days by month ignoring leap years
            CASE PARSENAME(REPLACE(Date, '/', '.'), 3) WHEN 2 THEN 29 WHEN 4 THEN 30 WHEN 6 THEN 30 WHEN 9 THEN 30 WHEN 11 THEN 30 ELSE 31 END)
        AND (CASE WHEN PARSENAME(REPLACE(Date, '/', '.'), 3) <> 2 THEN 1 -- not feb, set to 1 to insure it passes
                WHEN PARSENAME(REPLACE(Date, '/', '.'), 1) % 4 = 0 
                    AND (PARSENAME(REPLACE(Date, '/', '.'), 1) % 100 <> 0 OR PARSENAME(REPLACE(Date, '/', '.'), 1) % 400 = 0) THEN 1 -- a leap year
                ELSE PARSENAME(REPLACE(Date, '/', '.'), 2) END -- use the feb day in the non-leap year
                ) <= 28 -- 28 is the max for non-leap years
        THEN 1
        ELSE 0 END as [Result]
FROM @testdata
ID          Date                 Result
----------- -------------------- -----------
1           05/31/2022           1
2           31/05/2022           0
3           2022/05/31           0
4           5/31/2022            1
5           5/31/22              0
6           qwerty               0
7           5.31.2022            0
8           5-31-2022            0
9           May/31/2022          0
10          1//2022              0
11          123/123/2022         0
12          123/123/123/2022     0
13          5/10/2022            1
14          2/30/2022            0
15          2/29/2000            1
16          2/29/2001            0
17          2/29/2004            1
18          2/29/2100            0
Randy in Marin
  • 1,108
  • 4
  • 9
0

With the updated question, it's pretty simple because there is one pattern. If more than one pattern was required, each would need to be tested.

DECLARE @testdata as TABLE(ID int, Date varchar(20), Result int)

INSERT @testdata VALUES
(1, '05/31/2022', 1),
(2, '31/05/2022', 1),
(3, '2022/05/31', 0),
(4, '5/31/2022', 0),
(5, '5/31/22', 0),
(6, 'qwerty', 0),
(7, '5.31.2022', 0),
(8, '5-31-2022', 0),
(9, 'May/31/2022', 0)

SELECT *, PATINDEX('[0-9][0-9]/[0-9][0-9]/[0-9][0-9][0-9][0-9]', Date) as [Calc] from @testdata
Randy in Marin
  • 1,108
  • 4
  • 9
  • How about something like `19/99/2021`? – squillman May 26 '22 at 18:57
  • @squillman Yes, that is valid according to the requirement. If value restrictions or single digits are allowed, then it takes more effort. Checking if it's a date is entirely different. I might consider this a preliminary "gut" check on an import of raw data. – Randy in Marin May 26 '22 at 19:51