How Do Get a Random Datetime Rounded to Beginning of Hour in Rails

How do get a random DateTime rounded to beginning of hour in Rails?

I finally found what I was looking for. @Stoic's answer is very good but I found this available method (http://api.rubyonrails.org/classes/DateTime.html):

rand(1.year).ago.beginning_of_hour

Does exactly the same thing but looks neater and prevents you from having to write your own function.

Best way to create random DateTime in Rails

Here are set of methods for generating a random integer, amount, time/datetime within a range.

def rand_int(from, to)
rand_in_range(from, to).to_i
end

def rand_price(from, to)
rand_in_range(from, to).round(2)
end

def rand_time(from, to=Time.now)
Time.at(rand_in_range(from.to_f, to.to_f))
end

def rand_in_range(from, to)
rand * (to - from) + from
end

Now you can make the following calls.

rand_int(60, 75)
# => 61

rand_price(10, 100)
# => 43.84

rand_time(2.days.ago)
# => Mon Mar 08 21:11:56 -0800 2010

Ruby - how to generate random time intervals matching a total amount of hours?

I assume the question is loosely-worded in the sense that "random" is not meant in a probability sense; that is, the intent is not to select a set of intervals (that total a given number of hours in length) with a mechanism that ensures all possible sets of such intervals have an equal likelihood of being selected. Rather, I understand that a set of intervals is to be chosen (e.g., for testing purposes) in a way that incorporates elements of randomness.

I have assumed the intervals are to be non-overlapping and the number of intervals is to be specified. I don't understand what "with ideally weekdays" means so I have disregarded that.


The heart of the approach I will propose is the following method.

def rnd_lengths(tot_secs, target_nbr)      
max_secs = 2 * tot_secs/target_nbr - 1
arr = []
loop do
break(arr) if tot_secs.zero?
l = [(0.5 + max_secs * rand).round, tot_secs].min
arr << l
tot_secs -= l
end
end

The method generates an array of integers (lengths of intervals), measured in seconds, ideally having target_nbr elements. tot_secs is the required combined length of the "random" intervals (e.g., 150*3600).

