Extract date part from a file path string - ruby

I have a string that looks like this: log/archive/2016-12-21.zip, and I need to extract the date part.
So far I have tried these solutions:
1) ["log/archive/2016-12-21.zip"].map{|i|i[/\d{4}-\d{2}-\d{2}/]}.first
2) "log/archive/2016-12-21.zip".to_date
3) "log/archive/2016-12-21.zip".split("/").last.split(".").first
Is there a better way of doing this?

You can use File.basename passing the extension:
File.basename("log/archive/2016-12-21.zip", ".zip")
# => "2016-12-21"
If you want the value to be a Date, simply use Date.parse to convert the string into a `Date.
require 'date'
Date.parse(File.basename("log/archive/2016-12-21.zip", ".zip"))

require 'date'
def pull_dates(str)
str.split(/[\/.]/).map { |s| Date.strptime(s, '%Y-%m-%d') rescue nil }.compact
end
pull_dates "log/archive/2016-12-21.zip"
#=> [#<Date: 2016-12-21 ((2457744j,0s,0n),+0s,2299161j)>]
pull_dates "log/2016-12-21/archive.zip"
#=> [#<Date: 2016-12-21 ((2457744j,0s,0n),+0s,2299161j)>]
pull_dates "log/2016-12-21/2016-12-22.zip"
#=> [#<Date: 2016-12-21 ((2457744j,0s,0n),+0s,2299161j)>,
# #<Date: 2016-12-22 ((2457745j,0s,0n),+0s,2299161j)>]
pull_dates "log/2016-12-21/2016-12-32.zip"
#=> [#<Date: 2016-12-21 ((2457744j,0s,0n),+0s,2299161j)>]
pull_dates "log/archive/2016A-12-21.zip"
#=> []
pull_dates "log/archive/2016/12/21.zip"
#=> []
If you just want the date string, rather than the date object, change the method as follows.
def pull_dates(str)
str.split(/[\/.]/).
each_with_object([]) { |s,a|
a << s if (Date.strptime(s, '%Y-%m-%d') rescue nil)}
end
pull_dates "log/archive/2016-12-21.zip"
#=> ["2016-12-21"]

This regex should cover most cases. It allows an optional non-digit between year, month and day :
require 'date'
def extract_date(filename)
if filename =~ /((?:19|20)\d{2})\D?(\d{2})\D?(\d{2})/ then
year, month, day = $1.to_i, $2.to_i, $3.to_i
# Do something with year, month, day, or just leave it like this to return an array : [2016, 12, 21]
# Date.new(year, month, day)
end
end
p extract_date("log/archive/2016-12-21.zip")
p extract_date("log/archive/2016.12.21.zip")
p extract_date("log/archive/2016:12:21.zip")
p extract_date("log/archive/2016_12_21.zip")
p extract_date("log/archive/20161221.zip")
p extract_date("log/archive/2016/12/21.zip")
p extract_date("log/archive/2016/12/21")
#=> Every example returns [2016, 12, 21]

Please try this
"log/archive/2016-12-21.zip".scan(/\d{4}-\d{2}-\d{2}/).pop
=> "2016-12-21"
If the date format is invalid, it will return nil.
Example:-
"log/archive/20-12-21.zip".scan(/\d{4}-\d{2}-\d{2}/).pop
^^
=> nil
Hope it helps.

Related

Ruby library to parse strings into the appropriate data type Google Sheets-style [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 3 years ago.
Improve this question
I want to parse a Ruby string into the data type it represents in the same way that Google Sheets does this.
E.g., when you type "hello" into a Google Sheet it's interpreted as a string. "100" as a number, "$100" as a currency, etc.
More examples:
The string "Jan 1, 2001" should turn into a date.
The string "true" should turn into a boolean.
What's the best way to accomplish this without manually writing a regex-driven case statement?
The possible date formats must be specified. Were one to use Date#parse, the string "Theresa May has announced she will resign" would return #<Date: 2019-05-01 ((2458605j,0s,0n),+0s,2299161j)>.
Here's a start.
require 'date'
require 'bigdecimal'
def my_class(str)
return true if str == 'true'
return false if str == 'false'
return nil if str == 'nil'
date = DateTime.strptime(str, '%b %d, %Y') rescue nil
return date unless date.nil?
return BigDecimal(str) if str.match?(/\A-?0.\d+e\d+\z/i)
n = Float(str) rescue nil
return n if n && str.include?('.')
n = Integer(str) rescue nil
return n unless n.nil?
raise ArgumentError
end
my_class 'true' #=> true
my_class 'false' #=> true
my_class 'nil' #=> nil
my_class '-32' #=> -32
my_class '0' #=> 0
my_class '1.0' #=> 1.0
my_class '-1.03' #=> -1.03
my_class '1.02e-2' #=> 0.0102
my_class '-1.99E2' #=> -199.0
my_class '-0.99e1' #=> -0.99e1 (BigDecimal)
my_class '-0.99E1' #=> -0.99e1 (BigDecimal)
my_class '0.0' #=> 0.0
my_class 'Jan 1, 2001'
#=> #<DateTime: 2001-01-01T00:00:00+00:00 ((2451911j,0s,0n),+0s,2299161j)>
my_class 'January 1, 2001'
#=> #<DateTime: 2001-01-01T00:00:00+00:00 ((2451911j,0s,0n),+0s,2299161j)>
my_class 'January 32, 2001'
#=> nil
Except for str = "0.0", the return value for a string converted to a BigDecimal, then converted back to a string matches the pattern
r = /\A-?0\.\d+e\d+\z/
(e.g. BigDecimal("123.4").to_s #=> "0.1234e3"). By contrast a string representing a float in scientific notation customarily matches the pattern
/\A-?[1-9]\.\d+e\d+\z/
I therefore assumed that a string matches a BigDecimal if and only if it matches r (though I've made r case-indifferent so that, for example, "0.12E2" will be regarded as a BigDecimal).

Assign hash key and value from string using split

I have a few strings that I am retrieving from a file birthdays.txt. An example of a string is below:
Christopher Alexander, Oct 4, 1936
I would like to separate the strings and let variable name be a hash key and birthdate the hash value. Here is my code:
birthdays = {}
File.read('birthdays.txt').each_line do |line|
line = line.chomp
name, birthdate = line.split(/\s*,\s*/).first
birthdays = {"#{name}" => "#{birthdate}"}
puts birthdays
end
I managed to assign name to the key. However, birthdate returns "".
File.new('birthdays.txt').each.with_object({}) do
|line, birthdays|
birthdays.store(*line.chomp.split(/\s*,\s*/, 2))
puts birthdays
end
I feel like some of the other solutions are overthinking this a bit. All you need to do is split each line into two parts, the part before the first comma and the part after, which you can do with line.split(/,\s*/, 2), then call to_h on the resulting array of arrays:
data = <<END
Christopher Alexander, Oct 4, 1936
Winston Churchill, Nov 30, 1874
Max Headroom, Apr 4, 1985
END
data.each_line.map do |line|
line.chomp.split(/,\s*/, 2)
end.to_h
# => { "Christopher Alexander" => "Oct 4, 1936",
# "Winston Churchill" => "Nov 30, 1874",
# "Max Headroom" => "April 4, 1985" }
(You will, of course, want to replace data with your File object.)
birthdays = Hash.new
File.read('birthdays.txt').each_line do |line|
line = line.chomp
name, birthdate = line.split(/\s*,\s*/, 2)
birthdays[name]= birthdate
puts birthdays
end
Using #Jordan's data:
data.each_line.with_object({}) do |line, h|
name, _, bdate = line.chomp.partition(/,\s*/)
h[name] = bdate
end
#=> {"Christopher Alexander"=>"Oct 4, 1936",
# "Winston Churchill"=>"Nov 30, 1874",
# "Max Headroom"=>"Apr 4, 1985"}

Ruby: Iterate over days in previous month

I need to be able to loop through all days of the previous month and do something with the parsed day. This is what I have so far:
def dateTest
d = Date.parse(Time.now.to_s)
from_date = Date.new(d.year, d.month - 1, 1)
to_date = Date.new(d.year, d.month, -1)
from_date..to_date.each do |day|
#Do stuff with day
end
end
My issue is I can't seem to get to_date to equal the last day of the previous month. I've read many similar questions but most of them seem more geared towards Rails, which won't help me in this case.
require 'date'
d = Date.today << 1 # << 1 is one month earlier
(Date.new(d.year, d.month, 1)..Date.new(d.year, d.month,-1)).each{|date| p date}
You also can use ... exclude the end value.
For example:
(1..5).to_a
=> [1, 2, 3, 4, 5]
(1...5).to_a
=> [1, 2, 3, 4]
so you can write like this:
if d.month != 1
from_date = Date.new(d.year, d.month - 1, 1)
else
from_date = Date.new(d.year-1, -1)
end
to_date = Date.new(d.year, d.month)
(from_date...to_date).map { |day| puts day}
output:
2015-04-01
2015-04-02
2015-04-03
......
2015-04-29
2015-04-30
I would do it like this:
require 'date'
d = Date.today << 1
start = Date.new(d.year, d.month)
(start...(start >> 1)).each { |d| p d }
#-> #<Date: 2015-04-01 ((2457114j,0s,0n),+0s,2299161j)>
# #<Date: 2015-04-02 ((2457115j,0s,0n),+0s,2299161j)>
# ...
# #<Date: 2015-04-30 ((2457143j,0s,0n),+0s,2299161j)>
The prev_day function can also be used:
require 'date'
d = Date.today
from_date = Date.new(d.year, d.month - 1, 1)
to_date = Date.new(d.year, d.month, 1).prev_day
from_date..to_date.each do |day|
#Do stuff with day
end

Ruby : Find a Date in a array of strings

I am searching through an array of strings looking for a Date:
Is the method I'm using a good way to do it?
OR . . . is there a better alternative.
Perhaps a more "beautiful" way to do it?
query = {'Hvaða','mánaðardagur','er','í','dag?','Það','er','02.06.2011','hví','spyrðu?'}
def has_date(query)
date = nil
query.each do |q|
begin
date = Date.parse(q)
query.delete(q)
break
rescue
end
end
return date
end
Note that in Ruby we use square brackets [] for array literals (curly braces {} are for Hash literals).
Here is a solution that will find all dates in the array and return them as strings (thanks #steenslag):
require 'date'
arr = ['Hvaða', 'er', '02.06.2011', 'hví', '2011-01-01', '???']
dates = arr.select { |x| Date.parse(x) rescue nil }
dates # => ["02.06.2011", "2011-01-01"]
First you can try to validate the string, if it's a valid date format then you can parse it as a date:
query.each do |item|
if item =~ /^\d{2}([-\\\/.])\d{2}\1\d{4}$/ then
# coding
end
end
If you just want the first date and you want to be strict about a valid date:
arr = ['Hvaða', 'er', '02.06.2011', 'hví', '2011-01-01', '???']
date = arr.detect do |x| ## or find
d,m,y = x.split('.')
Date.valid_civil?(y.to_i, m.to_i, d.to_i)
end
p date #=> "02.06.2011"
(Date.parse is forgiving, it happily parses 02.13.2011)
If you want to parse more types of dates, use Chronic... I think it also has the side effect of not raising errors, just returns nil.
So if you want all valid dates found:
arr = ['Hvaða', 'er', '02.06.2011', 'hví', '2011-01-01', '???']
arr.collect {|a| Chronic.parse a}.compact
If you want the first:
arr.find {|a| Chronic.parse a}
and if you just want a true/false "is there a date here?"
arr.any? {|a| Chronic.parse a}

How to create an infinite enumerable of Times?

I want to be able to have an object extend Enumerable in Ruby to be an infinite list of Mondays (for example).
So it would yield: March 29, April 5, April 12...... etc
How can I implement this in Ruby?
In 1.9 (and probably previous versions using backports), you can easily create enumerator:
require 'date'
def ndays_from(from, step=7)
Enumerator.new {|y|
loop {
y.yield from
from += step
}
}
end
e = ndays_from(Date.today)
p e.take(5)
#=> [#<Date: 2010-03-25 (4910561/2,0,2299161)>, #<Date: 2010-04-01 (4910575/2,0,2299161)>, #<Date: 2010-04-08 (4910589/2,0,2299161)>, #<Date: 2010-04-15 (4910603/2,0,2299161)>, #<Date: 2010-04-22 (4910617/2,0,2299161)>]
Store a Date as instance variable, initialized to a Monday. You would implement an each method which increments the stored date by 7 days using date += 7.
You could do something by extending Date...
#!/usr/bin/ruby
require 'date'
class Date
def current_monday
self - self.wday + 1
end
def next_monday
self.current_monday + 7
end
end
todays_date = Date.today
current_monday = todays_date.current_monday
3.times do |i|
puts current_monday.to_s
current_monday = current_monday.next_monday
end
2010-03-22
2010-03-29
2010-04-05
2010-04-12
...with the usual warnings about extending base classes of course.
You can extend Date class with nw method mondays
class Date
def self.mondays(start_date=Date.today, count=10)
monday = start_date.wday > 1 ? start_date - start_date.wday + 8 : start_date - start_date.wday + 1
mondays = []
count.times { |i| mondays << monday + i*7}
mondays
end
end
Date.mondays will return by default Array of mondays with 10 elements from closest monday to Date.today. You can pass parameters:
Date.mondays(start_date:Date, count:Integer)
start_date - start point to find closest monday
count - number of mondays you are looking
IE:
Date.mondays(Date.parse('11.3.2002'))
Date.mondays(Date.parse('11.3.2002'), 30)
module LazyEnumerable
extend Enumerable
def select(&block)
lazily_enumerate { |enum, value| enum.yield(value) if
block.call(value) }
end
def map(&block)
lazily_enumerate {|enum, value| enum.yield(block.call(value))}
end
def collect(&block)
map(&block)
end
private
def lazily_enumerate(&block)
Enumerator.new do |enum|
self.each do |value|
block.call(enum, value)
end
end
end
end
...........
class LazyInfiniteDays
include LazyEnumerable
attr_reader :day
def self.day_of_week
dow = { :sundays => 0, :mondays => 1, :tuesdays => 2, :wednesdays =>
3, :thursdays => 4, :fridays => 5, :saturdays => 6, :sundays => 7 }
dow.default = -10
dow
end
DAY_OF_WEEK = day_of_week()
def advance_to_midnight_of_next_specified_day(day_sym)
year = DateTime.now.year
month = DateTime.now.month
day_of_month = DateTime.now.day
output_day = DateTime.civil(year, month, day_of_month)
output_day += 1 until output_day.wday == DAY_OF_WEEK[day_sym]
output_day
end
def initialize(day_sym)
#day = advance_to_midnight_of_next_specified_day(day_sym)
end
def each
day = #day.dup
loop {
yield day
day += 7
}
end
def ==(other)
return false unless other.kind_of? LazyInfiniteDays
#day.wday == other.day.wday
end
end
Ruby 2.7 introduced Enumerator#produce for creating an infinite enumerator from any block, which results in a very elegant, very functional way of implementing the original problem:
irb(main):001:0> require 'date'
=> true
irb(main):002:0> puts Date.today
2022-09-23
=> nil
irb(main):003:0> Date.today.friday?
=> true
irb(main):004:0> future_mondays = Enumerator.produce { |date|
date = (date || Date.today).succ
date = date.succ until date.monday?
date
}
=> #<Enumerator: #<Enumerator::Producer:0x00007fa4300b3070>:each>
irb(main):005:0> puts future_mondays.first(5)
2022-09-26
2022-10-03
2022-10-10
2022-10-17
2022-10-24
=> nil
irb(main):006:0> _

Resources