Shell bash - validate input date

This has to have a simple answer somewhere but cannot seem to find it for FreeBSD specifically.

Given this input:
Code:
DT='2022.02.30'
I want to determine that this date is invalid. I first turned to the date utility:
Code:
echo $DT ; date -j -f '%Y.%m.%d' $DT ; echo $?
2022.02.30
Wed Mar  2 15:56:22 EST 2022
0

But date helpfully converts the invalid date into a valid one and exits with success. Is there some way to get date to simply exit with a non-zero status instead? Or, is there some other utility of which I am unaware will will provide the same functionality?
 
Unfortunately that behavior is baked into strptime(3). I came up with this solution in C

C:
#include <time.h>

int main(int argc, char *argv[])
{
    struct tm date = {0};
    int year, month, day;
    char *pres;
    time_t res;
    
    pres = strptime(argv[2], argv[1], &date);
    if(pres == NULL) return 1;
    year = date.tm_year;
    month = date.tm_mon;
    day = date.tm_mday;
    
    res = mktime(&date);
    if(res == -1) return 1;
    
    date = *(localtime_r(&res, &date));    
    if(date.tm_year != year || date.tm_mon != month ||
        date.tm_mday != day) return 1;
    
    return 0;
}

Compile and run like this
Code:
$ cc -o vdate vdate.c                                                                                                
$ ./vdate %Y.%m.%d 2022.02.22                                                                                        
$ echo $?                                                                                                            
0
$ ./vdate %Y.%m.%d 2022.02.30                                                                                        
$ echo $?                                                                                                          
1

Based on this https://codereview.stackexchange.com/a/231086
 
To be explicit, something like this:
Bash:
$ DT="2022.02.30"
$ [ "$(date -j -f '%Y.%m.%d' +%Y.%m.%d "$DT")" == "$DT" ] || echo "Invalid date"
Invalid date

Alternatively, install sysutils/coreutils and use gdate(1):

Bash:
$ gdate --date="${DT//./-}"
gdate: invalid date ‘2022-02-30’
$ echo $?
1

$ DT="2022.02.28"
$ gdate --date="${DT//./-}"
Mon Feb 28 00:00:00 CST 2022
$ echo $?
0
 
Thank you for the examples. I had to proceed without this guidance and ended up writing a more involved bash function. My requirements went beyond simply determining whether a date was itself invalid according to the calendar (the date could not be in the future, it could not be too far in the past, multiple delimiter characters had to be permitted, etc.) but that is where I started attacking the problem.

When I tested the bash one-liner as provided I got this:
Code:
$ DT="2022.05.31" ; [ "$(date -j -f '%Y.%m.%d' +%Y.%m.%d "$DT")" == "$DT" ] || echo "Invalid date"
$ DT="2022.05.32" ; [ "$(date -j -f '%Y.%m.%d' +%Y.%m.%d "$DT")" == "$DT" ] || echo "Invalid date"
Failed conversion of ``2022.05.32'' using format ``%Y.%m.%d''
date: illegal time format
usage: date [-jnRu] [-d dst] [-r seconds|file] [-t west] [-v[+|-]val[ymwdHMS]]
            [-I[date | hours | minutes | seconds]]
            [-f fmt date | [[[[[cc]yy]mm]dd]HH]MM[.ss]] [+format]
Invalid date
I do not know what the is on my system to cause this behaviour to differ from the example.
 
I can reproduce this on 13.1-RELEASE. It seems date(1) has (or, got?) some simple input validation. There's never a 32nd day in a month, so this is rejected. 2022.02.31 OTOH is still accepted (and normalized into a valid date).

Well, so you just have to additionally check for success running date(1) (and silence stderr).

Oh, please don't script in "bash". Shellscripts should only use standard POSIX/bourne shell syntax, so they're portable. Scripts using extended bash features are among the more common linuxisms (and pretty annoying).
 
Example (no need checking the exit status actually, it's implicit cause the result is empty):
Code:
#!/bin/sh

while read DT; do
    DTN="$(date -j -f '%Y.%m.%d' +%Y.%m.%d "$DT" 2>/dev/null)"
    if [ "$DT" = "$DTN" ]; then
        echo valid.
    else
        echo invalid.
    fi
done

Code:
$ ./checkdate.sh
2022.05.32
invalid.
2022.05.31
valid.
2022.02.31
invalid.
 
The script I am writing is not meant to be portable as it is application specific. In my specific case using Bash is fundamentally no different than writing something in Ruby or Perl to do the same thing; albeit the syntax differs. If I am writing a general case system utility then yes, POSIX compliance is a consideration.
 
Oh, please don't script in "bash". Shellscripts should only use standard POSIX/bourne shell syntax, so they're portable. Scripts using extended bash features are among the more common linuxisms (and pretty annoying).

In this case, just using sh won't make it portable -- the date command used here isn't portable to linux. (But that could also be handled in a few lines without too much difficulty.)

If writing it in bash makes it more understandable and maintainable, and you understand the potential limitations that come with it, then you shouldn't feel like it's a cardinal sin.

That said, if you aren't actually using any bashisms (like where I used bash to replace . with - in my gtime example, avoiding a call to sed) to make your code more understandable and maintainable, trying to target posix-compatibility isn't a bad idea.
 
Back
Top