/*
 * DateTime class.
 *
 * Copyright (C) 2007 SATOH Takayuki All Rights Reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
package ts.util;

import java.io.Serializable;
import java.util.Date;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.TimeZone;
import java.text.SimpleDateFormat;
import java.text.ParseException;
import ts.util.text.StringOperation;

/**
 * 日時を表現するクラス。
 * <br>
 * 日付及び時刻に関する情報及び操作をまとめたクラスである。
 * デフォルトで使用する暦はグレゴリオ暦であり、そのカレンダオブジェクトとして
 * {@link java.util.GregorianCalendar GregorianCalendar}オブジェクトを使用する。
 * ロケールやタイムゾーンは、特に指定しなかった場合はシステムでデフォルトのもの
 * が使用される。
 * <br>
 * 使用する暦やロケール、タイムゾーンを変更したい場合は、カレンダオブジェクトを
 * 引数にとるコンストラクタを使用する。
 * <br/>
 * 日付の演算や不正チェックは、使用しているカレンダオブジェクトに任せている。
 * <br>
 * このクラスは、JDKの日付関連のクラスに比べて以下の機能が追加されている：
 * <ul>
 * <li>月を整数型で設定又は取得するとき、0〜11ではなく1〜12で指定するように
 *     した。用意した月定数（<code>JANUARY, FEBRUARY</code>, etc.)も1〜12の値
 *     が割り当てられている。</li>
 * <li>年月日時分秒などの値を取得するとき、<code>java.util.Calendar</code>を
 *     使って求める手間を隠蔽した。</li>
 * <li>日付を{@link java.sql.Date java.sql.Date}/
 *     {@link java.sql.Time java.sql.Time }/
 *     {@link java.sql.Timestamp java.sql.Timestamp}
 *     クラスのインスタンスとして取得するメソッドを追加した。</li>
 * <li>指定した日付の開始時刻と終了時刻、指定した月の開始日時と終了日時を設定
 *     するメソッドを用意した。</li>
 * </ul>
 *
 * @author  佐藤隆之. 
 * @version $Revision: 1.1.1.1 $, $Date: 2010-10-16 00:03:43 $
 */
public class DateTime implements Serializable, Cloneable, Comparable<DateTime>
{
  /** このクラスのシリアルバージョンID。 */
  static final long serialVersionUID = -2327361093160202340L;

  /** ミリ秒単位の日時。1970年1月1日0時0分0秒をゼロとする。 */
  private long dateTimeMillis_ ;

  /** スレッドローカルなカレンダオブジェクトを格納するオブジェクト。 */
  private static transient ThreadLocal<Calendar> threadLocalCalendar_ ;

  /** このオブジェクトに設定されたカレンダオブジェクト。 */
  private Calendar calendar_ = null;

  static 
  {
    threadLocalCalendar_ = new ThreadLocal<Calendar>();
  }

  /**
   * スレッドローカルなカレンダオブジェクトを取得する。
   *
   * @return スレッドローカルなカレンダオブジェクト。
   */
  private static Calendar getTLCalendar()
  {
    Calendar cal = threadLocalCalendar_.get();
    if (cal == null) {
      cal = new GregorianCalendar();
      cal.setLenient(false);
      threadLocalCalendar_.set(cal);
    }
    return cal;
  }

  /**
   * 内部で使用するカレンダオブジェクトを取得する。
   *
   * @return カレンダオブジェクト。
   */
  private Calendar getInnerCalendar()
  {
    return (calendar_ != null) ? calendar_ : getTLCalendar(); 
  }

  /**
   * デフォルトコンストラクタ。
   * <br>
   * 現在の日時を保持するインスタンスを構築する。
   */
  public DateTime()
  {
    this(System.currentTimeMillis());
  }

  /**
   * コピーコンストラクタ。
   * <br>
   * 引数と同じ日時及びカレンダを持つインスタンスを構築する。
   *
   * @throws AssertionError 引数がヌルの場合（デバッグモードのみ）。
   */
  public DateTime(DateTime datetime)
  {
    assert (datetime != null) : "@param:datetime is null.";

    setDateTime(datetime.getDateTimeMillis());

    if (datetime.calendar_ != null) {
      this.calendar_ = datetime.calendar_ ;
    }
  }

