Ruby Date range and matching - ruby

I'm writing a small log-sniffer program for my work environment that searches for a few key words in the logs and let's the user know they have some things to look at. That part is pretty simple, but one of the features I'm trying to implement is the option for the user to choose how far back form today they would like to go, so they're not getting errors from months ago that no longer matter.
I've got the date, and I have the gap from now and a user-specified range. I'm just not sure how to get the range and matching working. Here is my code:
require 'date'
### Get the logfile path from the user
p "What is the path to your log file?"
logfile = gets.chomp
### Get number of days user would like to go back
p "How many days back from today would you like to look?"
num_days = gets.chomp
############################################################################################
### Define Log class. Accept argumnet for path of the file. Called by foo = Log.new
############################################################################################
class Log
def scan_log(file_name, days)
error_exists = false
verify = false
apn = false
strict = false
days = days.to_i
# time = Time.now.strftime("%Y-%m-%d")
now = Date.today
days_ago = (now - days)
p now
p days_ago
File.open(file_name, "r") do |file_handle|
file_handle.each_line do |line|
if line =~ /Unable to verify signature/
verify = true
error_exists = true
end
if line =~ /gateway.push.apple.com/
apn = true
error_exists = true
end
if line =~ /Data truncation/
strict = true
error_exists = true
end
end
end
p "Verify signature error found" if verify
p "You have an APNS error" if apn
p "You have strict mode enabled" if strict
end
end
l = Log.new
l.scan_log(logfile, num_days)
My thought is that a loop under file_handle.each_line do... would work. I would include the existing if statements in the loop, and the loop would match the date range set by the user to the dates in the logs. The format in the log is:
2013-04-17 15:10:42, 767
I don't care about the timestamp, just the datestamp.
Thanks if you can help.

one robust way to parse a free-form date would be:
> Time.parse(" 2013-04-17 13:15:24 -0700 ").strftime("%Y-%m-%d")
=> "2013-04-17"
> Time.parse("Wed Apr 17 13:15:09 PDT 2013 ").strftime("%Y-%m-%d")
=> "2013-04-17"
assuming the user specifies the start_date in "%Y-%m-%d" format and the time range, you could do this:
start_date = Time.parse( user_provided_date ) # e.g. "2013-04-10"
log_date = Time.parse( line )
do_something if (start_date - log_date) < number_of_seconds # or whatever time range
If you just want to show all logs which are newer than a given time-range, then you could do this:
log_date = Time.parse( line )
do_something if (Time.now - log_date) < number_of_seconds # or whatever time range
To get pretty time ranges in Rails style, like 1.day or 12.hours , you can include ActiveSupport::Duration

You can construct a Range from dates (like the question title suggests) and use it's include? method.
require 'date'
puts "How many days back from today would you like to look?"
days_back = gets.to_i #input: 3
logline_date = "2013-04-17 15:10:42, 767"
end_date = Date.today
start_date = end_date - days_back
selected_range = start_date..end_date
p selected_range.include?( Date.parse( logline_date )) #true (today for me, that is)

Related

Creating an error message for an invalid input