Each element of the array is drawn randomly drawn from a uniform distribution that ranges from zero to max_secs (to be computed). This is done sequentially until tot_secs is reached. Should the last random value cause the total to exceed tot_secs it is reduced to make the total equal tot_secs.`

Suppose tot_secs equals 100 and we wish to generate 4 random intervals (target_nbr = 4). That means the average length of the intervals would be 25. As we are using a uniform distribution having an average of (1 + max_secs)/2, we may derive the value of max_secs from the expression

target_nbr * (1 + max_secs)/2 = tot_secs

which is

max_secs = 2 * tot_secs/target_nbr - 1

the first line of the method. For the example I mentioned, this would be

max_secs = 2 * 100/4 - 1
#=> 49

Let's try it.

rnd_lengths(100, 4)
#=> [49, 36, 15]

As you see the array that is returned sums to 100, as required, but it contains only 3 elements. That's why I named the argument target_nbr, as there is no assurance the array returned will have that number of elements. What to do? Try again!

rnd_lengths(100, 4)
#=> [14, 17, 26, 37, 6]

Still not 4 elements, so keep trying:

rnd_lengths(100, 4)
#=> [11, 37, 39, 13]

Success! It may take a few tries to get the correct number of elements, but for parameters likely to be used, and the nature of the probability distribution employed, I wouldn't expect that to be a problem.

Let's put this in a method.

def rdm_intervals(tot_secs, nbr_intervals)
loop do
arr = rnd_lengths(tot_secs, nbr_intervals)
break(arr) if arr.size == nbr_intervals
end
end

intervals = rdm_intervals(100, 4)
#=> [29, 26, 7, 38]

We can compute random gaps between intervals in the same way. Suppose the intervals fall within a range of 175 seconds (the number of seconds between the start time and end time). Then:

gaps = rdm_intervals(175-100, 5)
#=> [26, 5, 19, 4, 21]

As seen, the gaps sum to 75, as required. We can disregard the last element.


We can now form the intervals. The first interval begins at 26 seconds and ends at 26+29 #=> 55 seconds. The second interval begins at 55+5 #=> 60 seconds and ends at 60+26 #=> 86 seconds, and so on. We therefore find the intervals (each in ranges of seconds from zero) to be:

[26..55, 60..86, 105..112, 116..154]

Note that 175 - 154 = 21, the last element of gaps.


If one is uncomfortable with the fact that the last elements of intervals and gaps that are generally constrained in size one could of course randomly reposition those elements within their respective arrays.

One might not care if the number of intervals is exactly target_nbr. It would be simpler and faster to just use the first array of interval lengths produced. That's fine, but we still need the above methods to compute the random gaps, as their number must equal the number of intervals plus one:

gaps = rdm_intervals(175-100, intervals.size + 1)

We can now use these two methods to construct a method that will return the desired result. The argument tot_secs of this method equals total number of seconds spanned by the array intervals returned (e.g., 3600 * 150). The method returns an array containing nbr_intervals non-overlapping ranges of Time objects that fall between the given start and end dates.

require 'date'
def construct_intervals(start_date_str, end_date_str, tot_secs, nbr_intervals)
start_time = Date.strptime(start_date_str, '%Y-%m-%d').to_time
secs_in_period = Date.strptime(end_date_str, '%Y-%m-%d').to_time - start_time
intervals = rdm_intervals(tot_secs, nbr_intervals)
gaps = rdm_intervals(secs_in_period - tot_secs, nbr_intervals+1)
nbr_intervals.times.with_object([]) do |_,arr|
start_time += gaps.shift
end_time = start_time + intervals.shift
arr << (start_time..end_time)
start_time = end_time
end
end

See Date::strptime.


Let's try an example.

start_date_str = '2020-01-01'
end_date_str = '2020-01-31'
tot_secs = 3600*150
#=> 540000
construct_intervals(start_date_str, end_date_str, tot_secs, 4)
#=> [2020-01-06 18:05:04 -0800..2020-01-09 03:48:00 -0800,
# 2020-01-09 06:44:16 -0800..2020-01-11 23:33:44 -0800,
# 2020-01-20 20:30:21 -0800..2020-01-21 17:27:44 -0800,
# 2020-01-27 19:08:38 -0800..2020-01-28 01:38:51 -0800]
construct_intervals(start_date_str, end_date_str, tot_secs, 8)
#=> [2020-01-03 18:43:36 -0800..2020-01-04 10:49:14 -0800,
# 2020-01-08 07:55:44 -0800..2020-01-08 08:17:18 -0800,
# 2020-01-11 00:54:36 -0800..2020-01-11 23:00:53 -0800,
# 2020-01-14 05:20:14 -0800..2020-01-14 22:48:45 -0800,
# 2020-01-16 18:28:28 -0800..2020-01-17 22:50:24 -0800,
# 2020-01-22 02:59:31 -0800..2020-01-22 22:33:08 -0800,
# 2020-01-23 00:36:59 -0800..2020-01-24 12:15:37 -0800,
# 2020-01-29 11:22:21 -0800..2020-01-29 21:46:10 -0800]

See Date::strptime

Iterating between two DateTimes, with a one hour step

Similar to my answer in "How do I return an array of days and hours from a range?", the trick is to use to_i to work with seconds since the epoch:

('2013-01-01'.to_datetime.to_i .. '2013-02-01'.to_datetime.to_i).step(1.hour) do |date|
puts Time.at(date)
end

Note that Time.at() converts using your local time zone, so you may want to specify UTC by using Time.at(date).utc

Round to the nearest half an hour or hour in ruby fake time?

You can extend the time class with this method
I normally do this in the lib/core_ext directory

# lib/core_ext/time.rb
class Time
def round_off(seconds = 60)
Time.at((self.to_f / seconds).round * seconds)
end
end

now you can do something like

time = Time.zone.now - rand(3).days - rand(2).hours + rand(60).minutes
time.round_off(30.minutes)

I hope that this is able to help you

Number of hours between two dates - Ruby

This is because DateTime.tomorrow does not have any time value. Here:

DateTime.tomorrow
# => Wed, 22 Apr 2015

If you go through official document for DateTime you can see there is no method tomorrow. Its basically Date#tomorrow.

You can use .to_time to get default localtime 00:00:00

DateTime.tomorrow.to_time
# => 2015-04-22 00:00:00 +0530

(DateTime.tomorrow.to_time - DateTime.now) / 1.hours
# => 9.008116581638655

To get exact hour difference between dates:

(DateTime.tomorrow.to_time - Date.today.to_time) / 1.hours 
# => 24.0

Ruby summing duration of datetime

I think you're trying to sum the total differences between start and end times right? If so you should be able to just do:

<%= current_user.experiences
.pluck(:period_start, :period_end)
.map{|d| (d[0].to_f - d[1].to_f).abs}.sum %>

Ruby(/Rails) dates - bi-weekly and quarterly DateTime ranges

What about this:

This bi-week:

def bi_week_limits_for(date)
days_in_month = Time.days_in_month(date.month, date.year)
# this will round down. 30/31-day months will output 15. 28 / 29-day will output 14. Adjust as per your requirements
middle_day = days_in_month / 2
if date.day <= middle_day
[date.beginning_of_month, date.change(day: middle_day)]
else
[date.change(day: middle_day + 1), date.end_of_month]
end
end

In my console:

pry(main)> bi_week_limits_for Date.parse('29/2/2020')
=> [Sat, 15 Feb 2020, Sat, 29 Feb 2020]
pry(main)> bi_week_limits_for Date.parse('7/5/2018')
=> [Tue, 01 May 2018, Tue, 15 May 2018]

This quarter:

def bi_week_limits_for(date)
[date.beginning_of_quarter, date.end_of_quarter]
end

In my console

pry(main)> date = Date.parse('7/5/2018')
=> Mon, 07 May 2018
pry(main)> quarter_limits_for date
=> [Sun, 01 Apr 2018, Sat, 30 Jun 2018]

pry(main)> date = Date.parse '29/2/2020'
=> Sat, 29 Feb 2020
pry(main)> quarter_limits_for date
=> [Wed, 01 Jan 2020, Tue, 31 Mar 2020]

Reference: https://apidock.com/rails/DateAndTime/Calculations/beginning_of_quarter

Ruby: Create range of dates

Using @CaptainPete's base, I modified it to use the ActiveSupport::DateTime#advance call. The difference comes into effect when the time intervals are non-uniform, such as `:month" and ":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

My method also returns an Array. For comparison's sake, I altered @CaptainPete's answer to also return an array:

By hour

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]

By month

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]

By year

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]

ColdFusion - Date and Time formatting is rounding up 5 minutes

Nobody is rounding anything. You are outputting the month here instead of the minutes, and the month happens to be May, so that is 05 with leading zero.

https://cfdocs.org/datetimeformat:

mm: Month as digits; leading zero for single-digit months.

Minutes are not mm here, but

nn: minutes; a leading zero for single-digit minutes



Related Topics



Leave a reply



Submit