Ruby, difference between time an array was created and the current time - ruby-on-rails

I am trying to get the difference between the hours in the time an array was created and the current time, i cant seem to get my head around it, I did this, which seems perfectly logical, but it doesnt seem to ever work.
mergelisting = MergeList.Pluck(:created_at, :partner, :gambler, :amount)
(mergelisting).each do |i|
t = Time.now
nowtime = t.strftime("%I")
nowtime.to_i
mergetime = i[1].strftime("%I")
mergetime.to_i
if nowtime - mergetime == 6
do some stuff...
end
end
My real question is the difference in hours between 24 and 6 is 6 hours, but 24-6 would not give 6. What can i do here

I'm assuming MergeList.Pluck returns an Array, and the first item in this array will be a Rails created_at column from the database. Then:
mergelisting = MergeList.Pluck(:created_at, :partner, :gambler, :amount)
mergetime = mergelisting.first # Time from DB (created_at)
nowtime = Time.now # Current time
timediff_s = nowtime - mergetime # Difference in seconds
timediff_h = timediff_s / (60 * 60).to_f # Difference in hours as a floating point number
#
# Not exactly sure if this is what you wanted, but you now
# have the time difference as a floating point number expressed
# in hours. So you can do whatever you want with it
#
if (timediff_h.to_i == 6)
...the difference is anywhere between 6.0 and 7.0 hours...
...do whatever you need to do...
end

Related

Check if a time range is between another time range and how many hours Ruby on Rails

