shell scripting time difference in variables - shell

How can i get the time difference in 2 variables in shell
say i have 4 variables-
t1=07:50:19:612
t2=07:52:14:697
t3=10:20:54:201
t4=11:02:09:716
and i want to find difference in times
result=(t2-t1)+(t4-t3)

If milliseconds can't be ignored, I suggest you to define own shell functions :
function getMillis()
{
val=($(echo $1|grep -Eo "(00|[1-9][0-9]*)"))
mil=$(( ${val[0]} * 3600000 ))
mil=$(($mil + ${val[1]}*60000))
mil=$(($mil + ${val[2]}*1000))
mil=$(($mil + ${val[3]}))
echo $mil
}
function format()
{
hr=$(( $1 / 3600000 ))
mn=$(( $1 % 3600000 / 60000 ))
sc=$(( $1 % 60000 / 1000 ))
ms=$(( $1 % 1000 ))
echo "$hr hours, $mn mins, $sc secs, $ms millisecs"
}
Then you can obtain the desired result as :
res=$(( $(getMillis $t2) - $(getMillis $t1) + $(getMillis $t4) - $(getMillis $t3) ))
format $res
The code above is just to show how this can be done. There may be other elegant solutions present.

As far as I know, shell do not support date format for millisecond. The command date can handle time format, of which the precision is rounded up to second.
The following is an example for time format with second as mininum time unit:
t1=07:50:19
t2=07:52:14
t3=10:20:54
t4=11:02:09
t10=$(date -d $t1 +%s)
t20=$(date -d $t2 +%s)
t30=$(date -d $t3 +%s)
t40=$(date -d $t4 +%s)
result=$(expr $t20 - $t10 + $t40 - $t30)
echo $result
hour=$(expr $result / 3600)
min=$(expr $result % 3600 / 60)
sec=$(expr $result % 60)
echo $hour:$min:$sec

Related

How to calculate current, previous and next year quarter in Shell Script?

I need to calculate the current, previous and next year quarter from the current sysdate in the below format.
As per current date (02-Nov-2020), current quarter should be returned as 20204, previous quarter as 20203, next quarter as 20211. It should be dynamic and should work for all the date/ month. I am trying with some approach like below but it doesn't seems to be working. Please help me with this. Thanks in advance.
curr_yrqtr=$(date +%Y)$(( ($(date +%-m)-1)/3+1 ))
prev_yrqtr=$(date -d "-1 month" +%Y)$(( ($(date -d "-1 month" +%-m)-1)/3+1 ))
Seeing how the question has several different flags (bash, ksh, perl), I'm guessing the OP is open to any solution that generates the desired results.
If the OP's version of date includes support for the %q (quarter) format:
read -r curr_year curr_qtr < <(date '+%Y %q')
If the OP's version of date does not support the %q (quarter) format:
read -r curr_year curr_month < <(date '+%Y %m')
curr_qtr=$((curr_month / 3 + 1))
From here the rest of the code is the same regardless of the version of date:
prev_year="${curr_year}"
prev_qtr=$((curr_qtr - 1))
next_year="${curr_year}"
next_qtr=$((curr_qtr + 1))
case "${curr_qtr}" in
1) prev_year=$((curr_year - 1)) ; prev_qtr=4 ;;
4) next_year=$((curr_year + 1)) ; next_qtr=1 ;;
esac
echo "previous quarter : ${prev_year}${prev_qtr}"
echo "current quarter : ${curr_year}${curr_qtr}"
echo "next quarter : ${next_year}${next_qtr}"
Running the above where 'today' == 2 Nov 2020:
previous quarter : 20203
current quarter : 20204
next quarter : 20211
Your question has many different tags, this is a Perl solution:
#!/usr/bin/env perl
use strict;
use warnings;
my #now = localtime( time );
my $quarter = 1 + ($now[4]+1) % 4;
my $year = $now[5] + 1900;
my #q_prev = $quarter == 1 ? ($year-1, 4) : ($year, $quarter-1);
my #q_next = $quarter == 4 ? ($year+1, 1) : ($year, $quarter+1);
printf "%d%d\n", #q_prev;
printf "%d%d\n", $year, $quarter;
printf "%d%d\n", #q_next;
Change the day to 01 and then - or + 3 months to get the correct year and month number.
curr_yrqtr=$(date +%Y)$(($(date +%-m)/3+1))
prev_yrqtr=$(date -d "$(date +%Y%m01) -3 months" +%Y)$(($(date -d "$(date +%Y%m01) -3 months" +%-m)/3+1))
next_yrqtr=$(date -d "$(date +%Y%m01) +3 months" +%Y)$(($(date -d "$(date +%Y%m01) +3 months" +%-m)/3+1))
Using portable shell script you can say:
yrmo=$(date '+%Y%m')
: $((yr = yrmo / 100)) $((mo = yrmo % 100))
# let p = quarter number less 1 (0..3)
: $(( p = (mo-1)/3 ))
printf '%d-%02d:\t%d%d\t%d%d\t%d%d\n' \
"$yr" "$mo" \
$(( yr-(p<1) )) $(( p<1 ? 4 : p+0 )) \
$(( yr )) $(( p+1 )) \
$(( yr+(p>2) )) $(( p>2 ? 1 : p+2 ))
which adjusts the year if in 1st or 4th quarter (p being 0 or 3)
and folds the quarter into range 1..4.
Output:
2020-11: 20203 20204 20211

