万年カレンダーを作る(下)



下編のはじめに

 上編においては、基本の java メソッドを組み合わせるだけで可能なグレゴリオ暦、ユリウス暦、七曜と干支に関する部分のみとりあげた。しかし、これを旧暦(和暦)および旧暦から算出される六曜にまで拡張するには、通常の java メソッドだけでは不足する。言い換えれば、単純な計算式では算出することが難しい。実用上は外部にデータを持たせてそれを参照することで実現するのが現実的な回答となる。下編においては、これを jdbc および RDB (Relational Database) を使用して実現する。jdbc は java を使って RDB を利用するためのメソッド群だが、RDB および RDB にアクセスするための jdbc ドライバーは別途入手しなければならない。ここに商標を羅列したくないのでいちいち例示はしないが、複数のメーカーから RDB 製品が入手可能である。DB の設定方法、ドライバーの読み込み方法などは RDB 製品に依存するので適当な解説を参照されたい。

朔日

 「朔」(さく)は和語で「ついたち」と読む。陰暦月の第一日のことである。天文学的には月と太陽の赤経が一致した時刻を含む暦日と定義される。言い換えれば、朔から次の朔の前日までが一陰暦月となる。太陰暦はこの朔日が基準となる。しかし、陰暦月は暦日の整数倍にはならない。陰暦月は平均29.5日強となる。また平均太陽年である365.25日と29.5日の間にも整数比はなりたたない。一平均太陽年はおよそ12.4陰暦月となる。またこの陰暦月は常に同一の値をとるわけではない。地球の公転軌道が真円でないために時期によって多少変動する。また朔の瞬間が暦日のどの時刻にあたるかによっても、朔日が何日間隔で表われるかは変動する。こういった多くの要素をすべて考慮にいれて計算することは容易ではない。さらに言えば、どんなに厳密に計算して求められた朔日であっても現実に過去に使用された朔日とずれていては意味がない。この場合、計算を間違えているのは過去の暦であって正しいのは現代の計算であるにしても、実際問題としてこうしたずれは珍しいことではない。これを「間違いだから正しい値を使用するべきだ」と過去に向かって主張することは本末転倒である。こういった要素はどんな計算もカバーできない。そこで採用される手段はいきおい力技とならざるを得ない。朔日をひたすら羅列したDBを作成するのである。含まれるデータは「和暦年(もっとも近い西暦年で表わす。前1年は0と表記)」「月」「閏月か否か」「朔日のユリウス通日」となる。汎用的な csv 形式として一部を例示すると次のようになる。

saku.csv
(上略)
1866,12,0,2402973
1867,1,0,2403003
1867,2,0,2403032
1867,3,0,2403062
1867,4,0,2403091
1867,5,0,2403121
1867,6,0,2403150
1867,7,0,2403179
1867,8,0,2403208
1867,9,0,2403238
1867,10,0,2403267
1867,11,0,2403297
1867,12,0,2403327
1868,1,0,2403357
1868,2,0,2403386
1868,3,0,2403416
1868,4,0,2403446
1868,4,1,2403475
1868,5,0,2403504
1868,6,0,2403534
1868,7,0,2403563
1868,8,0,2403592
1868,9,0,2403622
1868,10,0,2403651
1868,11,0,2403681
1868,12,0,2403711
1869,1,0,2403740
(下略)
 例えば "1868,4,1,2403475" は1868(慶応4)年閏4月1日がユリウス通日で2403475日(西暦1868年5月22日)に当たることを示している。
 このデータを収容するべき表 SAKU は次のように定義される。
saku.ddl
CREATE TABLE SAKU
  (LYEAR  INTEGER  NOT NULL,
   LMONTH INTEGER  NOT NULL,
   LEAPM  INTEGER  NOT NULL,
   JDD    INTEGER,
   PRIMARY KEY(LYEAR,LMONTH,LEAPM))