  /**
   * ミリ秒単位の日時を引数にとるコンストラクタ。
   * <br>
   * 1970年1月1日0時0分0秒をゼロとするミリ秒単位の<code>long</code>値を設定
   * する。
   *
   * @param datetime ミリ秒単位の日時。
   */
  public DateTime(long datetime)
  {
    setDateTime(datetime);
  }

  /**
   * JDKの日付オブジェクトを引数にとるコンストラクタ。
   *
   * @param  date {@link java.util.Date Date}オブジェクト。
   * @throws AssertionError 引数がヌルの場合（デバッグモードのみ）。
   */
  public DateTime(java.util.Date date)
  {
    setDateTime(date);
  }

  /**
   * 年月日を引数にとるコンストラクタ。
   * <br>
   * 時分秒には、0時0分0秒を設定する。
   * <br>
   * 指定された年月日が不正な場合は例外をスローする。
   *
   * @param  year  年。
   * @param  month 月(1〜12)。
   * @param  day   日(1〜31)。
   * @throws IllegalArgumentException 指定された年月日が不正な場合。
   */
  public DateTime(int year, int month, int day) throws IllegalArgumentException
  {
    this(year, month, day, 0, 0, 0);
  }

  /**
   * 年月日時分秒を引数にとるコンストラクタ。
   * <br>
   * 指定された年月日時分秒が不正な場合は例外をスローする。
   *
   * @param  year  年。
   * @param  month 月(1〜12)。
   * @param  day   日(1〜31)。
   * @param  hour  時(0〜23)。
   * @param  min   分(0〜59)。
   * @param  sec   秒(0〜59)。
   * @throws IllegalArgumentException 指定された年月日が不正な場合。
   */
  public DateTime(int year, int month, int day, int hour, int min, int sec)
    throws IllegalArgumentException
  {
    setDateTime(year, month, day, hour, min, sec);
  }

  /**
   * 元号を示すコードと年月日時分秒を引数にとるコンストラクタ。
   * <br>
   * 指定された元号又は年月日時分秒が不正な場合は例外をスローする。
   *
   * @param  era   元号を示すコード。
   * @param  year  年。
   * @param  month 月(1〜12)。
   * @param  day   日(1〜31)。
   * @param  hour  時(0〜23)。
   * @param  min   分(0〜59)。
   * @param  sec   秒(0〜59)。
   * @throws IllegalArgumentException 指定された年月日が不正な場合。
   */
  public DateTime(
    int era, int year, int month, int day, int hour, int min, int sec
  ) throws IllegalArgumentException
  {
    setDateTime(era, year, month, day, hour, min, sec);
  }

  /**
   * カレンダオブジェクトを引数にとるコンストラクタ。
   * <br>
   * 使用したい暦に対応する{@link java.util.Calendar Calendar}オブジェクトを
   * 設定する。
   *
   * @param  calendar {@link java.util.Calendar Calendar}オブジェクト。
   * @throws AssertionError 引数がヌルの場合（デバッグモードのみ）。
   */
  public DateTime(Calendar calendar)
  {
    assert (calendar != null) : "@param:calendar is null.";
    calendar_ = calendar;
    setDateTime(calendar.getTimeInMillis());
  }

  /**
   * 日時をミリ秒単位で取得する。
   *
   * @return ミリ秒単位の日時。
   */
  public long getDateTimeMillis()
  {
    return dateTimeMillis_ ;
  }

  /**
   * 日時を{@link java.util.Date}オブジェクトとして取得する。
   *
   * @return {@link java.util.Date}オブジェクト。
   */
  public java.util.Date getDate()
  {
    return new java.util.Date(getDateTimeMillis());
  }

  /**
   * 日時を{@link java.sql.Date}オブジェクトとして取得する。
   *
   * @return {@link java.sql.Date}オブジェクト。
   */
  public java.sql.Date getSQLDate()
  {
    return new java.sql.Date(getDateTimeMillis());
  }

  /**
   * 日時を{@link java.sql.Time}オブジェクトとして取得する。
   *
   * @return {@link java.sql.Time}オブジェクト。
   */
  public java.sql.Time getSQLTime()
  {
    return new java.sql.Time(getDateTimeMillis());
  }

