Calculating days in Ruby - ruby

I have an issue where, I'm trying to work out if a certain alert on a webpage is calculating sums correctly. I'm using Capybara and Cucumber.
I have an alert that calculates records that expire within 30 days. When selecting this alert, the records are listed in a table and the date is presented in the following format, "1 feb 2016"
What I want to do is somehow take today's date, compare it to the date returned in the table and ensure that it's >= 30 days from the date in the alert.
I'm able to set today's date as the same format using Time.strftime etc.
When I try things like:
And(/^I can see record "([\d]*)" MOT is calculated due within 30 days$/) do |selection1|
today = Time.now.strftime('%l %b %Y')
thirty_days = (today + 30)
first_30day_mot = first('#clickable-rows > tbody > tr:nth-child(' + selection1 + ') > td:nth-child(3)')
if today + first_30day_mot <= thirty_days
puts 'alert correct'
else
(error handler here)
end
end
As you can see, this is quite a mess.
I keep getting the error TypeError: no implicit conversion of Fixnum into String
If anyone can think of a neater way to do this, please put me out of my misery.
Thanks

There are at least a couple of things wrong with your attempt.
You're converting dates to strings and then trying to compare lengths of time with strings. You should be converting strings to dates and then comparing them
#first returns the element in the page not the contents of the element
It's not 100% clear from your code what you're trying to do, but from the test naming I think you just want to make sure the date in the 3rd td cell (which is in the 1 feb 2016 format) of a given row is less than 30 days from now. If so the following should do what you want
mot_element = first("#clickable-rows > tbody > tr:nth-child(#{selection1}) > td:nth-child(3)")
date_of_mot = Date.parse(mot_element.text)
if (date_of_mot - Date.today) < 30
puts 'alert correct'
else
#error handler
end
Beyond that, I'm not sure why you're using #first with that selector since it seems like it should only ever match one element on the page, so you might want to swap that to #find instead, which would get you the benefits of Capybaras waiting behavior. If you do actually need #first, you might consider passing the minimum: 1 option to make sure it waits a bit for the matching element to appear on the page (if this is the first step after clicking a button to go to a new page for instance)

Convert selection1 to the string explicitly (or, better, use string interpolation):
first_30day_mot = first("#clickable-rows > tbody > tr:nth-child(#{selection1}) > td:nth-child(3)")
Also, I suspect that one line below it should be converted to integer to add it to today:
first_30day_mot.to_i <= 30
UPD OK, I finally got time to take a more thorough look at. You do not need all these voodoo magic with days calculus:
# today = Time.now.strftime('%l %b %Y') # today will be a string " 3 Feb 2016"
# thirty_days = (today + 30) this was causing an error
# correct:
# today = DateTime.now # correct, but not needed
# plus_30_days = today + 30.days # correct, but not needed
first_30day_mot = first("#clickable-rows > tbody > tr:nth-child(#{selection1}) > td:nth-child(3)")
if 30 > first_30day_mot.to_i
...
Hope it helps.

I'd strongly recommend not using Cucumber to do this sort of test. You'll find its:
Quite hard to set up
Has a high runtime cost
Doesn't give enough benefit to justify the setup/runtime costs
Instead consider writing a unit test of the thing that provides the date. Generally a good unit test can easily run 10 to 100 times faster than a scenario.
Whilst with a single scenario you won't experience that much pain, once you have alot of scenarios like this the pain will accumulate. Part of the art of using Cucumber is to get plenty of bang for each scenario you write.

Related

How to loop through days, starting today and through a target date