陰暦(太陰太陽暦)

 中国をはじめとし、日本を含む東アジアで広く用いられていたのが陰暦である。正確には太陰太陽暦と呼ぶ。月に関しては太陰(月)を基準とし、年に関しては太陽を基準として折衷したためにこの呼び名を持つ。陰暦において、年の基準となるのは冬至である。冬至は一年のうちでもっとも昼間が短い(夜が長い)日であることは説明するまでもあるまい。冬至を含む陰暦月を十一月とする、というのが陰暦における月配置の原則である。こうして陰暦十一月は自動的に定まる。これから翌年の陰暦十一月までの間に12陰暦月が収まる場合は問題はない。順に十二月、正月、二月と配置していけば済むことである。しかし既述の通り1太陽年は平均して12.4陰暦月である。数年に一度はこの陰暦十一月どうしの間が13陰暦月になってしまうことがある。この場合は閏月を置いて調整する。問題はどの月を閏月とするか、である。上古では十二月の後に必ず閏月を置くようなことをしていたらしいが、これでは季節と月の間にずれが生じる。そこで新たに基準となったのが二十四節気である。二十四節気は冬至と冬至の間(つまり1太陽年)を24等分したもので、考え方としては太陽基準である。月の配当のためには、この二十四節気のうちひとつ置きに抜き出した12個を用いる(中気と呼ぶ)。それぞれの中気がどの月に配当されるかは順番に割り当てていけば決まってしまう。例えば夏至を含む月は五月となる。しかし、1平均太陽年を12等分すると中気と中気の間隔は平均で30.4日強となり、1陰暦月の29.5日と比べるとほとんど1日近く大きい。したがって中には中気を含まない陰暦月が出てくる。この陰暦月を閏月とし、その前の陰暦月が例えば八月だった場合にはこれを閏八月というふうに呼ぶのである(後八月、壬八月などと表記することもある)。
 なお、陰暦には時差があることに注意する必要がある。朔の瞬間は全世界どこでも同一だが、その瞬間がどの暦日に含まれるかは、観測している地点によって異なるのである。日本の公式な(つまり明治5年以前の)暦の基準点は京都であった。したがって、日本の暦は陰暦発祥の地中国の暦とは一致しない。これは当然のことである。逆に中国の(つまり中国の都たとえば長安などを基準とした)暦を使用することは「正朔を奉じる」と称して中華王朝の封建体制に組み込まれることを意味する。これを恩恵とうけとるか屈辱的とうけとるかはその時々の力関係や考え方に依存するので一概には言えない。明治後の新暦が公式なものとして用いられている現代、陰暦は「旧暦」として細々と生き延びてきているが、この「旧暦」は「日本標準時」つまり東経135度線を基準にしてかつての規則をあてはめて導かれたものなので、現在用いられている「旧暦」とかつての京都基準の暦は厳密には同じではない。
 ここに示したのが、陰暦の日付をもとにユリウス通日を得るためのメソッド fromWareki2JD() である。これまで紹介してきたものと基本的に同一であるが、ひとつ注意点としては閏月を示すために負の値を用いているということがある。例えば閏三月は -3 として入力される。その一方で、以前用いたような一年を3月から14月の仮想の暦年で示すような配慮は必要ない。灰色で示された文字列の内容は RDB 製品に依存するので適切なもので置き換えなければならない。

long fromWareki2JD( long y, long m, long d )
 public static long fromWareki2JD( long y, long m, long d ) {

    long jd = 0;
    try {
        Class.forName ("--- driver string ---");
        }
    catch (ClassNotFoundException e) {
        System.out.println("Error loading Driver...");
        e.printStackTrace();
        return(-1);
        }

    try {
        int leap;

        Connection con = DriverManager.getConnection("--- data source string ---", "", "");

        PreparedStatement pstmt = con.prepareStatement(
          "SELECT JDD FROM SAKU WHERE LYEAR = ? AND LMONTH = ? AND LEAPM = ?");
        ResultSet rs = null;

        if( m < 0 )  {
            m = 0 - m;
            leap = 1;
            }
        else leap = 0;

        pstmt.setInt(1, (int)y);
        pstmt.setInt(2, (int)m);
        pstmt.setInt(3, (int)leap);
        rs = pstmt.executeQuery();
        while (rs.next()) {
            long jds = rs.getInt("JDD");
            jd = jds + d - 1;
            }

        con.commit();
        pstmt.close();
        con.close();
        }
    catch(SQLException se) {
        while(se != null) {
            System.out.println("SQLCODE  = " + se.getErrorCode());
            System.out.println("SQLMSG   = " + se.getMessage());
            System.out.println("SQLSTATE = " + se.getSQLState());
            se = se.getNextException();
        }
    }
    catch(Exception e) {
        System.out.println("Exception : " + e.toString());
        e.printStackTrace();
        }
    return( jd );
    }
 続いて逆向きのユリウス通日から陰暦の日付を得るためのメソッド printLunaCalendar() を紹介する。これは long 型になっているが、出力の数値は後々に別のメソッドで利用するための月と日の和(詳しくは後述)であり、肝心の日付は標準出力に出力される。入力はユリウス通日である。出力において閏月を示すために、年の次の "."(ピリオド)の代わりに "u" を用いている。例えば "1868u 4. 3" とあれば1868(慶応四)年閏四月三日を示し、"1868. 4. 3" の1868(慶応四)年四月三日と区別できる。