Creating function to perform display-time arithmetic

Trying to create a function to get a number for hours(0-24) and a number for minute(0-60) and add 15 minutes to it. Issue is when I try to put a number 45 and up, I get a number over 60.
Created if then statement that takes my input and if the minutes are over 60, it will subtract 60 from minutes variable and add 1 to hour variable.
Expected results are that it will convert the time if over 60 minutes into 1 hour and remainder into minutes. The actual results are that sometimes the if then statement doesn't run correctly and it does both if AND then statements or sometimes I get a negative number.
#!/bin/bash
read -p "Hours: " hr1
read -p "Minutes: " min1
min1=$((min1+15))
Time() {
echo $min1
if $min1 > 60
then
min1=$((min1-60))
if $min1 < 0
then $((min1*-1))
fi
hr1=$((hr1+1))
else
$min1 + 15
fi
}
Time
echo $min1
echo $hr1
You can greatly simplify your Time() function by using shell arithmetic expressions delimited by (( and )). Here stand-alone arithmetic expressions are only valid with Bash shell but not posix shells where it is undefined
(( hr1 += min1 / 60, min1 %= 60 ))
Splits into two expressions separated by a comma ,:
hr1 += min1 / 60 ⇔ hr1 = hr1 + min1 / 60
min1 / 60 is evaluated in priority to the addition.
Shell arithmetic is integer only
min1 %= 60 ⇔ min1 = min1 % 60
The modulo reminder of min1 / 60
#!/usr/bin/env bash
read -r -p 'Hours: ' hr1
read -r -p 'Minutes: ' min1
# Bash stand-alone arithmetic expression add 15 to min1
(( min1+=15 ))
Time() {
echo "${min1}"
# Two Bash stand-alone arithmetic expressions separated by ,
# Add integer division of min1 by 60 to hr1
# Truncate min1 to the integer division reminder of itself by 60
(( hr1 += min1 / 60, min1 %= 60 ))
}
Time
echo "${min1}"
echo "${hr1}"
There are many, many ways to add minutes to a given time and then obtain the resulting hours and minutes. A systematic way is using the date function as Oliver Gaida shows. A manual conversion is fine for learning purposes, but you will want to ensure you deal with the addition of time that causes the hours to roll into the next day.
In order to handle all aspects of the conversion, it is useful to convert the total time to time into seconds. At that point, you can perform all necessary tests to determine if the total time has rolled the hours into the next day, etc..
Since you seem to only want to capture the hours and minutes of the resulting time and are not concerned with the number of days, you can simply test the number of seconds against the seconds-per-day and if the number of seconds exceeds seconds-per-day, simply reduce the number of seconds modulo by seconds-per-day.
A short function getHM() (your Time() function) to update the values in hr1 and min1 could be similar to the following:
## function converting number of seconds to hours, minutes (discarding days)
getHM() {
test -z "$1" && { ## validate input given
printf "error: insufficient arguments getHM()\n" >&2
return 1
}
local secs="$1" ## local variables seconds
local secsperday=$((3600 * 24)) ## seconds-per-day
local days=$((secs / secsperday)) ## days
(( days > 0 )) && { ## seconds exceed seconds-per-day
printf "error: time exceeds 24 hours, days discarded.\n" >&2
secs=$((secs % secsperday)) ## reduce secs modulo by secsperday
}
hr1=$((secs / 3600)) ## update hr1 & min1 values
min1=$(((secs - hr1 * 3600) / 60))
}
As noted in the function, it simply discards any additional days in order to return the resulting hours (0-23) and minutes (0-59).
Adding that to a short example and you could do:
#!/bin/bash
declare -i hr1=-1 min1=-1 addminutes=15 ## initialize variables
## function converting number of seconds to hours, minutes (discarding days)
getHM() {
test -z "$1" && { ## validate input given
printf "error: insufficient arguments getHM()\n" >&2
return 1
}
local secs="$1" ## local variables seconds
local secsperday=$((3600 * 24)) ## seconds-per-day
local days=$((secs / secsperday)) ## days
(( days > 0 )) && { ## seconds exceed seconds-per-day
printf "error: time exceeds 24 hours, days discarded.\n" >&2
secs=$((secs % secsperday)) ## reduce secs modulo by secsperday
}
hr1=$((secs / 3600)) ## update hr1 & min1 values
min1=$(((secs - hr1 * 3600) / 60))
}
## loop until valid input received
while ((hr1 < 0)) || ((hr1 > 23)) || ((min1 < 0)) || ((min1 > 59)); do
read -p "Hours: " hr1
read -p "Minutes: " min1
done
## convert all to seconds adding desired 15 minutes
secs=$((hr1 * 3600 + (min1 + addminutes) * 60))
getHM "$secs" ## call function to update hr1 & min1
printf "\nHours : %d\nMinutes : %d\n" "$hr1" "$min1"
(note: when using the arithmetic operator for comparison, e.g. ((...)), any non-integer values are evaluated as 0, so if you want to validate the use provides only integer input, you need to do that in the while loop after the read is complete -- and reset either variable to -1 if a non-integer value is detected)
Example Use/Output
No adjustment in hour required:
$ bash gethm.sh
Hours: 23
Minutes: 44
Hours : 23
Minutes : 59
Addition causing total time to land precisely at the start of a day:
$ bash gethm.sh
Hours: 23
Minutes: 45
error: time exceeds 24 hours, days discarded.
Hours : 0
Minutes : 0
(note: the error message provided if the total time causes the hours to roll into the next day. It is informational only and you can remove it to suit your needs)
Example showing the roll to 1 minute past the new day:
$ bash gethm.sh
Hours: 23
Minutes: 46
error: time exceeds 24 hours, days discarded.
Hours : 0
Minutes : 1
Look things over and let me know if you have further questions
With date it is easy. Convert the date to epoche-seconds, add 900 seconds and convert it back.
date --date="#$(echo $(($(date --date="22:53" +"%s")+900)))" +"%H:%M"
23:08

