28

たとえば、日時の範囲を作成するエレガントな方法を探しています。

def DateRange(start_time, end_time, period)
  ...
end

>> results = DateRange(DateTime.new(2013,10,10,12), DateTime.new(2013,10,10,14), :hourly)
>> puts results
2013-10-10:12:00:00
2013-10-10:13:00:00
2013-10-10:14:00:00

ステップは、時間ごと、日ごと、月ごとなど、構成可能である必要があります。

times包括的になりたい、つまり include end_time.

追加の要件は次のとおりです。

  • 元のタイムゾーンを保持する必要があります。つまり、ローカルのタイムゾーンと異なる場合でも維持する必要があります。
  • Rails などの適切な高度なメソッドを使用:advanceして、月の可変日数などを処理する必要があります。
  • 理想的にはパフォーマンスが優れていることですが、それは主要な要件ではありません。

エレガントなソリューションはありますか?

4

9 に答える 9

31

@CaptainPete のベースを使用して、ActiveSupport::DateTime#advance呼び出しを使用するように変更しました。`:month" や ":year" のように時間間隔が不均一な場合、違いが有効になります。

require 'active_support/all'
class RailsDateRange < Range
  # step is similar to DateTime#advance argument
  def every(step, &block)
    c_time = self.begin.to_datetime
    finish_time = self.end.to_datetime
    foo_compare = self.exclude_end? ? :< : :<=

    arr = []
    while c_time.send( foo_compare, finish_time) do 
      arr << c_time
      c_time = c_time.advance(step)
    end

    return arr
  end
end

# Convenience method
def RailsDateRange(range)
  RailsDateRange.new(range.begin, range.end, range.exclude_end?)
end

私のメソッドも を返しますArray。比較のために、@CaptainPete の回答も配列を返すように変更しました。

時間別

RailsDateRange((4.years.ago)..Time.now).every(years: 1)
=> [Tue, 13 Oct 2009 11:30:07 -0400,
 Wed, 13 Oct 2010 11:30:07 -0400,
 Thu, 13 Oct 2011 11:30:07 -0400,
 Sat, 13 Oct 2012 11:30:07 -0400,
 Sun, 13 Oct 2013 11:30:07 -0400]


DateRange((4.years.ago)..Time.now).every(1.year)
=> [2009-10-13 11:30:07 -0400,
 2010-10-13 17:30:07 -0400,
 2011-10-13 23:30:07 -0400,
 2012-10-13 05:30:07 -0400,
 2013-10-13 11:30:07 -0400]

月ごと

RailsDateRange((5.months.ago)..Time.now).every(months: 1)
=> [Mon, 13 May 2013 11:31:55 -0400,
 Thu, 13 Jun 2013 11:31:55 -0400,
 Sat, 13 Jul 2013 11:31:55 -0400,
 Tue, 13 Aug 2013 11:31:55 -0400,
 Fri, 13 Sep 2013 11:31:55 -0400,
 Sun, 13 Oct 2013 11:31:55 -0400]

DateRange((5.months.ago)..Time.now).every(1.month)
=> [2013-05-13 11:31:55 -0400,
 2013-06-12 11:31:55 -0400,
 2013-07-12 11:31:55 -0400,
 2013-08-11 11:31:55 -0400,
 2013-09-10 11:31:55 -0400,
 2013-10-10 11:31:55 -0400]

年別

RailsDateRange((4.years.ago)..Time.now).every(years: 1)

=> [Tue, 13 Oct 2009 11:30:07 -0400,
 Wed, 13 Oct 2010 11:30:07 -0400,
 Thu, 13 Oct 2011 11:30:07 -0400,
 Sat, 13 Oct 2012 11:30:07 -0400,
 Sun, 13 Oct 2013 11:30:07 -0400]

DateRange((4.years.ago)..Time.now).every(1.year)

=> [2009-10-13 11:30:07 -0400,
 2010-10-13 17:30:07 -0400,
 2011-10-13 23:30:07 -0400,
 2012-10-13 05:30:07 -0400,
 2013-10-13 11:30:07 -0400]
于 2013-10-13T15:32:58.180 に答える
15

継続時間のサポートを追加Range#step

module RangeWithStepTime
  def step(step_size = 1, &block)
    return to_enum(:step, step_size) unless block_given?

    # Defer to Range for steps other than durations on times
    return super unless step_size.kind_of? ActiveSupport::Duration

    # Advance through time using steps
    time = self.begin
    op = exclude_end? ? :< : :<=
    while time.send(op, self.end)
      yield time
      time = step_size.parts.inject(time) { |t, (type, number)| t.advance(type => number) }
    end

    self
  end
end

Range.prepend(RangeWithStepTime)