long printLunaCalendar( long j )
 public static long printLunaCalendar( long j ) {

    int m = 0, d = 0;
    try {
        Class.forName ("--- driver string ---");
        }
    catch (ClassNotFoundException e) {
        System.out.println("Error loading Driver...");
        e.printStackTrace();
        return(-1);
        }

    try {
        if( j > 2477182 || j < 1477838 ) {
            System.out.println("Calendar date (Lunar) is:     !!! out of range !!!");
            return(-1);
            }

        Connection con = DriverManager.getConnection("--- data source string ---", "", "");
        ResultSet rs = null;

        PreparedStatement pstmt = con.prepareStatement(
          "SELECT LYEAR,LMONTH,LEAPM,JDD FROM SAKU " +
          "WHERE JDD = (SELECT MAX(JDD) FROM SAKU WHERE JDD <= ?)");

        pstmt.setInt(1, (int)j);
        rs = pstmt.executeQuery();
        int y = 0, leap = 0, jds = 0;
        while (rs.next()) {
            y = rs.getInt("LYEAR");
            m = rs.getInt("LMONTH");
            leap = rs.getInt("LEAPM");
            jds = rs.getInt("JDD");
            d = (int)j - jds + 1;
            }
        if( y <= 0 ) y--;
        StringBuffer sb = new StringBuffer();
        sb.append("Calendar date (Lunar) is:     ");
        if( y < 1000 && y > 99 )  sb.append(" ");
        if( y < 100 && y > 9 )    sb.append("  ");
        if( y < 10 && y > 0 )     sb.append("   ");
        if( y < 0 && y > -10 )    sb.append("  ");
        if( y < -9 && y > -100 )  sb.append(" ");
        sb.append(y);
        if( leap == 1 ) sb.append("u");
        else            sb.append(".");
        if( m < 10 )    sb.append(" ");
        sb.append(m);
        sb.append(".");
        if( d < 10 ) sb.append(" ");
        sb.append(d);

        String s = sb.toString();
        System.out.println(s);

        if (rs != null)
            rs.close();
        pstmt.close();
        con.close();
        }
    catch(SQLException se) {
        while(se != null) {
            System.out.println("SQLCODE  = " + se.getErrorCode());
            System.out.println("SQLMSG   = " + se.getMessage());
            System.out.println("SQLSTATE = " + se.getSQLState());
            se = se.getNextException();
            }
        }
    catch(Exception e) {
        System.out.println("Exception : " + e.toString());
        e.printStackTrace();
        }
    return( m + d );
    }
 少し余談となるが、西洋の暦では春分を年の基準とする。春分を3月21日に置くというもので、かつては春分が新年であった名残りらしい。東洋が基準とした冬至は夜が一番長い日、言い換えれば冬至までは夜が長くなっていく傾向にあるのに対して冬至で極限に達してこれより後はだんだん昼が長くなってくる。東洋的な表現をするならば陰が極まって陽に転じ始める日、と言えよう。一方の春分はそれ以前は昼よりも夜のほうが長かったのがこれより後は昼のほうが長くなる。つまり陽が陰をしのいで優勢になり始める日、と言えるだろう。多少の違いはあっても、考え方の根本は同じ。年の始まりというものに期待する思いは洋の東西を問わないようである。
 この関係から実は陰暦と陽暦の日付のおおまかな違いが算出できる。春分を3月21日前後に置く陽暦。一方、陰暦では冬至が必ず十一月になる。冬至は春分の約91日前になるはずだから、だいたい12月20日前後となる。これが陰暦十一月に該当するわけだ。仮に十一月一日が冬至だとするならば、この差は約50日。十一月三十日とすると20日となる。言い換えるならば、陽暦のある一日と同一表記となる陰暦の日は、実際にはその日の20日後ないし50日後の範囲に収まることになる。もう少し具体的に見てみよう。陽暦(グレゴリオ暦)1868年9月8日は、陰暦では慶応四(1868)年七月二十二日。ユリウス通日では2403584日となる。一方、陰暦の明治元(1868)年九月八日は陽暦で1868年10月23日。ユリウス通日は2403629日。差は45日。この差が20日を大きく下回ることは無いし、50日を大きく上回ることは無い。またかならず陰暦よりも陽暦のほうが進んだ日付になる。ここで「閏月が含まれているときはどうなるのだ」という指摘があるかもしれない。仮に冬至が陰暦十一月一日となり、その後春分までの間に閏月(例えば閏十一月)が含まれた場合は、まるまる1月加わるわけで、その差は最大80日にまでなってしまうのではないか。ところがそうはならないのである。閏月は「中気」を含まない月である。中気の間隔は30.4日、平均陰暦月は29.5日。閏月の直前の月に含まれる中気は必然的に末日となる。それよりも一日でも早くなれば、次の中気がその月の最後に含まれることとなり、閏月にはなり得ない。仮に春分の直前に閏正月が置かれたとしても、冬至までの差はせいぜい3日。結局、陰暦と陽暦の差は最大限でも50日を少し超える程度で、60日になることはない。この60日という数字は重要である。60日はつまり、干支が一巡する日数だ。上編で紹介した eto2d.java で干支を介して陰暦と陽暦が変換できる理論的根拠がこうして説明される。

