rescue節があるから複数行だ、というご指摘もあるでしょうが、そこはご容赦を。
イディオム
マッチしない場合には空文字列を返すことにします。
begin
  '%d:%d' % time_str.match(/(\d\d)時(\d\d)分/).captures
rescue
  ""
end
ムリに一行にすることも出来ますが、読みづらいので使わないほうが良いでしょう。
begin '%d:%d' % time_str.match(/(\d\d)時(\d\d)分/).captures rescue "" end
(コメント反映追記1)後置
rescue文なら読みやすさに問題なし
'%d:%d' % time_str.match(/(\d\d)時(\d\d)分/).captures rescue ""
>(コメント反映追記2)時分を`Array#join`で繋げてしまう
>```ruby
time_str.match(/(\d{2})時(\d{2})分/).captures.join(":")
(コメント反映追記3)対象文字列の必要部分が数字2文字×2なので、数字2文字を
String#scanで取得して配列化、それをArray#joinで繋げる
time_str.scan(/\d{2}/).join(':')
# 解説
## 部分マッチ抜き出し
`String#match`は`MatchData`オブジェクトを返します。
`MatchData`はマッチ全体`$&`及び部分マッチ(`$1`, `$2`, …)を含みます。このため単純に`MatchData#to_a`を使うと
```ruby
"12時34分".match(/(\d\d)時(\d\d)分/).to_a
=> ["12時34分", "12", "34"]
のようにマッチ全体も含んでいます。
ここで使いたいのは部分マッチ情報だけなのでMatchData#capturesを使います。
"12時34分".match(/(\d\d)時(\d\d)分/).captures
=> ["12", "34"]
ただし、部分マッチがなかった場合にMatchData#capturesはnilを返すので、その対策としてrescueしています。
フォーマット
MatchData#capturesで得られた配列をString#%でフォーマットします。
'%d:%d' % "12時34分".match(/(\d\d)時(\d\d)分/).captures
=> "12:34"
私はString#%の書き方に慣れていますが、違和感を感じる場合にはKernel.#formatやKernel.#sprintfなども使えます。ただし、Kernel.#formatやKernel.#sprintfの第2引数は配列を受けられないのでsplat(*)で開く必要があります。
format('%d:%d', *("12時34分".match(/(\d\d)時(\d\d)分/).captures))
=> "12:34"