Task for telegram bot - ruby

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 <=.

Related

(Ruby) Padding single digits when it comes to time

I have 2 methods in a Timer class I'm creating. One method is where hours, minutes, and seconds are calculated from any given amount of seconds and the other method will pad any single digits with a "0". Every things I've look up so far isn't work for me. Below is my code:
class Timer
attr_accessor :seconds=(time), :time_string
def initialize(seconds = 00)
#seconds = seconds
end
def time_string
hours = padded((#seconds/3600)
minutes = padded(#seconds/60 - hours * 60)
seconds = padded(#seconds%60)
puts '#{hours):#{minutes}:#{seconds}'
end
def padded(x)
if x.length == 1
"0"+x
end
end
end
so if I put in 7683, the output I want to get is "02:08:03". but when I execute it, I get the following error message:
(eval):6: (eval):6:in `-': String can't be coerced into Fixnum (TypeError)
from (eval):6:in `time'
from (eval):19
I'm confused where this is erroring out.
To answer your question as to why your code is not working, you have got couple of conversion issues between FixNum and String throughout your code, you can fix it as follows:
def time_string(seconds)
hours = seconds/3600
minutes = seconds/60 - (hours * 60)
seconds = seconds%60
puts padded(hours)+':'+padded(minutes)+':'+padded(seconds)
end
You use the hours variable in the second statement, but because its already converted to string, it crashes, therefore its better to do all the calculations first, and only later use the padded method which returns the padded digits in string format. The padded method must also be modified to be consistent:
def padded(x)
if x.to_s.length == 1
return "0"+x.to_s
else
return x.to_s
end
end
Just keep in mind that the combination of the two methods will work only for numbers up to 86399, which will return 23:59:59. Any number passed to time_string bigger than that will pass the 24 hour mark and will return something like: 26:00:00
There is a brilliant method for padding, why not use it?
3.to_s.rjust(10,"*") #=> "*********3"
4.to_s.rjust(2,"0") #=> "04"
44.to_s.rjust(2,"0") #=> "44"
If you want a better solution than writing your own class, use at
Time.at(7683).strftime("%H:%M:%S") #=> "02:08:03"
There's no need to reinvent the wheel.
t = 7683 # seconds
Time.at(t).strftime("%H:%M:%S")
Time.at(seconds) converts your seconds into a time object, which then you can format with strftime. From the strftime documentation you can see you can get the parameters you want non padded, white padded or zero padded.
I tend to use something like this
"%02d" % 2 #=> 02
"%02d" % 13 #=> 13
It's part of the Kernel module: http://ruby-doc.org/core-2.1.3/Kernel.html#M001433

Ruby nil converted to_date

I am writing a little updater for a app that will update the last time someone logs in to the app and then saves it. its in rails 3.2 and ruby 1.9.3p327
def update_last_seen
if current_account.present?
if (Date.current - 1.day) > current_account.last_login_at
current_account.last_login_at = Date.current
current_account.save
end
end
end
I stuck that into the application controller and call it with a before filter. The only thing is that sometimes i have dates that are nil. so comparing date to nil gives errors. you cant call to_date on a nil.
nil.to_f => 0.0
nil.to_i => 0
nil.to_s => ""
nil.to_date => NoMethodError: undefined method `to_date' for nil:NilClass
"2013/07/26".to_date => Fri, 26 Jul 2013
how can i have it set it to be accepted as a blank date as it were.
i could always do
if current_account.last_login_at.blank? || (Date.current - 1.day) > current_account.last_login_at
that way it will set it if its not there but is there a semantic way of doing it?
UPDATE:
You might think this is has no point. the reason i ask is because there are some engines that have a nil for a date. for example excel will return dates two ways 1. as text as in "06/12/2013" or 2. an integer as the number of days from 01/01/1900. that date is excels nil date i was hoping that there was a default date for nils for Ruby. if there isn't you can just comment nope there isn't sorry man. giving a downvote without explanation as to why means that you really don't care about helping/teaching anything you're just there pushing buttons. if i did something wrong with this question you can tell me ill try fix it, if it doesnt make sence?
You could add in another method to clean up the code a little bit.
def new_login_since?(last_login)
last_login.blank? || (Date.current - 1.day) > last_login
end
def update_last_seen
if current_account.present? && new_login_since?(current_account.last_login_at)
current_account.update_attributes { last_login_at: Date.current }
end
end
To answer the actual question ... you can monkey-patch the NilClass like this
class NilClass
def to_date
Date.today
end
end
nil.to_date # => #<Date: 2013-09-26 ((2456562j,0s,0n),+0s,2299161j)>
Of course, the accepted answer shows the better approach.

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.

Calculating a time before 1970 (fails with negative seconds)

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

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