I have a date of birth column on my user table that takes a DATE. As this datatype appears as YYYY-MM-DD, I assume that when inputting a date to the database it must have the format, for example: 2013-12-26.
I have seen methods on StackOverflow for creating a random DateTime in Ruby, such as here. However, after much searching I can't find a way to generate a random date without the time, for example in the past 100 years, and have it properly formatted for the DATE datatype. In Rails, what is the best way to generate a random date without the time?
This seems to work:
def rand_date(days)
date = Date.today-rand(days)
date.to_s(:db)
end
But is there a more elegant solution that comes with Rails? I am new to Rails and programming, so any assistance would be most helpful!
Your method is correct. If you are using Rails, there are some trivial improvements such as
def rand_date(days)
rand(days).days.ago(Date.today)
end
which is mostly equivalent to
def rand_date(days)
rand(days).days.ago.to_date
end
The second version is less efficient because it creates more Date/Time objects during the internal conversions.
Apply to_s(:db) if you need the Date to be formatted as String.
A different approach would require you to construct a date passing the result of a rand to Date.new.
This is in core ruby:
1 #!usr/bin/ruby
2
3 require 'date'.
4
5 10.times do |t|
6 random_year = Random.new.rand(2000..2014) # custom range for years
7 random_month =Random.new.rand(1..12)
8 random_day = Random.new.rand(1..30)
9 puts "#{Date.new(random_year,random_month,random_day)}"
10 end
2014-11-29
2010-10-20
2006-02-23
2009-09-17
2006-01-14
2009-01-06
2002-07-06
2005-11-05
2013-06-20
2005-12-02
Here is something I used to generate random birth dates when populating a customer database. It works on days, and in this example, gives random dates between 1967-01-09 and 1993-01-12 by using the Date#jd method:
Date.jd(2439500 + rand(9500))
You can twiddle the dates generated by setting the base (in this case 2439500, which is 1967-01-09) and the random number to increase or decrease the range of dates produced.
Example:
irb(main):043:0> 10.times { puts Date.jd(2439500 + rand(9500)) }
1973-06-07
1973-11-09
1983-07-27
1990-11-03
1967-06-18
1967-06-20
1970-07-28
1990-05-13
1986-11-26
1989-02-15
Related
I have a rails and react app and i noticed i have been logging the date into my backend as day/month/year instead of month/day/year. what is the best way to go into my rails backend and fix theses dates. For example I want to turn "7/3/2018" into "3/7/2018" (yes they are strings).
I'm assuming i would start off with something like this:
date = "7/3/2018"
date.split("/")
but then how will i swap the values? Or is there a better way to do this other than to use split?
You can get each value in the array from the splitted string, and then interpolate them in a new string in the order you need:
date = '7/3/2018'
month, day, year = date.split('/')
p "#{day}/#{month}/#{year}" # "3/7/2018"
Other way could be using Date#parse and strftime to handle the format output:
require 'date'
date = '7/3/2018'
p Date.parse(date).strftime('%m/%d/%Y') # "03/07/2018"
my table has 3 columns: data type timestamp,
|created_At | final_time| duracion(difference between created at and final_time)
| | |
the column difference should save the difference in hours and minutes, in this format HH:MM
this is my controller:
def horario
horario.update(duracion: params[:duracion]) // this params is "00:59"
end
but in the table Horarios, in column duracion i have this:
2017-12-24 03:59:00
so i want to save 00:59 (59 minutes) but postgres save all current date and add 3 hours more.
i want to save so in the future i will be able tu sum column duracion. Or should i change data type for this column? In this case which datatype you recomend me for rails to save HH:MM??
thanks.
Rails 5 supports PostgreSQL's interval type to some extent. You can create interval columns in the usual way and they will be properly represented in db/schema.rb. You can also assign them values in the usual way so you can say things like:
model.some_interval = '6 hours'
and get 06:00:00 inside the database. However, there is nothing in Ruby or Rails that properly represents a time interval (we only have various timestamp and date classes) so when that interval comes out of the database, you'll have a string on your hands, i.e:
> model = Model.find(some_id)
> model.some_interval.class
=> String
so you might end up having to manually parse some strings in Ruby. For simple intervals like '6 hours', this will be easy but it won't be so easy with more complicated intervals like '6 years 23 days 11 hours'.
If you'll only be working with your time intervals inside the database then interval would be natural and easy, you can say things like:
select some_timestamp + some_interval
and
where some_timestamp + some_interval < some_other_timestamp
and everything will work nicely.
However, if you need to work with the intervals back in Ruby then you'd probably be better off storing the interval as a number of seconds in an integer column (or whatever resolution you need). Then you could say things like:
where some_timestamp + (some_interval_in_seconds || 'seconds')::interval < some_other_timestamp
inside the database and
some_time + model.some_interval_in_seconds
back in Ruby.
In any case, strings are probably the wrong approach unless you really like parsing strings everywhere all the time.
As others already pointed out, Rails handles the Postgres Interval type as a string. A string that, unfortunately, is not easy to parse.
If you do this:
u = Users.select('edited_at - created_at as time_dif')
puts u.first['time_dif']
You can get something like 168 days 12:51:20.851115. Ugly right?
Well, using Ruby to convert this string into an useful number is not easy, but you can use Postgres to do the job for you. You will need to do a plain SQL query though, but it's the best method I've found so far:
query = ActiveRecord::Base.connection.execute("
SELECT EXTRACT(epoch FROM time_dif)/3600 as hours_dif
FROM
(
SELECT (edited_at - created_at) as time_dif
FROM users
) AS MainQuery
")
In this example, Postgres' EXTRACT function will convert the Interval type into a number which represents the total seconds of the interval. If you divide this number by 3600 you will get the different in hours as in the example above.
Then, if you want to iterate over the results:
query.each do |r|
puts r['hours_dif']
end
You could save duracion as a float type, where duracion would equal something like final_time - created_at and this value would be the difference in seconds. You can then perform arithmetic with these values and always convert back to minutes, hours, or whatever you need.
I have the following semi-advanced DB query that is going through hourly prices for the last 10 years and returning daily average prices for the last seven days:
averages = Trade.where('date >= ?', 7.days.ago).average(:price, :group => "DATE_TRUNC('day', date - INTERVAL '1 hour')")
This returns the date (for that day) and an averageprice like this:
"2012-12-29 00:00:00"=>#<BigDecimal:7f97932be328,'0.2513458333 33333333E2',27(27)>
I then loop through each response and save them as new records in a TradeDailyAverage Model.
# Loops through each daily average produced above
averages.each do |date, avg|
# Converts the BigDecimal to Floating Point(?)
averagefloat = avg.to_f
# Rounds the Daily Average to only two decimal points
dailyaverage = number_with_precision(averagefloat, :precision => 2)
# Creates a new Object in the PpDailyAverage table
TradeDailyAverage.create(date: date, averageprice: dailyaverage)
This works, but since this will be an hourly Rake Task, with new prices coming in every hour, how can I change this to first find a TradeDailyAverage by date and if it exists, update the averageprice attribute, or create a new record if it doesn't exist.
Validate_uniqueness is set on TradeDailyAverage Model.
Update
When I do this, 7 items appear, with accurate averages. But they just won't save. When I add newaverage.save! I get a "Validation Error: Date has already been taken!"
newaverage = TradeDailyAverage.find_or_initialize_by_date(date: date)
newaverage.averageprice = dailyaverage
puts newaverage.date
puts newaverage.averageprice
Also, if I do newaverage.new_record? Ever average returns TRUE
I think you'd want something like this:
tda = TradeDailyAverage.first_or_initialize(date: date)
tda.averageprice = dailyaverage
tda.save
The issue (thanks to Alex's help) was due to a difference in the datetimes. Upon saving to a PG db, the hour would change on the datetime. This may be due to a timezone issue in the db. So, the code above could not find existing records, since it contained a different hourly time than what has been saved in the database.
Since I am producing daily averages, I don't need the time, just the date in the date column. So, I converted my dates to date to avoid the time difference issue. Also I changed the code a bit with a Case, so that I can report errors. I don't think this is very efficient, but it is working for now. I believe Alex's solution above may also work as long as the datetime values are converted to dates with .to_date:
# Loops through each daily average produced above
averages.each do |datetime, avg|
# Converts the BigDecimal to Floating Point(?)
avgfloat = avg.to_f
# Rounds the Daily Average to only two decimal points
avgprice = number_with_precision(avgfloat, :precision => 2)
# Converts datetime to date since this will not work with datetime (PostgreSQL is time-zoned challenged)
avgdate = datetime.to_date
# These are printed to use for testing.
puts avgdate
puts avgprice
# Starts Case to either report an error, update an existing record, or create new.
case
when avgdate.blank? || avgprice.blank?
puts "Something went terribly wrong with your seven day averages producing algorithm."
when TradeDailyAverage.exists?(:date=>avgdate)
updateavg = TradeDailyAverage.find_by_date(avgdate)
updateavg.averageprice = avgprice
updateavg.save
else
TradeDailyAverage.create(:date=>avgdate, :averageprice=>avgprice)
end # Ends Case
end # Ends Loop for each Daily Average
I'm writing an app that keeps track of school classes.
I need to store the schedule. For example: Monday-Friday from 8:am-11am.
I was thinking about using a simple string column but I'm going to need to make time calculations later.
For example, I need to store a representation of 8am, such as start_at:8am end_at:11am
So how should I store the time? What datatype should I use? Should I store start time and number of seconds or minutes and then calculate from there? or is there an easier way?
I use MySQL for production and SQLite for development.
I made an app recently that had to tackle this problem. I decided to store open_at and closed_at in seconds from midnight in a simple business hour model. ActiveSupport includes this handy helper for finding out the time in seconds since midnight:
Time.now.seconds_since_midnight
This way I can do a simple query to find out if a venue is open:
BusinessHour.where("open_at > ? and close_at < ?", Time.now.seconds_since_midnight, Time.now.seconds_since_midnight)
Any tips for making this better would be appreciated =)
If you're using Postgresql you can use a time column type which is just the time of day and no date. You can then query
Event.where("start_time > '10:00:00' and end_time < '12:00:00'")
Maybe MySQL has something similar
Check out the gem 'tod' for Rails 4 or Time_of_Day for Rails 3. They both solve the problem of storing time in a database while using an an Active Record model.
SQL has a time data type but Ruby does not. Active Record addresses this difference by representing time attributes using Ruby’s Time class on the canonical date 2000-01-01. All Time attributes are arbitrarily assigned the same dates. While the attributes can be compared with one another without an issue, (the dates are the same), errors arise when you attempt to compare them with other Time instances. Simply using Time.parse on a string like ”10:05” adds today’s date to the output.
Lailson Bandeira created a created solution for this problem, the Time_of_Day gem for Rails 3. Unfortunately the gem is no longer maintained. Use Jack Christensen’s ‘tod’ gem instead. It works like a charm.
This ruby gem converts time of day to seconds since midnight and back. The seconds value is stored in the database and can be used for calculations and validations.
Define the time of day attributes:
class BusinessHour < ActiveRecord::Base
time_of_day_attr :opening, :closing
end
Converts time of day to seconds since midnight when a string was set:
business_hour = BusinessHour.new(opening: '9:00', closing: '17:00')
business_hour.opening
=> 32400
business_hour.closing
=> 61200
To convert back to time of day:
TimeOfDayAttr.l(business_hour.opening)
=> '9:00'
TimeOfDayAttr.l(business_hour.closing)
=> '17:00'
You could also omit minutes at full hour:
TimeOfDayAttr.l(business_hour.opening, omit_minutes_at_full_hour: true)
=> '9'
I would store the starting hour and the duration within the database, using two integer columns.
By retrieving both values, you could convert the starting hour as in (assuming that you know the day already:
# assuming date is the date of the day, datetime will hold the start time
datetime = date.change({:hour => your_stored_hour_value , :min => 0 , :sec => 0 })
# calculating the end time
end_time = datetime + your_stored_duration.seconds
Otherwise, hava a look at Chronic. The gem makes handling time a little bit easier. Note that the changemethod is part of rails, and not available in plain Ruby.
The documentation on DateTime for plain Ruby can be found here.
Also, whatever you do, don't start storing your dates/time in 12-hour format, you can use I18nin Rails to convert the time:
I18n.l Time.now, :format => "%I.%m %p", :locale => :"en"
I18n.l Time.now + 12.hours, :format => "%I.%m %p", :locale => :"en"
You can also get from this notation, that you can store you duration in hours, if you want, you can then convert them rather easily by:
your_stored_value.hours
if stored as an integer, that is.
Suggestion:
Don’t worry about a specific datatype for that. A simple solution would be:
In the database, add an integer type column for start_time and another for end_time. Each will store the number of minutes since midnight.
Ex: 8:30am would be stored as 510 (8*60+30)
In the form, create a select field (dropdown) that displays all available times in time format:Ex.: 10am, 10:30am and so on.
But the actual field values that get saved in the database are their integer equivalents:
Ex: 600, 630 and so on (following the example above)
I assume you are using some kind of database for this. If you are using MySQL or Postgresql, you can use the datetime column type, which Ruby/Rails will automatically convert to/from a Time object when reading/writing to the database. I'm not sure if sqlite has something similar, but I imagine it probably does.
From the SQLite 3 website,
"SQLite does not have a storage class set aside for storing dates and/or times. Instead, the built-in Date And Time Functions of SQLite are capable of storing dates and times as TEXT, REAL, or INTEGER values:
TEXT as ISO8601 strings ("YYYY-MM-DD HH:MM:SS.SSS").
REAL as Julian day numbers, the number of days since noon in Greenwich on November 24, 4714 B.C. according to the proleptic Gregorian calendar.
INTEGER as Unix Time, the number of seconds since 1970-01-01 00:00:00 UTC.
Applications can chose to store dates and times in any of these formats and freely convert between formats using the built-in date and time functions."
You can then manipulate the values using the Date and Time functions outlined here.
In my application I have a variety of date sequences, such as Weekly, Monthly and Annually. Given an arbitrary date in the past, I need to calculate the next future date in the sequence.
At the moment I'm using a sub-optimal loop. Here's a simplified example (in Ruby/Rails):
def calculate_next_date(from_date)
next_date = from_date
while next_date < Date.today
next_date += 1.week # (or 1.month)
end
next_date
end
Rather than perform a loop (which, although simple, is inefficient especially when given a date in the distant past) I'd like to do this with date arithmetic by calculating the number of weeks (or months, years) between the two dates, calculating the remainder and using these values to generate the next date.
Is this the right approach, or am I missing a particularly clever 'Ruby' way of solving this? Or should I just stick with my loop for the simplicity of it all?
Because you tagged this question as ruby-on-rails, I suppose you are using Rails.
ActiveSupport introduces the calculation module which provides an helpful #advance method.
date = Date.today
date.advance(:weeks => 1)
date.advance(:days => 7)
# => next week
I have used the recurrence gem in the past for this purpose. There are a few other gems that model recurring events listed here.
If you are using a Time object, you can use Time.to_a to break the time into an array (with fields representing the hour, day, month, etc), adjust the appropriate field, and pass the array back to Time.local or Time.utc to build a new Time object.
If you are using the Date class, date +/- n will give you a date n days later/earlier, and date >>/<< n will give you a date n months later/earlier.
You can use the more generic Date.step instead of your loop. For example,
from_date.step(Date.today, interval) {|d|
# Each iteration of this block will be passed a value for 'd'
# that is 'interval' days after the previous 'd'.
}
where interval is a length of time in days.
If all you are doing is calculating elapsed time, then there is probably a better approach to this. If your date is stored as a Date object, doing date - Date.today will give you the number of days between that date and now. To calculate months, years, etc, you can use something like this:
# Parameter 'old_date' must be a Date object
def months_since(old_date)
(Date.today.month + (12 * Date.today.year)) - (old_date.month + (12 * old_date.year))
end
def years_since(old_date)
Date.today.year - old_date.year
end
def days_since(old_date)
Date.today - old_date
end