Subtraction in bash

printf "$(( $(date '+%H * 60 + %M') ))\n"
date '+%H * 60 + %M' | bc
date '+%H 60 * %M + p' | dc
The above will give the minutes that have passed in the day.
Using any of the above time outputs, how do I subtract it from the total minutes in the day (i.e., 1440) to display the minutes left in the day?
Given your three exemplars, what about:
printf "$(( 1440 - ( $(date '+%H * 60 + %M') ) ))\n"
date '+1440 - ( %H * 60 + %M )' | bc
date '+1440 %H 60 * %M + - p' | dc
You don't quite need all the spaces added, but you do need the added parentheses.
The following computes the difference of timestamps in minutes:
printf '%d\n' $(( ( $(date -d 'tomorrow 00:00' '+%s') - $(date '+%s') ) / 60 ))
Note the use of format string %d\n. You shouldn't pass your data in the first argument for printf, as the first argument is the format string, and printf may interpret some sequences as the format specifiers (%d as integer specifier, %s as string specifier, etc.).
You could use something like this:
minutesElapsed="$(date '+%H * 60 + %M' | bc)"
minutesDay="1440"
minutesLeft="$(($minutesDay-$minutesElapsed))"
Output:
echo "$minutesLeft"
332
Short version:
echo "$((1440-$(date '+%H * 60 + %M' | bc)))"

