Matching a sliced string - ruby

I am trying to use the case statement:
week # => "03 – 09 MAR 2019"
first_day = week.slice(0..2) # => 03
last_day = week.slice(5..7) # => 09
month = week.slice(8..11) # => MAR
year = week.slice(12..17) # => 2019
puts month # >> MAR
case month
when 'JAN' then month_num = '01'
when 'FEB' then month_num = '02'
when 'MAR' then month_num = '03'
when 'APR' then month_num = '04'
when 'MAY' then month_num = '05'
when 'JUN' then month_num = '06'
when 'JUL' then month_num = '07'
when 'AGO' then month_num = '08'
when 'SEP' then month_num = '09'
when 'OCT' then month_num = '10'
when 'NOV' then month_num = '11'
when 'DEC' then month_num = '12'
else month_num = 'ERROR'
end
puts month_num # >> ERROR
However, the case statement always goes to the else branch.
Why is the var month_num equal to the string "ERROR" instead of "03"?

You are using puts to examine what you have, and therefore you are missing to observe whitespaces in your results. You actually have:
week.slice(0..2) # => "03 "
week.slice(5..7) # => "09 "
week.slice(8..11) # => "MAR "
week.slice(12..17) # => "2019"
To observe what you have, it is better to use p rather than puts.
You have the wrong range. Actually, there is no reason to use ranges here. It is much easier to use the second argument to specify the length:
week.slice(0, 2) # => "03"
week.slice(5, 2) # => "09"
week.slice(8, 3) # => "MAR"
week.slice(12, 4) # => "2019"

Your month is "MAR "
Try
month = week.slice(8..10)
And makes sense, from 8 to 10 inclusive are three characters. Same for the other parts.

Seems like you want to parse a string containing data in a specific format. Instead of relying on absolute indices, you could use a regular expression to match the date format, e.g:
PATTERN = /
(?<first_day>\d{2}) # 2-digit first day
\s* # optional whitespace
[–-] # delimiter(s)
\s*
(?<last_day>\d{2}) # 2-digit last day
\s*
(?<month>\w{3}) # 3-letter month name
\s*
(?<year>\d{4}) # 4-digit year
/ix
To extract the data:
str = '03 – 09 MAR 2019'
m = str.match(PATTERN)
#=> #<MatchData "03 – 09 MAR 2019" first_day:"03" last_day:"09" month:"MAR" year:"2019">
m[:first_day] #=> "03"
m[:last_day] #=> "09"
m[:month] #=> "MAR"
m[:year] #=> "2019"
The results could further be fed into Date.strptime:
require 'date'
from = m.values_at(:first_day, :month, :year).join(' ') #=> "03 MAR 2019"
to = m.values_at(:first_day, :month, :year).join(' ') #=> "09 MAR 2019"
Date.strptime(from, '%d %b %Y') #=> #<Date: 2019-03-03 ...>
Date.strptime(to, '%d %b %Y') #=> #<Date: 2019-03-09 ...>
Or _strptime if you're just interested in the raw values:
Date._strptime(from, '%d %b %Y')
#=> {:mday=>3, :mon=>3, :year=>2019}

Related

How is : Date.parse("123 456 789") == Fri, 03 May 2019?