六曜

 六曜は現代ではかなり浸透しているが、実は明治より古い時代にはほとんど顧みられたことがない。明治新政府がそれまで暦に付随していたさまざまな「由緒正しい」吉凶の暦注を迷信として全廃してしまったときに、六曜という「由緒正しくない」暦注は迷信とすら考えられずに新政府によって無視されたため、暦業者が「何もないのもさびしいから」と記入を始めたものが普及した、というのが実情らしい。
 六曜の仕組みは至極単純、陰暦正月は先勝から、二月は友引から、三月は先負から、四月は仏滅から、五月は大安から、六月は赤口から(七月から十二月は繰り返し、閏月は前月と同じ)順次始める、というただそれだけである。したがって、陰暦の月と日が決まれば六曜は機械的に定まる。この規則にしたがえば、中秋の名月すなわち陰暦八月十五日は必ず仏滅になるというのは、暦に関心のあるものの間ではよく知られた話である。陰暦月が改まるとこの六曜の順序が飛ぶ。陽暦で生活している現代では、この跳躍が脈絡なく発生しているように見えるので神秘的に思えてありがたみを感じているのではないかという意見もあるようだ。先に fromWareki2JD() のところで説明した月と日の和は、この計算のために出力しているのである。この数値を入力にして六曜を計算するメソッドが次の ret6yo() と printJ6yo() である。入力はそれぞれ陰暦月(閏月は数字をそのまま、例えば閏四月なら4)と陰暦日であるが、両方の和にのみ意味があるので、ふたつの入力パラメーターのうち片方に月と日の和、もう片方にゼロを入れても期待した結果は得られる。

long ret6yo( long m, long d )
 public static long ret6yo( long m, long d ) {

    long rokuyo = (m + d -1) % 6;
    if (rokuyo == 0) rokuyo = 6;

    return( rokuyo );
    }
String printJ6yo( long m, long d )
 public static String printJ6yo( long m, long d ) {

    long rokuyo = ret6yo( m, d );

    StringBuffer sb = new StringBuffer();
    sb.append("6-Yo:      ");

    if ( rokuyo == 1)    sb.append("先勝");
    if ( rokuyo == 2)    sb.append("友引");
    if ( rokuyo == 3)    sb.append("先負");
    if ( rokuyo == 4)    sb.append("仏滅");
    if ( rokuyo == 5)    sb.append("大安");
    if ( rokuyo == 6)    sb.append("赤口");

    String s = sb.toString();
    return(s);
    }

和暦(元号)

 元号について特に細かく説明することはしない。ただここではいくつかの注意点を挙げておく。もっとも問題になるのは、改元された年の改元日より前の日付を何年と表記するべきかという点である。厳密に言えば改元された年の新元号はその年の元日にまでさかのぼって適用されるべきものであるが、実際問題として改元前は前の元号を使うしかない。この期間の元号をどう表記するかは考え方次第であるが、ここで紹介するプログラムでは改元日より前は併記し、改元日以後は新元号のみを示すこととしている。もうひとつの問題は、同時に複数の元号が用いられているケースである。南北朝時代がもっとも顕著な事例だが、ほかにも源平合戦期の末期(寿永・元暦)や、南北朝直前の後醍醐天皇の時代(元徳・元弘・正慶)にも例がある。ほかにも室町時代に鎌倉公方の足利持氏が将軍主導の改元を認めずに旧元号を使い続けた例もあるが主体的に元号を定めたわけではないので同一には論じられまい。むしろ南北朝時代の観応の擾乱で足利尊氏が南朝に降伏して一時南朝の元号である正平を用いた事例のほうが重大である。ここでは複数の元号が用いられた事例のうち南北朝時代の元号だけをとりあげる。南北朝直前の時代では、元弘改元後は元弘を、正慶改元後は正慶を、そして正慶を廃した後はまた元弘を採用している。このためにプログラミング上で多少例外処理が必要になった。
 元号を処理するために RDB 上に新たな表 KAIGEN および NANCHOKAIGEN が必要になった。構造は同一である。

kaigen.ddl
CREATE TABLE KAIGEN
  (JDD    INTEGER  NOT NULL,
   GENGO  GRAPHIC(5),
   PRIMARY KEY(JDD))
 この表に入力するべきデータは次のような形となる。前と同じく csv 形式で例示する。
kaigen.csv
(上略)
2374529,"寛政"
2378939,"享和"
2380038,"文化"
2385216,"文政"
2389841,"天保"
2394941,"弘化"
2396119,"嘉永"
2398599,"安政"
2400509,"万延"
2400864,"文久"
2401958,"元治"
2402358,"慶応"
2403629,"明治"
2419614,"大正"
2424875,"昭和"
2447534,"平成"
 南朝元号について以下にそれぞれ定義文と csv 形式データを示す。