I have the following code that returns the # of days in any given month, which works fine unless someone types in something that isn't a date, or they format the date wrong. To remedy this I want to send out an error message for an invalid input, but I don't know how. So how do I create an error message for this small app?
#type in the month and year you want like so ---> "Feb 2034"
require 'date'
input = gets.chomp
inputArray = input.split(" ").to_a
textMonth = inputArray[0]
textYear = inputArray[1]
startOfMonth = Date.strptime(input, "%b %Y")
nextMonth = startOfMonth.next_month
endOfMonth = nextMonth - 1
daysInMonth = (endOfMonth - startOfMonth + 1).to_i
puts "#{textMonth} of year #{textYear} has #{daysInMonth} days!"
Probably the best way to do this is putting your input in a while loop, prompting for a new answer every time the input isn't what you expected it to be.
To check the input you should use a Regexp. Here's an explanation
how to write a regexp to match a date.
For Creating a Custom Error refer below code:
Here I create and raise InvalidDateError for the wrong date input.
#type in the month and year you want like so ---> "Feb 2034"
class InvalidDateError < StandardError
end
require 'date'
require 'pry-byebug'
input = gets.chomp
inputArray = input.split(" ").to_a
textMonth = inputArray[0]
textYear = inputArray[1]
begin
startOfMonth = Date.strptime(input, "%b %Y")
nextMonth = startOfMonth.next_month
endOfMonth = nextMonth - 1
daysInMonth = (endOfMonth - startOfMonth + 1).to_i
puts "#{textMonth} of year #{textYear} has #{daysInMonth} days!"
rescue StandardError=> e
raise InvalidDateError.new("Invalid Date : #{input}")
end
If you don't want to raise an error and only want to show error message then replace raise InvalidDateError.new("Invalid Date : #{input}")
with puts "Invalid Date : #{input}"
As suggested by Viktor, and stolen :) from crantok
require 'date'
date_valid = false
while !date_valid
puts 'Insert date as yyyy-mm-dd:'
input_date = gets.chomp
begin
parsed_date = Date.parse(input_date)
date_valid = true
rescue ArgumentError
puts 'format error'
end
end
month = parsed_date.month
year = parsed_date.year
days_in_month = Date.new(year, month, -1).day
puts "In #{year} month #{month} has #{days_in_month} days"

Ruby: file output blank

