find instances of regex, transform into integers, do math and replace in the same place?

Viewed 42

I have a long string input that contains Taiwanese dates. I need to transform them into American notation and leave them in the same exact place of the input. I have created regex that do match against the text in the input but I cannot find the way to transform those matches into integers, do math (to change the year) and then replace the new values in the exact same place where the old values were.

Taiwanese dates are formed as yyy/mm/dd.
For ex: 107年02月13日, that is 02/13/2018

Up to now I have:

input = gets
texto = input.gsub(/\s+/, "")
Date_with_characters = /\d{3}年\d{2}月\d{2}日/
if input.match(Date_with_characters)
  puts "Encuentro fechas con caracteres"
elsif
  puts "NO encuentro un joraca con caracteres"
end

Date_with_slashes = %r{\d{3}/\d{2}/\d{2}}
if input.match(Date_with_slashes)
  puts "Encuentro fechas con barras"
elsif
  puts "NO encuentro un joraca con barras"
end

The code above finds all dates (both with characters and slashes) but I was hoping I could then turn the first 3 indexes in the year to integers, do the math to turn it into western calendar, and put it back in the same place (skipping moving year to the end for now). I have had no luck yet and I tried lot of things.

Thanks a lot!

2 Answers

You can put the numbers in parentheses to capture their values separately and use gsub with a block to perform a dynamic substitution:

str = 'foo 107年02月13日 bar'
str.gsub(/(\d{3})年(\d{2})月(\d{2})日/) { "#{$2}/#{$3}/#{$1.to_i + 1911}" }
#=> "foo 02/13/2018 bar"

Within the block, the special variables $1, $2 and $3 refer to the text matched by the corresponding capture group, i.e. "107", "02" and "13" respectively. The block's result becomes the new value for the matched text.

There's also gsub! (with a !) which performs the substitution in-place (i.e. it modifies the receiver), returning nil if no matches were found. This can be useful if you want to provide a message depending on whether a replacement occured:

if input.gsub!(/(\d{3})年(\d{2})月(\d{2})日/) { "#{$2}/#{$3}/#{$1.to_i + 1911}" }
  puts 'dates have been converted'
else
  puts 'no dates could be found'
end

This is a way to do it that also confirms that dates are valid. The latter is essentially a freebie with the approach that I have taken.

require 'date'

def convert_date(str)
  str.gsub(/\d{3}年\d{2}月\d{2}日/) do |s|
    dt = DateTime.strptime(s, '%Y年%m月%d日') rescue nil
    (raise ArgumentError, "#{s} is not a valid date") if dt.nil?
    "#{dt.month}/#{dt.day}/#{dt.year.to_i + 1911}"
  end
end

convert_date 'foo 107年02月13日 bar and 106年11月25日 baz'
  #=> "foo 2/13/2018 bar and 11/25/2017 baz" 
convert_date 'foo 107年02月13日 bar and 106年02月29日 baz'
  #=> ArgumentError (106年02月29日 is not a valid date)

See DateTime::strptime and Kernel#raise.

Related