  /**
   * 日時を{@link java.sql.Timestamp}オブジェクトとして取得する。
   *
   * @return {@link java.sql.Timestamp}オブジェクト。
   */
  public java.sql.Timestamp getSQLTimestamp()
  {
    return new java.sql.Timestamp(getDateTimeMillis());
  }

  /**
   * 日時を{@link java.util.Calendar}オブジェクトとして取得する。
   *
   * @return {@link java.util.Calendar}オブジェクト。
   */
  public Calendar getCalendar()
  {
    Calendar cal = (Calendar) getInnerCalendar().clone();
    cal.setTimeInMillis(getDateTimeMillis());
    return cal;
  }

  /**
   * タイムゾーンを取得する。
   *
   * @return タイムゾーンを取得する。
   */
  public TimeZone getTimeZone()
  {
    return getInnerCalendar().getTimeZone();
  }

  /**
   * 元号を示す<code>int</code>値を取得する。
   *
   * @return 元号を示す<code>int</code>値。
   */
  public int getEra()
  {
    Calendar cal = getInnerCalendar();
    cal.setTimeInMillis(getDateTimeMillis());
    return cal.get(Calendar.ERA);
  }

  /**
   * 年を取得する。
   *
   * @return 年。
   */
  public int getYear()
  {
    Calendar cal = getInnerCalendar();
    cal.setTimeInMillis(getDateTimeMillis());
    return cal.get(Calendar.YEAR);
  }

  /**
   * 月を取得する。
   *
   * @return 月(1〜12)。
   */
  public int getMonth()
  {
    Calendar cal = getInnerCalendar();
    cal.setTimeInMillis(getDateTimeMillis());
    return cal.get(Calendar.MONTH) + 1;
  }

  /**
   * 日を取得する。
   *
   * @return 日(1〜31)。
   */
  public int getDay()
  {
    Calendar cal = getInnerCalendar();
    cal.setTimeInMillis(getDateTimeMillis());
    return cal.get(Calendar.DAY_OF_MONTH);
  }

  /**
   * 曜日を表すコードを取得する。
   * <br>
   * コードは、日曜日から順に1〜7の値をとる。
   *
   * @return 曜日を表すコード。
   */
  public int getWeek()
  {
    Calendar cal = getInnerCalendar();
    cal.setTimeInMillis(getDateTimeMillis());
    return cal.get(Calendar.DAY_OF_WEEK);
  }

  /**
   * 時を取得する。
   *
   * @return 時(0〜23)。
   */
  public int getHour()
  {
    Calendar cal = getInnerCalendar();
    cal.setTimeInMillis(getDateTimeMillis());
    return cal.get(Calendar.HOUR_OF_DAY);
  }

  /**
   * 分を取得する。
   *
   * @return 分(0〜59)。
   */
  public int getMinute()
  {
    Calendar cal = getInnerCalendar();
    cal.setTimeInMillis(getDateTimeMillis());
    return cal.get(Calendar.MINUTE);
  }

  /**
   * 秒を取得する。
   *
   * @return 秒(0〜59)。
   */
  public int getSecond()
  {
    Calendar cal = getInnerCalendar();
    cal.setTimeInMillis(getDateTimeMillis());
    return cal.get(Calendar.SECOND);
  }

  /**
   * 日時をミリ単位で設定する。
   *
   * @param  millis ミリ秒単位の日時。
   */
  public void setDateTime(long millis)
  {
    dateTimeMillis_ = millis;
  }

  /**
   * 日時を日付オブジェクトで設定する。
   *
   * @param  date {@link java.util.Date}オブジェクト。
   * @throws AssertionError 引数がヌルの場合（デバッグモードのみ）。
   */
  public void setDateTime(java.util.Date date)
  {
    assert (date != null) : "@param:date is null.";

    setDateTime(date.getTime() - 0L);
  }

  /**
   * 日時を年月日で設定する。
   * <br>
   * 時分秒にはそれぞれゼロが設定される。
   *
   * @param  year  年。
   * @param  month 月(1〜12)。
   * @param  day   日(1〜31)。
   * @throws IllegalArgumentException 指定された年月日が不正な場合。
   */
  public void setDateTime(int year, int month, int day)
    throws IllegalArgumentException
  {
    setDateTime(year, month, day, 0, 0, 0);
  }

