わさっきhb

大学(教育研究)とか ,親馬鹿とか,和歌山とか,とか,とか.

Slad: 1日1行カレンダー

1日1行カレンダーの機能強化版を作りました.
さっそくですがソースです.

#!/usr/bin/env ruby
# -*- coding: utf-8 -*-

# slad.rb : Simple Line-A-Day Calendar

require "date"
require "optparse"

class Slad
  FORMAT = {
    :j => {
      :pattern => '%Y年%m月%d日',
      :week => %w(日 月 火 水 木 金 土)
    },
    :e => {
      :pattern => '%Y-%m-%d',
      :week => %w(Sun Mon Tue Wed Thu Fri Sat)
    }
  }

  def initialize(opt = {})
    @from = opt[:from] || Time.now
    @to = opt[:to] || 6
    @sep = opt[:sep] || ""
    @lang = opt[:lang] || get_lang
    @dir = opt[:dir]
  end

  def get_lang
    (/ja/ =~ ENV["LANG"]) ? :j : :e
  end

  def get_date(param, base_date = nil)
    case param
    when Date
      param
    when Time
      Date.new(param.year, param.mon, param.mday)
    when Array
      Date.new(*param)
    when /(\d\d\d\d)\D*(\d\d)\D*(\d\d)/
      Date.new($1.to_i, $2.to_i, $3.to_i)
    when Fixnum, /^[-+]?\d+$/
      plus = param.to_i
      if Date === base_date
        base_date + plus * (@dir == :b ? -1 : 1)
      else
        raise
      end
    else
      raise
    end
  end

  def init_dates
    @d_from = get_date(@from)
    @d_to = get_date(@to, @d_from)
  end

  def setup_order
    if @d_from > @d_to
      @d_from, @d_to = @d_to, @d_from
      if @dir == :r
        @dir = :f
      elsif @dir.nil?
        @dir = :b
      end
    else
      if @dir == :r
        @dir = :b
      elsif @dir.nil?
        @dir = :f
      end
    end
    # Note: @d_from <= @d_to is always true.
  end

  def get_date_array
    fmt = FORMAT[@lang]
    d_array = []
    @d_from.upto(@d_to) do |d|
      str = d.strftime(fmt[:pattern]) + "(#{fmt[:week][d.wday]})" + @sep
      d_array << str
    end
    d_array.reverse! if @dir == :b
    d_array
  end

  def start
    init_dates
    setup_order
    puts get_date_array
  end

  def self.setup(opt = {})
    s = self.new(opt)
    s.init_dates
    s.setup_order
    s
  end

  def self.generate(opt = {})
    self.setup(opt).get_date_array
  end
end

if __FILE__ == $0
  op = OptionParser.new
  opt = {}
  op.on("-f", "--from=VAL", "\"from\" date") {|v| opt[:from] = v}
  op.on("-t", "--to=VAL", "\"to\" date or interval") {|v| opt[:to] = v}
  op.on("-a", "--after=VAL", "interval days (0 origin)") {|v| opt[:to] = v.to_i}
  op.on("-A", "--After=VAL", "number of days (1 origin)") {|v|
    num = v.to_i
    opt[:to] = num - (num > 0 ? 1 : 0) + (num < 0 ? 1 : 0)
  }
  op.on("-s", "--sep=VAL", "separator") {|v| opt[:sep] = v}
  op.on("-:", "--colon", "colon as separator") {opt[:sep] = ': '}
  op.on("-.", "--Colon", "zenkaku colon as separator") {opt[:sep] = ''}
  op.on("-l", "--lang=VAL", "language (j or e)") {|v| opt[:lang] = v.split(//).first.to_sym}
  op.on("-j", "--japanese", "Japanese") {|v| opt[:lang] = :j}
  op.on("-e", "--english", "English") {|v| opt[:lang] = :e}
  op.on("-o", "--fore", "print foreword") {opt[:dir] = :f}
  op.on("-b", "--back", "print backword") {opt[:dir] = :b}
  op.on("-r", "--rev", "print reversely") {opt[:dir] = :r}
  op.parse!(ARGV)
  Slad.new(opt).start
end

ソフトウェア名を決めるにあたり,辞書を引いたところ,page-a-day calendarという言葉が載っていました.「日めくりカレンダー」のことです.なるほど,page a dayは「1日につき1枚」ですね.
1日につき1行なら,line a day.ladでは分かりにくいのでsimpleをつけて,Simple Line-A-Day (Slad) Calendarとした次第です.
入力に応じて,さまざまな形で,1日1行カレンダーを出力します.前回からの変更点として,開始日・終了日はコマンドライン上で指定でき,「いつまで」は日付でなくても日数でもよく,さらに,日付をさかのぼった出力も可能になっています.いくつか実行例を示します.

$ ruby slad.rb -f 2010-12-03
2010年12月03日(金)
2010年12月04日(土)
2010年12月05日(日)
2010年12月06日(月)
2010年12月07日(火)
2010年12月08日(水)
2010年12月09日(木)
$ ruby slad.rb -f 20101203 -a 2
2010年12月03日(金)
2010年12月04日(土)
2010年12月05日(日)
$ ruby slad.rb -f 20101203 -a -2
2010年12月03日(金)
2010年12月02日(木)
2010年12月01日(水)
$ ruby slad.rb -f 20101203 -a -2 --fore
2010年12月01日(水)
2010年12月02日(木)
2010年12月03日(金)
$ ruby slad.rb -f 20101203 -A 3
2010年12月03日(金)
2010年12月04日(土)
2010年12月05日(日)
$ ruby slad.rb --from=2010-12-03 --After=3 --english --colon
2010-12-03(Fri):
2010-12-04(Sat):
2010-12-05(Sun):
$ ruby slad.rb --help
Usage: slad [options]
    -f, --from=VAL                   "from" date
    -t, --to=VAL                     "to" date or interval
    -a, --after=VAL                  interval days (0 origin)
    -A, --After=VAL                  number of days (1 origin)
    -s, --sep=VAL                    separator
    -:, --colon                      colon as separator
    -., --Colon                      zenkaku colon as separator
    -l, --lang=VAL                   language (j or e)
    -j, --japanese                   Japanese
    -e, --english                    English
    -o, --fore                       print foreword
    -b, --back                       print backword
    -r, --rev                        print reversely

言語については日本語と英語の2種類から選べます.コマンドでの指定がなければ,環境変数LANGの値が"ja"を含むか否かで判断しています.
オプションの-s, -:, -.ほかは,行の右にイベントや実施した内容を書けるようにするための,境界線を指定するものです.
単独のRubyスクリプトですが,別途Test::Unitでテストを通して,かなりのバグ退治をしています.既知の問題として,ファイルに出力するオプションはありません.リダイレクション(コマンドの後ろに「> ファイル名」)で保存してください.
今後の予定ですが…考えてみると,Ruby処理系がなくても生成できそうです.具体的には,開始日終了日その他もろもろを,HTMLのインタフェースで指定し,ボタンを押せば,内部でJavaScriptのプログラムが計算し,画面上に表示するという形です.もちろんWebサーバもいりません.暇を見つけて検討したいと思います.あとそのうち,GitHubに登録します.