The title pretty much says it all. I'm trying to assess the validity of dates from a CSV import but if someone chooses a telephone number column for date of birth the Date parsing still passes
How is : Date.parse(“123 456 789”) == Fri, 03 May 2019?
Date._parse (with an underscore) returns the raw data:
Date._parse('123 456 789')
#=> {:yday=>123}
Ruby treats 123 as the day of the year and the 123rd day of the current year is May 3.
The documentation on Date#parse explicitly states:
This method does not function as a validator.
That means, this method is omnivorous, it’ll produce a date from whatever input. You need to use Date#iso8601 instead:
main > Date.iso8601("2019-03-19")
#⇒ #<Date: 2019-03-19 ((2458562j,0s,0n),+0s,2299161j)>
main > Date.iso8601("123 456 789")
#⇒ ArgumentError: invalid date
You (and possibly others) might find the following method useful. I've made the following assumptions:
years must be four digits;
invalid commas can be disregarded and removed (e.g., "Jan, 3 2019"); and
the day of the week can be disregarded and removed, even if it is invalid for the date.
MON_NAMES = Date::MONTHNAMES.drop(1).concat(Date::ABBR_MONTHNAMES.drop(1))
#=> ["January", "February", "March", "April", "May", "June",
# "July", "August", "September", "October", "November", "December",
# "Jan", "Feb", "Mar", "Apr", "May", "Jun",
# "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
MON_REGEX = /\b#{Regexp.union(MON_NAMES)}\b(?!\A)/
# => /\b(?-mix:January|February|...|December|Jan|Feb|...|Dec)\b(?!\A)/
MONTH_STR_TO_NBR = MON_NAMES.each_with_index.map { |mon,i| [mon, " #{1 + (i%12)} "] }.to_h
#=> {"January"=>" 1 ", "February"=>" 2 ", ... , "December"=>" 12 ",
# "Jan"=>" 1 ", "Feb"=>" 2 ", ... , "Dec"=>" 12 "}
DAY_REGEX = /\b#{Regexp.union(Date::DAYNAMES + Date::ABBR_DAYNAMES)}\b,?/
#=> /\b(?-mix:Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sun|Mon|Tue|Wed|Thu|Fri|Sat)\b,?/
def my_parse(date_str, locale = :US)
a = date_str.gsub(MON_REGEX, MONTH_STR_TO_NBR).
gsub(DAY_REGEX, '').
gsub(/(?<!\p{Alpha})(?:st|rd|th|,)(?!\p{Alpha})/, '').
gsub(/[-\/]/, ' ').
strip.
split
return nil if a.size != 3 || a.any? { |s| s.match?(/\D/) }
yr_idx = a.index { |s| s.size == 4 }
return nil if yr_idx.nil? || yr_idx == 1
yr = a.delete_at(yr_idx)
return nil unless a.all? { |s| [1,2].include? s.size }
if yr_idx == 0
mon, day = a
else
mon, day = locale == :US ? a : a.reverse
end
begin
Date.strptime("%s %s %s" % [mon, day, yr], '%m %d %Y')
rescue ArgumentError
nil
end
end
my_parse("Tue, 12th January 2019") #=> #<Date: 2019-01-12 (...)>
my_parse("Tue, 12th January 2019", :UK) #=> #<Date: 2019-12-01 (...)>
my_parse("12/4/2019", :US) #=> #<Date: 2019-04-12 (...)>
my_parse("12/4/2019", :UK) #=> #<Date: 2019-12-04 (...)>
my_parse("Jan 12, 2019") #=> #<Date: 2019-12-01 (...)>
my_parse("2019 Jan 23rd") #=> #<Date: 2019-01-23 (...)>
my_parse("Jan 2019 4") #=> nil
my_parse("1/2019/4") #=> nil
my_parse("1/2019/4") #=> nil
my_parse("Jen 12, 2019") #=> nil
my_parse("3 Jan 12, 2019") #=> nil
I would encourage readers to identify any other assumptions required that I have not mentioned. This could of course be modified as needed. One change that could be made fairly easily would be to confirm that the day of week, if present, is correct for the given date.

Ruby date-time string to epoch time local

I have date-time represented in string format like below in the input data I'm getting. I need to convert this to epoch time local. How do I go about this ?
example date-time string
str = "Aug 23 2018 03:49:17:017 PM IST"
Maybe this can put you on track.
Please refer to http://ruby-doc.org/stdlib-2.5.0//libdoc/date/rdoc/DateTime.html.
This is a usage example.
require 'date'
str = "Aug 23 2018 03:49:17:017 PM IST"
d = DateTime.strptime(str, '%b %d %Y %I:%M:%S:%L %p %z')
# Maybe you need %e - Day of the month, blank-padded ( 1..31) instead of %d - Day of the month, zero-padded (01..31)
p d.hour # => 15
p d.min # => 49
p d.sec # => 17
p d.second_fraction # => (17/1000)
p d.day # => 23
p d.month # => 8
p d.year # => 2018
p d.zone # => "+05:30"
# this way you can switch timezone
d = d.new_offset('utc')
p d.zone # => "+00:00"
p d.hour # => 10
d = d.new_offset('brt')
p d.hour # => 7
p d.zone # => "-03:00"
# to print back as string, use strftime:
p d.strftime('%Y-%m-%d %I:%M:%S %z') # => "2018-08-23 07:19:17 -0300"