  /**
   * 日時を年月日時分秒で設定する。
   *
   * @param  y  年。
   * @param  m  月(1〜12)。
   * @param  d  日(1〜31)。
   * @param  hh 時(0〜23)。
   * @param  mm 分(0〜59)。
   * @param  ss 秒(0〜59)。
   * @throws IllegalArgumentException 指定された年月日時分秒が不正な場合。
   */
  public void setDateTime(int y, int m, int d, int hh, int mm, int ss)
    throws IllegalArgumentException
  {
    Calendar cal = getInnerCalendar();
    cal.clear();

    cal.set(Calendar.ERA, cal.getMaximum(Calendar.ERA));
    cal.set(y, m-1, d, hh, mm, ss);

    setDateTime(cal.getTimeInMillis());
  }

  /**
   * 日時を元号及び年月日時分秒で設定する。
   *
   * @param  e  元号を表すコード。
   * @param  y  年。
   * @param  m  月(1〜12)。
   * @param  d  日(1〜31)。
   * @param  hh 時(0〜23)。
   * @param  mm 分(0〜59)。
   * @param  ss 秒(0〜59)。
   * @throws IllegalArgumentException 指定された元号や年月日時分秒が不正な場合。
   */
  public void setDateTime(int e, int y, int m, int d, int hh, int mm, int ss)
    throws IllegalArgumentException
  {
    Calendar cal = getInnerCalendar();
    cal.clear();

    cal.set(Calendar.ERA, e);
    cal.set(y, m-1, d, hh, mm, ss);

    setDateTime(cal.getTimeInMillis());
  }

  /**
   * 設定されている日付の時刻を、開始時刻(00:00:00)に設定する。
   */
  public void setStartTimeOfDay()
  {
    Calendar cal = getInnerCalendar();
    cal.setTimeInMillis(getDateTimeMillis());
    int y = cal.get(Calendar.YEAR);
    int m = cal.get(Calendar.MONTH);
    int d = cal.get(Calendar.DAY_OF_MONTH);
    cal.clear();
    cal.set(y, m, d, 0, 0, 0);

    setDateTime(cal.getTimeInMillis());
  }

  /**
   * 設定されている日付の時刻を、終了時刻(23:59:59.999)に設定する。
   */
  public void setEndTimeOfDay()
  {
    Calendar cal = getInnerCalendar();
    cal.setTimeInMillis(getDateTimeMillis());

    int y = cal.get(Calendar.YEAR);
    int m = cal.get(Calendar.MONTH);
    int d = cal.get(Calendar.DAY_OF_MONTH);
    cal.clear();
    cal.set(y, m, d, 0, 0, 0);
    cal.add(Calendar.DAY_OF_MONTH, 1);

    setDateTime(cal.getTimeInMillis() - 1L);
  }

  /**
   * 設定されている日付を、その月の開始日時に設定する。
   */
  public void setStartDayOfMonth()
  {
    Calendar cal = getInnerCalendar();
    cal.setTimeInMillis(getDateTimeMillis());

    int y = cal.get(Calendar.YEAR);
    int m = cal.get(Calendar.MONTH);
    cal.clear();
    cal.set(y, m, 1, 0, 0, 0);

    setDateTime(cal.getTimeInMillis());
  }

  /**
   * 設定されている日付を、その月の終了日時に設定する。
   */
  public void setEndDayOfMonth()
  {
    Calendar cal = getInnerCalendar();
    cal.setTimeInMillis(getDateTimeMillis());

    int y = cal.get(Calendar.YEAR);
    int m = cal.get(Calendar.MONTH);
    int d = cal.get(Calendar.DAY_OF_MONTH);
    cal.clear();
    cal.set(y, m, 1, 0, 0, 0);
    cal.add(Calendar.MONTH, 1);

    setDateTime(cal.getTimeInMillis() - 1L);
  }

