/*
* Copyright (c) 2007 NTT DATA Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jp.terasoluna.fw.util;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* 日付・時刻・カレンダー関連のユーティリティクラス。
*
*/
public class DateUtil {
/**
* ログクラス
*/
private static Log log = LogFactory.getLog(DateUtil.class);
/**
* システム時刻を取得する。
*
* <p>WebサーバとAPサーバを分離したりクラスタ構成にした場合は、マシンに
* よりシステム日付が異なる可能性がある。これを避けるため、システム日付
* 取得には必ずこのメソッドを利用し、必要に応じて特定マシンの日付を取得
* するなどの措置がとれるようにしておく。</p>
*
* @deprecated 本staticメソッドはJavaプロセス実行環境下でのシステム時刻を取得しているが、{@code java.util.Calendar}あるいはJoda-Timeの{@code org.joda.time.DateTime}などのAPIで容易に代替可能であるため非推奨とする。
* @return システム時刻
*/
@Deprecated
public static java.util.Date getSystemTime() {
Calendar calendar = Calendar.getInstance();
return calendar.getTime();
}
/**
* java.util.Dateインスタンスを和暦として指定のフォーマットに
* 変換する。
*
* <p>
* ApplicationResources.properties
* で指定された日付フォーマットを用いて、西暦、和暦の変換を
* 行うことができる。<br>
* 下記は、和暦の境界となる日付、和暦名、アルファベット頭文字の
* 設定例である。
* <strong> ApplicationResources.properties による
* 和暦の設定例</strong><br>
* <code><pre>
* wareki.gengo.0.name = 平成
* wareki.gengo.0.roman = H
* wareki.gengo.0.startDate = 1989/01/08
* wareki.gengo.1.name = 昭和
* wareki.gengo.1.roman = S
* wareki.gengo.1.startDate = 1926/12/25
* wareki.gengo.2.name = 大正
* wareki.gengo.2.roman = T
* wareki.gengo.2.startDate = 1912/07/30
* wareki.gengo.3.name = 明治
* wareki.gengo.3.roman = M
* wareki.gengo.3.startDate = 1868/09/04
* </pre></code>
* </p>
*
* <strong>フォーマット文字列</strong><br>
* <p>フォーマットは、java.text.SimpleDateFormat クラスの
* <i>時刻パターン文字列</i> として解釈されるが、以下のパターン文字の解釈が
* (デフォルトロケールの) SimpleDateFormat クラスと異なる。
* </p>
*
* <div width="90%" align="center">
* <table border="1">
* <tr>
* <th>記号</th>
* <th><code> SimpleDateFormat</code> </th>
* <th><code> dateToWarekiString()</code> </th>
* </tr>
* <tr>
* <td>G</td>
* <td align="left">紀元<br><br>例:<br>AD</td>
* <td align="left">和暦元号<br><br>
* 例:<br>
* (4個以上の連続したパターン文字)<br>
* 明治、大正、昭和、平成<br>
* (3個以下の連続したパターン文字)<br>
* M、T、S、H</td>
* </tr>
* <tr>
* <td>y</td>
* <td align="left">年(西暦)<br><br>例:<br>2002</td>
* <td align="left">年(和暦)<br><br>例:<br>14</td>
* </tr>
* <tr>
* <td>E</td>
* <td align="left">曜日<br><br>例:<br>Tuesday</td>
* <td align="left">曜日(日本語表記)<br><br>
* 例:<br>
* (4個以上の連続したパターン文字)<br>
* 月曜日、火曜日、水曜日<br>
* (3個以下の連続したパターン文字)<br>
* 月、火、水</td>
* </tr>
* </table>
* </div>
*
* <p>これらのうち、曜日(E)については SimpleDateFotmat
* のインスタンス作成時に、ロケールを "ja"
* に指定することで変換される。</p>
*
* <p>和暦元号名、および和暦年については、getWarekiGengoName()、
* getWarekiGengoRoman()、getWarekiYear() メソッドによって取得する。
* これらのメソッドで参照する和暦の設定は、AplicationResources ファイルで
* 以下の書式で行う。</p>
*
* <p><code><pre>
* wareki.gengo.<i>ID</i>.name=<i>元号名</i>
* wareki.gengo.<i>ID</i>.roman=<i>元号のローマ字表記</i>
* wareki.gengo.<i>ID</i>.startDate=<i>元号法施行日(西暦:yyyy/MM/dd形式)</i>
* </pre></code></p>
*
* <p>IDは、上記の三つの設定を関連付けするためのものであり、任意の文字列を
* 指定できる。</p>
*
* @param format フォーマット
* @param date 文字列に変換する時刻データ
* @return 和暦としてフォーマットされた文字列
*/
public static String dateToWarekiString(String format,
java.util.Date date) {
// SimpleDateFormatによるフォーマットの前に元号'G'、および年'y'の
// パターン文字を和暦に置換する
StringBuilder sb = new StringBuilder();
boolean inQuote = false; // シングルクォートの中であるかどうか
char prevCh = 0;
int count = 0;
for (int i = 0; i < format.length(); i++) {
char ch = format.charAt(i);
if (ch != prevCh && count > 0) {
if (prevCh == 'G' && count >= 4) {
sb.append(getWarekiGengoName(date));
} else if (prevCh == 'G') {
// 元号のローマ字表記の場合は、クォートしておく
sb.append('\'');
sb.append(getWarekiGengoRoman(date));
sb.append('\'');
} else if (prevCh == 'y') {
sb.append(getWarekiYear(date));
}
count = 0;
}
if (ch == '\'') {
sb.append('\'');
inQuote = !inQuote;
} else if (!inQuote && (ch == 'G' || ch == 'y')) {
// chは和暦変換で独自に解釈するフォーマット文字である場合は、
// 繰り返し回数をカウントする。
prevCh = ch;
++count;
} else {
// その他の文字は、素通しする
sb.append(ch);
}
}
// フォーマット中の最後のアイテムを処理する。
if (count > 0) {
if (prevCh == 'G' && count >= 4) {
sb.append(getWarekiGengoName(date));
} else if (prevCh == 'G') {
sb.append('\'');
sb.append(getWarekiGengoRoman(date));
sb.append('\'');
} else if (prevCh == 'y') {
sb.append(getWarekiYear(date));
}
}
SimpleDateFormat sdf = new SimpleDateFormat(sb.toString(),
Locale.JAPAN);
sdf.getCalendar().setLenient(false);
sdf.setLenient(false);
return sdf.format(date);
}
/**
* ApplicationResources ファイルにおいて和暦関連の設定を
* 取得する際のキーのプリフィックス。
*/
private static final String GENGO_KEY = "wareki.gengo.";
/**
* 元号施行日から元号名へのマップ。
*/
private static final Map<Date, String> GENGO_NAME
= new HashMap<Date, String>();
/**
* 元号施行日から元号のローマ字表記(短縮形)へのマップ。
*/
private static final Map<Date, String> GENGO_ROMAN =
new HashMap<Date, String>();
/**
* 和暦の各元号が施行された西暦日付のリスト。リストの先頭から、
* 古い順に並べられる。
*/
private static final Date[] GENGO_BEGIN_DATES;
/**
* 和暦の各元号が施行された西暦年のリスト。リストの先頭から、
* 古い順に並べられる。
*/
private static final int[] GENGO_BEGIN_YEARS;
/**
* クラスロード時に、ApplicationResources ファイルで指定された
* とおりに和暦データを初期化する。
*/
static {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
// プロパティから「元号の開始日」と「元号」のマップを作成する
Enumeration<String> enumaration =
PropertyUtil.getPropertyNames(GENGO_KEY);
Set<String> ids = new HashSet<String>();
GENGO_LOOP:
while (enumaration.hasMoreElements()) {
String key = enumaration.nextElement();
String id = key.substring(GENGO_KEY.length(),
key.lastIndexOf("."));
if (!ids.contains(id)) {
String name
= PropertyUtil.getProperty(GENGO_KEY + id + ".name", "");
String roman
= PropertyUtil.getProperty(GENGO_KEY + id + ".roman", "");
String start
= PropertyUtil.getProperty(GENGO_KEY + id + ".startDate");
if (start == null) {
log.error(GENGO_KEY + id + ".startDate not found");
continue GENGO_LOOP;
}
try {
Date date = sdf.parse(start);
GENGO_NAME.put(date, name);
GENGO_ROMAN.put(date, roman);
log.info("registerd: "
+ date + ", " + name + ", " + roman);
} catch (ParseException e) {
log.error(e.getMessage());
}
ids.add(id);
}
}
// 元号の開始日の配列を作成し、ソートしておく
Set<Date> keySet = GENGO_NAME.keySet();
int size = keySet.size();
GENGO_BEGIN_DATES = keySet.toArray(new Date[size]);
Arrays.sort(GENGO_BEGIN_DATES);
// 「元号の開始日の配列」と対応するように元号の開始年の配列を作成する
GENGO_BEGIN_YEARS = new int[size];
Calendar calendar = Calendar.getInstance();
for (int i = 0; i < GENGO_BEGIN_DATES.length; i++) {
calendar.setTime(GENGO_BEGIN_DATES[i]);
GENGO_BEGIN_YEARS[i] = calendar.get(Calendar.YEAR);
}
}
/**
* 指定された日付の和暦元号を取得する。
*
* <p>
* 和暦元号は、ApplicationResources ファイルで指定する。
* </p>
*
* @param date 日付
* @return 和暦元号
*/
public static String getWarekiGengoName(Date date) {
for (int i = GENGO_BEGIN_DATES.length - 1; i >= 0; i--) {
if (!date.before(GENGO_BEGIN_DATES[i])) {
return GENGO_NAME.get(GENGO_BEGIN_DATES[i]);
}
}
throw new IllegalArgumentException("Wareki Gengo Name not found for "
+ date);
}
/**
* 指定された日付の和暦元号のローマ字表記(短縮形)を取得する。
*
* <p>和暦元号のローマ字表記は、ApplicationResourcesファイルで指定する。</p>
*
* @param date 日付
* @return 和暦元号のローマ字表記
*/
public static String getWarekiGengoRoman(Date date) {
for (int i = GENGO_BEGIN_DATES.length - 1; i >= 0; i--) {
if (!date.before(GENGO_BEGIN_DATES[i])) {
return GENGO_ROMAN.get(GENGO_BEGIN_DATES[i]);
}
}
throw new IllegalArgumentException("Wareki Gengo Roman not found for "
+ date);
}
/**
* 指定された日付の和暦年を取得する。
*
* @param date 日付
* @return 和暦年
*/
public static int getWarekiYear(Date date) {
for (int i = GENGO_BEGIN_DATES.length - 1; i >= 0; i--) {
if (!date.before(GENGO_BEGIN_DATES[i])) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
int year = calendar.get(Calendar.YEAR);
return year - GENGO_BEGIN_YEARS[i] + 1;
}
}
throw new IllegalArgumentException("Wareki Gengo not found for "
+ date);
}
}