■ Ruby でカレンダー CGI version

CGI のカレンダーを作成しました。 ちょっと殺風景ですが... CGI のサンプル? 的なものです。

カレンダー関係は、

などがありますが、CGI も作ってあったので... 基本的には、Ruby でカレンダー の出力部分を cgi-lib.rb で処理しているだけです。 コマンドライン上で動かすと、HTML の出力をしますよ。

cgi-lib.rb は 1.9 系ではサポートされません。

■ 使い方

引数がない場合は、当年当月です。 引数として、「1970 〜 2037」までは年、「1 〜 12」までは月として扱います。

cgi-bin に登録してある場合には、URL は次のようになります。

http://localhost/cgi-bin/cal.rb
http://localhost/cgi-bin/cal.rb?2000+1
http://localhost/cgi-bin/cal.rb?12
上から順番に、「当月」「2000 年 1 月」「今年の 12 月」です。

出力は次のような感じです。

     April 1999     
Su Mo Tu We Th Fr Sa
             1  2  3 
 4  5  6  7  8  9 10 
11 12 13 14 15 16 17 
18 19 20 21 22 23 24 
25 26 27 28 29 30
ページにこれしか出力しないので、ちょっとつまらないですね。

■ ソースコード

Web ブラウザでの表示上は Ruby のスクリプトですが、HTML の特殊文字をエスケープしています。 テキストとしてセーブしてご利用ください。

#! /usr/local/bin/ruby
# /home/tetsu/src/ruby/cgi/cal.rb
# Created: March 26,1999 Friday 14:55:05
# Author: tetsu(WATANABE Tetsuya)
# $Id: cal.rb,v 1.6 2007/04/28 12:22:49 tetsu Exp $
# usage:

def mkLastMod
  t = Time.at(0)
  [
    '/home/httpd/cgi-bin/cal.rb'
  ].each do |f|
    next if not File.exist?(f)
    w = File.mtime(f)
    t = w if w > t
  end
  t
end

require 'cgi-lib'
require 'tcal'

year = 0
month = 0

ARGV.each do |arg|
  foo = arg.to_i
  if foo >= 1 and foo <= 12
    month = foo
  elsif foo >= 1970 and foo <= 2037
    year = foo 
  end
end

cal = TCalendar.new(year, month)
msg = cal.header

1.upto(31) do |d|
  wday = cal.wday(d)
  break unless wday

  status = cal.status(d)

  if (d == 1)
    msg.concat('   ' * wday)
  elsif wday == 0
    msg.concat("\n")
  end

  day_str =
    case status
    when 'weekend', 'holiday'
      CGI::tag("font", {"color"=>"red"}) {
      format('%2d', d)
    }
    when 'workday'
      format('%2d', d)
    end

  if cal.today?(d)
    msg.concat(CGI::tag('b') {day_str})
  else
    msg.concat(day_str)
  end

  msg.concat(' ')
end

# last_mod = mkLastMod.gmtime.strftime('%a, %d %b %Y %X %Z')
# CGI::print("Content-Type: text/html", "Last-Modified: " + last_mod) {

CGI::print {
  CGI::tag("html") {
    CGI::tag("head") {
      CGI::tag("meta",
               {"http-equiv"=>"Content-Type",
                 "content"=>"text/html; charset=ISO-2022-JP"}) +\
      CGI::tag("title") { 'cal' }
    } +\
    CGI::tag("body",
             {"bgcolor"=>"#e0e0e0",
               "text"=>"#0f0f0f",
               "link"=>"#0000ff",
               "vlink"=>"#000080",
               "alink"=>"#ff0000"
             }) {
      CGI::tag('pre') { msg }
    }
  }
}

print "\n"

クラスライブラリです。 cgi ですので、site_ruby などのシステムレベルのライブラリを入れる場所にお願いします。

#! /usr/local/bin/ruby
# /home/tetsu/src/ruby/class/tcal.rb
# created: September 24,2005 Saturday 13:38:32
# author: tetsu(WATANABE Tetsuya)
# $Id: tcal.rb,v 1.2 2005/09/24 04:53:40 tetsu Exp $
# usage:

class TCalendar
  def initialize(year = 0, month = 0)
    @now = Time.now
    @year = if year == 0 then @now.year else year end
    @month = if month == 0 then @now.month else month end
    @holiday = []
    @holiday_name = []
    @mday_arr = []
    1.upto(31) do |d|
      @mday_arr[d] = Time.local(@year, @month, d, 0, 0, 0)
      if d > 28
        if @mday_arr[d].month != @month
          @mday_arr[d] = nil
        end
      end
      @holiday[d] = false
    end
    month_name = @mday_arr[1].strftime('%b')

# このデータ形式は次のようになっています。
# 月名<space>日付<space>有効年<space>コメント
# 行の先頭の「#」以降はコメント
# 木村さん感謝!
# HM2 Happy Monday(2nd monday)
# HM3 Happy Monday(3rd monday)

    holiday_str = '
