結果の日付ではなく、GNU ツールを使用して日付の期間を計算してフォーマットするにはどうすればよいですか?

結果の日付ではなく、GNU ツールを使用して日付の期間を計算してフォーマットするにはどうすればよいですか?

これまでに見つけた膨大な数の記事はすべて、結果の日付を取得することに焦点を当てているようでした。これは確かに便利ですが、この場合、私が求めているものではありません。

リンク例:Unix & Linux SE - 日付の差を素早く計算する

他の Q&Aではdatediff、これは日付/時刻の期間の結果を参照したいものであり、基本的には前述の変換で Unix タイムスタンプの計算を使用しますが、粒度を秒まで下げたいので、86400 で割らないのです。

秒単位の期間が利用可能になったので、次のように GNU コマンドを使用してそれをフォーマットしますdatedate -d "@69600" "+ %Y years %m months %e days %H:%M:%S"

すべてが本質的に Unix エポックからの日付/時刻の期間であることは理解していますが、日、月、年の数字が 0 から始まらないという問題が発生しており、必要な期間の値を正しく表すことができません。

このフォーマットをカットして、より多くのexprコマンドを適用して、各値から基本的に 1 または 1970 を減算することもできますが、数学とフォーマットの手順を連鎖させる以外に、日付/時刻の計算を処理して期間の結果を得るためのより簡単でシンプルな方法が他にあるかどうか疑問に思っています。おそらく、GNU でdate利用できる他のオプションがあるか、または 2 つの日付/時刻引数を受け入れて、探している結果をすぐに提供する別のツールがあるでしょう。

Mac 用の Homebrew で利用できるとさらに良いですね :)。

簡単な日付計算リンク:ウォーカーニュース - Linux シェル スクリプトでの日付演算

ランダムな日付書式設定リンク:

答え1

日付ユーティリティdatediff(GNU ではありません、申し訳ありません)、(以前はddiffDebiandateutils.ddiffでは )。

$ dateutils.ddiff -f '%Y years, %m months, %d days, %H:%0M:%0S' \
    '2012-01-23 15:23:01' '2017-06-01 09:24:00'
5 years, 4 months, 8 days, 18:00:59

(日付は UTC として扱われ、--from-zone=Europe/London対応するタイムゾーンのローカル時間として扱われる日付については のような記述を追加します。 は--from-zone=localtimeシステムのデフォルトのタイムゾーンで機能する可能性があります。が IANA タイムゾーン ファイルのパスを指定する--from-zone="${TZ#:}"限り機能します( など、POSIX スタイルの TZ 指定ではありません)。)$TZTZ=:Europe/LondonTZ=GMT0BST,M3.5.0/1:00:00,M10.5.0/2:00:00

ast-opendate申し訳ありませんが、GNU ツールではまだ使用できません (ksh93シェルとして使用する場合は、組み込みとして使用できる可能性があります) 。2 つの日付の差を単位付きの 2 つの数値で取得するには、次dateのように使用できます。date -E

$ date -E 1970-01-01 2017-06-01
47Y04M
$ date -E 2017-01-01 2017-06-01
4M29d
$ date -E 12:12:01 23:01:43
10h49m

上記の時間はあいまいです。夏時間 (23、24、または 25) のある地域では日数が異なるため、また月と年も日数が異なるため、上記の 2 つの単位よりも精度を上げることはあまり意味がないかもしれません。

たとえば、2015-01-01 00:00:00 から 2016-01-01 00:00:00 の間、または 2016-01-01 00:00:00 から 2017-01-01 00:00:00 の間にはちょうど 1 年ありますが、その期間は同じではありません (一方は 365*24 時間、もう一方は 366*24 時間)

2017-03-24 12:00:00から2017-03-25 12:00:00まで、または2017-03-25 12:00:00から2017-03-26 12:00:00までの間にはちょうど1日ありますが、これらがヨーロッパ諸国の現地時間(2017-03-26に夏時間に変更)である場合、ある日一方の場合は 24 時間、もう一方は 23 時間です。

言い換えると、年、月、週、または日数で表された期間は、それが適用される境界の 1 つ (およびタイムゾーン) に関連付けられている場合にのみ意味を持ちます。したがって、それが適用される時間 (開始時刻または終了時刻) がわからないと、秒数をそのような期間に変換することはできません (日 (24 時間)、月 (30*24 時間)、または年 (365*24 時間) のおおよその定義を使用する場合を除きます)。

ある程度、秒単位の期間も曖昧です。簡略化のため、Unix エポック タイムの秒は、地球の 1 日の 86400 分の 1 として定義されます1。地球の自転が遅くなるにつれて、これらの秒はどんどん長くなります。