nanchokaigen.ddl
CREATE TABLE NANCHOKAIGEN
  (JDD    INTEGER  NOT NULL,
   GENGO  GRAPHIC(5),
   PRIMARY KEY(JDD))
nanchokaigen.csv
2209133,"延元"
2210638,"興国"
2213069,"正平"
2221678,"建徳"
2222305,"文中"
2223453,"天授"
2225533,"弘和"
2226702,"元中"
2229809,
 これだけのデータを準備した上で、和暦を表示するためのプログラムがようやく実現できるのである。メソッド printWareCalendar() を紹介する。入力は例によってユリウス通日である。
void printWareCalendar( long jd )
 public static void printWareCalendar( long jd ) {
    try {
        Class.forName ("--- driver string ---");
        }
    catch (ClassNotFoundException e) {
        System.out.println("Error loading Driver...");
        e.printStackTrace();
        return;
        }

    try {
        if( jd > 2477182 || jd < 1477838 ) {
            System.out.println("Calendar date (Japanese) is:  !!! out of range !!!");
            return;
            }
        int y = 0, m = 0, leap = 0, d = 0, jds = 0;
        int jdg = 0, gstarty = 0, gyear = 0;

        Connection con = DriverManager.getConnection("--- data source string ---", "", "");
        ResultSet rs = null;

        PreparedStatement pstmt1 = con.prepareStatement(
           "SELECT LYEAR,LMONTH,LEAPM,JDD FROM SAKU WHERE JDD = " +
           "(SELECT MAX(JDD) FROM SAKU WHERE JDD <= ?)");
        PreparedStatement pstmt2 = con.prepareStatement(
           "SELECT JDD,GENGO FROM KAIGEN WHERE JDD = " +
           "(SELECT MAX(JDD) FROM KAIGEN WHERE JDD <= ?)");
        PreparedStatement pstmt3 = con.prepareStatement(
           "SELECT LYEAR FROM SAKU WHERE JDD = " +
           "(SELECT MAX(JDD) FROM SAKU WHERE JDD <= ? " +
           "AND LMONTH=1 AND LEAPM=0)");
        PreparedStatement pstmt4 = con.prepareStatement(
           "SELECT JDD,GENGO FROM KAIGEN WHERE JDD = " +
           "(SELECT MIN(JDD) FROM KAIGEN WHERE JDD > ?)");

        if( jd > 2405159 ) {
            y = (int)getGregCalY( jd );
            long wd = getGregCalJD( jd );
            m = (int)getMonth( wd );
            d = (int)getDate( wd, m );
            leap = 0;
            if( m > 12 ) {
                m -= 12;
                y++;
                }
            }
        else {
            pstmt1.setInt(1, (int)jd);
            rs = pstmt1.executeQuery();
            while (rs.next()) {
                y = rs.getInt("LYEAR");
                m = rs.getInt("LMONTH");
                leap = rs.getInt("LEAPM");
                jds = rs.getInt("JDD");
                d = (int)jd - jds + 1;
                }
            }

        String gengo = new String();
        pstmt2.setInt(1, (int)jd);
        rs = pstmt2.executeQuery();
        while (rs.next()) {
            jdg = rs.getInt("JDD");
            gengo = rs.getString("GENGO");
            if( gengo == null) {
                if( y == 1333 || y == 1334 ) {
                    pstmt2.setInt(1,2207460);
                    ResultSet rs1 = pstmt2.executeQuery();
                    while( rs1.next() ) {
                        jdg = rs1.getInt("JDD");
                        gengo = rs1.getString("GENGO");
                        }
                    }
                }
            }

        int exgyear = 0;
        int ejdg = 0;
        String exGengo = new String();
        if( gengo != null ) {
            if( jdg > 2405159 ) {
                gstarty = (int)getGregCalY( jdg );
                long wd = getGregCalJD( jdg );
                long mx = (int)getMonth( wd );
                if( mx > 12 )   gstarty++;
                }
            else {
                pstmt3.setInt(1, jdg);
                rs = pstmt3.executeQuery();
                while (rs.next())
                gstarty = rs.getInt("LYEAR");
                }
            gyear = y - gstarty + 1;

            pstmt4.setInt(1, (int)jd);
            rs = pstmt4.executeQuery();
            while( rs.next() ) {
                ejdg = rs.getInt("JDD");
                exGengo = rs.getString("GENGO");
                if( exGengo != null ) {
                    int exgstarty = 0;
                    if( ejdg > 2405159 ) {
                        exgstarty = (int)getGregCalY( ejdg );
                        long wd = getGregCalJD( ejdg );
                        long mx = (int)getMonth( wd );
                        if( mx > 12 )   exgstarty++;
                        }
                    else {
                        pstmt3.setInt(1, ejdg);
                        rs = pstmt3.executeQuery();
                        while (rs.next())
                            exgstarty = rs.getInt("LYEAR");
                        }
                    exgyear = y - exgstarty + 1;
                    }
                }
            }

        if( gengo == null && ( y == 1351 || y == 1352 ) )
            return;
        StringBuffer sb = new StringBuffer();
        sb.append("Calendar date (Japanese) is:  ");
        if( jdg != 0 ) {
            sb.append(gengo);
            if( gyear == 1 )   sb.append("元");
            else {
                if( gyear < 10 ) sb.append(" ");
                sb.append(gyear);
                }
            if( exgyear == 1 ) {
                sb.append("/");
                sb.append(exGengo);
                sb.append("元");
                }
            sb.append("年");
            sb.append("(");
            sb.append(retJKanshi(retYKanshiK(y),retYKanshiS(y)));
            sb.append(")");
            }
        else {
            sb.append(retJKanshi(retYKanshiK(y),retYKanshiS(y)));
            sb.append("年");
            }
        if( leap == 1 ) sb.append("閏");
        if( m == 1 )    sb.append("正");
        else {
            if( m < 10 ) sb.append(" ");
            sb.append(m);
            }
        sb.append("月");
        if( d < 10 ) sb.append(" ");
        sb.append(d);
        sb.append("日");

        String s = sb.toString();
        System.out.println(s);

        if (rs != null)
            rs.close();
        pstmt1.close();
        con.close();
        }
    catch(SQLException se) {
        while(se != null) {
            System.out.println("SQLCODE  = " + se.getErrorCode());
            System.out.println("SQLMSG   = " + se.getMessage());
            System.out.println("SQLSTATE = " + se.getSQLState());
            se = se.getNextException();
            }
        }
    catch(Exception e) {
        System.out.println("Exception : " + e.toString());
        e.printStackTrace();
        }
    }
