Javaタイムゾーンの処理Date、Calendar、TimeZone、SimpleDateFormatを詳しく説明します。


一、概要
1、問題の説明
Java処理時間を使うと、8時間の差など、時間が違うことがよく分かります。その原因はTimeZoneです。TimeZoneを正確かつ合理的に運用してこそ、システム時間はいつでも正確であることが保証されます。私は外資系企業で働いていますので、サーバーは米国にあります。米国にも多くのタイムゾーンがあります。いつも異なるタイムゾーンにあるサーバーに要求する時、タイムゾーンの切り替えを考慮する必要があります。例えば、サーバーは西八区(GMT-8:00)にありますが、東八区にいるユーザーは当日の販売記録を調べたいです。東八区の「今日」という時間範囲をサーバーのあるタイムゾーンの時間範囲に変換します。
2、タイムゾーン認識
GMT時間:つまりGreen Wich Mean Timeです。太陽の平時は太陽を見る時に対応していますが、地球の軌道は円形ではないため、運行速度は地球と太陽の距離によって変化します。したがって、太陽を見る時は均一性に欠けています。この不均一性を是正するために、天文学者は地球の非円形軌跡と極軸の傾斜が太陽を見る時の効果を計算します。一方、太陽の平時とは修正された後の視太陽のことです。グリニッジ子午線の平太は世界時(UTC)、グリニッジ平日(GMT)とも呼ばれます。
3、Java時間とタイムゾーンAPI
3.1、Date
クラスDateは特定の瞬間を表し、ミリ秒まで正確です。現在の時間を表すDateオブジェクトを得るには、2つの方法があります。

Date date = new Date(); 
Date date = Calendar.getInstance().getTime(); 
Dateオブジェクト自体が格納されているミリ秒数は、date.getTime()方法によって得ることができる。この関数は1970年1月1日00:00 GMT以来のオブジェクト表示のミリ秒数を返します。タイムゾーンと地域とは関係がないです。また、このタイムゾーンはサマータイムを使うかどうか教えてくれます。この情報があれば、タイムゾーンオブジェクトと日付フォーマットを他のタイムゾーンと他の言語で時間を表示し続けることができます。
3.2、Calendar
Calendarのget Instance()メソッドにはTimeZoneとLocaleのパラメータがあり、指定されたタイムゾーンと言語環境を使ってカレンダーを取得することができます。参加なしにデフォルトのタイムゾーンと言語環境を使ってカレンダーを取得します。
3.3、TimeZone
TimeZoneオブジェクトは、元のオフセット量、つまりGMTとの差のマイクロ秒数、すなわちTimeZoneは、タイムゾーンオフセット量を表し、実質的にはミリ秒でGMTとの差を保存します。
TimeZoneを取得すると、タイムゾーンIDで「America/New_」などとなります。York"は、GMT+/-hh:mmで設定することもできます。例えば北京時間はGMT+8:00と表現できます。
TimeZone.getRawOffset()方法は、現在のタイムゾーンの標準時間からGMTまでのオフセット量を得るために使用され得る。前段の「America/New_York"と"GMT+8:00"二つのタイムゾーンのオフセットはそれぞれ-1800000と28800000です。
4、TimeZoneに影響を与える要因
1.オペレーティングシステムのタイムゾーン設定。
2.データ転送時エリアの設定。
第一の原因は根本的な原因です。データが異なるオペレーティングシステム間を流れると、オペレーティングシステムの違いによって時間がずれてしまう可能性があります。JVMがデフォルトで取得したのはオペレーティングシステムのタイムゾーン設定です。このため、プロジェクトではタイムゾーンを事前に設定しておくといいです。例えば、

TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai")); 
5、解決の方法:
以上の分析から、タイムゾーンの問題を解決するのは簡単であり、時間区間の転換時間には、まず元の時間があるタイムゾーンのGMTに対するオフセット量を元の時間で減らし、元の時間がGMTに対する値を得て、ターゲットタイムエリアのGMTに対するオフセット量を加えるとよいことが分かります。このようにして得られた結果はまだミリ秒ですので、指定された日付書式に従ってDateオブジェクトに再変換すればいいです。
6、実例:
実例の前に、現在のタイムゾーンは中国の東八区と仮定します。GMT+8:00です

package com.wsheng.aggregator.timezone;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;

/**
 * @author Josh Wang(Sheng)
 * 
 * @email [email protected]
 * 
 */
public class TimeZone1 {
	