So I have a start_time and end_time. Which is maximum 24 hours.
I guess I firstly need to check if this timerange is within another timerange say 21:00-01:00. If it is I need to know how many of the hours is within the timerange.
Any ideas?
Use ((two- one) / 3600).round
for example
one=Time.now #=>2019-01-23 21:19:19 +0530
two=Time.now + 1*60*60 #=>2019-01-23 22:19:27 +0530
((two- one) / 3600).round #=>1
difference is 1 hour for two different Time instance.
What do you mean by "within"? Do you mean that you want to test if there's any overlap between the ranges, or if the first range is fully contained within another?
You can use Time objects in ranges and exploit #cover? to check for overlap
start_time = 5.hours.ago
end_time = 4.hours.ago
check_range_start = (4.5).hours.ago
check_range_end = (3.5).hours.ago
check_range = (check_range_start..check_range_end)
check_range.cover?(end_time) # => true
check_range.cover?(start_time) # =>false
If both ends of the range is covered by your check range, then your range is fully covered. If only one end is covered, then you have overlap. If neither end is covered, then the ranges do not intersect.
Once you know if there's intersection, then you can trivially compute the distance from one end of the time range to one end of your check range; if you want to know how much time between your check range start and your input start time, simply subtract start_time - check_range_start.
I assume the four arguments of the method below are strings of the form "HH:MM" or "HH:MM:SS". I have provided a pure-Ruby solution that makes string comparisons only (for example: "17:26" < "18:00" #=> true). That is, I do not convert the strings to Time objects.
def time_within_ref?(time_start, time_end, ref_start, ref_end)
(time_start >= ref_start) &&
(adj_hour(time_start, time_end) <= adj_hour(ref_start, ref_end))
end
def adj_hour(start_str, end_str)
end_str[0,2] = (end_str[0,2].to_i + 24).to_s if end_str < start_str
end_str
end
Here are a few examples.
time_within_ref?("19:00", "02:00", "18:00", "03:00")
#=> true
time_within_ref?("19:00", "04:00", "18:00", "03:00")
#=> false
time_within_ref?("17:00", "02:00", "18:00", "03:00")
#=> false
time_within_ref?("18:00", "02:00", "18:00", "03:00")
#=> true
There are several ways to perform this in the database which are far more performant than loading a bunch of records into Rails.
While "pure ruby" solutions are a quick fix they will exhaust the available memory and crash your server given a non-trivial amount of records.
You can use a range together with .where to create a WHERE start_time BETWEEN a AND b clause that ensures that a time is within A and B.
starts_today = Model.where(
start_time: Time.current.beginning_of_day..Time.current.end_of_day
)
If you want to ensure that the entire span between start_time and end_time is within a set of bounds you can write you own where clause:
Model.where(
"start_time > :a AND end_time < :b",
a: Time.current.beginning_of_day,
b: Time.current.end_of_day
)
I need to know how many of the hours is within the timerange.
You can do this with the time/date functions in your database.
On postgres for example you can do:
bounds = Time.current.beginning_of_day..Time.current.end_of_day
Model.select(
"models.*, (EXTRACT(HOUR FROM end_time - start_time ) - EXTRACT(HOUR FROM end_time - '#{ bounds.end.iso8601 }')) AS number_of_hours"
)
Again calculating this in the DB is vital if you for example want to use this in a WHERE clause.
I think I figured it out.
Shift.start_time.strftime("%H:%M") => '20:00' && Shift.end_time.strftime("%H:%M") <= '01:00'
The .to_time was messing with me and also the ordering.

Save the most recent months of a hash

I have an array of hashes. Something like this...
transactions = [{"date"=>"2014-07-21", "amount"=>200},
{"date"=>"2012-06-21", "amount"=>400},
{"date"=>"2014-08-21", "amount"=>100},
{"date"=>"2014-08-12", "amount"=>150},
{"date"=>"2014-06-15", "amount"=>230}
{"date"=>"2013-05-21", "amount"=>900},]
I want to be able to save each months total amounts and then show the most recent 3 months to todays date and their total amount. Something like this...
Totals: 06-14 $230
07-14 $200
08-14 $250
I have this method but i am not sure how to get only the last 3 months to put in my database field and how to print it out.
def income_by_month
#payroll_transactions = current_user.transactions
#recent_payroll = #payroll_transactions.find_all {90.days.ago.to_date..Date.today}.map #finds transactions within 90 days
#amount_by_month = #recent_payroll.group_by { |t| t.date.to_date.month }.map do |month, transactions|
[month, transactions.sum(:amount)] #Groups transactions by month and adds month total
end.to_h
-EDIT-
I figured out a method to only get the transactions from the last 30 days I updated my method to show it. Now my question is how do I save the answer (Do i save it in one field as an Array?) and then how to show the answer in my view. Like I show it here. How do I print each key and value line by line in an order?
Totals: 06-14 $230
07-14 $200
08-14 $250
-EDIT-
Sorry my database is a mongoid db. And I want to save the most recent 3 months to todays date regardless of if an amount is available.
Let me start with a quick note on your code snippet:
group_by { |t| t.date.to_date.month }
Note that grouping objects by a single month does not take a year in count, so it would end summing up amounts for transactions of both 2012 and 2014 years in a one container. So what you really want is to group based on both month and year values.
Thinking of reducing the amount of redundant iterations through the input array (and using unnecessary aggregations), I've came to the following suggestion:
last_months = transactions.map{|i| Date.parse(i["date"]).strftime("%m-%Y")}.uniq.sort.last(3)
result = last_months.inject({}){|result, input| result[input] = 0; result}
transactions.inject(result) do |result, object|
# NOTE: we're already doing dates parsing and strftime two times here.
# In case you operate on Date objects themselves in your code, this is not the case.
# But the real perfomance measurement between summing all values up
# and strftiming more than once should be done additionally.
month = Date.parse(object["date"]).strftime("%m-%Y")
result[month] += object["amount"] if result[month]
result
end
# result now equals to {"06-2014"=>230, "07-2014"=>200, "08-2014"=>250}
First, we obtain those last three months (and years).
Next we create a hash to contain aggregated values with only those last months keys. At the end we sum up amount for only those transactions which seem to be one of the latter 3 months.
So, as long as ruby hashes (ruby v.1.9+) preserve the keys order, you can simply iterate over them to print out:
result.each{|k,v| puts "#{k}: #{v}"}
# 06-2014: 230
# 07-2014: 200
# 08-2014: 250
One last thing to note here is that doing this kind of aggregation inside of your server code is not quite efficient. Much more tempting option would be to move this calculations to your database layer.
ActiveSupport has some pretty slick date methods like Date#beginning_of_month:
require "date"
require "active_support/core_ext"
def process_transaction_group(month, transactions)
{
month: month.strftime("%Y/%m"),
total: transactions.map {|t| t["amount"] }.reduce(:+)
}
end
def process_transactions(transactions)
transactions
.group_by {|t| Date.parse(t["date"]).beginning_of_month }
.select {|month, _trxs| month < 3.months.ago }
.map {|month, trxs| process_transaction_group(month, trxs) }
end
###############
transactions = [{"date"=>"2014-07-21", "amount"=>200},
{"date"=>"2012-06-21", "amount"=>400},
{"date"=>"2014-08-21", "amount"=>100},
{"date"=>"2014-08-12", "amount"=>150},
{"date"=>"2014-06-15", "amount"=>230},
{"date"=>"2013-05-21", "amount"=>900}]
process_transactions(transactions)
#=> [{:month=>"2014/07", :total=>200}, {:month=>"2012/06", :total=>400}, {:month=>"2014/08", :total=>250}, {:month=>"2014/06", :total=>230}, {:month=>"2013/05", :total=>900}]

Rails 3.1, Ruby 1.9.2, Mongoid 2.3.3 - the poltergeist is in my datetimez

I just spent a considerable amount of time trying to get this issue fixed, and although I did fix it (sort of) I'm nowhere near understanding what's going on.
Take a Mongoid model:
class Game
include Mongoid::Document
include Mongoid::Timestamps
field :start, type: DateTime
index :start
def process(attrs = nil, role = :default, guard_protected_attributes = true)
if attrs.keys.include?('start(1i)')
now = DateTime.now
year = attrs.delete('start(1i)').to_i || now.year
month = attrs.delete('start(2i)').to_i || now.month
day = attrs.delete('start(3i)').to_i || now.day
hour = attrs.delete('start(4i)').to_i || now.hour
min = attrs.delete('start(5i)').to_i || now.minute
sec = attrs.delete('start(6i)').to_i || 0 # seconds
zone = attrs.delete('start(7i)').to_i || 0 # UTC
# I'm not sure what is happening here, but we need to adjust the hour
# otherwise Rails/Mongoid will mangle the time...
start = DateTime.new(year, month, day, hour, min, sec, zone)
# First we set the time and self.start will be wrong by 6 hours (my timezone)
self.start = start
# We do this and the time will change by several hours!!!
self.start -= 0.seconds
# Can't make a simple substraction as we'll get a Rational? The WTFs just keep piling up...
diff = (self.start.to_i - start.to_i).seconds
self.start -= diff
self.start -= diff # Yeah, twice?
end
super(attrs, role, guard_protected_attributes)
end
end
Why the process method? Well, I can't let the start attribute be handled by Mongoid::MultiParameterAttributes, as "something" will "fix" it by adjusting it using my local time zone (haven't figured out which brilliant code is doing this).
In that code, the start variable will always have the correct time, but as you saw, self.start has to be beaten with a mallet until the time is correct too.
Weird? Man don't get me started. This is related to Rails multi-parameter attributes, which allows me to use several select tags for setting a date/time (the start(1i) stuff).
Time.zone is UTC, so I don't know why it's using my local time zone to mangle the time.
But the weirdest stuff is why do I have to make so many adjustments...
I'm not expecting to find a real solution anytime soon, but I wanted to know your thoughts.
PS: Awww, SO didn't let me add the poltergeist tag.
Turns out I just had to:
Mongoid::Config.use_utc = true
Mongoid::Config.use_activesupport_time_zone = true
That still doesn't explain the weird behavior do. Or maybe it does and I'm not seeing it :)