  /**
   * 設定されている日付をその週の開始日時（日曜日）に設定する。
   */
  public void setStartDayOfWeek()
  {
    Calendar cal = getInnerCalendar();
    cal.setTimeInMillis(getDateTimeMillis());

    int y = cal.get(Calendar.YEAR);
    int m = cal.get(Calendar.MONTH);
    int d = cal.get(Calendar.DAY_OF_MONTH);
    int w = cal.get(Calendar.DAY_OF_WEEK);
    cal.clear();
    cal.set(y, m, d, 0, 0, 0);
    cal.add(Calendar.DAY_OF_MONTH, -w + cal.SUNDAY);

    setDateTime(cal.getTimeInMillis());
  }

  /**
   * 設定されている日付を、その週の終了日時（土曜日）に設定する。
   */
  public void setEndDayOfWeek()
  {
    Calendar cal = getInnerCalendar();
    cal.setTimeInMillis(getDateTimeMillis());
    int y = cal.get(Calendar.YEAR);
    int m = cal.get(Calendar.MONTH);
    int d = cal.get(Calendar.DAY_OF_MONTH);
    int w = cal.get(Calendar.DAY_OF_WEEK);
    cal.clear();
    cal.set(y, m, d, 0, 0, 0);
    cal.add(Calendar.DAY_OF_MONTH, 7 - w + cal.SUNDAY);

    setDateTime(cal.getTimeInMillis() - 1L);
  }

  /**
   * 分を指定された値で切り下げる。
   * <br>
   * 引数が0の場合は、切り下げを行わない。
   *
   * @param  minute 切り下げの単位となる値。
   * @throws AssertionError 引数が負の場合（デバッグモードのみ）。
   */
  public void floorMinute(int minute)
  {
    assert (minute >= 0) : "@param:minute is null.";

    if (minute == 0) {
      return;
    }

    Calendar cal = getInnerCalendar();
    cal.setTimeInMillis(getDateTimeMillis());
    int y = cal.get(Calendar.YEAR);
    int m = cal.get(Calendar.MONTH);
    int d = cal.get(Calendar.DAY_OF_MONTH);
    int h = cal.get(Calendar.HOUR_OF_DAY);
    int min = cal.get(Calendar.MINUTE);

    min = (min / minute) * minute;

    cal.clear();
    cal.set(y, m, d, h, min, 0);

    setDateTime(cal.getTimeInMillis());
  }

  /**
   * 分を指定された値で切り上げる。
   * <br>
   * 引数が0の場合は、切り上げを行わない。
   *
   * @param  minute 切り上げの単位となる値。
   */
  public void ceilMinute(int minute)
  {
    assert (minute >= 0) : "@param:minute is null.";

    if (minute == 0) {
      return;
    }

    Calendar cal = getInnerCalendar();
    cal.setTimeInMillis(getDateTimeMillis());
    int y = cal.get(Calendar.YEAR);
    int m = cal.get(Calendar.MONTH);
    int d = cal.get(Calendar.DAY_OF_MONTH);
    int h = cal.get(Calendar.HOUR_OF_DAY);
    int min = cal.get(Calendar.MINUTE);

    min = 60 - ((60 - min ) / minute) * minute;

    cal.clear();
    cal.set(y, m, d, h, 0, 0);
    cal.add(Calendar.MINUTE, min);

    setDateTime(cal.getTimeInMillis());
  }

  /**
   * 設定されている日付に指定された年数を加算する。
   *
   * @param  years 加算する年数。
   */
  public void addYear(int years)
  {
    Calendar cal = getInnerCalendar();
    cal.setTimeInMillis(getDateTimeMillis());
    cal.add(Calendar.YEAR, years);

    setDateTime(cal.getTimeInMillis());
  }

  /**
   * 指定されている日付に指定された月数を加算する。
   *
   * @param  months 加算する月数。
   */
  public void addMonth(int months)
  {
    Calendar cal = getInnerCalendar();
    cal.setTimeInMillis(getDateTimeMillis());
    cal.add(Calendar.MONTH, months);

    setDateTime(cal.getTimeInMillis());
  }

  /**
   * 指定されている日付に指定された日数を加算する。
   *
   * @param  days 加算する日数。
   */
  public void addDay(int days)
  {
    Calendar cal = getInnerCalendar();
    cal.setTimeInMillis(getDateTimeMillis());
    cal.add(Calendar.DAY_OF_MONTH, days);

    setDateTime(cal.getTimeInMillis());
  }

