Using Bash shell on Linux, and given a datetime, how can I determine how many hours there are on that particular day?
The datetime pertains to some time zone with daylight saving, e.g. MET.
In order to completely account for all scenarios, you need to consider a few things:
Not every local day has a midnight, and the date
command will fail if you pass a date on one of these days, unless you also pass a time and an offset from UTC. This primarily occurs on the spring-forward transition days. For example:
$ TZ=America/Sao_Paulo date -d '2016-10-16'
date: invalid date '2016-10-16'
Not every DST transition is 1 hour. America/Lord_Howe
switches by 30 minutes. Bash only performs integer division, so you have to use one of these techniques if you want decimals.
Here is a function that accounts for these:
seconds_in_day() {
# Copy input date to local variable
date=$1
# Start with the offset at noon on the given date.
# Noon will almost always exist (except Samoa on 2011-12-30)
offset1=$(date -d "$date 12:00" +%z)
# Next get the offset for midnight. If it doesn't exist, the time will jump back to 23:00 and we'll get a different offset.
offset1=$(date -d "$date 00:00 $offset1" +%z)
# Next get the offset for the next day at midnight. Again, if it doesn't exist, it will jump back an hour.
offset2=$(date -d "$date 00:00 $offset1 + 1 day" +%z)
# Get the unix timestamps for both the current date and the next one, at midnight with their respective offsets.
unixtime1=$(date -d "$date 00:00 $offset1" +%s)
unixtime2=$(date -d "$date 00:00 $offset2 + 1 day" +%s)
# Calculate the difference in seconds and hours. Use awk for decimal math.
seconds=$((unixtime2 - unixtime1))
hours=$(awk -v seconds=$seconds 'BEGIN { print seconds / 3600 }')
# Print the output
echo "$date had $seconds secs in $TZ, or $hours hours."
}
Examples:
$ TZ=America/Los_Angeles seconds_in_day 2016-03-12
2016-03-12 had 86400 secs in America/Los_Angeles, or 24 hours.
$ TZ=America/Los_Angeles seconds_in_day 2016-03-13
2016-03-13 had 82800 secs in America/Los_Angeles, or 23 hours.
$ TZ=America/Los_Angeles seconds_in_day 2016-03-14
2016-03-14 had 86400 secs in America/Los_Angeles, or 24 hours.
$ TZ=America/Los_Angeles seconds_in_day 2016-11-05
2016-11-05 had 86400 secs in America/Los_Angeles, or 24 hours.
$ TZ=America/Los_Angeles seconds_in_day 2016-11-06
2016-11-06 had 90000 secs in America/Los_Angeles, or 25 hours.
$ TZ=America/Los_Angeles seconds_in_day 2016-11-07
2016-11-07 had 86400 secs in America/Los_Angeles, or 24 hours.
$ TZ=America/Sao_Paulo seconds_in_day 2016-02-19
2016-02-19 had 86400 secs in America/Sao_Paulo, or 24 hours.
$ TZ=America/Sao_Paulo seconds_in_day 2016-02-20
2016-02-20 had 90000 secs in America/Sao_Paulo, or 25 hours.
$ TZ=America/Sao_Paulo seconds_in_day 2016-02-21
2016-02-21 had 86400 secs in America/Sao_Paulo, or 24 hours.
$ TZ=America/Sao_Paulo seconds_in_day 2016-10-15
2016-10-15 had 86400 secs in America/Sao_Paulo, or 24 hours.
$ TZ=America/Sao_Paulo seconds_in_day 2016-10-16
2016-10-16 had 82800 secs in America/Sao_Paulo, or 23 hours.
$ TZ=America/Sao_Paulo seconds_in_day 2016-10-17
2016-10-17 had 86400 secs in America/Sao_Paulo, or 24 hours.
$ TZ=Australia/Lord_Howe seconds_in_day 2016-04-02
2016-04-02 had 86400 secs in Australia/Lord_Howe, or 24 hours.
$ TZ=Australia/Lord_Howe seconds_in_day 2016-04-03
2016-04-03 had 88200 secs in Australia/Lord_Howe, or 24.5 hours.
$ TZ=Australia/Lord_Howe seconds_in_day 2016-04-04
2016-04-04 had 86400 secs in Australia/Lord_Howe, or 24 hours.
$ TZ=Australia/Lord_Howe seconds_in_day 2016-10-01
2016-10-01 had 86400 secs in Australia/Lord_Howe, or 24 hours.
$ TZ=Australia/Lord_Howe seconds_in_day 2016-10-02
2016-10-02 had 84600 secs in Australia/Lord_Howe, or 23.5 hours.
$ TZ=Australia/Lord_Howe seconds_in_day 2016-10-03
2016-10-03 had 86400 secs in Australia/Lord_Howe, or 24 hours.