出力例
>java printWareCalendar 2403446

Calendar date (Japanese) is:  慶応 4/明治元年(戊辰) 4月 1日
 南朝の元号を表示するために用意したメソッドが printNancCalendar() である。使用法は printWareCalendar() と同様だが、南朝元号の範囲外のユリウス通日を入力とした場合は何も出力せずに終了する。つまり呼び出す側は入力値の内容を考慮せずに無条件に呼び出すことができる。
void printNancCalendar( long jd )
 public static void printNancCalendar( long jd ) {
    try {
        Class.forName ("--- driver string ---");
        }
    catch (ClassNotFoundException e) {
        System.out.println("Error loading Driver...");
        e.printStackTrace();
        return;
        }

    try {
        int y = 0, m = 0, leap = 0, d = 0, jds = 0;
        int jdg = 0, gstarty = 0, gyear = 0;

        Connection con = DriverManager.getConnection("--- data source string ---", "", "");
        ResultSet rs = null;

        PreparedStatement pstmt1 = con.prepareStatement(
          "SELECT LYEAR,LMONTH,LEAPM,JDD FROM SAKU WHERE JDD = " +
          "(SELECT MAX(JDD) FROM SAKU WHERE JDD <= ?)");
        PreparedStatement pstmt2 = con.prepareStatement(
          "SELECT JDD,GENGO FROM NANCHOKAIGEN WHERE JDD = " +
          "(SELECT MAX(JDD) FROM NANCHOKAIGEN WHERE JDD <= ?)");
        PreparedStatement pstmt3 = con.prepareStatement(
          "SELECT LYEAR FROM SAKU WHERE JDD = (SELECT MAX(JDD) FROM SAKU "+
          "WHERE JDD <= ? AND LMONTH=1 AND LEAPM=0)");
        PreparedStatement pstmt4 = con.prepareStatement(
          "SELECT JDD,GENGO FROM NANCHOKAIGEN WHERE JDD = " +
          "(SELECT MIN(JDD) FROM NANCHOKAIGEN WHERE JDD > ?)");

        pstmt1.setInt(1, (int)jd);
        rs = pstmt1.executeQuery();
        while (rs.next()) {
            y = rs.getInt("LYEAR");
            m = rs.getInt("LMONTH");
            leap = rs.getInt("LEAPM");
            jds = rs.getInt("JDD");
            d = (int)jd - jds + 1;
            }

        String gengo = new String();
        pstmt2.setInt(1, (int)jd);
        rs = pstmt2.executeQuery();
        while (rs.next()) {
            jdg = rs.getInt("JDD");
            gengo = rs.getString("GENGO");
            }

        int exgyear = 0;
        String exGengo = new String();
        if( gengo != null && jdg != 0 ) {
            pstmt3.setInt(1, jdg);
            rs = pstmt3.executeQuery();
            while (rs.next())
                gstarty = rs.getInt("LYEAR");
            gyear = y - gstarty + 1;

            pstmt4.setInt(1, jdg);
            rs = pstmt4.executeQuery();
            while( rs.next() ) {
                jdg = rs.getInt("JDD");
                exGengo = rs.getString("GENGO");
                if( exGengo != null ) {
                    pstmt3.setInt(1, jdg);
                    ResultSet rsx = pstmt3.executeQuery();
                    while( rsx.next() ) {
                        exgyear = y - rsx.getInt("LYEAR") + 1;
                        }
                    }
                }

            StringBuffer sb = new StringBuffer();
            sb.append("Calendar date (Nancho) is:    ");
            sb.append(gengo);
            if( gyear == 1 )   sb.append("元");
            else   {
                if( gyear < 10 ) sb.append(" ");
                sb.append(gyear);
                }
            if( exgyear == 1 ) {
                sb.append("/");
                sb.append(exGengo);
                sb.append("元");
                }
            sb.append("年");
            sb.append("(");
            sb.append(retJKanshi(retYKanshiK(y),retYKanshiS(y)));
            sb.append(")");
            if( leap == 1 ) sb.append("閏");
            if( m == 1 )    sb.append("正");
            else {
                if( m < 10 ) sb.append(" ");
                sb.append(m);
                }
            sb.append("月");
            if( d < 10 ) sb.append(" ");
            sb.append(d);
            sb.append("日");

            String s = sb.toString();
            System.out.println(s);
        }

        if (rs != null)
            rs.close();
        pstmt1.close();
        con.close();
        }
    catch(SQLException se) {
        while(se != null) {
            System.out.println("SQLCODE  = " + se.getErrorCode());
            System.out.println("SQLMSG   = " + se.getMessage());
            System.out.println("SQLSTATE = " + se.getSQLState());
            se = se.getNextException();
            }
        }
    catch(Exception e) {
        System.out.println("Exception : " + e.toString());
        e.printStackTrace();
        }
    }