Ruby on Rails time helper

I'm working on my first Rails Application. I am a little stuck with the time. I'm working on a recipe application. I need to add two fields.
Preparation Time
Cook Time
Out of the two, i would like to add the two fields to come up with the Total Time needed to prepare the meal.
I approached it the wrong way which doesn't have logic :(. Basically i have two fields and i used f.select to select predefined times. But the problem i have with that approach is that when adding the two, it ignores the Gregorian format e.g 40 minutes + 50 mins will become 90 Min instead of 1hour 30.
I would appreciate any help from the community.
A quick example:
prep_time = 40.minutes
cook_time = 50.minutes
total_time = prep_time + cook_time
formatted_total_time = Time.at(total_time).gmtime.strftime('%I:%M')
# outputs 01:30 which is HOURS:MINUTES format
If you wanted 90 minutes instead:
formatted_total_time = total_time / 60
# outputs 90
Update:
Put this in the helper file associated with whatever view you are using this in (i.e. app/helpers/recipes_helper.rb)
module RecipesHelper
def convert_to_gregorian_time(prep_time, cook_time)
# returns as 90 mins instead of 1hr30mins
return (prep_time + cook_time) / 60
end
end
Then you'd just call it in your view (i.e. app/views/recipes/show.html.haml like:
# Note: this is HAML code... but ERB should be similar
%p.cooking_time
= convert_to_gregorian_time(#recipe.prep_time, #recipe.cook_time)
If you are storing the times in the database as integers (which you SHOULD be doing), then you can do this:
%p.cooking_time
= convert_to_gregorian_time(#recipe.prep_time.minutes, #recipe.cook_time.minutes)
where #recipe.prep_time is an integer with a value of 40 and #recipe.cook_time is an integer with a value of 50
and your database schema would look something like:
# == Schema Information
#
# Table name: recipes
#
# id :integer not null, primary key
# prep_time :integer
# cook_time :integer
# # other fields in the model...

ruby on rails average per day

Referencing this post:
ruby on rails average per day
I'm getting the average per day, which works fine. Until now that the month switched over...
I have this code
score = (7.days.ago.to_date.to_s(:db)..Date.today.tomorrow.to_s(:db)).map{|dt| [dt, ave_score[dt] || 0]}
Which works, but now that the month switches over, it comes back with dates that don't exist, like 2009-08-32 to 2009-08-99 and 2009-09-00. All in the array. How can I delete dates that don't actually exist.
Try waiting to make the call to to_s:
score = (7.days.ago.to_date..Date.today.tomorrow).map{ |dt| d = dt.to_s(:db); [d, ave_score[d] || 0] }
I figured it out, I'm not sure if this is the best way of doing it so if anybody else would like to add something go for it:
arr_count = 0
length = score.length
while arr_count < length
begin
score[arr_count][0].to_date
arr_count = arr_count + 1
rescue
#score.delete_at(arr_count)
length = length - 1
end
end

Resources