How to reverse Date.parse() - ruby

My code:
require 'Date'
s = "I'm going away on Oct 2, 2012th"
puts Date.parse(s)
=> 2012-10-02
I want to delete the date from my strings, that I found with Date.parse(s). The problem is, I know that there is a date, but not how it was written in the string. I know that Date.parse found it and converted "2012-10-02" to a new format.

Here is a quick and dirty solution. The function date_string
returns just the portion of the string containing the date
found by parse.
require 'date'
DATE_ERROR = -1
# If the string doesn't contain a date, it raises an
# exception. This little helper routine catches the
# exception.
def get_date(s)
date = 0
begin
date = Date.parse(s)
rescue
date = DATE_ERROR
end
date
end
# Returns just the part of the string containing the date
def date_string(s)
# First, find the date contained in the string
date = get_date(s)
return "" if date == DATE_ERROR
# Repeatedly chop off characters from the front to find the
# start of the date
first = 1
while date == get_date(s[first..-1])
first += 1
end
# Repeatedly chop off characters from the end to find the
# end of the date
last = s.length - 2
while date == get_date(s[0..last])
last -= 1
end
#Return just the date
s[first - 1..last + 1]
end
puts date_string("I'm going away on Oct 2, 2012th")
puts date_string("I'm going away on 10/2/12 and not coming back")
puts date_string("10 Nov 1999")
puts date_string("I see no date here")
This outputs:
Oct 2, 2012
10/2/12
10 Nov 1999
So you could do something like:
s = "I'm going away on Oct 2, 2012th"
datestr = date_string(s)
s.gsub!(datestr, "")
puts s

Date doesn't appear to be able to tell you where it found the date. You're probably going to have to write your own custom date finder.

Related

Ruby - How do you add data into a nested array?