How to convert date in to string words like that input 11-12-2001 output should be eleven December two thousand one, in ruby

How to convert date in to string words in ruby
Input date
2-12-2002
Output
Two December two thousand two
require 'humanize'
day, month, year = '2-12-2002'.split('-')
month_numeric_range = (1..12).to_a.map(&:to_s)
month_words = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
month_numeric_to_words = Hash[month_numeric_range.zip(month_words)]
day.to_i.humanize.capitalize + ' ' + months_numeric_to_words[month] + ' ' + year.to_i.humanize.capitalize
# => "Two December Two thousand and two"
This is a partial option using Class: Date:
require 'date'
date_string = "2-12-2002"
date = Date.strptime(date_string, "%d-%m-%Y")
p date.day # => 2
p date.month # => 12
p date.year # => 2002
p date.strftime("%d %B %Y") # => "02 December 2002"
If you want also to spell numbers, check this out:
Using Ruby convert numbers to words?
Number to English Word Conversion Rails

`Date.strptime` unexpected behaviour

I try to parse string "01.09 2015" with "%d.%m %y", but it returns year 2020.
require 'date'
year = Time.now.year.to_s # => "2015"
ny = (Time.now.year+1).to_s # => "2016"
sem = "01.09"
Date.strptime(sem+" "+year, "%d.%m %y")
# => #<Date: 2020-09-01 ((2459094j,0s,0n),+0s,2299161j)>
I don't get what's happening here.
You need to replace %y (meaning year % 100 (00..99)) with %Y (4 digits year):
Date.strptime("01.09 2015", "%d.%m %Y")

Invalid date ArgumentError and incorrect parse on a valid date?

dates = ["11/12/08 10:47", "11/12/08 13:23", "11/12/08 13:30",
"11/25/08 19:21", "2/2/09 11:29", "11/12/08 15:00"]
This throws an invalid argument error:
dates.each do |date|
d = Date.parse(date)
d.mon
end
#=> ArgumentError: invalid date
But take the first date in dates and this is the output:
d = Date.parse('11/12/08 10:47')
puts d.mon
#=> #<Date: 2011-12-08 ((2455904j,0s,0n),+0s,2299161j)>
#=> 12 but this should be 11
In the first example why am I getting an invalid ArgumentError?
In example 2, why is the Date object created with the mon and day swapped?
Given your input, Date.parse is parsing your dates assuming they are in the format YY/MM/DD, so when it try to parse 11/25/08 it fails because 25 is not a valid month:
d = Date.parse('11/12/08 10:47')
d.year
# => 2011
d.month
# => 12
d.day
# => 8
Date.parse('11/25/08 19:21')
# ArgumentError: invalid date
Given that your dates are all in the same format, you should use the Date.strptime method instead:
d = Date.strptime('11/12/08 10:47', '%m/%d/%y')
d.year
# => 2008
d.month
# => 11
d.day
# => 12
Date.strptime('11/25/08 19:21', '%m/%d/%y')
# => #<Date: 2008-11-25 ((2454796j,0s,0n),+0s,2299161j)>
Edit Instead of the format string %m/%d/%y the shortcut %D can be used:
Date.strptime('11/25/08 19:21', '%D')
# => #<Date: 2008-11-25 ((2454796j,0s,0n),+0s,2299161j)>
Ruby's Date.parse is expecting either a YYYY-MM-DD (see also ISO8601 for more information) or a DD-MM-YYYY as well but not DD-MM-YY (i.e. 2 digits only for year). The last is treated instead as YY-MM-DD.

Resources