Jan 1       0         元旦
Jan 15      -1999     成人の日
Jan HM2     2000-     成人の日
Feb 11      0         建国記念の日
Mar SHUNBUN 0         春分の日
Apr 29      -1988     天皇誕生日
Apr 29      1989-2006 みどりの日
Apr 29      2007-     昭和の日
May 3       0         憲法記念日
# May 4       1986-2006 国民の休日
May 4       2007-     みどりの日
May 5       0         こどもの日
Jul 20      1996-2002 海の日
Jul HM3     2003-     海の日
Sep 15      -2002     敬老の日
Sep HM3     2003-     敬老の日
Sep SYUBUN  0         秋分の日
Oct 10      -1999     体育の日
Oct HM2     2000-     体育の日
Nov 3       0         文化の日
Nov 23      0         勤労感謝の日
Dec 23      1989-     天皇誕生日
'

    holiday_str.split(/\n/).each do |l|
      next if l == '' or l =~ /^\#/
      l.sub!(/\#.*$/, '')

      m, d, y, c = l.split(/\s+/, 4)

      if y != '0'
        if y[0,1] == '-'
          next if @year > y[1,4].to_i
        elsif y[-1,1] == '-'
          next if @year < y[0,4].to_i
        elsif y[4,1] == '-'
          next if @year < y[0,4].to_i || @year > y[5,4].to_i
        end
      end

      if month_name == m
        case d
        when 'SHUNBUN'
          d = syunbun(@year).to_s
        when 'SYUBUN'
          d = syubun(@year).to_s
        when 'HM2'
          d = nMonday(2).to_s
        when 'HM3'
          d = nMonday(3).to_s
        end
        @holiday[d.to_i] = true
        @holiday_name[d.to_i] = c
      end
    end

    if @year >= 1986
      i = 0
      while i < 31 - 2
        if @holiday[i] and @holiday[i + 1] == false and @mday_arr[i + 1].wday != 0 and @holiday[i + 2] 
          @holiday[i + 1] = true
          @holiday_name[i + 1] = '国民の休日'
          i += 1                # skip
        end
        i += 1
      end
    end
  end
  attr_reader :holiday_name, :year, :month

  def nMonday(n)
    count = 0
    @mday_arr.each_index do |d|
      next if d < 1
      count += 1 if @mday_arr[d].wday == 1
      return d if count == n
    end
  end

  def today?(mday)
    @now.year == @year and @now.month == @month and @now.mday == mday
  end

  def wday(mday)
    return nil unless @mday_arr[mday]
    @mday_arr[mday].wday
  end

  def status(mday)
    return nil unless @mday_arr[mday]
    return 'holiday' if @holiday[mday]

    case @mday_arr[mday].wday
    when 1
      if mday > 1 and @holiday[mday - 1]
        return 'holiday'
      else
        return 'workday'
      end
    when 2, 3, 4, 5
      'workday'
    when 0, 6
      'weekend'
    end
  end

  def header
    msg = @mday_arr[1].strftime('%B %Y').center(20)
    msg + "\n" + 'Su Mo Tu We Th Fr Sa' + "\n"
  end

#| From: hajima@crimson.gen.u-tokyo.ac.jp (Ryoichi Hajima)
#| Newsgroups: fj.questions.misc
#| Subject: Re: vernal/autumnal equinox
#| Message-ID: <HAJIMA.94Jul13161542@tanelorn.gen.u-tokyo.ac.jp>
#| Date: 13 Jul 94 07:15:42 GMT
#|
#| 春分日 (31y+2213)/128-y/4+y/100    (1851年-1999年通用)
#|     (31y+2089)/128-y/4+y/100    (2000年-2150年通用)
#|
#| 秋分日 (31y+2525)/128-y/4+y/100    (1851年-1999年通用)
#|     (31y+2395)/128-y/4+y/100    (2000年-2150年通用)

  def syunbun(year)
    if year > 2150
      STDERR.print "over year's: #{year}\n"  #'
      exit 1
    end
    v = if year < 2000 then 2213 else 2089 end
    (31 * year + v)/128 - year/4 + year/100
  end

  def syubun(year)
    if year > 2150
      STDERR.print "over year's: #{year}\n" #'
      exit 1
    end
    v = if year < 2000 then 2525 else 2395 end
    (31 * year + v)/128 - year/4 + year/100
  end
end

■ 解説

「年」として有効なものは、UNIX としての一般的年の 1970 〜 2037 までです。

祝日はとても簡単な形で実現しています。 定義中「XX」の場合には、不定のため計算します。 この計算については、同じ月に不定の祝日が 2 日もないと決めつけていますので、もし対応できないような場合になれば修正が必要です。

休日の形式は単純なので、個人の休日などを加えるのは容易と思います。 ただ、「年」の指定が、当年だけというフォーマットは、現状ではありません。

ちょっと無駄なことをしています。 出力する「月」について、毎日の Time クラスのオブジェクトを生成しています。 曜日の計算を自分で行えば、こういうことはしなくてすむとは思いますが、便利なのでついつい。

引数の「年」と「月」には、指定のための順番がありません。 好きな順番で指定してください。 URL で表記するという意味では、決まりがあった方がいいと思うのですが...

■ 履歴

1.6 2007/4/28

クラスライブラリを別ファイルにしました。 この別ファイルは Ruby でカレンダー と共通になっています。

1.5 2001/11/1

ハッピーマンデーの「海の日」「敬老の日」の開始年は 2003 年から


渡辺哲也(WATANABE Tetsuya): Tetsuya.WATANABE atmark nifty.com