So I'm trying to build a program which tells me how many days are left till someone's birthday. I have a text file that I'm drawing data from. The problem is the save method is not working for some reason, and nothing is being printed to the output file. Thank you so much in advance!
require 'date'
Person = Struct.new(:name, :bday)
module Family
Member = Hash.new
File.readlines("bday_info.txt").each do |line|
name, bday = line.split(',')
person = Person.new(name, bday)
Member[name] = bday
end
def self.next_bday(name)
birthday = Date.parse(Family::Member[name])
this_year = Date.new(Date.today.year, birthday.month, birthday.day)
next_year = Date.new(Date.today.year + 1, birthday.month, birthday.day)
if this_year > Date.today
puts "\n#{(this_year - Date.today).to_i} days to #{name}'s birthday"
else
puts "\n#{(next_year - Date.today).to_i} days to #{name}'s birthday"
end
end
def self.save(name)
File.open("days_left.txt", "w") do |file|
file.puts "#{next_bday(name)}"
end
end
end
loop do
puts "\nName:"
response = gets.chomp.capitalize
if response.downcase == "quit"
break
elsif Family::Member.has_key?("#{response}") == false
puts "\nYou ain't on the list"
else
Family.next_bday(response)
Family.save(response) #WHY IS THIS LINE NOT WORKING???
end
end
Your Family.next_bday does not return anything (to be more precise - it's returning nil that last puts returns), thus nothing is written to the file.
Other notes(not directly related):
No reason in calling Family.next_bday(response) twice, just save return value from the first call
Family::Member constant looks more like a variable, more clean design will be to make the whole thing a class that takes input file name, reads data in initializer and then stores into a instance variable

Converting military time in Ruby

What is the easiest way to convert military (24 hour time) to HH:MM format in Ruby?
I have two 24 hour time values stored in a record and I want to display them in the view as something like "2:00 - 4:00" when starting with two integer values, say 1400 and 1600.
My hack-y solution was to create a helper function:
def format_time(time)
if (time-1200 < 1000)
if (time-1200 < 100)
return time.to_s.insert(2, ":")
else
return (time-1200).to_s.insert(1, ":")
end
else
return (time-1200).to_s.insert(2, ":")
end
end
I'm not even sure that works all the time. I'm assuming there is a better way to do this.
UPDATE: I also need this to work on values that do not end in '00'. IE 1430 or 830.
You could parse the string with Time.strptime to get a time object. This can be used to be printed with Time.strftime:
require 'time'
%w{1400 1600}.each{|t|
p Time.strptime(t, '%H%M').strftime('%l:%M')
}
Advantage: Changes of input and output format are quite easy.
Alternative:
require 'date' #load DateTime.strptime
%w{1400 1600}.each{|t|
p DateTime.strptime(t, '%H%M').strftime('%l:%M')
}
I hope the solution with DateTime works also with ruby 1.8.
Update for 830:
This solution will not work with 830 - strptime does not accpt %l. But 0830 would work fine. For this case you need an adapted solution:
require 'date' #load DateTime
%w{1400 1600 830}.each{|t|
t = '%04i' % t
p DateTime.strptime(t, '%H%M').strftime('%l:%M')
}
This will work, and it's easier o read
def fomat_time(time)
time.to_s.sub(/^(\d{1,2})(\d{2})$/,'\1:\2')
end
You could do something like this:
def format_time(time)
t = (time <= 1200)?time : time - 1200
return (t.to_s.size == 3)?t.to_s.insert(1,":") : t.to_s.insert(2,":")
end
That should work...
If you always have the times in the form of integers like eg like 1600, 1630, 930 or 130, then this might be a solution:
require 'time'
def format_time(time)
# normalize time
time = time.to_s.rjust(4, '0') if time[0] !~ /[12]/
time = time.to_s.ljust(4, '0') if time[0] =~ /[12]/
Time.strptime(time, '%H%M').strftime('%l:%M').strip
end
time = 900
p format_time(time) # "9:00"
time = 1630
p format_time(time) # "4:30"
time = 930
p format_time(time) # "9:30"
time = 130
p format_time(time) # "1:30"

How to check whether a string contains today's date in a specific format

I'm parsing some RSS feeds that aggregate what's going on in a given city. I'm only interested in the stuff that is happening today.
At the moment I have this:
require 'rubygems'
require 'rss/1.0'
require 'rss/2.0'
require 'open-uri'
require 'shorturl'
source = "http://rss.feed.com/example.xml"
content = ""
open(source) do |s| content = s.read end
rss = RSS::Parser.parse(content, false)
t = Time.now
day = t.day.to_s
month = t.strftime("%b")
rss.items.each do |rss|
if "#{rss.title}".include?(day)&&(month)
# does stuff with it
end
end
Of course by checking whether the title (that I know contains the date of event in the following format: "(2nd Apr 11)") contains the day and the month (eg. '2' and 'May') I get also info about the events that happen on 12th May, 20th of May and so on. How can I make it foolproof and only get today's events?
Here's a sample title: "Diggin Deeper # The Big Chill House (12th May 11)"
today = Time.now.strftime("%d:%b:%y")
if date_string =~ /(\d*).. (.*?) (\d\d)/
article_date = sprintf("%02i:%s:%s", $1.to_i, $2, $3)
if today == article_date
#this is today
else
#this is not today
end
else
raise("No date found in title.")
end
There could potentially be problems if the title contains other numbers. Does the title have any bounding characters around the date, such as a hyphen before the date or brackets around it? Adding those to the regex could prevent trouble. Could you give us an example title? (An alternative would be to use Time#strftime to create a string which would perfectly match the date as it appears in the title and then just use String#include? with that string, but I don't think there's an elegant way to put the 'th'/'nd'/'rd'/etc on the day.)
Use something like this:
def check_day(date)
t = Time.now
day = t.day.to_s
month = t.strftime("%b")
if date =~ /^#{day}nd\s#{month}\s11/
puts "today!"
else
puts "not today!"
end
end
check_day "3nd May 11" #=> today!
check_day "13nd May 11" #=> not today!
check_day "30nd May 11" #=> not today!

get next/previous month from a Time object

I have a Time object and would like to find the next/previous month. Adding subtracting days does not work as the days per month vary.
time = Time.parse('21-12-2008 10:51 UTC')
next_month = time + 31 * 24 * 60 * 60
Incrementing the month also falls down as one would have to take care of the rolling
time = Time.parse('21-12-2008 10:51 UTC')
next_month = Time.utc(time.year, time.month+1)
time = Time.parse('01-12-2008 10:51 UTC')
previous_month = Time.utc(time.year, time.month-1)
The only thing I found working was
time = Time.parse('21-12-2008 10:51 UTC')
d = Date.new(time.year, time.month, time.day)
d >>= 1
next_month = Time.utc(d.year, d.month, d.day, time.hour, time.min, time.sec, time.usec)
Is there a more elegant way of doing this that I am not seeing?
How would you do it?
Ruby on Rails
Note: This only works in Rails (Thanks Steve!) but I'm keeping it here in case others are using Rails and wish to use these more intuitive methods.
Super simple - thank you Ruby on Rails!
Time.now + 1.month
Time.now - 1.month
Or, another option if it's in relation to the current time (Rails 3+ only).
1.month.from_now
1.month.ago
Personally I prefer using:
Time.now.beginning_of_month - 1.day # previous month
Time.now.end_of_month + 1.day # next month
It always works and is independent from the number of days in a month.
Find more info in this API doc
you can use standard class DateTime
require 'date'
dt = Time.new().to_datetime
=> #<DateTime: 2010-04-23T22:31:39+03:00 (424277622199937/172800000,1/8,2299161)>
dt2 = dt >> 1
=> #<DateTime: 2010-05-23T22:31:39+03:00 (424282806199937/172800000,1/8,2299161)>
t = dt2.to_time
=> 2010-05-23 22:31:39 +0200
There are no built-in methods on Time to do what you want in Ruby. I suggest you write methods to do this work in a module and extend the Time class to make their use simple in the rest of your code.
You can use DateTime, but the methods (<< and >>) are not named in a way that makes their purpose obvious to someone that hasn't used them before.
If you do not want to load and rely on additional libraries you can use something like:
module MonthRotator
def current_month
self.month
end
def month_away
new_month, new_year = current_month == 12 ? [1, year+1] : [(current_month + 1), year]
Time.local(new_year, new_month, day, hour, sec)
end
def month_ago
new_month, new_year = current_month == 1 ? [12, year-1] : [(current_month - 1), year]
Time.local(new_year, new_month, day, hour, sec)
end
end
class Time
include MonthRotator
end
require 'minitest/autorun'
class MonthRotatorTest < MiniTest::Unit::TestCase
describe "A month rotator Time extension" do
it 'should return a next month' do
next_month_date = Time.local(2010, 12).month_away
assert_equal next_month_date.month, 1
assert_equal next_month_date.year, 2011
end
it 'should return previous month' do
previous_month_date = Time.local(2011, 1).month_ago
assert_equal previous_month_date.month, 12
assert_equal previous_month_date.year, 2010
end
end
end
below it works
previous month:
Time.now.months_since(-1)
next month:
Time.now.months_since(1)
I just want to add my plain ruby solution for completeness
replace the format in strftime to desired output
DateTime.now.prev_month.strftime("%Y-%m-%d")
DateTime.now.next_month.strftime("%Y-%m-%d")
You can get the previous month info by this code
require 'time'
time = Time.parse('2021-09-29 12:31 UTC')
time.prev_month.strftime("%b %Y")
You can try convert to datetime.
Time gives you current date, and DateTime allows you to operate with.
Look at this:
irb(main):041:0> Time.new.strftime("%d/%m/%Y")
=> "21/05/2015"
irb(main):040:0> Time.new.to_datetime.prev_month.strftime("%d/%m/%Y")
=> "21/04/2015"
Here is a solution on plain ruby without RoR, works on old ruby versions.
t=Time.local(2000,"jan",1,20,15,1,0);
curmon=t.mon;
prevmon=(Time.local(t.year,t.mon,1,0,0,0,0)-1).mon ;
puts "#{curmon} #{prevmon}"
Some of the solutions assume rails. But, in pure ruby you can do the following
require 'date'
d = Date.now
last_month = d<<1
last_month.strftime("%Y-%m-%d")
Im using the ActiveSupport::TimeZone for this example, but just in case you are using Rails or ActiveSupport it might come in handy.
If you want the previous month you can substract 1 month
time = Time.zone.parse('21-12-2008 10:51 UTC')
time.ago(1.month)
$ irb
irb(main):001:0> time = Time.now
=> 2016-11-21 10:16:31 -0800
irb(main):002:0> year = time.year
=> 2016
irb(main):003:0> month = time.month
=> 11
irb(main):004:0> last_month = month - 1
=> 10
irb(main):005:0> puts time
2016-11-21 10:16:31 -0800
=> nil
irb(main):006:0> puts year
2016
=> nil
irb(main):007:0> puts month
11
=> nil
irb(main):008:0> puts last_month
10
=> nil

Resources