つまり、次のようになります (GNU を使用date)。

d1='2016-01-01 00:00:00' d2='2017-01-01 00:00:00'
eval "$(date -d "@$(($(date -d "$d2" +%s) - $(date -d "$d1" +%s)))" +'
  delta="$((%-Y - 1970)) years, $((%-m - 1)) months, $((%-d - 1)) days, %T"')"
echo "$delta"

またはfish

date -d@(expr (date -d $d2 +%s) - (date -d $d1 +%s)) +'%Y %-m %-d %T' | \
  awk '{printf "%s years, %s months, %s days, %s\n", $1-1970, $2-1, $3-1, $4, $5}'

時間デルタは実際の開始日 2016 年ではなく 1970 年に適用されるため、通常は正しい結果は得られません。

たとえば、上記では次のようになります (ヨーロッパ/ロンドンのタイムゾーンの場合)。

1 years, 0 months, 1 days, 01:00:00

の代わりに

1 years, 0 months, 0 days, 00:00:00

(それでもあなたにとっては十分な近似値であるかもしれません)。


1技術的には、1 日は 86400 Unix 秒ですが、ネットワーク接続されたシステムは通常、SI 秒を使用して時計を原子時計に同期します。原子時計が 12:00:00.00 を示す場合、それらの Unix システムも 12:00:00.00 を示します。例外は、うるう秒が導入された場合のみです。うるう秒では、1 つの Unix 秒が 2 秒間続くか、または数秒が少し長く続き、その余分な秒がより長い期間に混ざります。

したがって、2 つの Unix タイムスタンプ (原子時計に同期されたシステムによって発行される) 間の正確な期間 (原子秒単位) を知ることができます。2 つのタイムスタンプ間の Unix エポック時間の差を取得し、その間に追加されたうるう秒数を加算します。

datediff-f %rSの数を取得するための本物2 つの日付間の秒数:

$ dateutils.ddiff -f %S '1990-01-01 00:00:00' '2010-01-01 00:00:00'
631152000
$ dateutils.ddiff -f %rS '1990-01-01 00:00:00' '2010-01-01 00:00:00'
631152009

違いは、1990 年から 2010 年の間に追加された 9 回のうるう秒によるものです。

さて、その数は本物秒数は日数を計算するのに役立ちません。日数は一定ではないからです。本物秒です。この 2 つの日付の間にはちょうど 20 年があり、この 9 回のうるう秒は、その値を取得するのに邪魔になります。また、 はddiff%rSの書式指定子と組み合わせない場合にのみ機能することに注意してください。また、将来のうるう秒はずっと前にはわかりませんが、最近のうるう秒に関する情報も利用できない場合があります。たとえば、私のシステムは 2012-07-01 以降のうるう秒を認識しません。

答え2

dateさて、その間の期間を計算したい 2 つの時点を定義する 2つの - 互換の文字列があるとします。

echo $(($(date +%s -d "3 days ago")-$(date +%s -d "4 weeks 2 hours 1 minutes 33 seconds ago"))) | sed 's/-//'

しかし、さらに読みやすくしたいとしましょう。

convertsecs() {
    hours=$(($1/3600))
    minutes=$(($1%3600/60))
    seconds=$(($1%60))
}
totalduration="$(($(date +%s -d "3 days ago")-$(date +%s -d "4 weeks 2 hours 1 minutes 33 seconds ago"))) | sed 's/-//'"
convertsecs "$totalduration"
echo "Duration was ${hours} hours, ${minutes} minutes, and ${seconds} seconds."

答え3

時間は複雑なものであり、計算する際には期待を管理することが重要です。うるう秒など、それ自体が完全に予測可能な基準で発生するわけではないものを考慮すると、特定の秒数が別の分数に対応する可能性があるため、一般的なケースでは、要求されていることを正確かつ厳密に行うことは技術的に不可能だと思います。

言い換えれば、1日は24時間であると言うが、1 日は正確に 24 時間なので、特定の期間が何日、何分、何秒だったかを知りたい場合は、その期間をリアルタイムのどこかに固定して、そこにうるう秒があったかどうか、あるいは期間のどこかに 4 月 29 日が存在したかどうかを知る必要があります。

さらに奇妙なことに、すべての月の日数は同じではありません。

それが問題にならない場合は、秒数を基本的な演算で計算するだけです。つまり、 を使用してdateエポックからの秒数に変換し、86400 で割って日数を取得し、余りを 3600 で割って時間を取得し、余りを分数、というように繰り返します。

関連情報