简体   繁体   中英

Bash - number of hours in given day

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, eg 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.

Oct 30 was the last summer time change here in Britain. I can get 25 hours for that day from the shell in this way:

t1=$(TZ='Europe/London' date --date='20161030' +%s)
t2=$(TZ='Europe/London' date --date='20161031' +%s)
echo $((($t2 - $t1) / 3600))

I am not totally sure this will work in every bash shell and may need to be adjusted a little bit.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM