I have a model in my Rails 3 application which has a date field:
class CreateJobs < ActiveRecord::Migration
def self.up
create_table :jobs do |t|
t.date "job_date", :null => false
...
t.timestamps
end
end
...
end
I would like to prepopulate my database with random date values.
What is the easiest way to generate a random date ?
Here's a slight expansion on Chris' answer, with optional from and to parameters:
def time_rand from = 0.0, to = Time.now
Time.at(from + rand * (to.to_f - from.to_f))
end
> time_rand
=> 1977-11-02 04:42:02 0100
> time_rand Time.local(2010, 1, 1)
=> 2010-07-17 00:22:42 0200
> time_rand Time.local(2010, 1, 1), Time.local(2010, 7, 1)
=> 2010-06-28 06:44:27 0200
Generate a random time between epoch, the beginning of 1970, and now:
Time.at(rand * Time.now.to_i)
Keeping simple..
Date.today-rand(10000) #for previous dates
Date.today+rand(10000) #for future dates
PS. Increasing/Decreasing the '10000' parameter, changes the range of dates available.
rand(Date.civil(1990, 1, 1)..Date.civil(2050, 12, 31))
my favorite method
def random_date_in_year(year)
return rand(Date.civil(year.min, 1, 1)..Date.civil(year.max, 12, 31)) if year.kind_of?(Range)
rand(Date.civil(year, 1, 1)..Date.civil(year, 12, 31))
end
then use like
random_date = random_date_in_year(2000..2020)
For recent versions of Ruby/Rails, You can use rand on a Time range ❤️ !!
min_date = Time.now - 8.years
max_date = Time.now - 1.year
rand(min_date..max_date)
# => "2009-12-21T15:15:17.162+01:00" (Time)
Feel free to add to_date, to_datetime, etc. to convert to your favourite class
Tested on Rails 5.0.3 and Ruby 2.3.3, but apparently available from Ruby 1.9+ and Rails 3+
The prettiest solution to me is:
rand(1.year.ago..50.weeks.from_now).to_date
The following returns a random date-time in the past 3 weeks in Ruby (sans Rails).
DateTime.now - (rand * 21)
Here's also a more (in my oppinion) improved version of Mladen's code-snippet. Luckily Ruby's rand() function can also handle Time-Objects. Regarding the Date-Object is defined when including Rails, the rand() method gets overwritten so it can handle also Date-Objects. e.g.:
# works even with basic ruby
def random_time from = Time.at(0.0), to = Time.now
rand(from..to)
end
# works only with rails. syntax is quite similar to time method above :)
def random_date from = Date.new(1970), to = Time.now.to_date
rand(from..to)
end
Edit: this code won't work before ruby v1.9.3
Here's my one liner to generate a random date in the last 30 days (for example):
Time.now - (0..30).to_a.sample.days - (0..24).to_a.sample.hours
Works great for my lorem ipsum. Obviously minutes and seconds would be fixed.
Since you're using Rails, you can install the faker gem and make use of the Faker::Date module.
e.g. The following generates a random date in the year of 2018:
Faker::Date.between(Date.parse('01/01/2018'), Date.parse('31/12/2018'))
Mladen's answer is a little difficult to understand from just a single look. Here's my take on this.
def time_rand from=0, to= Time.now
Time.at(rand(from.to_i..to.to_i))
end
Related
I have a model called Event with a datetime column set by the user.
I'm trying to get a total number of events in each season (spring, summer, fall, winter).
I'm trying with something like:
Event.where('extract(month from event_date) >= ? AND extract(day from event_date) >= ? AND extract(month from event_date) < ? AND extract(day from event_date) < ?', 6, 21, 9, 21).count
The example above would return the number of events in the Summer, for example (at least in the northern hemisphere).
It doesn't seem like my example above is working, i'm getting no events returned even though there are events in that range. I think my order of operations (ands) may be messing with it. Any idea of the best method to get what I need?
Edit: actually looking at this more this will not work at all. Is there anyway select dates within a range without the year?
Edit 2: I'm trying to somehow use the answer here to help me out, but this is Ruby and not SQL.
require 'date'
class Date
def season
day_hash = month * 100 + mday
case day_hash
when 101..320 then :winter
when 321..620 then :spring
when 621..920 then :summer
when 921..1220 then :fall
when 1221..1231 then :winter
end
end
end
You can concat the month and day and query everything in between.
e.g 621..921
In SQL it would be something like
SUMMER_START = 621
SUMMER_END = 921
Event.where("concat(extract(month from event_date), extract(day from event_date)) > ? AND concat(extract(month from event_date), extract(day from event_date)") < ?,
SUMMER_START, SUMMER_END)
This can be easily made into a scope method that accepts a season (e.g 'winter'), takes the appropriate season start and end and returns the result.
This is what I ended up doing:
in lib created a file called season.rb
require 'date'
class Date
def season
day_hash = month * 100 + mday
case day_hash
when 101..320 then :winter
when 321..620 then :spring
when 621..920 then :summer
when 921..1220 then :fall
when 1221..1231 then :winter
end
end
end
in lib created a file called count_by.rb:
module Enumerable
def count_by(&block)
list = group_by(&block)
.map { |key, items| [key, items.count] }
.sort_by(&:last)
Hash[list]
end
end
Now I can get the season for any date, as well as use count_by on the model.
So then ultimately I can run:
Event.all.count_by { |r| r.event_date.season }[:spring]
I'm trying to extract the time component from a DateTime object (which is represented as "at" in my example). How do I do this, I am absolutely stumped? (I don't want to parse it to a string with strftime as i did here):
#session_date.at.strftime("%H:%M")
I would really like to return the hours and minutes as a Time object.
Is there a specific reason you want a Time object?
Just so we're clear, the Time class in Ruby isn't just "DateTime without the date." As "What's the difference between DateTime and Time in Ruby?" explains, "Time is a wrapper around POSIX-standard time_t, or seconds since January 1, 1970." Like DateTime, a Time object still has year, month, and day, so you don't really gain anything by using Time instead. There's not really a way to represent just hour and minute using either Time or DateTime.
The best you could do with Time, I think, would be this:
date_time = DateTime.now
seconds = date_time.hour * 60 * 60 + date_time.minute * 60
time = Time.at(seconds)
# => 1970-01-01 09:58
...but then you still have to call time.hour and time.min to get at the hour and minute.
If you're just looking for a lightweight data structure to represent an hour and minute pair, though, you might as well just roll your own:
HourAndMinute = Struct.new(:hour, :minute) do
def self.from_datetime(date_time)
new(date_time.hour, date_time.minute)
end
end
hm = HourAndMinute.from_datetime(DateTime.now)
# => #<struct HourAndMinute hour=15, minute=58>
hm.to_h
# => { :hour => 15, :minute => 58 }
hm.to_a
# => [ 15, 58 ]
Edit re:
I have a variable that stores an appointment -- this variable is a DateTime object. I have two table fields that store the start and end times of a location. I need to check if the time scheduled for that appointment lies between the start and end times.
Ah, it seems you had a bit of a XY problem. This makes a lot more sense now.
Absent any more information, I'm going to assume your "fields that store the start and end times of a location" are MySQL TIME columns called start_time and end_time. Given MySQL TIME columns, Rails casts the values to Time objects with the date component set to 1/1/2000. So if your database has the values start_time = '09:00' and end_time = '17:00', Rails will give you Time objects like this:
start_time = Time.new(2000, 1, 1, 9, 0) # => 2000-01-01 09:00:00 ...
end_time = Time.new(2000, 1, 1, 17, 0) # => 2000-01-01 17:00:00 ...
Now you say your appointment time is a DateTime, so let's call it appointment_datetime and suppose it's at 10:30am tomorrow:
appointment_datetime = DateTime.new(2014, 11, 18, 10, 30) # => 2014-11-18 10:30:00 ...
So now to rephrase your question: How do we tell if the time part of appointment_datetime is between the time part of start_time and end_time. The answer is, we need to either change the date part of start_time and end_time to match the date part of appointment_datetime, or the other way around. Since it's easier to change one thing than two, let's do it the other way around and change appointment_datetime to match start_time and end_time (and, since those two are Time objects, we'll create a Time object):
appointment_time = DateTime.new(2000, 1, 1, appointment_datetime.hour, appointment_datetime.minute)
# => 2000-01-01 10:30:00 ...
Now we can compare them directly:
if appointment_time >= start_time && appointment_time <= end_time
puts "Appointment time is good!"
end
# Or, more succinctly:
if (start_time..end_time).cover?(appointment_time)
puts "Appointment time is good!"
end
You would, of course, want to wrap all of this up in a method, perhaps in your Location model (which, again, I'm assuming has start_time and end_time attributes):
class Location < ActiveRecord::Base
# ...
def appointment_time_good?(appointment_datetime)
appointment_time = DateTime.new(2000, 1, 1,
appointment_datetime.hour, appointment_datetime.minute)
(start_time..end_time).cover?(appointment_time)
end
end
location = Location.find(12) # => #<Location id: 12, ...>
location.appointment_time_good?(appointment_time) # => true
I hope that's helpful!
P.S. Another way to implement this would be to ditch the date/time objects entirely and do a straight numeric comparison:
def appointment_time_good?(appointment_datetime)
appointment_hour_min = [ appointment_datetime.hour, appointment_datetime.minute ]
appointment_hour_min >= [ start_time.hour, start_time.min ]
&& appointment_hour_min <= [ end_time.hour, end_time.min ]
end
If you have a DateTime object:
date_time = DateTime.now
date_time.hour
# => 16
date_time.minute
# => 1
If you are looking for the Time since now in words (which is common in Rails apps), then this may be a good read: http://api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html#method-i-time_ago_in_words
I'm trying to do something like this in my controller:
#inventory_items = #store.inventory_items.where(:updated_at < Time.now - 1.minute)
I keep getting a comparison of Symbol with Time failed error.
I tried to call to_datetime and to_date on :updated_at, but perhaps those only work on strings or integers?
How can I get :updated_at into a proper date format to compare with Time.now - 1.minute?
Thanks in advance!
Well, there are some ways you can do it.
The reason it doesn't work is because the symbol is only a pointer to the column and not the column itself.
So, either you do
#inventory_items = #store.inventory_items.where(["updated_at < ?", Time.now - 1.minute])
or as an alternative
#inventory_items = #store.inventory_items.where(["updated_at < :one_minute_ago", {one_minute_ago: Time.now - 1.minute]})
Or, you could do
#inventory_items = #store.inventory_items.where.not(:updated_at => Time.now - 1.minute..Time.now)
I do not think with the hash style you can use less than or greater than checks. Try the following:
#inventory_items = #store.inventory_items.where('inventory_items.updated_at < ?', Time.now - 1.minute)
As far as "proper date format" is concerned, you need not worry about them here. All database dates are by default converted to UTC.
How would I get the most recently occurring Wednesday, using Ruby (and Rails, if there's a pertinent helper method)?
Ultimately need the actual date (5/1/2013).
time = Time.now
days_to_go_back = (time.wday + 4) % 7
last_wed = days_to_go_back.days.ago
This works in Ruby:
require 'date'
def last_wednesday(date)
date - (date.wday - 3) % 7
end
last_wednesday(Date.today)
# => #<Date: 2013-05-01 ((2456414j,0s,0n),+0s,2299161j)>
In Rails there's beginning_of_week:
Date.today.beginning_of_week(:wednesday)
# => Wed, 01 May 2013
If you are okay with using another gem, I recommend Chronic.
With it, you can get last Wednesday by doing:
Chronic.parse('last Wednesday')
The simplest way (to me) is:
require 'date'
date = Date.today
date -= 1 until date.wednesday?
Pretty straightforward with Date:
require 'date'
today = DateTime.now.to_date
last_wednesday = today.downto(today - 6).select { |d| d.wednesday? }
You can even get the last weekday of your choice like this (here without error handling):
def last_weekday(weekday)
today = Time.now.to_date
today.downto(today-6).select do |d|
d.send((weekday.to_s + "?").to_sym)
end
end
Example I have:
range = start.to_date..(end.to_date + 1.day)
end and start are dates.
How do I create a month array based on this range?
Example:
I have the dates 23/1/2012 and 15/3/2012
The months are Januar, Februar and Marts.
I want to get a array like ["1/1/2012", "1/2/2012", "1/3/2012"]
and if the range was betweeen 25/6/2012 to the 10/10/2012
the array would be: ["1/6/2012", "1/7/2012", "1/8/2012", "1/9/2012", "1/10/2012"]
require 'date'
date_from = Date.parse('2011-10-14')
date_to = Date.parse('2012-04-30')
date_range = date_from..date_to
date_months = date_range.map {|d| Date.new(d.year, d.month, 1) }.uniq
date_months.map {|d| d.strftime "%d/%m/%Y" }
# => ["01/10/2011", "01/11/2011", "01/12/2011", "01/01/2012",
# "01/02/2012", "01/03/2012", "01/04/2012"]
Rails ActiveSupport core extensions includes a method for Date: beginning_of_month. Your function could be written as follows:
def beginning_of_month_date_list(start, finish)
(start.to_date..finish.to_date).map(&:beginning_of_month).uniq.map(&:to_s)
end
Caveats: this could be written more efficiently, assumes start and finish are in the expected order, but otherwise should give you the months you're looking for. You could also rewrite to pass a format symbol to the #to_s method to get the expected month format.
I was curious about performance here so I tested some variations. Here's a solution better optimized for performance (about 8x faster in my benchmark than the accepted solution). By incrementing by a month at a time we can remove the call to uniq which cuts quite a bit of time.
start_date = 1.year.ago.to_date
end_date = Date.today
dates = []
date = start_date.beginning_of_month
while date <= end_date.beginning_of_month
dates << date.to_date.to_s
date += 1.month
end
dates
#=> ["2019-02-01", "2019-03-01", "2019-04-01", "2019-05-01", "2019-06-01", "2019-07-01", "2019-08-01", "2019-09-01", "2019-10-01", "2019-11-01", "2019-12-01", "2020-01-01", "2020-02-01"]
Benchmark Results:
Comparison:
month increment loop: 17788.3 i/s
accepted solution: 2140.1 i/s - 8.31x slower
gist of the benchmark code
Similar to one of the solutions above using beginning_of_month .. but taking less space (by using Set) and is neater for using inject.
(start_month..end_month).inject(Set.new) { |s, i| s << i.beginning_of_month; s }.to_a