Creating an error message for an invalid input - ruby

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"

Related

Date validation for invalid years

I am trying to do what feels like something quite basic, but struggling. I want to be able to determine if a date is valid, if it is, parse it and output in a formatted string, else return 'bad date'. This is where I have got so far;
require 'date'
date_last_printed = current_item.getProperties['Last Printed']
begin
d = DateTime.parse(date_last_printed.to_s)
if Date.valid_date?(d.year, d.month, d.day)
d.strftime("%d/%m/%Y %H:%M:%S")
else
'bad date'
end
rescue Exception => ex
ex.message
end
However 01/01/1601 is returning as a valid date.
How can I adjust my snippet to return only valid dates (on or after the start of Unix epoch time).
you could perform epoch conversion inside the begin statement.
begin
raise if (t=Time.parse(date_last_printed)).to_i < 0
t.strftime("%d/%m/%Y %H:%M:%S")
rescue
'bad date'
end

ArgumentError: invalid strptime format - `%m/%d/%y' work around

I am in the process of working with an sftp import bug in which I'm trying to flag any dates that are imported that are incorrect. There are two types of dates that could be off. The first is when the year is in the future, or way in the past; the second is when the actual months and days are too high. (Example, 13/20/1995, or 11/35/2000)
I'm using strptime and for dates that are off, flagging them and displaying them as a specific message. The problem I'm running into is that with the strptime format that I'm using, the errors happen right before I sub in the error message.
table_birth_dates = self.class.connection.execute("SELECT birth_date FROM #{temp_table_name}").values.flatten
table_birth_dates.map! do |date|
birth_date = Date.strptime(date, '%m/%d/%Y')
if birth_date.nil?
month_day_error_message = 'Invalid Month/Day'
elsif birth_date > Time.zone.today
future_error_message = 'Year in Future'
elsif birth_date.year < 1900
past_error_message = 'Year too old'
else
birth_date
end
end
The error is happening at the birth_date = Date.strptime(date, '%m/%d/%Y')
For a date such as 10/3/1891, it displays them as Sat, 03 Oct 1891.
However, for the messed up dates such as 33/33/2000 it shows me an error (which makes sense) however I was hoping to fix this error in my conditional.
Would anyone know what I could do?
If you want to use strptime your only option really is to rescue the error:
begin
birth_date = Date.strptime(date, '%m/%d/%Y')
rescue ArgumentError => ex
raise ex unless ex.message == 'invalid date'
# handle invalid date
end
You could set date to, e.g., :invalid there and then have date == :invalid in your conditional if you want to keep all the logic there instead of in the rescue itself.

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

Calling multiple methods on a CSV object

I have constructed an Event Manager class that performs parsing actions on a CSV file, and produces html letters using erb. It is part of a jumpstart labs tutorial
The program works fine, but I am unable to call multiple methods on an object without the earlier methods interfering with the later methods. As a result, I have opted to create multiple objects to call instance methods on, which seems like a clunky inelegant solution. Is there a better way to do this, where I can create a single new object and call methods on it?
Like so:
eventmg = EventManager.new("event_attendees.csv")
eventmg.print_valid_phone_numbers
eventmg_2 = EventManager.new("event_attendees.csv")
eventmg_2.print_zipcodes
eventmg_3 = EventManager.new("event_attendees.csv")
eventmg_3.time_targeter
eventmg_4 = EventManager.new("event_attendees.csv")
eventmg_4.day_of_week
eventmg_5 = EventManager.new("event_attendees.csv")
eventmg_5.create_thank_you_letters
The complete code is as follows
require 'csv'
require 'sunlight/congress'
require 'erb'
class EventManager
INVALID_PHONE_NUMBER = "0000000000"
Sunlight::Congress.api_key = "e179a6973728c4dd3fb1204283aaccb5"
def initialize(file_name, list_selections = [])
puts "EventManager Initialized."
#file = CSV.open(file_name, {:headers => true,
:header_converters => :symbol} )
#list_selections = list_selections
end
def clean_zipcode(zipcode)
zipcode.to_s.rjust(5,"0")[0..4]
end
def print_zipcodes
puts "Valid Participant Zipcodes"
#file.each do |line|
zipcode = clean_zipcode(line[:zipcode])
puts zipcode
end
end
def clean_phone(phone_number)
converted = phone_number.scan(/\d/).join('').split('')
if converted.count == 10
phone_number
elsif phone_number.to_s.length < 10
INVALID_PHONE_NUMBER
elsif phone_number.to_s.length == 11 && converted[0] == 1
phone_number.shift
phone_number.join('')
elsif phone_number.to_s.length == 11 && converted[0] != 1
INVALID_PHONE_NUMBER
else
phone_number.to_s.length > 11
INVALID_PHONE_NUMBER
end
end
def print_valid_phone_numbers
puts "Valid Participant Phone Numbers"
#file.each do |line|
clean_number = clean_phone(line[:homephone])
puts clean_number
end
end
def time_targeter
busy_times = Array.new(24) {0}
#file.each do |line|
registration = line[:regdate]
prepped_time = DateTime.strptime(registration, "%m/%d/%Y %H:%M")
prepped_time = prepped_time.hour.to_i
# inserts filtered hour into the array 'list_selections'
#list_selections << prepped_time
end
# tallies number of registrations for each hour
i = 0
while i < #list_selections.count
busy_times[#list_selections[i]] += 1
i+=1
end
# delivers a result showing the hour and the number of registrations
puts "Number of Registered Participants by Hour:"
busy_times.each_with_index {|counter, hours| puts "#{hours}\t#{counter}"}
end
def day_of_week
busy_day = Array.new(7) {0}
d_of_w = ["Monday:", "Tuesday:", "Wednesday:", "Thursday:", "Friday:", "Saturday:", "Sunday:"]
#file.each do |line|
registration = line[:regdate]
# you have to reformat date because of parser format
prepped_date = Date.strptime(registration, "%m/%d/%y")
prepped_date = prepped_date.wday
# adds filtered day of week into array 'list selections'
#list_selections << prepped_date
end
i = 0
while i < #list_selections.count
# i is minus one since days of week begin at '1' and arrays begin at '0'
busy_day[#list_selections[i-1]] += 1
i+=1
end
#busy_day.each_with_index {|counter, day| puts "#{day}\t#{counter}"}
prepared = d_of_w.zip(busy_day)
puts "Number of Registered Participants by Day of Week"
prepared.each{|date| puts date.join(" ")}
end
def legislators_by_zipcode(zipcode)
Sunlight::Congress::Legislator.by_zipcode(zipcode)
end
def save_thank_you_letters(id,form_letter)
Dir.mkdir("output") unless Dir.exists?("output")
filename = "output/thanks_#{id}.html"
File.open(filename,'w') do |file|
file.puts form_letter
end
end
def create_thank_you_letters
puts "Thank You Letters Available in Output Folder"
template_letter = File.read "form_letter.erb"
erb_template = ERB.new template_letter
#file.each do |line|
id = line[0]
name = line[:first_name]
zipcode = clean_zipcode(line[:zipcode])
legislators = legislators_by_zipcode(zipcode)
form_letter = erb_template.result(binding)
save_thank_you_letters(id,form_letter)
end
end
end
The reason you're experiencing this problem is because when you apply each to the result of CSV.open you're moving the file pointer each time. When you get to the end of the file with one of your methods, there is nothing for anyone else to read.
An alternative is to read the contents of the file into an instance variable at initialization with readlines. You'll get an array of arrays which you can operate on with each just as easily.
"Is there a better way to do this, where I can create a single new object and call methods on it?"
Probably. If your methods are interfering with one another, it means you're changing state within the manager, instead of working on local variables.
Sometimes, it's the right thing to do (e.g. Array#<<); sometimes not (e.g. Fixnum#+)... Seeing your method names, it probably isn't.
Nail the offenders down and adjust the code accordingly. (I only scanned your code, but those Array#<< calls on an instance variable, in particular, look fishy.)

Ruby Date range and matching

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)

Resources