  /**
   * 指定されている日付の時刻に指定された時間を加算する。
   *
   * @param  hours 加算する時間。
   */
  public void addHour(int hours)
  {
    Calendar cal = getInnerCalendar();
    cal.setTimeInMillis(getDateTimeMillis());
    cal.add(Calendar.HOUR_OF_DAY, hours);

    setDateTime(cal.getTimeInMillis());
  }

  /**
   * 指定されている日付の時刻に指定された分を加算する。
   *
   * @param  minutes 加算する分。
   */
  public void addMinute(int minutes)
  {
    Calendar cal = getInnerCalendar();
    cal.setTimeInMillis(getDateTimeMillis());
    cal.add(Calendar.MINUTE, minutes);

    setDateTime(cal.getTimeInMillis());
  }

  /**
   * 設定されている日付の時刻に指定された秒を加算する。
   *
   * @param  seconds 加算する秒。
   */
  public void addSecond(int seconds)
  {
    Calendar cal = getInnerCalendar();
    cal.setTimeInMillis(getDateTimeMillis());
    cal.add(Calendar.SECOND, seconds);

    setDateTime(cal.getTimeInMillis());
  }

  /**
   * このオブジェクトのハッシュコードを取得する。
   *
   * @return ハッシュコード。
   */
  public int hashCode()
  {
    return (int) getDateTimeMillis();
  }

  /**
   * 指定された日時オブジェクトとカレンダが等しいかどうかを判定する。
   *
   * @param  dttm 比較する日時オブジェクト。
   * @return <tt>等しければ<tt>true</tt>を返す。
   * @throws AssertionError 引数がヌルの場合（デバッグモードのみ）。
   */
  protected boolean equalsCalendar(DateTime dttm)
  {
    assert (dttm != null) : "@param:dttm is null.";

    Calendar cal1 = getInnerCalendar();
    cal1.clear();
    Calendar cal2 = dttm.getInnerCalendar();
    cal2.clear();
    return cal1.equals(cal2) ? true : false;
  }

  /**
   * 指定されたオブジェクトと内容が等しいかどうかを比較する。
   * <br>
   * 引数が{@link ts.util.DateTime DateTime}オブジェクトの場合は、
   * このオブジェクトの日時と指定されたオブジェクトの日時が等しいかどうかを
   * 比較する。
   * <br>
   * 引数がヌルの場合または{@link ts.util.DateTime DateTime}オブジェクト
   * ではない場合は常に<tt>false</tt>を返す。
   *
   * @param  obj 比較するオブジェクト。
   * @return このオブジェクトと引数のオブジェクトの内容が等しい場合は
   *           <tt>true</tt>を、そうでなければ<tt>false</tt>を返す。
   */
  public boolean equals(Object obj)
  {
    if (obj == null) {
      return false;
    }

    if (!(obj instanceof DateTime)) {
      return false;
    }

    DateTime dttm = (DateTime) obj;
    
    if (getDateTimeMillis() != dttm.getDateTimeMillis()) {
      return false;
    }

    if (! equalsCalendar(dttm)) {
      return false;
    }

    return true;
  }

  /**
   * 指定されたオブジェクトとの大小を比較する。
   * <br>
   * このオブジェクトの日時と指定されたオブジェクトの日付を比較して、
   * このオブジェクト日時の方が早い場合は負の整数値を、遅い場合は正の整数値を、
   * 等しい場合はゼロを返す。
   *
   * @param  dttm 比較する日時オブジェクト。
   * @return このオブジェクトの日時の方が早い場合は負の整数値を、遅い場合は正の
   *           整数値を、等しい場合はゼロを返す。
   * @throws NullPointerException 引数がヌルの場合。
   * @throws IllegalArgumentException このオブジェクトと引数の日時オブジェクトの
   *           カレンダが異なる場合。
   */
  public int compareTo(DateTime dttm) throws NullPointerException
  {
    if (! dttm.equalsCalendar(this)) {
      throw new IllegalArgumentException(
        "Calendar of the specified date-time is different from this.");
    }
   
    long v1 = getDateTimeMillis();
    long v2 = dttm.getDateTimeMillis();
    if (v1 > v2) {
      return 1;
    }
    else if (v1 < v2) {
      return -1;
    }
    else {
      return 0;
    }
  }

