Calculating a time before 1970 (fails with negative seconds) - ruby

I am new in ruby,and when I learn the Time class in ruby(In fact I follow the VTC video) I found something I can not make ,I want to caculate the born year of one person according to his age,
for example,when a person tell his age is "20",then I should caculate his born year.
class Person
attr_accessor :name,:age,:year_born
def initialize(name,age)
#name=name
#age=age
#year_born=(Time.now - age*31556962).year
end
def days_alive
#age*365
end
end
In the following code everything works well except the
#year_born=(Time.now - age*31556962).year
I got an error when I try
Person.new("name",43).year_born
which says:
ArgumentError: time must be positive
./person.rb:6:in `-'
./person.rb:6:in `initialize'
I know Time.now will return the seconds from 1970,that's to say
(2011-1970)<43
So the Time.now-43*31556962 return a invalid value,but I want to know how to implement my requirement?

According to Programming Ruby:
Time is an abstraction of dates and
times. Time is stored internally as
the number of seconds and microseconds
since the epoch, January 1, 1970 00:00
UTC. On some operating systems, this
offset is allowed to be negative. Also
see the Date library module on page
742. (emphasis mine)
Which implies that on some operating systems, the offset is not allowed to be negative. So any of us elderly folks who were born before 1970 may blow up your code. Also keep in mind you're actually calculating number_of_seconds_per_year * age_in_years, which won't be very accurate.

why not do it like this:
note that i am using
(Time.now.year - age)
and that i have year_born as a method.
class Person
attr_accessor :name,:age
def initialize(name,age)
#name=name
#age=age
end
def year_born
(Time.now.year - age)
end
def days_alive
#age*365
end
end
However do not store the age in your DB(if you are going to save this in your DB). Just save the birth date.

The problem is you're using Time, but should be using either Date or DateTime, which have a greater range. Date doesn't know about times, which might fit your application better since you want day granularity.
require 'date'
Date.today - 20 * 365 # => #<Date: 1991-05-07 (4896767/2,0,2299161)>
Date.today - 50 * 365 # => #<Date: 1961-05-14 (4874867/2,0,2299161)>
(Date.today - 50 * 365).year # => 1961

Related

Task for telegram bot

I wrote a telegram bot on ruby with the help of rack. My questions is what can help me do this task. I need bot to send one message when starts_at field of my activerecord object will be equal to now. Basically I need to perform this task all the time so it monitor my database and send this message. I thought about something like delayed_job, don't know how it can help me to achieve my goal.
I need to close the event at a specific time:
class Event < ActiveRecord::Base
def close
if starts_at == Time.now
send_message("Some farewell message")
end
end
end
I need to check all the time if this event is ready to close and send a message after this.
You don't tell us what datatype your starts_at field is, but odds are really good that you'll almost never find an instant where starts_at is equal to Time.now so
if starts_at == Time.now
will usually fail.
Time.now has a granularity well beyond a second. This is from the Time class documentation:
All times may have fraction. Be aware of this fact when comparing times with
each other -- times that are apparently equal when displayed may be different
when compared.
and from the Time#to_f documentation:
t = Time.now
"%10.5f" % t.to_f #=> "1270968744.77658"
t.to_i #=> 1270968744
If you're using a DateTime or Time field it's going to be very hard to match that microsecond. Instead, you need to change your code from using == to <= which will trigger whenever start_time is less than or equal to Time.now, assuming it's a compatible field of course.
Change:
if starts_at == Time.now
to:
if starts_at <= Time.now
and it should work better.
Alternately, you can make your starts_at field be an integer or fixnum and then change the values in it to integers, then compare using Time#to_i which results in just the seconds, not the sub-seconds:
[1] (pry) main: 0> foo = Time.now
2017-01-25 15:21:08 -0700
[2] (pry) main: 0> foo.to_f
1485382868.014835
[3] (pry) main: 0> foo.to_i
1485382868
But you'll still want to use <= instead of ==.
Here's something to mull over:
foo = Time.now
bar = Time.now
foo # => 2017-01-25 15:25:42 -0700
bar # => 2017-01-25 15:25:42 -0700
foo.to_f # => 1485383142.157932
bar.to_f # => 1485383142.1579342
foo.to_i # => 1485383142
bar.to_i # => 1485383142
Even at full-speed, Ruby still returns a different time for foo vs. bar, and in that gap your code would miss firing. to_i gives you a one-second granularity but that's still not enough, hence the need for <=.