Bash convert two Epoch times to remaining time

I'm trying to convert two Epoch (Unix timestamp) dates to human readable date (time left)
AT=$(echo $RES | jq '.results[0].unixtime' | tr -d '"') # Returns unix time in JST.
NOW=$(TZ=":Asia/Tokyo" date +%s) # Returns current time in JST
DIFF=$(echo $AT-$NOW | bc)
As an example;
AT=1470038400
NOW=1470032871
DIFF=5529
How do I get the remaining time between the two in the following format: DAYd HOURh MINm SECs?
You can try the following
printf "%dd %dh %dm %ds\n" $(( DIFF / (3600 * 24) )) $(( (DIFF / 3600 ) % 24)) $(( (DIFF / 60) % 60)) $((DIFF % 60))

calculating days and displaying them as year, months, days between two dates shell script

I am trying to write a shell script which is going to determine the difference in years, months and days between the present date and Easter from a user input year. For example the user inputs 1995 and the script should calculate how many years have passed since then and to convert these days into years, months and days and display the results.
I'm pasting all of my code
#!/bin/bash
echo "This script will show you on which day is Easter for the chosen year of the Gregorian calendar!"
x=0
read x
A=$((x % 19))
B=$((x / 100))
C=$((x % 100))
D=$((B / 4))
E=$((B % 4))
G=$(((8 * B + 13) / (25)))
H=$(((19 * A + B - D - G + 15) % (30)))
M=$(((A + 11 * H) / (319)))
J=$((C / 4))
K=$((C % 4))
L=$(((2 * E + 2 * J - K - H + M + 32) % (7)))
N=$(((H - M + L + 90) / (25)))
P=$(((H - M + L + N + 19) % (32)))
Z=$(date --date="$x-$N-$P" +%A)
echo
echo "Easter is `date --date="$x-$N-$P"`"
([ "`cal 2 $x | grep 29`" != "" ] && echo -e "\n$x is a leap year\n")
([ "`cal 2 $x | grep 29`" = "" ] && echo -e "\n$x is not a leap year\n")
yearnow=$(date +%Y)
year=$(($x - $yearnow))
year1=$(($yearnow - $x))
if (($x > $yearnow))
then
echo "There are $year years until Easter in $x."
else
echo "$year1 years have passed since Easter in $x."
fi
pmonths=0
if (($x > $yearnow))
then
pmonths=$(($year * 12))
echo "There are $pmonths months until Easter."
else
pmonths=$(($year1 * 12))
echo "$pmonths months have passed since Easter in $x."
fi
#checking and counting how many leap and normal years are there between the present year and the chosen one
counter=1
leapycounter=0
nycounter=0
if (($x > $yearnow))
then
while (($counter < $year))
do
leapy=$(($x + $counter))
if (($leapy == (($leapy / 4) - ($leapy / 100) + ($leapy / 400))))
then leapycounter=$(($leapycounter + 1))
else nycounter=$(($nycounter + 1))
fi
counter=$(($counter + 1))
done
fi
#checking if the present year is leap so the days left from this year can be calculated
if (($x > $yearnow))
then
datenow=$(date +%j)
datenow=`echo $datenow|sed 's/^0*//'`
if (($yearnow == (($yearnow / 4) - ($yearnow / 100) + ($yearnow / 400))))
then
datenow=$((366 - $datenow))
else
datenow=$((365 - $datenow))
fi
datethen=$(date --date="$x-$N-$P" +%j)
datethen=`echo $datethen|sed 's/^0*//'`
days=$(($datethen + $datenow))
lyc=$((($leapycounter * 366) + ($nycounter * 365)))
dayspassed=$(($lyc + $days))
echo "There are $dayspassed days until Easter."
else
datethen=$(date --date="$x-$N-$P" +%j)
datethen=`echo $datethen|sed 's/^0*//'`
if (($yearnow == (($yearnow / 4) - ($yearnow / 100) + ($yearnow / 400))))
then
datethen=$((366 - $datethen))
else
datethen=$((365 - $datethen))
fi
datenow=$(date +%j)
datenow=`echo $datenow|sed 's/^0*//'`
days=$(($datethen + $datenow))
lyc=$((($leapycounter * 366) + ($nycounter * 365)))
dayspassed=$(($lyc + $days))
echo "$dayspassed days have passed since Easter in $x."
fi
#this should be converting the days into years, months and days
dtomconst=$(((((365/12)*3)+(366/12))/4))
months=$(($dayspassed / $dtomconst))
monthsleft=$(($months % 12))
years=$(($months / 12))
daysleft=$((($dayspassed - ($monthsleft * $dtomconst)) - (365*$years)))
echo "months are $months"
echo "daysleft are $daysleft"
echo $years
months=$(($months + $monthsleft))
echo $monthsleft
echo "months after calculations: $months"
So the problem is that it doesn't calculate the days properly especially for past years. Also if the user inputs a year like 1888 the script displays a mistake and I don't know why.
If somebody can say a word or two about my problem I would be really grateful. Thank you in advance.
As pointed out in the comments, the challenge with the script will be determining the day on which Easter occurred for a given year as the date varies from year to year given the order of weeks within each year. Further complicating the difference calculate is the concept of month as leap-year varies the length of February. There is also the occasional leap-second thrown in for good measure.
However, as indicated in the comment, once you have arrived at Easter for a given year, you can let the date function do most of the remaining work for you. Given any date, you can pass that value to the date function and covert the value into seconds since epoch. Note, this doesn't directly help for years prior to 1970, but you can extend further back manipulating the number of seconds per year as a close approximation.
Once you have Easter, getting the current time, expressed in terms of seconds since epoch, is trivial with date. You then have a difference to work with, converting the number of seconds that represent the time from the chosen Easter and now that can then be expressed in terms of years, days, hours, minutes, seconds. Granted these will have to be augmented to account for leap effects depending on the level of exactness required.
The following is a simple example of approaching the time difference problem. The function included, provides the difference given the time in seconds and then declares (as needed) the years, days, hours, minutes and seconds represented by the time given as an argument. This doesn't solve all of your issues, but hopefully it will help as a framework for handling that information in an easier manner. Let me know if you have any questions about the content:
#!/bin/bash
## time difference function takes an argument in seconds, and then calculates,
# declares and fills variables 'years' 'days' 'hours' 'minutes' and 'seconds'
# representing the time in seconds given as the argument. The values are only
# declared as necessary allowing a test for their presence.
function sec2ydhms {
[ -n $1 ] || { printf "%s() error: insufficient arguments\n" "$FUNCNAME"; return 1; }
local secperday=$((24 * 3600))
local secperyr=$((365 * secperday))
local remain=$1
# printf "\nremain: %s\n\n" "$remain"
if [ "$remain" -ge "$secperyr" ]; then
declare -g years=$((remain/secperyr))
remain=$((remain - (years * secperyr)))
fi
if [ "$remain" -ge "$secperday" ]; then
declare -g days=$((remain/secperday))
remain=$((remain - (days * secperday)))
fi
if [ "$remain" -ge 3600 ]; then
declare -g hours=$((remain/3600))
remain=$((remain - (hours * 3600)))
fi
if [ "$remain" -ge 60 ]; then
declare -g minutes=$((remain/60))
fi
declare -g seconds=$((remain - (minutes * 60)))
}
oifs=$IFS # save old IFS, and set to only break on newline
IFS=$'\n' # allowing date formats containing whitespace
printf "\n Enter the date for Easter (in past): "
read edate # read date entered
eepoch=$(date -d "$edate" +%s) # convert Easter date to seconds since epoch
now=$(date +%s) # get current time since epoch
sec2ydhms $((now-eepoch)) # compute time from Easter in Y,D,H,M,S
## print time since Easter
printf "\n Time since %s:\n\n" "$(date -d #"${eepoch}")"
[ -n "$years" ] && printf " %4s years\n" "$years"
[ -n "$days" ] && printf " %4s days\n" "$days"
[ -n "$hours" ] && printf " %4s hours\n" "$hours"
[ -n "$minutes" ] && printf " %4s minutes\n" "$minutes"
[ -n "$seconds" ] && printf " %4s seconds\n\n" "$seconds"
exit 0
output:
$ bash easter.sh
Enter the date for Easter (in past): 03/21/1985
Time since Thu Mar 21 00:00:00 CST 1985:
29 years
254 days
21 hours
12 minutes
16 seconds

Resources