  /**
   * 日時を文字列に変換して出力する。
   * <br>
   * 日時は、{@link java.text.SimpleDateFormat SimpleDateFormat}を使用して、
   * <tt>yyyy-MM-dd HH:mm:ss</tt>の形式で出力される。
   *
   * @return 日時を表す文字列。
   * @see #toString(DateTime dttm)
   */
  public String toString()
  {
    return toString(this);
  }

  /**
   * 引数に指定された日時を文字列に変換する。
   * <br>
   * 日時は、{@link java.text.SimpleDateFormat SimpleDateFormat}を使用して、
   * <tt>yyyy-MM-dd HH:mm:ss</tt>の形式で出力される。
   * 元号が今と異なる場合には、<tt>"yyyy-MM-dd HH:mm:ss (G)</tt>の形式で出力
   * する。
   *
   * @param  dttm 日時オブジェクト。
   * @return 日時を表す文字列。
   * @throws AssertionError 引数がヌルの場合（デバッグモードのみ）。
   * @see java.text.SimpleDateFormat
   */
  public static String toString(DateTime dttm)
  {
    assert (dttm != null) : "@param:dttm is null.";

    if (dttm.getEra() == dttm.getInnerCalendar().getMaximum(Calendar.ERA)) {
      SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
      return fmt.format(dttm.getDate());
    }
    else {
      SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss (G)");
      return fmt.format(dttm.getDate());
    }
  }

  /**
   * 引数に指定された文字列を日時に変換する。
   * <br>
   * 文字列の解釈には、{@link java.text.SimpleDateFormat SimpleDateFormat}
   * を使用する。
   * 文字列が日時として解釈されるためには、その書式が<tt>"yyyy-MM-dd"</tt>、
   * <tt>"yyyy-MM-dd HH:mm:ss"</tt>、<tt>"yyyy-MM-dd HH:mm:ss (G)"</tt>
   * のいずれかに従っている必要がある。
   * <br>
   * 引数の文字列の書式が不正な場合は、例外をスローする。
   *
   * @param  str 日時を表す文字列。 
   * @return 日時オブジェクト。
   * @throws ParseException 引数文字列の書式が不正な場合。
   * @throws AssertionError 引数がヌルの場合（デバッグモードのみ）。
   * @see java.text.SimpleDateFormat
   */
  public static DateTime parseDateTime(String str) throws ParseException
  {
    assert (str != null) : "@param:str is null.";

    SimpleDateFormat fmt;

    int len = StringOperation.length(str);
    if (len <= 10) {
      fmt = new SimpleDateFormat("yyyy-MM-dd");
    }
    else if (len <= 19) {
      fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    }
    else {
      fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss (G)");
    }

    fmt.setLenient(false);
    return new DateTime(fmt.parse(str));
  }

  /* -- Constants -- */

  /** １月を表す定数。 */
  public final static int JANUARY = 1;

  /** ２月を表す定数。 */
  public final static int FEBRUARY = 2;

  /** ３月を表す定数。 */
  public final static int MARCH = 3;

  /** ４月を表す定数。 */
  public final static int APRIL = 4;

  /** ５月を表す定数。 */
  public final static int MAY = 5;

  /** ６月を表す定数。 */
  public final static int JUNE = 6;

  /** ７月を表す定数。 */
  public final static int JULY = 7;

  /** ８月を表す定数。 */
  public final static int AUGUST = 8;

  /** ９月を表す定数。 */
  public final static int SEPTEMBER = 9;

  /** 10月を表す定数。 */
  public final static int OCTOBER = 10;

  /** 11月を表す定数。 */
  public final static int NOVEMBER = 11;

  /** 12月を表す定数。 */
  public final static int DECEMBER = 12;


  /** 日曜日を表す定数。 */
  public final static int SUNDAY = 1;

  /** 月曜日を表す定数。 */
  public final static int MONDAY = 2;

  /** 火曜日を表す定数。 */
  public final static int TUESDAY = 3;

  /** 水曜日を表す定数。 */
  public final static int WEDNESDAY = 4;

  /** 木曜日を表す定数。 */
  public final static int THURSDAY = 5;

  /** 金曜日を表す定数。 */
  public final static int FRIDAY = 6;

  /** 土曜日を表す定数。 */
  public final static int SATURDAY = 7;
}
