Techioz Blog

Ruby (および必要に応じて Rails) で 2 つの日付の差を ISO8601 期間に変換します

概要

周期を 1 つだけ設定して、カップル (start_at、end_at) を ISO 8601 期間に変換したいと考えています。

例えば:

あいまいな場合、私は常に、より大きな期間が勝つことを望んでいます。

例えば:

それは非常に具体的であることはわかっていますが、おそらく誰かがどのように進めるかについてアイデアを持っていますか?これは人々がすでに解決した問題のように思えます。私がインターネットで見つけた唯一のリソースは、継続時間 (秒単位) を ISO 8601 に変換することでしたが、これは前述の一部の特殊なケース (たとえば 4 番目) では機能しません。

解決策

まず、2 つの日付間の (丸) 日の差を計算します。

d = (to_date - from_date).to_i

他のすべてが失敗した場合は、PdD としてこれに頼ることになります。

月や閏年に関係なく、週は常に 7 日です。 divmod を介して週の差を決定できます。

w, r = d.divmod(7)

余り r が 0 の場合、2 つの日付はちょうど w 週間離れている、つまり PwW です。

閏年と閏月では日数が異なるため、残りの 2 つのケースでは別のアプローチをとります。次のようにして、日付間のおおよその月数を計算します (30.436875 は、閏年を含む場合の 1 か月の平均日数です)

m = (d / 30.436875).round

次に、この値が完全に一致するかどうかを確認します。

from_date.next_month(m) == to_date

もしそうなら、PmMがあります。

私は何年もの間、同様のアプローチをとってきました。

y = m / 12

そして次のようにして確認してください:

from_date.next_year(y) == to_date

true の場合は PyY が返されます。

すべてを 1 つのメソッドで: (純粋な Ruby、Rails なし)

def duration(from_date, to_date)
  d = (to_date - from_date).to_i  # number of days
  m = (d / 30.436875).round       # number of months
  y = m / 12                      # number of years
  w, r = d.divmod(7)              # number of weeks

  return "P#{y}Y" if from_date.next_year(y) == to_date
  return "P#{m}M" if from_date.next_month(m) == to_date
  return "P#{w}W" if r == 0

  "P#{d}D"
end

例:

duration(Date.parse('2023/03/01'), Date.parse('2023/03/10')) #=> "P9D"
duration(Date.parse('2023/03/31'), Date.parse('2023/04/28')) #=> "P4W"
duration(Date.parse('2023/03/31'), Date.parse('2023/04/29')) #=> "P29D"
duration(Date.parse('2023/03/31'), Date.parse('2023/04/30')) #=> "P1M"
duration(Date.parse('2024/02/29'), Date.parse('2025/02/28')) #=> "P1Y"
duration(Date.parse('2024/02/29'), Date.parse('2024/03/29')) #=> "P1M"
duration(Date.parse('2023/01/31'), Date.parse('2023/02/28')) #=> "P1M"