I'm relatively new to Ruby and I'm trying to design a code that will help me with work.
What I would like the code to do is allow the user to input the "invoice numbers" for each day of the week starting on Monday, then when the user has finished entering the invoice numbers, the program should ask how many hours were worked for each day. Then, I would like for the program to divide the amount of hours worked by the amount of invoice numbers inputted for each respective day and output the "billable time" in a format like this:
say Monday worked 10 hours and you inputted invoice #123 and #124
The program should output the following -
Monday
#123 - 5 Hours
#124 - 5 Hours
but I would like for this to happen for every day of the week. I'm assuming I'll need to use a nested Array but I'm just confused as to how to go about adding the entries from the user and having the program know when to "move" to the next day to add the next set of entries.
Here is the code I have so far:
days = ["Mon","Tue","Wed","Thurs","Fri","Sat","Sun"]
entry = Array.new
days.each do |day|
while entry != "ok"
puts "Enter PR # for " + day
puts "/n Type 'ok' when finished"
entry.each do |input|
input << gets.to_s.chomp
end
end
end
Essentially I would just like for the program to recognize that the user is done inputting entries for that day by typing "ok" or something so that it can move from Monday to Tuesday, etc.
In a perfect world... at the end of the sequence, I would like for the program to combine the values of all similarly named invoice numbers from each day into one result (i.e. if invoice #123 applied on Monday and Tuesday, the program would add the sums of the billable hours from those two days and output the result as one total billable amount.)
Thank you in advance for any assistance with this as the knowledge that comes with it will be deeply valued!!
You might collect the needed information as follows.
DAYS = ["Mon", "Tue", "Wed", "Thurs", "Fri", "Sat", "Sun"]
def invoices_by_day
DAYS.each_with_object({}) do |day, inv_by_day|
prs = []
loop do
puts "Enter PR # for #{day}. Press ENTER when finished"
pr = gets.chomp
break if pr.empty?
prs << pr
end
hours =
if prs.any?
puts "Enter hours worked on #{day}"
gets.to_f
else
0
end
inv_by_day[day] = { prs: prs, hours: hours }
end
end
Suppose
h = invoices_by_day
#=> { "Mon"=>{ prs: ["123", "124"], hours: 21.05 },
# "Tue"=>{ prs: ["125"], hours: 31.42 },
# "Wed"=>{ prs: ["126", "127"], hours: 68.42 },
# "Thu"=>{ prs: ["128"], hours: 31.05 },
# "Fri"=>{ prs: [], hours: 0 },
# "Sat"=>{ prs: ["129", "130"], hours: 16.71 }
# "Sun"=>{ prs: ["131"], hours: 55.92 } }
Then you could display this information in various ways, such as the following.
h.each do |day, g|
puts day
if g[:prs].empty?
puts " No invoices"
else
avg = (g[:hours]/g[:prs].size).round(2)
g[:prs].each { |pr| puts " ##{pr}: #{avg}" }
end
end
Mon
#123: 10.53
#124: 10.53
Tue
#125: 31.42
Wed
#126: 34.21
#127: 34.21
Thurs
#128: 31.05
Fri
No invoices
Sat
#129: 8.36
#130: 8.36
Sun
#131: 55.92
As a rule it is good practice to separate the data collection from the manipulation of the data and the presentation of the results. That makes it easier to change either at a later date.
I use Kernel#loop with break for virtually all looping. One advantage of loop is that employs a block (unlike while and until), confining the scope of local variables created within the block to the block. Another advantage is that it handles StopIteration exceptions by breaking out of the loop. That exception is raised by an enumerator when it attempts to generate an element beyond its last value. For example, if e = 3.times #=> #<Enumerator: 3:times>, then e.next #=> 0, e.next #=> 1 e.next #=> 2, e.next #=> StopIteration.
Hey – here you go –> https://gist.github.com/Oluwadamilareolusakin/4f147e2149aa97266cfbb17c5c118fbf
Made a gist for you that may help, let me know!
NOTE: Be careful with the while true so you don't run into an infinite loop
Here's it is for easy reference:
# frozen_string_literal: true
def display_billable_hours(entries)
entries.each do |key, value|
puts "#{key}:"
puts value
end
end
def handle_input
days = ["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"]
entries = {}
days.each do |day|
loop do
p "Enter your invoice number for #{day}:"
invoice_number = gets.chomp
break if invoice_number.length > 0
end
loop do
p "How many hours did you work on #{day}?:"
hours_worked = gets.chomp
break if hours_worked.length > 0
end
entries[day] = "##{invoice_number} - #{hours_worked} Hours"
end
entries
end
def do_audit
entries = handle_input
display_billable_hours(entries)
end
do_audit

Ruby Time.parse incorrectly returns timestamp for String

I am facing a weird bug (?) in Ruby
Time.parse("David").to_i returns "no time information in "David"`
Time.parse("David1").to_i returns "no time information in "David1"`
However
Time.parse("David10").to_i returns 1570654800
It seems that any string with more than 2 numbers at the end manages to pass the Time conversion in Ruby. Is this a bug?
I am trying to create a single method than can handle conversion of strings to Timestamps where relevant or simply back to strings if conversion is not possible but for instances where my string includes 2+ numbers, it fails
if value.is_a? String
# if it's string of a date format
begin
Time.parse(value).to_i
rescue StandardError => e
value.downcase
end
# it's another object type - probably DateTime, Time or Date
else
value.nil? ? 0 : value.to_f
end
Internally time.rb uses, following,
def parse(date, now=self.now)
comp = !block_given?
d = Date._parse(date, comp)
year = d[:year]
year = yield(year) if year && !comp
make_time(date, year, d[:mon], d[:mday], d[:hour], d[:min], d[:sec], d[:sec_fraction], d[:zone], now)
end
It used to parse day, month later year by precision, Range of digits when exeed to 3, it consider it as year

Return strings within a range of dates in Ruby

I'm working on a script which looks at a group of files, checks to see to if they have any lines within a "#due" tag followed by a date range, then takes those lines and prints them to a separate file. (Basically, I have a set of text files that have items with due dates, and I want a daily synopsis of what is overdue, due today, and due within the next 4 days.)
Right now, I'm doing this with blunt force:
tom1 = (Time.now + 86400).strftime('%Y-%m-%d')
tom2 = (Time.now + (86400 * 2)).strftime('%Y-%m-%d')
tom3 = (Time.now + (86400 * 3)).strftime('%Y-%m-%d')
etc., and then:
if line =~ /#due\(#{tom1}\)/
found_completed = true
project += line.gsub(/#due\(.*?\)/,'').strip + " **1 day**" + "\n" + "\t"
end
if line =~ /#due\(#{tom2}\)/
found_completed = true
project += line.gsub(/#due\(.*?\)/,'').strip + " **2 days**" + "\n" + "\t"
end
Etc. etc.
I do this for 30 days in the past, the current day, and then 4 days in the future. I'm wondering, perhaps if I require "Date" instead of "Time," and then set up some sort of range, if there isn't a more elegant way to do this.
Thanks!
I believe this will work, or with light massaging will work. I tested it on a very few dates and it worked for them. When you run the script, make sure you pass in the date through the command line as YYYY-MM-DD. I wrote to the console rather than another file just so I could check the test values easier. I used the begin-rescue block in the event that a line in your file did not have a well formed date value.
require 'date'
def due(due_dates)
due_dates.each do |x|
puts x
end
end
today = Date.parse(ARGV.shift)
f = File.readlines('path_to_file')
due_today = []
due_within_four = []
past_due = []
f.each do |line|
begin
d = Date.parse(line)
due_today << line if d == today
due_within_four << line if (today+1..today+4).cover? d
past_due << line if (today-30..today-1).cover? d
rescue
next
end
end
puts "DUE TODAY"
due(due_today)
puts "\nDUE WITHIN FOUR DAYS"
due(due_within_four)
puts "\nOVERDUE"
due(past_due)
Some might argue it's overkill for a relatively simply situation like this but I like using the ice_cube (https://github.com/seejohnrun/ice_cube) gem for recurring date functionality. Here's an example of how you could approach it, salt to taste.
require 'ice_cube'
lines = File.readlines('myfile.txt')
start_date = Time.now - 30 * 86400
end_date = Time.now + 4 * 86400
matched_lines = IceCube::Schedule.new(start_date).tap do |schedule|
schedule.add_recurrence_rule IceCube::Rule.daily
end.occurrences(end_date).collect do |time|
formatted_time = time.strftime('%Y-%m-%d')
lines.map { |line| line if line =~ /#due\(#{formatted_time}\)/ }
end.flatten

Finding the date for a given week number

I am trying to do some date math based on the week number of a given year. For example:
date = Date.today # Monday, March 5, 2012
puts date.cwyear # 2012
puts date.cweek # 10 (10th week of 2012)
Now that I know what the current week is, I want to figure out what the next week and previous week are. I need to take the year (2012) and the week number (10) and turn it back into a date object so I can calculate the value for the next/previous week. How can I do this?
You want Date.commercial:
require 'date'
now = Date.today #=> 2012-03-05
monday_next_week = Date.commercial(now.cwyear,now.cweek+1) #=> 2012-03-12
next_sunday_or_today = monday_next_week - 1 #=> 2012-03-11
Note that weeks start on Monday, so if you are on a Sunday and ask for next monday - 1 you'll get the same day.
Note also that if you don't want Mondays you can also specify the day number in the method:
thursday_next_week = Date.commercial(now.cwyear,now.cweek+1,4) #=> 2012-03-15
Calculating on a day basis is pretty simple with Date objects. If you just want to get the previous / next week from a given Date object use the following:
date = Date.today
previous_week = (date - 7).cweek
next_week = (date + 7).cweek
In ActiveSupport you have helper to convert Fixnum to time http://as.rubyonrails.org/classes/ActiveSupport/CoreExtensions/Numeric/Time.html use:
date = Date.today
week_ago = date - 1.week
next_week = date + 1.week
I have created some methods to get week number of a given date
something like this:
def self.get_week(date)
year = date.year
first_monday_of_the_year = self.get_first_monday_of_the_year(year)
# The first days of January may belong to the previous year!
if date < first_monday_of_the_year
year -= 1
first_monday_of_the_year = self.get_first_monday_of_the_year(year)
end
day_difference = date - first_monday_of_the_year
week = (day_difference / 7).to_i + 1
week
end
def self.get_monday_of_year_week(year, week)
d = self.get_first_monday_of_the_year(year)
d + ((week - 1) * 7).days
end
def self.get_first_monday_of_the_year(year)
d = Date.new(year, 1, 7) # 7 Jan is always in the first week
self.get_monday_of_week(d)
end
def self.get_monday_of_week(date)
wday = (date.wday + 6) % 7
date - wday.days
end
Assuming you mean "a given week number in the current year", you can do the following:
2.weeks.since(Time.gm(Time.now.year))
=> Fri Jan 15 00:00:00 UTC 2010
Substitute (week_number - 1) for the 1 in the above, and you'll get a date in the desired week.

Iterating over days in Ruby

I've tried the following:
def next_seven_days
today = Date.today
(today .. today + 7).each { |date| puts date }
end
But this just gives me the first and last date. I can't figure out how to get all the ones in between.
I was trying to follow the example here: http://www.whynotwiki.com/Ruby_/_Dates_and_times
I think you want something more like this:
def next_seven_days
today = Date.today
(today .. today + 7).inject { |init, date| "#{init} #{date}" }
end
In this case, the return value is a concatenated string containing all the dates.
Alternatively, if it's not a concatenated string you want, you could change the "#{init} #{date}" part of it.
As a side note, using puts in ruby on rails won't print to the web page. When you use <%= next_seven_days %>, the return value of that function is what will be printed to the page. The each function returns the range in parentheses.
Your code will definitely print all eight days to stdout. Your problem is that you're looking at the return value (which since each returns self will be (today .. today + 7)).
Note that if you print to stdout inside an erb template, that won't cause the output to show up in the rendered template.
Your function RETURNS an enumeration designated by 2011-07-07..2011-07-14 which is displayed in your view, but your puts prints to STDOUT which is not going to be your view, but the console screen your server is running in =)
If you want your view to show a list of the seven days, you need to actually create the string that does that and RETURN that :)
def next_seven_days
outputstr = ""
today = Date.today
(today..(today+7.days)).each { |date| outputstr += date.to_s }
return outputstr
end
def next_seven_days
today = Date.today
(today..(today+7.days)).each { |date| puts date }
end
Use the "next" method
def next_seven_days
today= Date.today
7.times do
puts today
today=today.next
end
end

Resources