How to recalculate values when the new year comes, Ruby

I'm trying to write an app that calculates sick/vacation days and how much an employee has available in either category.
Next step is adding a method that every year on January first recalculates all the employee's available vacation/sick time (without scrubbing/overwriting the data from the previous year, as that information needs to be accessed).
I have an Employee model (which is posted below), which :has_many Furloughs (which have their own model which I won't post unless requested, but basically handles how the date ranges are calculated).
class Employee < ActiveRecord::Base
has_many :furloughs, :dependent => :destroy
def years_employed
(DateTime.now - hire_date).round / 365
end
def vacation_days
if years_employed < 1 && newbie
((12 - hire_date.month) * 0.8).ceil
elsif years_employed <= 6
10
elsif years_employed <= 16
years_employed + 4
else
20
end
end
def newbie
Time.now.year == hire_date.year
end
def sick_days
5.0
end
def vacation_days_used
self.furloughs.collect(&:vacation_duration).inject(0) { | memo, n | memo + n }
end
def sick_days_used
self.furloughs.collect(&:sick_duration).inject(0) { | memo, n | memo + n }
end
def remaining_vacation_days
vacation_days - vacation_days_used
end
def remaining_sick_days
sick_days - sick_days_used
end
end
On January 1st, vacation/sick_days and vacation/sick_days_used methods need to reset, and then I also need to add a rollover method like
def rollover
if some_method_determining_last_year(remaining_vacation_days) >= 2
2
else
remaining_vacation_days
end
end
that needs to add to the newly calculated total as well. I'd appreciate any thoughts on what how I should approach this.
A database solution would probably be the simplest way to go, but since you're already on a track of trying to store as little information as possible, I might have a solution for you. You already have a method for the number of vacation/sick days (probably really should be hours) an employee has. From there you can have a method that calculates the total vacation and sick days an employee has ever earned. Then it's just a simple calculation between the number of days they have used and the number of hours the have earned. You will probably have to add a filter to those methods for expired hours, but you get the idea. I also assume that vacation_days_used are days used for the entirety of their employment (can't tell since I can't see the furlough model):
def total_vacation_days_earned
# vacation_days has to be recalculate, probably should be a mapped function.
# for each year, how many vacation days did you earn, but this is a rough method
vacation_days * years_employed # can put cutoffs like, first 2 years employed, etc.
end
def total_sick_days_earned
sick_days * years_employed
end
def vacation_days_used
self.furloughs.collect(&:vacation_duration).inject(0) { | memo, n | memo + n }
end
def sick_days_used
self.furloughs.collect(&:sick_duration).inject(0) { | memo, n | memo + n }
end
def remaining_vacation_days
total_vacation_days_earned - vacation_days_used
end
def remaining_sick_days
total_sick_days_earned - sick_days_used
end
Now you shouldn't need a rollover method since at the beginning of the year each employee has a running total of time they have ever accrued minus a running total of time they have ever used.
Most employers will give you a fraction of the holidays you have accrued each payroll period, not upfront at the beginning of the year. You will probably eventually need to segment it out by payroll period as well as years_employed by payroll (i.e., Employee has been here 24 payroll cycles, or 2 years). You will need a more robust years_employed method, I've written up a gist that can be used as a starting point for a method that calculates years served as incremented calendar (not always 365 days) year from the hire_date. You could modify this to increment every two weeks, fifteen days, whatever the payroll cycle is. You could also add a service or additional model to handle payroll methods.
I would recommend extending the Employee model/schema to include two additional columns: remaining_vacation_days and sick_days.
Upon creation of Employee, set these values appropriately and decrement remaining_vacation_days and increment sick_days after a Furlough save (see after_save).
Finally, on Jan 1st use your vacation_days and sick_days methods to reset these values for the new year.

Time-of-day range in Ruby?

I want to know if a time belongs to an schedule or another.
In my case is for calculate if the time is in night schedule or normal schedule.
I have arrived to this solution:
NIGHT = ["21:00", "06:00"]
def night?( date )
date_str = date.strftime( "%H:%M" )
date_str > NIGHT[0] || date_str < NIGHT[1]
end
But I think is not very elegant and also only works for this concrete case and not every time range.
(I've found several similar question is SO but all of them make reference to Date ranges no Time ranges)
Updated
Solution has to work for random time ranges not only for this concrete one. Let's say:
"05:00"-"10:00"
"23:00"-"01:00"
"01:00"-"01:10"
This is actually more or less how I would do it, except maybe a bit more concise:
def night?( date )
!("06:00"..."21:00").include?(date.strftime("%H:%M"))
end
or, if your schedule boundaries can remain on the hour:
def night?(date)
!((6...21).include? date.hour)
end
Note the ... - that means, basically, "day time is hour 6 to hour 21 but not including hour 21".
edit: here is a generic (and sadly much less pithy) solution:
class TimeRange
private
def coerce(time)
time.is_a? String and return time
return time.strftime("%H:%M")
end
public
def initialize(start,finish)
#start = coerce(start)
#finish = coerce(finish)
end
def include?(time)
time = coerce(time)
#start < #finish and return (#start..#finish).include?(time)
return !(#finish..#start).include?(time)
end
end
You can use it almost like a normal Range:
irb(main):013:0> TimeRange.new("02:00","01:00").include?(Time.mktime(2010,04,01,02,30))
=> true
irb(main):014:0> TimeRange.new("02:00","01:00").include?(Time.mktime(2010,04,01,01,30))
=> false
irb(main):015:0> TimeRange.new("01:00","02:00").include?(Time.mktime(2010,04,01,01,30))
=> true
irb(main):016:0> TimeRange.new("01:00","02:00").include?(Time.mktime(2010,04,01,02,30))
=> false
Note, the above class is ignorant about time zones.
In Rails 3.2 it has added Time.all_day and similars as a way of generating date ranges. I think you must see how it works. It may be useful.

How to get the number of days in a given month in Ruby, accounting for year?

I'm sure there's a good simple elegant one-liner in Ruby to give you the number of days in a given month, accounting for year, such as "February 1997". What is it?
If you're working in Rails, chances are you'll get hamstrung eventually if you switch among Time, Date, and DateTime, especially when it comes to dealing with UTC/time zones, daylight savings, and the like. My experience has been it's best to use Time, and stick with it everywhere.
So, assuming you're using Rails's Time class, there are two good options, depending on context:
If you have a month m and year y, use the class method on Time:
days = Time.days_in_month(m, y)
If you have a Time object t, cleaner to ask the day number of the last day of the month:
days = t.end_of_month.day
require 'date'
def days_in_month(year, month)
Date.new(year, month, -1).day
end
# print number of days in February 2012
puts days_in_month(2012, 2)
This is the implementation from ActiveSupport (a little adapted):
COMMON_YEAR_DAYS_IN_MONTH = [nil, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
def days_in_month(month, year = Time.now.year)
return 29 if month == 2 && Date.gregorian_leap?(year)
COMMON_YEAR_DAYS_IN_MONTH[month]
end
How about:
require 'date'
def days_in_month(year, month)
(Date.new(year, 12, 31) << (12-month)).day
end
# print number of days in Feburary 2009
puts days_in_month(2009, 2)
You may also want to look at Time::days_in_month in Ruby on Rails.
In Rails project for current date
Time.days_in_month(Time.now.month, Time.now.year)
For any date t which is instance of Time
Time.days_in_month(t.month, t.year)
or
t.end_of_month.day
.
If you have UTC seconds, you need to get an instance of Time first
Time.at(seconds).end_of_month.day
For a given Date object I feel like the easiest is:
Date.today.all_month.count
Use Time.days_in_month(month) where month = 1 for January, 2 for Feb, etc.
revise the input for other format
def days_in_a_month(date = "2012-02-01")
date.to_date.end_of_month.day
end
as of rails 3.2... there's a built in version:
http://api.rubyonrails.org/classes/Time.html#method-c-days_in_month
(alas, it shows up after this answer, which takes folks on a long hike)
Time.now.end_of_month.day - for current month
Date.parse("2014-07-01").end_of_month.day - use date of first day in month.
Depends on ActiveSupport
A simple way using Date:
def days_of_month(month, year)
Date.new(year, month, -1).day
end
I think it's the simplest way to get it
def get_number_of_days(date = Date.today)
Date.new(date.year, date.month, -1).mday
end
Since the time is irrelevant for this purpose then you just need a date object:
[*Date.today.all_month].size

How do I get elapsed time in milliseconds in Ruby?

If I have a Time object got from :
Time.now
and later I instantiate another object with that same line, how can I see how many milliseconds have passed? The second object may be created that same minute, over the next minutes or even hours.
As stated already, you can operate on Time objects as if they were numeric (or floating point) values. These operations result in second resolution which can easily be converted.
For example:
def time_diff_milli(start, finish)
(finish - start) * 1000.0
end
t1 = Time.now
# arbitrary elapsed time
t2 = Time.now
msecs = time_diff_milli t1, t2
You will need to decide whether to truncate that or not.
You can add a little syntax sugar to the above solution with the following:
class Time
def to_ms
(self.to_f * 1000.0).to_i
end
end
start_time = Time.now
sleep(3)
end_time = Time.now
elapsed_time = end_time.to_ms - start_time.to_ms # => 3004
I think the answer is incorrectly chosen, that method gives seconds, not milliseconds.
t = Time.now.t­o_f
=> 1382471965.146
Here I suppose the floating value are the milliseconds
DateTime.now.strftime("%Q")
Example usage:
>> DateTime.now.strftime("%Q")
=> "1541433332357"
>> DateTime.now.strftime("%Q").to_i
=> 1541433332357
To get time in milliseconds, it's better to add .round(3), so it will be more accurate in some cases:
puts Time.now.to_f # => 1453402722.577573
(Time.now.to_f.round(3)*1000).to_i # => 1453402722578
ezpz's answer is almost perfect, but I hope I can add a little more.
Geo asked about time in milliseconds; this sounds like an integer quantity, and I wouldn't take the detour through floating-point land. Thus my approach would be:
t8 = Time.now
# => Sun Nov 01 15:18:04 +0100 2009
t9 = Time.now
# => Sun Nov 01 15:18:18 +0100 2009
dif = t9 - t8
# => 13.940166
(1000 * dif).to_i
# => 13940
Multiplying by an integer 1000 preserves the fractional number perfectly and may be a little faster too.
If you're dealing with dates and times, you may need to use the DateTime class. This works similarly but the conversion factor is 24 * 3600 * 1000 = 86400000 .
I've found DateTime's strptime and strftime functions invaluable in parsing and formatting date/time strings (e.g. to/from logs). What comes in handy to know is:
The formatting characters for these functions (%H, %M, %S, ...) are almost the same as for the C functions found on any Unix/Linux system; and
There are a few more: In particular, %L does milliseconds!
The answer is something like:
t_start = Time.now
# time-consuming operation
t_end = Time.now
milliseconds = (t_start - t_end) * 1000.0
However, the Time.now approach risks to be inaccurate. I found this post by Luca Guidi:
https://blog.dnsimple.com/2018/03/elapsed-time-with-ruby-the-right-way/
system clock is constantly floating and it doesn't move only forwards. If your calculation of elapsed time is based on it, you're very likely to run into calculation errors or even outages.
So, it is recommended to use Process.clock_gettime instead. Something like:
def measure_time
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
yield
end_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
elapsed_time = end_time - start_time
elapsed_time.round(3)
end
Example:
elapsed = measure_time do
# your time-consuming task here:
sleep 2.2321
end
=> 2.232
%L gives milliseconds in ruby
require 'time'
puts Time.now.strftime("%Y-%m-%dT%H:%M:%S.%L")
or
puts Time.now.strftime("%Y-%m-%d %H:%M:%S.%L")
will give you current timestamp in milliseconds.
Time.now.to_f can help you but it returns seconds.
In general, when working with benchmarks I:
put in variable the current time;
insert the block to test;
put in a variable the current time, subtracting the preceding current-time value;
It's a very simple process, so I'm not sure you were really asking this...
Try subtracting the first Time.now from the second. Like so:
a = Time.now
sleep(3)
puts Time.now - a # about 3.0
This gives you a floating-point number of the seconds between the two times (and with that, the milliseconds).
If you want something precise, unaffected by other part of your app (Timecop) or other programs (like NTP), use Process#clock_gettime with Process::CLOCK_MONOTONIC to directly get the processor time.
t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
# other code
t2 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
Also, if you are trying to benchmark some code tho, there is the Benchmark module for that!
require "benchmark"
time = Benchmark.realtime do
# code to measure
end

Resources