出力例
>java printNancCalendar 2211647

Calendar date (Nancho) is:    興国 4年(癸未) 2月 4日

>java printNancCalendar 2403446
>
 ここまで紹介してきたメソッドを適宜組み合わせることにより(ユリウス通日を媒介として)グレゴリオ暦、ユリウス暦、陰暦(元号を含む)を相互に変換することができ、それぞれの七曜、干支および六曜までも得られる。あとは各自が使いやすいよう上位メソッドを用意するなりGUI画面を作成するなりしていただきたい。作者はCUIな人なので入力する日付(グレゴリオ暦、ユリウス暦、陰暦)の種類だけインターフェースを準備して出力はすべて共通のものを使用している。
 例としてその日の各暦での日付を表示する today.java を紹介しておく。
today.java
import java.io.*;
import java.util.*;

class today {
 public static void main(String args[]) {
   try {
   Calendar cal = Calendar.getInstance();

   long y = cal.get(Calendar.YEAR);
   long m = cal.get(Calendar.MONTH) + 1;
   long d = cal.get(Calendar.DATE);

   long jd;
   jd = fromDate2JD( y, m, d );

   System.out.println("");
   System.out.println("Julian date is:               " + jd);
   System.out.println("");

   printWareCalendar( jd );
   printNancCalendar( jd );
   System.out.println(printJuliCalendar( jd ));
   System.out.println(printGregCalendar( jd ));
   long md = printLunaCalendar( jd );
   System.out.println(printWeekday( jd ));
   System.out.println(printJKanshi( jd ));
   if( md != -1 )   System.out.println(printJ6yo( md, 0 ));
   }

    catch (Exception e) {
     System.out.println("Exception: " + e );
    }
  }
}
出力例
>java today

Julian date is:               2453469

Calendar date (Japanese) is:  平成17年(乙酉) 4月 8日
Calendar date (Julian) is:    2005. 3.26
Calendar date (Gregorian) is: 2005. 4. 8
Calendar date (Lunar) is:     2005. 2.30
Weekday:   Fri
Kan-shi:   壬戌
6-Yo:      先勝

附:イスラム暦

 最後にイスラム暦について紹介する。これまで紹介してきた陰暦は正確には太陰太陽暦であるが、イスラム暦は完全な太陰暦である。陰暦月12ヶ月で1イスラム暦年となる。1陰暦月はおよそ29.5日であるから、1イスラム暦年は354日(閏年は355日)となる。当然季節と年は一致しない。イスラム暦の新年はある時期は真夏になり、ある時期は真冬になる。閏年は30年に11回おかれる。この30年周期の第2、5、7、10、13、16、18、21、24、26、29年目の12月が30日となる(イスラム暦では奇数月が30日、偶数月が29日)。しかし実際にはイスラム暦の置閏法は実測主義をとっており、事前に計算された暦が常に正しいとは限らない。であるからここに紹介したイスラム暦を計算するためのメソッドはあくまで参考であって、実際のものとは異なる可能性がある。  まずイスラム暦をユリウス通日に変換するメソッド fromIslaDate2JD() を示す。本来イスラム暦の月はそれぞれ固有の名をもっている(例えば第9月ラマダーン Ramadan)。しかしここでは陰暦と同じように第何月にあたるかという数字で扱っている。