	public static void main(String[] args) {
	 Date date = new Date(1391174450000L); // 2014-1-31 21:20:50 
	 String dateStr = "2014-1-31 21:20:50 "; 
	 SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 
	 dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); 
	 try { 
	  Date dateTmp = dateFormat.parse(dateStr); 
	  System.out.println(dateTmp); 
	  } catch (ParseException e) { 
	  e.printStackTrace(); 
	 } 
	 String dateStrTmp = dateFormat.format(date); 
	 System.out.println(dateStrTmp); 
	}
	

}
実行結果:
Sat Feb 01 05:20:50 CST 2014
2014-01-31:20:50
同じ時間に文字列と日付が違っていることが分かりましたが、どうやって理解すればいいですか?
すべては根本的な原因、つまり現在のオペレーティングシステムの時間を基準とします。
私の操作システムは「Asia/Shanghai」で、GMT+8の北京時間です。日付変換文字列のformat方法を実行する場合、日付生成時にはデフォルトがオペレーティングシステムタイムゾーンですので、2014-1-31 21:20は北京時間です。文字列の日付を変更するparse方法を実行する場合、文字列自体にタイムゾーンの概念がないため、2013-31-31 22:17:14はGMT(UTC)時間を指します。【ps:すべての文字列はGMT時間と見なします。】日付に変換する場合は、デフォルトのタイムゾーン、すなわち「Asia/Shanghai」を加えます。
Calendarを使うと、次のようになります。

package com.wsheng.aggregator.timezone;

import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;

/**
 * @author Josh Wang(Sheng)
 * 
 * @email [email protected]
 * 
 */
public class TimeZone2 {
	
 public static void main(String[] args) { 
 	Date date = new Date(1391174450000L); // 2014-1-31 21:20:50 
  System.out.println(date); 
  Calendar calendar = Calendar.getInstance(); 
  calendar.setTimeZone(TimeZone.getTimeZone("GMT")); 
  //      Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT")); 
  calendar.setTime(date); 
  System.out.println(calendar.get(Calendar.HOUR_OF_DAY) + ":" + calendar.get(Calendar.MINUTE)); 
 } 

}
実行結果:
Fri Jan 31 21:20:50 CST 2014
13:20
Calendarは日付と文字列の転化に関係しないので、SimpleDateFormatのように複雑ではなく、日付の文字列を回転する考え方と似ています。ただし、タイムゾーンを設定した後、直接にDate日付をcareendar.getTime()で取得することはできません。この時の日付は最初のsetTimeの時と同じ値ですので、あるタイムゾーンの時間を取得したいです。正確な方法はcareendar.get()方法で、どうやってDateタイプの日付を取得しますか?
正しいやり方は以下の通りです。

package com.wsheng.aggregator.timezone;

import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;

/**
 * @author Josh Wang(Sheng)
 * 
 * @email [email protected]
 * 
 */
public class TimeZone3 {
	
 public static void main(String[] args) { 
 	Date date = new Date(1391174450000L); // 2014-1-31 21:20:50 
  System.out.println(date); 
  Calendar calendar = Calendar.getInstance(); 
  calendar.setTimeZone(TimeZone.getTimeZone("GMT")); 
  //      Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT")); 
  calendar.setTime(date); 
  Calendar calendar2 = Calendar.getInstance(); 
  calendar2.set(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH), calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE), calendar.get(Calendar.SECOND)); 
  System.out.println(calendar2.getTime()); 
 } 

}
実行結果:
Fri Jan 31 21:20:50 CST 2014
Fri Jan 31 13:20:50 CST 2014
完全共通変換方法
実は上の二つの変換方法はいずれもオペレーティングシステムのタイムゾーン設定に影響されます。もしソフトウェアが異なるオペレーティングシステムで動作しても、時間誤差があります。どうやって統一できますか?

/**
 * 
 */
package com.wsheng.aggregator.timezone;

import java.util.Date;
import java.util.TimeZone;

/**
 * @author Josh Wang(Sheng)
 * 
 * @email [email protected]
 * 
 */
public class TimeZone4 {
	
 public static void main(String[] args) { 
 	Date date = new Date(1391174450000L); // 2014-1-31 21:20:50 
  System.out.println(date); 
  date = changeTimeZone(date, TimeZone.getTimeZone("Asia/Shanghai"), TimeZone.getTimeZone("GMT")); 
  System.out.println(date); 
 } 
  
 /** 
  *            
  * @param date    
  * @param oldZone       
  * @param newZone       
  * @return    
  */ 
 public static Date changeTimeZone(Date date, TimeZone oldZone, TimeZone newZone) { 
  Date dateTmp = null; 
  if (date != null) { 
   int timeOffset = oldZone.getRawOffset() - newZone.getRawOffset(); 
   dateTmp = new Date(date.getTime() - timeOffset); 
  } 
  return dateTmp; 
 } 

}
実行結果:
Fri Jan 31 21:20:50 CST 2014
Fri Jan 31 13:20:50 CST 2014
より一般的に、タイプ転換をサポートするクラスを書くことができます。

