Within a function a date string should be parsed.
function my_func {
DATE_DIFF=$(( ($(date -d $1 +%s) - $(date +%s)) / 86400))
echo $DATE_DIFF
...
}
Problem is that the given date is a user given entry and if something like this 2022-11-02,some-value is given (from CSV file) the date is invalid and date: invalid date is printed.
$ my_func 2022-11-02,some-value
date: invalid date ‘2022-11-02,some-value’
-19298
Somehow the variable DATE_DIFF is now -19298.
How can I detect an invalid date?
To return early, you need to act on the exit status of the date command:
my_func() {
local d1 d2
d1=$(date -d "$1" +%s) || return 1
d2=$(date -d "$2" +%s) || return 1
echo $((d1 - d2))
}
The local declaration and the variable assignment must be on separate lines, otherwise the exit status of local overrides the exit status of date.
Then:
$ diff=$(my_func "2022-01-02 09:53:00" "2022-01-02 09:51:30") && echo $diff || echo "could not get date diff"
90
$ diff=$(my_func "2022-01-02 09:53:00" "2022-01-02 09:51:xx") && echo $diff || echo "could not get date diff"
date: invalid date ‘2022-01-02 09:51:xx’
could not get date diff
Related
There are two functions : start и stop
they write the date and time to the file wrk.txt
This is what the contents of the file looks like after executing these commands:
START 10-08-2022 08:00:00
STOP 10-08-2022 08:06:33
function code:
#!/bin/bash
function start { echo 'START' $(date '+%d-%m-%Y %H:%M:%S') >> ~/Documents/wrk.txt
echo 'work started at:' $(date '+%d-%m-%Y %H:%M:%S') }
function stop { echo 'STOP' $(date '+%d-%m-%Y %H:%M:%S') >> ~/Documents/wrk.txt
echo 'work finished at:' $(date '+%d-%m-%Y %H:%M:%S')
}`
Question: how to take the last two lines from the file, remove the word START or STOP and calculate the time difference and save the result to a file
file data before calculations:
START 10-08-2022 08:00:00
STOP 10-08-2022 08:06:33
expected file data after calculations:
START 10-08-2022 08:00:00
STOP 10-08-2022 08:06:33
totalworktime: 00:06:33
The following may work for you:
#!/bin/bash
output_file="${1:-time_delta.out}"
df="%m-%d-%Y %H:%M:%S"
start=$(awk '{this=last; last=$0} END{print this}' wrk.txt)
sdate="${start/START /}"
end=$(awk '{this=last; last=$0} END{print last}' wrk.txt)
edate="${end/END /}"
printf "%s\n%s\n" "$start" "$end" >> "${output_file}"
echo $(( $(date -j -f "$df" "${edate}" +%s) \
- $(date -j -f "$df" "${sdate}" +%s) )) \
| awk '{printf("totalworktime: %02d:%02d:%02d\n", ($1/60/60),($1/60%60),($1%60))}' >> "${output_file}"
1) Set variable containing desired output file path/name either from first parameter passed to script or will default to ./time_delta.out. Set the date format expected from the wrk.txt contents.
output_file="${1:-time_delta.out}"
df="%m-%d-%Y %H:%M:%S"
2) Parse the second to the last line and the last line from wrk.txt and store the raw lines in variables start and end. Remove the leading START and END text from each variable using bash string replacement and store the results in sdate and edate variables.
start=$(awk '{this=last; last=$0} END{print this}' wrk.txt)
sdate="${start/START /}"
end=$(awk '{this=last; last=$0} END{print last}' wrk.txt)
edate="${end/END /}"
3) Add the raw $start AND $end variable contents to the $output_file
printf "%s\n%s\n" "$start" "$end" >> "${output_file}"
4) Convert the $sdate and $edate values to seconds from the epoch via date -j -f "$df" "${sdate}" +%s and date -j -f "$df" "${edate}" +%s. Subtract those values to get the delta and pass the result to awk for formatting as HH:MM:SS and append to ${output_file}
echo $(( $(date -j -f "$df" "${edate}" +%s) \
- $(date -j -f "$df" "${sdate}" +%s) )) \
| awk '{printf("totalworktime: %02d:%02d:%02d\n", ($1/60/60),($1/60%60),($1%60))}' >> "${output_file}"
Note, you might also consider replacing your start and stop functions with a single parameterized
function. At a minimum, store the date in a variable to avoid duplicate calls to date. Something
like the following:
add_time_log() {
local label="${1:-START}"
local msg="${2:-work started at}"
local log_file="${3:-time_log.txt}"
local now=$(date +"%m-%d-%Y %H:%M:%S")
printf "${msg}: ${now}\n"
printf "${label} ${now}\n" >> "${log_file}"
}
You can call the function as follows:
The folowing 2 are equivalent due to default parameter values
add_time_log
add_time_log START "work started at"
Adding END log
add_time_log END "work finished at"
Specifying all 3 parameters
add_time_log START "work started at" out.txt
add_time_log END "work finished at" out.txt
I would like to validate the format of the date input (in YYYY/MM/DD format) and only allow the user to enter current date or date after it.
For example, if the user enters 2022/03/28 (before current date), then error message should appear. If the user enters 2022/04/01 (current date) or 2022/04/06 (after current date), then it will not show error message.
May I know how can I do the validation?
echo "Enter date (in YYYY/MM/DD format): "
read date
It seems to me the code needs to perform two different validations:
did the user enter a valid date (eg, 2020/10/40 is not a valid date)
did the user enter a date that is earlier than today's date
For the 1st validation we can have date process the input as a date, eg:
$ input='2020/10/40' # invalid date
$ date -d "${input}"
date: invalid date ‘2020/10/40’
$ echo $?
1 # non-zero (false) return code
$ input='2020/10/20' # valid date
$ date -d "${input}"
Tue Oct 20 00:00:00 CDT 2020
$ echo $?
0 # zero (true) return code
For the 2nd validation we can let bash compare our dates as strings, eg:
$ input='2020/10/20'
$ today=$(date '+%Y/%m/%d')
$ typeset -p input today
declare -- input="2020/10/20"
declare -- today="2022/03/31"
$ [[ "${input}" < "${today}" ]] && echo 'error'
error
$ input='2022/03/31'
$ today=$(date '+%Y/%m/%d')
$ typeset -p input today
declare -- input="2022/03/31"
declare -- today="2022/03/31"
$ [[ "${input}" < "${today}" ]] && echo 'error'
# no output
$ input='2022/05/31'
$ today=$(date '+%Y/%m/%d')
$ typeset -p input today
declare -- input="2022/05/31"
declare -- today="2022/03/31"
$ [[ "${input}" < "${today}" ]] && echo 'error'
# no output
Pulling all of this into a demo:
today=$(date '+%Y/%m/%d')
while true
do
echo "Enter date (in YYYY/MM/DD format): "
read input
# validate input:
date -d "${input}" > /dev/null 2>&1
[[ $? != 0 ]] && \
echo "ERROR: invalid date [${input}]; please try again." && \
continue
# compare input with today:
[[ "${input}" < "${today}" ]] && \
echo "ERROR: input [${input}] is less than today [${today}]; try again." && \
continue
break
done
echo "Congrats! input [${input}] is greater-than/equal-to today [${today}]."
Testing:
Enter date (in YYYY/MM/DD format):
2020/10/40
ERROR: invalid date [2020/10/40]; please try again.
Enter date (in YYYY/MM/DD format):
2020/10/20
ERROR: input [2020/10/20] is less than today [2022/03/31]; try again.
Enter date (in YYYY/MM/DD format):
2022/03/20
ERROR: input [2022/03/20] is less than today [2022/03/31]; try again.
Enter date (in YYYY/MM/DD format):
2022/03/31
Congrats! input [2022/03/31] is greater-than/equal-to today [2022/03/31].
Enter date (in YYYY/MM/DD format):
2022/03/10
ERROR: input [2022/03/10] is less than today [2022/03/31]; try again.
Enter date (in YYYY/MM/DD format):
2022/05/31
Congrats! input [2022/05/31] is greater-than/equal-to today [2022/03/31].
How to validate the date in Unix.
Where the date format is DD-MM-YYYY.
For example if i run one script ./ValidateDate.sh DD-MM-YYYY then it should check that the date is in this format and also that the date exists.
If date is valid and in above format then proceed for next else print message 'Invalid date'
Thanks.
Well... This is a fine can o' worms.
Any shell script that you create may not work on all of the various flavors of Unix/Linux.
The BSD date command (found on OS X and Solaris) does a great job at taking in the date and verifying the format. It requires you to specify your date format, but once you do, it has no problems:
if date -j -f "%d-%m-%Y" "$DATE" 2>&1 > /dev/null # No output
then
echo "ERROR: Date '$DATE' is not a valid format."
exit 2
else
echo "Date is '$DATE'."
fi
Linux and other systems that use GNU's date can also validate the date, but use a different syntax:
date -d "$DATE" 2>&1 /dev/null # With a bit of luck this will work...
if $? -ne 0
then
echo "ERROR: Date '$DATE' is not a valid format."
else
echo "Date is '$DATE'."
fi
I say With a bit of luck because it's up to the date command to figure out your date's format. This normally works, and will work with your YYYY-MM-DD format, but it can be confusing:
Imagine this:
$ DATE="15/6/2014" # It's June 15, 2014
$ date -d "$DATE"
date: invalid date `15/6/2014' # Invalid?
That's because in my timezone, the dd/mm/yyyy format isn't a valid format. In my timezone, the date should be mm/dd/yyyy. To get around this you can use Wintermute's suggestion and format the date into ISO format before using GNU's date format.
A more universal possibility is to use Perl or Python to figure out if the date is correct:
if perl -mTime::Piece -e "Time::Piece->strptime(\"$DATE\", \"%Y-%m-%d\")" 2> /dev/null
then
echo "ERROR: Date '$DATE' is not a valid format."
else
echo "Date is '$DATE'."
fi
This Perl oneliner will return a non-zero error status if $DATE isn't a valid date in %Y-%m-%d format.
You can validate the date with the date utility, but you first have to transform the input into something it can understand. I suggest ISO representation (always). It could, for example, look like this:
#!/bin/sh
PATTERN='\([0-9]\{1,2\}\)-\([0-9]\{1,2\}\)-\([0-9]\+\)'
# if the pattern doesn't match, this will give date an empty string, so it will fail as expected.
if date -d $(echo "$1" | sed -n "/$PATTERN/ { s/$PATTERN/\3-\2-\1/; p }") > /dev/null 2>&1 ; then
echo valid
else
echo invalid
fi
awk way
awk 'split($0,a,"-"){print strftime("%d-%m-%Y",mktime(a[3]" "a[2]" "a[1]" 00 00 00"))==$0?"valid":"not valid"}' <<< "31-12-1992"
It Converts the string to epoch,converts epoch back then checks against the original.
Edit:
Thought i would add after testing this works for dates
FROM 01-01-0
TO 31-12-2147483647
Although a drawback is after you go below the year 1000 you have to remove leading zeros from the year.
You can do this fairly easily if you are willing to break the validation into two steps. First, check that the string is in the right format:
date=$1
[[ $date =~ ([0-9][0-9])-([0-9][0-9])-([0-9]+) ]] || { printf "Invalid date format\n"; exit 1; }
If that test passes, you can extract the day, month, and year fields, then verify that each falls in the correct range.
day=${BASH_REMATCH[1]}
month=${BASH_REMATCH[2]}
year=${BASH_REMATCH[3]}
thirty_one_days='0[1-9]|[12][0--9]|3[01]'
thirty_days='0[1-9]|[12][0--9]|30'
twenty_eight_days='0[1-9]|1[0-9]|2[0-8]'
case $month in
01|03|05|07|08|10|12)
[[ $day =~ $thirty_one_days ]] ;;
04|06|09|11)
[[ $day =~ $thirty_days ]] ;;
02)
# 1-28, but 29 OK in a leap year.
[[ $day =~ $twenty_eight_days ]] ||
(( year % 4 == 0 && $year % 400 == 0 && day == 29 ))
*)
false
esac || { print "Invalid date\n"; exit 1; }
This question already has an answer here:
Bash shell: How to check for specific date format?
(1 answer)
Closed 1 year ago.
I would like to test if a variable is a date (not a problem), but if the variable have the correct date format (yyyy-MM-dd).
I tried :
export DATE_REFRESH=01/01/1900
if ! date -d $DATE_REFRESH "+%Y-%m-%d"; then
echo "$DATE_REFRESH is not a valid date. Expected format is : yyyy-MM-dd"
fi
But it doesn't work.
I can try with this :
if [[ $DATE_REFRESH == [0-9][0-9][0-9][0-9]-[0-1][0-9]-[0-3][0-9] ]]
But I don't want to have a date to 33/19/2000...
You can use this snippet:
isValidDate() {
if [[ "$1" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]] && date -d "$1">/dev/null 2>&1; then
echo "valid"
else
echo "invalid"
fi;
}
Testing:
isValidDate "1900-12-25"
valid
isValidDate "1900-14-25"
invalid
isValidDate "01/01/1900"
invalid
You have to do both tests because the date string can be everything, see the manpage:
The --date=STRING is a mostly free format human readable date
string such as ...
Use that:
if ! [[ "$DATE_REFRESH" =~ ^[[:digit:]]{4}-[[:digit:]]{2}-[[:digit:]]{2}$ ]] \
|| ! date -d $DATE_REFRESH >/dev/null 2>&1; then
echo "$DATE_REFRESH is not a valid date. Expected format is : yyyy-MM-dd"
fi
I trying to print date between 2 dates using while loop in a bash script.
But when i execute i am getting below error:
test.sh: line 8: [: 02-12-14: integer expression expected
Below is my code, can anyone help me out
#!/bin/bash
sdate=02-12-14
edate=02-25-14
while [ "$sdate" -le "$edate" ]
do
echo $sdate
sdate=$(date +%m-%d-%y -d "$sdate + 1 day")
done
You should store them as timestamps:
#!/bin/bash
sdate=$(date -d '2014-02-12' +%s)
edate=$(date -d '2014-02-25' +%s)
while [[ sdate -le edate ]]; do
date -d "#$sdate" '+%m-%d-%y'
sdate=$(date -d "$(date -d "#${sdate}" ) + 1 day" +%s)
done
Output:
02-12-14
02-13-14
02-14-14
02-15-14
02-16-14
02-17-14
02-18-14
02-19-14
02-20-14
02-21-14
02-22-14
02-23-14
02-24-14
02-25-14
Always prefer [[ ]] over [ ] when it comes to conditional expressions in Bash. (( )) may also be a preference.
It requires GNU date. e.g. date --version = date (GNU coreutils) 8.21 ...
mm-dd-yy is not a format acceptable by date for input so I used yyyy-mm-dd which is acceptable.