long fromIslaDate2JD( long y, long m, long d )
 public static long fromIslaDate2JD( long y, long m, long d ) {

    y = y + 3000;
    long wy30q, wy30r, wy1 = 0, wm;

    wy30q = y / 30;
    wy30r = y % 30;

    wm = (((m-1) * 295) + 5) / 10;

    wy1 = 354* (wy30r - 1);
    int i;
    for( i = 1 ; i < (wy30r - 1) ; i++ ) {
        if( i == 2 || i == 5 || i == 7 || i == 10 || i == 13 || i == 16 ||
            i == 18 || i == 21 || i == 24 || i == 26 || i == 29 )  wy1++;
       }

    long wy30 = wy30q * (30*354+11);

    long jd = wy30 + wy1 + wm + d + 1948439 - 100 * (30*354+11);

    return( jd );
    }
 続いて、ユリウス通日からイスラム暦に変換する一連のメソッド群を示す。今回は新しい試みとして中心となるべきメソッドをふたつにわけてみた。
String printIslaLineCalendar( long j )
String printIslaCalendar( long y, long m, long d )
long getIslaCalY( long j )
long getIslaCalJD( long j )
long getIslaMonth( long jdy )
long getIslaDate( long jdy, long m )
 public static String printIslaLineCalendar( long j ) {

    long y = getIslaCalY( j );
    long wd = getIslaCalJD( j );
    long m = getIslaMonth( wd );
    long d = getIslaDate( wd, m );
    if( y <= 0 ) y--;
    String sd = printIslaCalendar(y,m,d);

    StringBuffer sb = new StringBuffer();
    sb.append("Calendar date (Islamic) is:   ");
    if( sd.length() < 10 )
        for( int i = 1 ; i <= 10-sd.length() ; i++ )
            sb.append(" ");
    sb.append(sd);

    String s = sb.toString();
    return(s);
    }

 public static String printIslaCalendar( long y, long m, long d ) {

    StringBuffer sb = new StringBuffer();
    sb.append(y);
    sb.append(".");
    if( m < 10 ) sb.append(" ");
    sb.append(m);
    sb.append(".");
    if( d < 10 ) sb.append(" ");
    sb.append(d);

    String s = sb.toString();
    return(s);
    }

 public static long getIslaCalY( long j ) {

    long wy = j - 1948439;
    long wy30q = wy / (354*30+11);
    long wy30r = wy % (354*30+11);
    if( wy30r <= 0 ) {
        wy30r += (30*354+11);
        wy30q--;
        }

    int i;
    long wd = wy30r;
    for( i = 1 ; i < 30 ; i++ ) {
        long yd = 354;
        if( i == 2 || i == 5 || i == 7 || i == 10 || i == 13 || i == 16 ||
            i == 18 || i == 21 || i == 24 || i == 26 || i == 29 )  yd = 355;
        if( wd <= yd ) {
            break;
            }
        else wd -= yd;
        }
    return( wy30q*30+i );
    }

 public static long getIslaCalJD( long j ) {

    long wy = j - 1948439;
    long wy30q = wy / (354*30+11);
    long wy30r = wy % (354*30+11);
    if( wy30r <= 0 ) {
        wy30r += (30*354+11);
        wy30q--;
        }

    int i;
    long wd = wy30r;
    for( i = 1 ; i < 30 ; i++ ) {
        long yd = 354;
        if( i == 2 || i == 5 || i == 7 || i == 10 || i == 13 || i == 16 ||
            i == 18 || i == 21 || i == 24 || i == 26 || i == 29 )  yd = 355;
        if( wd <= yd ) {
            break;
            }
        else wd -= yd;
        }
    return( wd );
    }

 public static long getIslaMonth( long jdy ) {

    long m = ((jdy * 10) / 295) + 1;
    if((((m-1) * 295) + 5) / 10 == jdy || m == 13 ) m--;

    return( m );
    }

 public static long getIslaDate( long jdy, long m ) {

    long wmd = (((m-1) * 295) + 5) / 10;
    long d = jdy - wmd;

    return( d );
    }

終わりに

 実は、下編でとりあげた RDB の部分は著者の専門分野であるのだが、プライベートに仕事をもちこまないためにわざと詳しく説明しなかった。そもそもの本筋ではない、ということも理由のひとつではある。下編に含まれる部分の多くはデータがないとどうしようもないのであるが、非常に量が多いので抄録にとどめざるを得なかった。機会があったらデータそのものも公開してみたい。



TOPページへ

Copyright © Hiroshi Nishida (Misohito), 2005.