package com.wsheng.aggregator.timezone;
import java.text.*; 
import java.util.*; 
 
/**
 * 
 * @author Josh Wang(Sheng)
 * 
 * @email [email protected]
 *
 */
public class DateTransformer { 
 public static final String DATE_FORMAT = "MM/dd/yyyy HH:mm:ss"; 
   
 public static String dateTransformBetweenTimeZone(Date sourceDate, DateFormat formatter, 
  TimeZone sourceTimeZone, TimeZone targetTimeZone) { 
  Long targetTime = sourceDate.getTime() - sourceTimeZone.getRawOffset() + targetTimeZone.getRawOffset(); 
  return DateTransformer.getTime(new Date(targetTime), formatter); 
 } 
   
 public static String getTime(Date date, DateFormat formatter){ 
  return formatter.format(date); 
 } 
   
 public static void main(String[] args){ 
  DateFormat formatter = new SimpleDateFormat(DATE_FORMAT); 
  Date date = Calendar.getInstance().getTime(); 
  System.out.println(" date: " + date);
  
  TimeZone srcTimeZone = TimeZone.getTimeZone("EST"); 
  TimeZone destTimeZone = TimeZone.getTimeZone("GMT+8"); 
  System.out.println(DateTransformer.dateTransformBetweenTimeZone(date, formatter, srcTimeZone, destTimeZone)); 
 } 
} 
DateFormatは、日付/時間の書式化されたクラスの抽象的なクラスで、言語とは無関係に日付または時間をフォーマットして解析します。日付/時間書式化子クラス(SimpleDateFormat)は、フォーマット(つまり日付->>テキスト)、解析(テキスト->日付)と標準化を許可します。日付をDateオブジェクトとして表示したり、GMT(グリニッジ標準時間)から1970年1月1日00:00の時点からミリ秒数として表示します。SimpleDateFormatは、言語環境に関する形式で日付を書式設定し、解析するための具体的なクラスであり、日付と時間パターンの文字列で日付と時間書式を指定できます。私達の関数で使っているモード文字列は「MM/dd/yyy HH:mm:ss」です。出力日付は「07/16/2013 4:00」です。
他の一般的なパターンのアルファベットの定義は以下の通りです。
文字  日付または時刻要素  表示  例
G ErraフラグText AD
y   年Year 1996;96 
M年中の月Month Juily;Jul07 
w年間の週数Number 27
W月中の週数Number 2
D年間の日数Number 189
d月の日数Number 10
F月中の月曜日Number 2
E週中の日数Text Tuesday;Tue 
a Am/pmタグText PM
H一日の時間数(0-23)Number 0
k一日の時間数(1-24)Number 24
K am/pmの時間数(0-11)Number 0
h am/pmの時間数(1-12)Number 12
m時間の分数Number 30
s分間の秒数Number 55
Sミリ秒数Number 978
zタイムゾーンGeneral time zone Pacific Standard Time;PSTGMT-08:00 
ZタイムゾーンRFC 822 time zone-0800
上の分析と事例説明から分かります。
1.コンピュータ内部記録の時間(Date date=new Date()は、グリニッジ標準時(GMT).java.util.Dateは、1970年1月1日から00:00までのミリ秒数を表します。だから、タイムゾーンとLocaleの概念がないと考えられます。
2.日付フォーマットクラスのDateFormatは、異なる地域の配置に対して、一般的に二つの点があります。一つはLocaleで、一つはTimeZoneです。
     前者(Locale)はDateFormatに配置された地域特性によって文字を出力させる(例えば中国、米国、フランスの異なる地域で日付の表示形式が異なり、中国は2001年10月5日かもしれない)。
     後者(TimeZone)はDateFormatにどのように変換するかを教えて、時間のオフセットを調整して、配置に合うタイムゾーンの時間を得ます。
(現在のタイムゾーンをGMT+0とすると、new Date()と最終的に変換した時間ミリ秒数が一致すると仮定して)2:00となります。DateFormat.set TimeZome(GMT+8)を設定すると、北京時間のタイムゾーンが10:00になります。システムが元のミリ秒数に対してタイムシフトします。出力日付を書式設定した文字列形式)
3.GMTとUTCのタイムゾーンは同じで、ロンドン時間を基準にしています。GMT+8時区は北京時間のあるタイムゾーンです。同じ時刻はGMTより8時間早いです。
以上、Javaタイムゾーンで処理されているDate、Calender、TimeZone、SimpleDateFormatの使い分けについての記事を紹介します。Javaタイムゾーンで処理されているDate、Calender、TimeZone、SimpleDateFormatの内容は以前の文章を検索したり、次の関連記事を引き続き閲覧してください。