このアプローチにより、

  • タイムゾーン保持の暗黙的なサポート
  • 既存のRange#stepメソッドに期間サポートを追加します (サブクラスや の便利なメソッドは必要ありませんが、Objectそれでも楽しかったです)
  • のようなマルチパート期間をサポート1.hour + 3.secondsしますstep_size

これにより、既存の API を使用して Range に期間のサポートが追加されます。これにより、単純に「機能する」と予想されるスタイルで通常の範囲を使用できます。

# Now the question's invocation becomes even
# simpler and more flexible

step = 2.months + 4.days + 22.3.seconds
( Time.now .. 7.months.from_now ).step(step) do |time|
  puts "It's #{time} (#{time.to_f})"
end

# It's 2013-10-17 13:25:07 +1100 (1381976707.275407)
# It's 2013-12-21 13:25:29 +1100 (1387592729.575407)
# It's 2014-02-25 13:25:51 +1100 (1393295151.8754072)
# It's 2014-04-29 13:26:14 +1000 (1398741974.1754072)

以前のアプローチ

...クラス+ 「コンストラクター」#everyを使用してを追加し、内部で時間を整数に変換して、秒単位でステップスルーすることでした。これは、もともとタイムゾーンでは機能しませんでした。タイム ゾーンのサポートが追加されましたが、一部のステップ期間が動的であるという別の問題が見つかりました (例: )。DateRange < RangeDateRangeObjectstep1.month

Rubinius のRange実装を読むと、誰かがActiveSupport::Duration;のサポートを追加する方法が明らかになりました。そのため、アプローチが書き直されました。#advanceこれに関するヒントとデバッグを提供してくれた Dan Nguyen と、Range#step美しく書かれた Rubinius の の実装に感謝します :D

2015-08-14 更新

  • このパッチはRails/ActiveSupport にマージされませんでした。forを使用してループに固執する必要があります#advancecan't iterate from Timeまたはそのようなものを取得している場合は、このパッチを使用するか、使用を避けてRangeください。

  • Rails 4+prependスタイルを反映するようにパッチを更新しましたalias_method_chain

于 2013-09-30T12:38:13.200 に答える
4

1 時間は 1 日の 24 分の 1 です。

d1 = DateTime.now
d2 = d1 + 1
d1.step(d2, 1/24r){|d| p d}

1/24rFloat よりも正確な Rational です。

于 2019-02-22T21:54:19.600 に答える
3

n値を2つの日付に均等に分散させたい場合は、次のことができます

n = 8
beginning = Time.now - 1.day
ending = Time.now
diff = ending - beginning
result = []
(n).times.each do | x |
  result << (beginning + ((x*diff)/(n-1)).seconds)
end
result

を与える

2015-05-20 16:20:23 +0100
2015-05-20 19:46:05 +0100
2015-05-20 23:11:48 +0100
2015-05-21 02:37:31 +0100
2015-05-21 06:03:14 +0100
2015-05-21 09:28:57 +0100
2015-05-21 12:54:40 +0100
2015-05-21 16:20:23 +0100
于 2015-05-21T15:21:13.150 に答える
2

開始時間と終了時間で使用DateTime.parseして、配列を設定するために必要な反復をロックできます。例えば;

#Seconds
((DateTime.parse(@startdt) - DateTime.now) * 24 * 60 * 60).to_i.abs

#Minutes
((DateTime.parse(@startdt) - DateTime.now) * 24 * 60).to_i.abs

等々。これらの値を取得したら、必要な時間のスライスで配列を設定することをループできます。@fotanusに同意しますが、おそらくこれのために配列を具体化する必要はありませんが、そうすることであなたの目標が何であるかがわからないので、本当に言えません.

于 2013-09-30T12:15:26.157 に答える
1

Ruby 標準ライブラリには、これを実現するために使用できるdate組み込みメソッドがあります。step

require 'date'
array_of_datetimes = DateTime.parse("2021-09-01 13:00:00")
                            .step(DateTime.parse("2021-09-21 13:00:00"), (1.0/24))
                            .to_a

読みやすいように分解してみましょう

require 'date'

start_date = DateTime.parse("2021-09-01 13:00:00")
end_date = DateTime.parse("2021-09-21 13:00:00")

# Date#step expects a number representing a day. 
# Let's convert into an hour
period = 1.0/24 

array_of_datestimes = start_date.step(end_date, period).to_a

の出力puts array_of_datetime

....
2021-09-03T17:00:00+00:00
2021-09-03T18:00:00+00:00
2021-09-03T19:00:00+00:00
2021-09-03T20:00:00+00:00
2021-09-03T21:00:00+00:00
2021-09-03T22:00:00+00:00
....

PS 元の質問は2013年のものなので、 docker image を使用してこれをテストしましruby:2.0.0-slimた。Ruby 2.0.0 は 2012 年にリリースされ、問題なく動作します。

于 2021-09-23T22:56:26.210 に答える