I tried using answers for this question but was not able to get something that worked.
I want to be able to loop through days, starting today and going up to a target date set for an object.
The target date is of type Date.
I have tried a few things, all variations on the answers above, this was last attempt.
count = 0
Time.now..goal.target_date do
count += 1
end
This does not loop through anything and returns 0 as the count. Right now this is only in dev, with one object, so I know there are many days between now and the target (which is set for December 31 of this year).
I also tried this.
count = 0
Date.new(Time.now)..goal.target_date do |date|
count += 1
end
Which returns the error undefined method 'div' for 2018-10-06 17:23:41 -0500:Time. (Same error if I use Date.today just with :DATE instead of :Time at the end).
Can anyone help me get this to run the loop for each day between now and the target date?
Thanks!
Just wrote this one using de Date.upto() method and it worked... you just gotta make sure that 'goal.target_date' is also a valid instance of Date
require 'date'
from = Date.today
goto = from + 3
from.upto(goto) do |date|
puts date
end
Do you need a loop? If not, try this:
count = (goal.target_date - Date.current).to_i
I hope it useful for you. :)

Python Birthday paradox math not working

it run corectly but it should have around 500 matches but it only has around 50 and I dont know why!
This is a probelm for my comsci class that I am having isues with
we had to make a function that checks a list for duplication I got that part but then we had to apply it to the birthday paradox( more info here http://en.wikipedia.org/wiki/Birthday_problem) thats where I am runing into problem because my teacher said that the total number of times should be around 500 or 50% but for me its only going around 50-70 times or 5%
duplicateNumber=0
import random
def has_duplicates(listToCheck):
for i in listToCheck:
x=listToCheck.index(i)
del listToCheck[x]
if i in listToCheck:
return True
else:
return False
listA=[1,2,3,4]
listB=[1,2,3,1]
#print has_duplicates(listA)
#print has_duplicates(listB)
for i in range(0,1000):
birthdayList=[]
for i in range(0,23):
birthday=random.randint(1,365)
birthdayList.append(birthday)
x= has_duplicates(birthdayList)
if x==True:
duplicateNumber+=1
else:
pass
print "after 1000 simulations with 23 students there were", duplicateNumber,"simulations with atleast one match. The approximate probibilatiy is", round(((duplicateNumber/1000)*100),3),"%"
This code gave me a result in line with what you were expecting:
import random
duplicateNumber=0
def has_duplicates(listToCheck):
number_set = set(listToCheck)
if len(number_set) is not len(listToCheck):
return True
else:
return False
for i in range(0,1000):
birthdayList=[]
for j in range(0,23):
birthday=random.randint(1,365)
birthdayList.append(birthday)
x = has_duplicates(birthdayList)
if x==True:
duplicateNumber+=1
print "after 1000 simulations with 23 students there were", duplicateNumber,"simulations with atleast one match. The approximate probibilatiy is", round(((duplicateNumber/1000.0)*100),3),"%"
The first change I made was tidying up the indices you were using in those nested for loops. You'll see I changed the second one to j, as they were previously bot i.
The big one, though, was to the has_duplicates function. The basic principle here is that creating a set out of the incoming list gets the unique values in the list. By comparing the number of items in the number_set to the number in listToCheck we can judge whether there are any duplicates or not.
Here is what you are looking for. As this is not standard practice (to just throw code at a new user), I apologize if this offends any other users. However, I believe showing the OP a correct way to write a program should be could all do us a favor if said user keeps the lack of documentation further on in his career.
Thus, please take a careful look at the code, and fill in the blanks. Look up the python doumentation (as dry as it is), and try to understand the things that you don't get right away. Even if you understand something just by the name, it would still be wise to see what is actually happening when some built-in method is being used.
Last, but not least, take a look at this code, and take a look at your code. Note the differences, and keep trying to write your code from scratch (without looking at mine), and if it messes up, see where you went wrong, and start over. This sort of practice is key if you wish to succeed later on in programming!
def same_birthdays():
import random
'''
This is a program that does ________. It is really important
that we tell readers of this code what it does, so that the
reader doesn't have to piece all of the puzzles together,
while the key is right there, in the mind of the programmer.
'''
count = 0
#Count is going to store the number of times that we have the same birthdays
timesToRun = 1000 #timesToRun should probably be in a parameter
#timesToRun is clearly defined in its name as well. Further elaboration
#on its purpose is not necessary.
for i in range(0,timesToRun):
birthdayList = []
for j in range(0,23):
random_birthday = random.randint(1,365)
birthdayList.append(random_birthday)
birthdayList = sorted(birthdayList) #sorting for easier matching
#If we really want to, we could provide a check in the above nester
#for loop to check right away if there is a duplicate.
#But again, we are here
for j in range(0, len(birthdayList)-1):
if (birthdayList[j] == birthdayList[j+1]):
count+=1
break #leaving this nested for-loop
return count
If you wish to find the percent, then get rid of the above return statement and add:
return (count/timesToRun)
Here's a solution that doesn't use set(). It also takes a different approach with the array so that each index represents a day of the year. I also removed the hasDuplicate() function.
import random
sim_total=0
birthdayList=[]
#initialize an array of 0's representing each calendar day
for i in range(365):
birthdayList.append(0)
for i in range(0,1000):
first_dup=True
for n in range(365):
birthdayList[n]=0
for b in range(0, 23):
r = random.randint(0,364)
birthdayList[r]+=1
if (birthdayList[r] > 1) and (first_dup==True):
sim_total+=1
first_dup=False
avg = float(sim_total) / 1000 * 100
print "after 1000 simulations with 23 students there were", sim_total,"simulations with atleast one duplicate. The approximate problibility is", round(avg,3),"%"

Ruby - new_offset losing a second

I'm not sure if this behaviour is intended, but it seems a bit weird to me. I'm using the code from How do you get DateTime.parse to return a time in your time zone?
require 'date'
estHoursOffset = +10 # Brisbane/Australia
estOffset = Rational(estHoursOffset, 24)
With some times, the DateTime that's returned is a second earlier:
(DateTime.parse("2012-07-15 16:56:00") - (estHoursOffset/24.0)).new_offset(estOffset)
=> #<DateTime: 2012-07-15T16:55:59+10:00 (2456123.788888889,5/12,2299161)>
But with other times, it seems correct:
(DateTime.parse("2012-07-15 16:16:00") - (estHoursOffset/24.0)).new_offset(estOffset)
=> #<DateTime: 2012-07-15T16:16:00+10:00 (2456123.7611111114,5/12,2299161)>
The program I'm writing only cares about the minutes, which means I'm getting back 16:55 when I want 16:56.
So my questions are;
Is this intentional? (If so, is it documented somewhere - I haven't been able to find anything.)
Is there a simple way of fixing this programmatically? Since I don't care about seconds,I suppose I could "round up" the DateTimes returned, but it'd be good to know if this could bring up any other problems in edge cases.
This is probably because floating point numbers are imprecise - the 10/24.0 you are subtracting cannot be represented exactly.
If instead of subtracting that float you subtracted a rational, ie Rational(estHoursOffset, 24) then you should be ok
I tried both times ("2012-07-15 16:56:00" & "2012-07-15 16:16:00") and Ruby was always yielding the times parsed initially. I don't know mate how you managed to get 1 sec less; it is a miracle!! Only joking :)
If this still is giving you a hard time try getting the date (& time) - simpler like this..:
require 'date'
$date = Time.now #current date/time
puts $date
puts $date.min #if you want to use only the minutes
$date="2012-07-15 16:56:00" #if you want to parse it yourself
Moving on to your questions:
-No this is not international and it could be intermittent as well. I've tested your code above (+10h Australia) & from my location London, England (+1h). ALWAYS GOT the time parsed; never a second less or more.
Now if you need to round up the seconds so you will be 100% sure that each & every time you are getting the same results..:
def round_up(seconds)
divisor = 10**Math.log10(seconds).floor
i = seconds / divisor
remainder = seconds % divisor
if remainder == 0
i * divisor
else
(i + 1) * divisor
end
end
I cannot see why the rounding will cause problems in boundary conditions; as long as you always keep rounding everything! Hope this helps! Good luck mate :)

Get the count of elements in a ruby range made of Time objects

How would I be able to get the size or count of a range made up of Time objects?
Something that would achieve the same result as my pseudo Ruby code, which doesn't work:
((Time.now.end_of_day - 31.days)..(Time.now.end_of_day - 1.day)).size == 30
currently doing the above gives an error:
NoMethodError: undefined method `size' for 2012-05-18 23:59:59 -0400..2012-06-17 23:59:59 -0400:Range
and trying to turn it into array (range).to_a :
can't iterate from Time
update
Interesting, Just tried to do
((Date.today.end_of_day - 31.days)..(Date.today.end_of_day - 1.day)).count
Users/.../gems/ruby/1.9.1/gems/activesupport-3.0.15/lib/active_support/time_with_zone.rb:322: warning: Time#succ is obsolete; use time + 1
However
((Date.today - 31.days)..(Date.today - 1.day)).count == 31
I would be willing to settle for that?
Also ((Date.today - 31.days)..(Date.yesterday)).count == 31
update 2
On the other hand, taking Mu's hint we can do:
(((Time.now.end_of_day - 31.days)..(Time.now.end_of_day - 1.day)).first.to_date..((Time.now.end_of_day - 31.days)..(Time.now.end_of_day - 1.day)).last.to_date).count == 31
There's no such method as Range#size, try Range#count (as suggested by Andrew Marshall), though it still won't work for a range of Time objects.
If you want to perform number-of-days computations, you're better off using Date objects, either by instantiating them directly (Date.today - 31, for example), or by calling #to_date on your Time objects.
Date objects can be used for iteration too:
((Date.today - 2)..(Date.today)).to_a
=> [#<Date: 2012-06-17 ((2456096j,0s,0n),+0s,2299161j)>,
#<Date: 2012-06-18 ((2456097j,0s,0n),+0s,2299161j)>,
#<Date: 2012-06-19 ((2456098j,0s,0n),+0s,2299161j)>]
((Date.today - 2)..(Date.today)).map(&:to_s)
=> ["2012-06-17", "2012-06-18", "2012-06-19"]
It's because a size for a date range doesn't make senseā€”it doesn't know if you want to view it as days, minutes, seconds, months, or something else. The reason the error mentions iterating is that in order to determine the size of the range, it must know how to iterate over them so that it may count the number of elements.
Since what you want is the difference in days, just do that:
date_one = Time.now.end_of_day - 31.days
date_two = Time.now.end_of_day - 1.day
((date_one - date_two) / 1.day).abs
#=> 30.0
You must divide by 1.day since a difference of Times returns seconds.
To have any chance of your code working you should wrap everything before .size in parentheses.
Instead of using a range, maybe you can just subtract one time object from another?
I know you make ranges out of Date objects so you could convert to that.

UTC time resets to 2000-01-01 (ruby). How do I prevent the time from resetting?

I'm using a task and the spreadsheet gem to read in an excel spreadsheet into my database. One of the columns I'm reading in is "start_time." To do this, I'm forming an array of values, then passing in each of these array values, one by one.
cnum_array = [] # for start times
sheet1.each 3 do |row|
unless row[9].blank?
time = Time.parse(row[9])
cnum_array << time.utc
end
end
count = 0
for course in Course.all
course.update_attribute :start_time, cnum_array[count]
count += 1
end
This seems to work fine. If I insert a "puts course.start_time" statement within this last loop, it prints off the right time. Like so:
count = 0
for course in Course.all
course.update_attribute :start_time, cnum_array[count]
puts course.start_time
count += 1
end
This gives me the right time, e.g. "2012-01-23 15:30:00."
But when I look up the course time later (e.g. via my console's Course.find(1).start_time), it gives me "2000-01-01 15:20:00." So the time of day is right, but the day itself goes back to 2000-01-01.
Does anyone know why this is happening, and how I can fix it? Thanks!
You are using the Time class. This class deals with times, not dates. My guess is that your database column is of type time as well.
I recommend you use a datetime (or possibly timestamp) column type.

Resources