Rubyで実働時間を測って顧客にタイムチャージしよう!
仕事の生産性を上げるためには時間管理は重要です
きちっとした時間管理ができてこそ
プロフェッショナルと言えるでしょう
欧米の弁護士の報酬は多くはタイムチャージとなっていて
彼らは案件ごとの実働時間をカウントして
それに自分の単価を掛けて顧客に請求します
やっぱり
プロフェッショナルのやることは違いますね^^;
したがってあなたが
プロフェッショナルと認められるようになるためには
たとえあなたの報酬がその労働時間に依存していなくとも
案件ごとに使った時間をきっちりと数えて
あなたの報酬額と実働時間の関係を
常日頃から意識することが重要です..
そんなわけで..
私もプロフェッショナルを目指して
今日から実働時間を計測したいと思います:)
実働時間の計測には次のステップが必要です
- 案件ごとの開始・終了時間を毎日記録する
- 案件ごとに時間を集計する
案件ごとの開始・終了時間を毎日記録する
案件に対する実働時間というのは
一日のうちでも小間切れになるので
これを記録するのはなかなか面倒です
よって時間の記録をできるだけ
簡単に行えるようにする必要があります
そうなるとやっぱりテキストファイルに
単純なフォーマットで記録するのがいいです
例えばこんな感じで
<案件名> <開始日時> - <終了日時> <開始日時> - <終了日時> <開始日時> - <終了日時> <開始日時> - <終了日時> <案件名> <開始日時> - <終了日時> <開始日時> - <終了日時> <開始日時> - <終了日時> <開始日時> - <終了日時>
日時の入力は面倒ですが
私はVimを使うので.vimrcに
以下を追加して省力化します
" insert timestamp map <D-F2> <ESC>:r !date<CR>
これでcmd+F2で日時が打てるようになります
こんなイメージです
AA123 Sat Jan 21 08:57:36 JST 2012 - Sat Jan 21 12:00:00 JST 2012 Sat Jan 21 14:24:14 JST 2012 - Sat Jan 21 15:10:17 JST 2012 Sun Jan 22 07:33:36 JST 2012 - Sun Jan 22 10:47:47 JST 2012 Sun Jan 22 14:22:24 JST 2012 - Sun Jan 22 18:39:39 JST 2012 Tue Jan 24 10:13:54 JST 2012 - Tue Jan 24 12:07:55 JST 2012 Tue Jan 24 13:08:44 JST 2012 - Tue Jan 24 15:19:13 JST 2012 Tue Jan 24 16:38:05 JST 2012 - Tue Jan 24 18:15:15 JST 2012 Project-XYZ Sat Feb 11 08:57:36 JST 2012 - Sat Feb 11 11:00:00 JST 2012 Sat Feb 11 18:11:36 JST 2012 - Sat Feb 11 19:00:00 JST 2012 Sun Feb 12 08:33:36 JST 2012 - Sun Feb 12 10:47:47 JST 2012 Sat Feb 13 12:44:14 JST 2012 - Sat Feb 13 18:30:17 JST 2012 Sat Feb 13 22:24:24 JST 2012 - Sat Feb 14 01:50:07 JST 2012
案件ごとに時間を集計する
さあ仕事が終わったら上のファイルを
Rubyに読み込ませて集計しましょう
次のような出力が見やすくていいですね
AA123:    2012-01-21: 02:48    2012-01-22: 03:31    2012-01-24: 06:41         Total: 13:01 -------------------- Project-XYZ:    2012-01-21: 02:02    2012-01-22: 02:14    2012-01-23: 03:46    2012-01-24: 02:54         Total: 10:56 --------------------
次のような方針にしましょう
- ファイルを案件ごとに分ける
- 日時のラインをパースして日にちごとの時間に集計する
- フォーマットを用意して集計データを出力する
ファイルを案件ごとに分ける
split_by_projectメソッドを定義します
def split_by_project(lines, time_pattern=/JST/) lines.map(&:chomp) .reject(&:empty?) .chunk { |line| !line.match(time_pattern) } .each_slice(2) .map { |(_,names), (_,timelines)| [names.last, timelines] } end
このメソッドは
読み込んだファイルの各行に対し
改行を除去し(chomp)
空行を除去し(empty?)
日時の行の前後で行分割し(chunk)
案件名と日時行をセットとし(each_slice(2))
最後に
案件名と日時行の配列を返します(map)
chunkでは日時行を識別するために
/JST/をパターンとしていますが
第2引数に任意のパターンを取ることができます
出力は次のようになります
split_by_project(ARGF) # => [["AA123", [" Sat Jan 21 08:57:36 JST 2012 - Sat Jan 21 12:00:00 JST 2012", " Sat Jan 21 14:24:14 JST 2012 - Sat Jan 21 15:10:17 JST 2012", " Sun Jan 22 07:33:36 JST 2012 - Sun Jan 22 10:47:47 JST 2012", " Sun Jan 22 14:22:24 JST 2012 - Sun Jan 22 18:39:39 JST 2012", " Tue Jan 24 10:13:54 JST 2012 - Tue Jan 24 12:07:55 JST 2012", " Tue Jan 24 13:08:44 JST 2012 - Tue Jan 24 15:19:13 JST 2012", " Tue Jan 24 16:38:05 JST 2012 - Tue Jan 24 18:15:15 JST 2012"]], ["Project-XYZ", [" Sat Feb 11 08:57:36 JST 2012 - Sat Feb 11 11:00:00 JST 2012", " Sat Feb 11 18:11:36 JST 2012 - Sat Feb 11 19:00:00 JST 2012", " Sun Feb 12 08:33:36 JST 2012 - Sun Feb 12 10:47:47 JST 2012", " Sat Feb 13 12:44:14 JST 2012 - Sat Feb 13 18:30:17 JST 2012", " Sat Feb 13 22:24:24 JST 2012 - Sat Feb 14 01:50:07 JST 2012"]]]
日時のラインをパースして日にちごとの時間に集計する
次にparse_and_arrange_timelinesメソッドを定義します
require 'date' def parse_and_arrange_timelines(timelines, spliters=%w(- =>)) timelines.map { |line| from, to = line.split(/\s*#{spliters.join('|')}\s*/) .map { |str| DateTime.parse str } [from.to_date.to_s, to-from] }.inject(Hash.new(0)) { |h, (date, lap)| h[date] += lap; h } end
このメソッドは
split_by_projectで生成された日時行を
開始日時と終了日時に分けて(split)
DateTimeオブジェクトに変換し(DateTime.parse)
日付のラベルと経過時間の配列とし(map)
さらに
日付ごとの経過時間に集計します(inject)
ここでは規定のspliterを'-', '=>'としていますが
第2引数に任意のspliterを取ることができます
出力は次のようになります
timelines = split_by_project(DATA)[0][1] # => [" Sat Jan 21 08:57:36 JST 2012 - Sat Jan 21 12:00:00 JST 2012", " Sat Jan 21 14:24:14 JST 2012 - Sat Jan 21 15:10:17 JST 2012", " Sun Jan 22 07:33:36 JST 2012 - Sun Jan 22 10:47:47 JST 2012", " Sun Jan 22 14:22:24 JST 2012 - Sun Jan 22 18:39:39 JST 2012", " Tue Jan 24 10:13:54 JST 2012 - Tue Jan 24 12:07:55 JST 2012", " Tue Jan 24 13:08:44 JST 2012 - Tue Jan 24 15:19:13 JST 2012", " Tue Jan 24 16:38:05 JST 2012 - Tue Jan 24 18:15:15 JST 2012"] parse_and_arrange_timelines(timelines) # => {"2012-01-21"=>(1523/9600), "2012-01-22"=>(13543/43200), "2012-01-24"=>(205/864)}
経過時間はRationalで表されています
フォーマットを用意して集計データを出力する
最後に
出力用のフォーマットメソッドformat_forを用意します
require "term/ansicolor" def format_for(name, times_by_date, w=20) times = times_by_date.map { |date, fractime| [date, frac2time(fractime)] } total = frac2time times_by_date.map(&:last).inject(:+) [ "#{name}:".cyan, times.map { |date, time| "#{date}: #{time}".rjust(w) }.sort, "Total: #{total}".rjust(w).magenta, "-"*w ] end def frac2time(fractime, opt=[:h,:m]) label = [:h, :m, :s, :f] hmsf = Date.send(:day_fraction_to_time, fractime) Hash[label.zip hmsf].select { |k, v| opt.include? k } .values.map { |v| format "%02d", v }.join(':') end
このメソッドは案件名と日付ごとの集計時間を取って
経過時間をhr:min:secのフォーマットに変換し(frac2time)
総経過時間を計算し(total)
rjustを使って出力用にフォーマットします
折角ですからterm-ansicolorで色付けしています
さあこれらをまとめて実働時間を集計する
worktime.rbの完成です!
これであなたも私も
プロフェッショナル道まっしぐらですね!