Java 8のDate-Time API良く知らんのだよな~ということでThe Java Tutorials の Date-Time のセクションを読んだ。 例によって機械翻訳よりごましお程度にマシな精度なのでそこのところご容赦願いたい。
Trail: Date Time
Java SE 8リリースで導入された、Date-Timeパッケージ、java.time、日付(date)と時間(time)に関する包括的なモデルを提供するもので、JSR 310: Date and Time APIの下で開発されました。java.time
は国際標準化機構(International Organization for Standardization:ISO)カレンダーシステムに基づきますが、一般的に使用されるグローバルなカレンダーもサポートされています。
このトレールは、日付と時間を表現するためと、日付と時間の値を操作するための、ISOベースのクラスの使い方の基本を扱います。
Lesson: Date-Time Overview
時間とは、単純なテーマのように見えます。安価な時計でもかなり正確な日付と時間を扱えます。しかしながら、詳細な調査をすると、微妙な複雑性とあなたの時間理解に影響を及ぼす多くの要因があることに気付きます。たとえば、1月31日に一か月を足した結果は、うるう年かそうでないかで異なるものとなります。タイムゾーン(Time zones)もまた複雑さを増加させます。たとえば、ある国は突然にサマータイムに入ったり出たりするかもしれませんし、年に一回以上かもしれないし、または、ある年は完全にサマータイムをスキップするかもしれません。
Date-Time APIは、デフォルトカレンダーとして、ISO-8601で定義されたカレンダーシステムを使用します。このカレンダーはグレゴリオ暦(Gregorian calendar)に基づくもので、日付と時間を表現するためのグローバルなデファクトスタンダードとして使われています。Date-Time APIのコアクラスは、LocalDateTime
, ZonedDateTime
, OffsetDateTime
のような名前を持っています。これらはすべてISOカレンダーシステムを使用します。もし、Hijrah*1やThai Buddhist*2のような別のカレンダーシステムを使用したい場合、java.time.chrono
パッケージで事前に定義されたカレンダーシステムの内の一つを使うことができます。もしくは、自身用のものを作成することもできます。
Date-Time APIは、Unicode Common Locale Data Repository (CLDR)を使用します。このリポジトリは世界の言語をサポートし、利用可能なロケールデータの世界最大のコレクションを持っています。このリポジトリの情報は数百の言語にローカライズされています。また、Date-Time APIはTime-Zone Database (TZDB)を使用します。このデータベースは、その概念が導入されて以来の主要なタイムゾーンの歴史と共に、1970年以来のすべてのタイムゾーンが変更した情報についての情報を提供しています。
Date-Time Design Principles
Date-Time APIは、いくつかの設計原則に基づいて開発されました。
Clear
APIのメソッドは明確に定義され、その振る舞いはクリアで予測可能なものです。たとえば、Date-Timeのメソッドをnull
パラメータ値で呼び出すことは、おおむねNullPointerException
となります。
Fluent
Date-Time APIは、流れるようなインタフェース(fluent interface)を提供しており、コードを読みやすくします。多くのメソッドはnull
値のパラメータを許可せず、null
値を返さないので、メソッドはが互いにチェーンさせることができ、結果としてコードはすぐに理解できるものになります。たとえば、
LocalDate today = LocalDate.now();
LocalDate payday = today.with(TemporalAdjuster.lastDayOfMonth()).minusDays(2);
Immutable
Date-Time API のクラスの大半はイミュータブル(immutable)なオブジェクトを生成します。この意味は、生成されたオブジェクトは、変更不能ということです。イミュータブルなオブジェクトの値を変更するには、新しいオブジェクトをオリジナルの修正コピーとして生成する必要があります。このことはまた、Date-Time APIはスレッドセーフで定義されていることも意味しています。日付や時間のオブジェクトを生成するためには、コンストラクタよりはむしろ、of
, from
, with
のプレフィクスを持つメソッドが多数となり、また、set
メソッドは存在しません。たとえば、
LocalDate dateOfBirth = LocalDate.of(2012, Month.MAY, 14); LocalDate firstBirthday = dateOfBirth.plusYears(1);
Extensible
Date-Time APIは可能な限り、拡張可能です。たとえば、時間調整(time adjusters)とクエリ(queries)を定義したり、自前のカレンダーシステムを構築することができます。
The Date-Time Packages
Date-Time APIは、java.time
と4つのサブパッケージから構成されています。
java.time
日付と時間を表現するためのコアAPI。日付、時間、日付と時間の組み合わせ、タイムゾーン、瞬間(instants)、期間、時計、のためのクラスが含まれています。これらのクラスはISO-8601で定義されるカレンダーシステムに基づいており、イミュータブルでスレッドセーフです。java.time.chrono
デフォルトのISO-8601以外のカレンダーシステムを表現するためのAPI。自前のカレンダーシステムを定義可能です。このチュートリアルではこのパッケージの詳細については触れません。java.time.format
日付と時間のフォーマットとパースのためのクラス。java.time.temporal
主にフレームワークとライブラリ作成者のための、拡張されるAPIで、日付と時間クラス間の相互作用や、クエリと時刻合わせなどが可能です。このパッケージには、フィールド(Fields)(TemporalField
とChronoField
)とユニット(units)(TemporalUnit
とChronoUnit
) が定義されています。java.time.zone
タイムゾーンをサポートするクラスで、タイムゾーンのオフセットやタイムゾーンルールがあります。もしタイムゾーンを使用する場合、多くの開発者はZonedDateTime
とZoneId
かZoneOffset
のみ使う必要があります。
Method Naming Conventions
Date-Time APIは、豊富なクラスとメソッドを提供しています。メソッド名はクラス間で可能な限り一貫性があるように構成されています。たとえば、多くのクラスは、そのクラスに関連する現在の瞬間の日付もしくは時間を捕捉する、now
メソッドを提供します。from
メソッドは、あるクラスから別のクラスへの変換を行います。
メソッド名のプレフィクスには統一性を持たせています。Date-Time APIのクラスの多くはイミュータブルなので、APIはset
メソッドを持ちません。(生成後、イミュータブルオブジェクトの値は変更不可能です。イミュータブルのset
と同等なものはwith
です。)以下の表は良く使われるプレフィクスの一覧です。
プレフィクス | メソッドタイプ | 使い方 |
---|---|---|
of |
static factory | 主にファクトリが入力パラメータを検証し、変換をせずに、インスタンスを生成します。 |
from |
static factory | 入力パラメータをターゲットクラスのインスタンスへ変換する。このとき、入力値の情報を切り捨てる可能性があります。 |
parse |
static factory | ターゲットクラスのインスタンス生成のために入力文字列をパースします。 |
format |
instance | 指定されたformatterで、文字列生成のための一時オブジェクトで値をフォーマットします。 |
get |
instance | ターゲットオブジェクトの状態の一部を返します。 |
is |
instance | ターゲットオブジェクトの状態を問い合わせます。 |
with |
instance | ある要素が変更されたターゲットオブジェクトのコピーを返します。JavaBeanにおけるset メソッドと同等なものです。 |
plus |
instance | 時間を加算したターゲットオブジェクトのコピーを返します。 |
minus |
instance | 時間を減算したターゲットオブジェクトのコピーを返します。 |
to |
instance | 別の型へオブジェクトを変換します。 |
at |
instance | 別のオブジェクトと結合します。 |
Lesson: Standard Calendar
Date-Time APIのコアパッケージはjava.timeです。java.time
で定義されているクラスはISOのカレンダーシステムに基づいており、これは世界的に標準的な日付と時間の表現方法です。ISOカレンダーはproleptic Gregorian rules*3に従います。グレゴリオ暦は1582年に導入されて、proleptic Gregorian calendarでは、日付は、一貫性のある時刻生成、統一された年表、日付計算を単純化するため、過去方向*4へ拡張されています。
このレッスンは以下のトピックを扱います。
Overview
このセクションでは、人的時間(human time)と、java.time
パッケージの主要なtemporal-basedクラスを表にまとめたマシンタイム(machine time)のコンセプトを比較します。
DayOfWeek and Month Enums
このセクションでは、曜日(DayOfWeek
)定義のenumと月(Month
)定義のenumについて説明します。
Date Classes
このセクションでは、時間とタイムゾーンを含まない、日付のみを扱うtemporal-basedクラスを示します。4つのクラスがあり、それぞれLocalDate
, YearMonth
,MonthDay
, Year
です。
Date and Time Classes
このセクションでは、LocalTime
とLocalDateTime
クラスについて説明します。これらはそれぞれ、時間、日付と時間、を扱うもので、ただし、タイムゾーンは含みません。
Time Zone and Offset Classes
このセクションでは、タイムゾーン(もしくはタイムゾーンオフセット)を格納するtemporal-basedクラス、ZonedDateTime
, OffsetDateTime
, OffsetTime
、について説明します。サポートクラスであるZoneId
, ZoneRules
, ZoneOffset
についても説明します。
Instant Class
このセクションでは、時間の流れにおけるある瞬間を表現するための、Instant
クラスについて説明します。、
Parsing and Formatting
このセクションでは、事前定義されたフォーマットと日付および時間のパースの使用方法の概要を示します。
The Temporal Package
このセクションでは、temporalクラス、フィールド(TemporalField
とChronoField
)とユニット(TemporalUnit
とChronoUnit
)、をサポートするjava.time.temporal
パッケージの概要について説明します。また、"the first Tuesday after April 11(4/11後で最初の火曜日)"のような、調整時刻を検索するためのtemporal adjusterの使用方法と、temporal queryの実行法について、説明します。
Period and Duration
このセクションでは、時間の合計の計算方法について、Period
とDuration
クラス、ChronoUnit.between
メソッドを使用して、説明します。
Clock
このセクションでは、Clock
クラスの概要について説明します。このクラスを使用することで、システム時計の代わりとなる時計を提供できます。
Non-ISO Date Conversion
このセクションでは、ISOカレンダーシステムの日付から、JapaneseDate
やThaiBuddhistDate
のような、非ISOの日付へと変換する方法について説明します。
Legacy Date-Time Code
このセクションでは、旧来のjava.util.Date
とjava.util.Calendar
コードからDate-Time APIへ変換する方法についていくつかのTipsを示します。
Summary
このセクションでは、Standard Calendarレッスンのサマリを示します。
Overview
時間を表現する一般的な方法は二つあります。一つは人間にとっての時間表現で、年、月、日、時、分、秒のような、human timeと呼ばれるものです。もう一つの方法は、epochと呼ばれるもので、ある起点からの時間の流れをナノ秒単位で計測する、machine timeです。Date-Timパッケージは日付と時間を表現するクラスを豊富に提供しています。Date-Time APIのいくつかのクラスはmachine timeを表現するために使用され、その他はhuman timeを表現するために使用できます。
まず決定が必要なのは日付と時間に関する要求で、それから要求を満たすクラスを選択します。temporal-basedクラスを選択するとき、まず最初にhuman timeかmachine timeのどちかが必要とされているか、を決定します。それから、表現する必要のある時間の要求について確認します。タイムゾーンは必要なのか? 日付と時間なのか? 日付のみか? もし日付が必要な場合、月・日と年、もしくはそのサブセットか?
用語(Terminology):Date-Time APIのクラスは日付もしくは時間の値を扱うために、このチュートリアルにおいて、Instant
,LocalDateTime
, ZonedDateTime
のような、temporal-basedクラス(もしくは型)と呼ばれるものを使用します。TemporalAdjuster
インタフェースもしくはDayOfWeek
enumのようなサポート型は、この定義には含まれません。
たとえば、誕生日を表現するにはLocalDate
オブジェクトを使用しますが、その理由は、彼らが同じ場所で生まれようと国際日付変更線の反対側の地球上で生まれようと、ほとんどの人は誕生日が同じ人がいるためです*5。もし占星術的な時間を記録する場合、出生の日付と時間を表現するためにLocalDateTime
オブジェクトか、タイムゾーンを含むZonedDateTime
を使用することになるでしょう。もしタイムスタンプを生成する場合、`時間軸上のある瞬間と別の瞬間とを比較できる、Instant
を使用するでしょう。
以下の表はjava.time
パッケージのtemporal-basedクラスの、日付と時間の情報を格納するか、時間を計測することが出来るか、について要約したものです。列のチェックマークは、そのクラスが使用する個々のデータタイプを示しており、toString Output列はtoString
メソッドを使用してインスタンスを出力したときの様子を示しています。Where Discussed列はチュートリアルの関連ページへのリンクです。
※訳注:下記表のうち、チェックマークは○、Where Discussedのリンクはめんどいんで省略。
ClassかEnum | 年 | 月 | 日 | 時 | 分 | 秒* | ZoneOffset | ZoneID | toString Output | Where Discussed |
---|---|---|---|---|---|---|---|---|---|---|
Instant |
○ | 2013-08-20T15:16:26.355Z |
||||||||
LocalDate |
○ | ○ | ○ | 2013-08-20 |
||||||
LocalDateTime |
○ | ○ | ○ | ○ | ○ | ○ | 2013-08-20T08:16:26.937 |
|||
ZonedDateTime |
○ | ○ | ○ | ○ | ○ | ○ | ○ | ○ | 2013-08-21T00:16:26.941+09:00[Asia/Tokyo] |
|
LocalTime |
○ | ○ | ○ | 08:16:26.943 |
||||||
MonthDay |
○ | ○ | --08-20 |
|||||||
Year |
○ | 2013 |
||||||||
YearMonth |
○ | ○ | 2013-08 |
|||||||
Month |
○ | AUGUST |
||||||||
OffsetDateTime |
○ | ○ | ○ | ○ | ○ | ○ | ○ | 2013-08-20T08:16:26.954-07:00 |
||
OffsetTime |
○ | ○ | ○ | ○ | 08:16:26.957-07:00 |
|||||
Duration |
** | ** | ** | ○ | PT20H (20 hours) |
|||||
Period |
○ | ○ | ○ | *** | *** | P10D (10 days) |
* 秒はナノ秒精度で取得される。
** このクラスは印がつけられた情報は保存しませんが、ユニットの時間を提供するためのメソッドは持っています。
*** ZonedDateTime
にPeriod
が加えられたとき、サマータイムもしくは他のローカル時間の差が生まれます。
DayOfWeek and Month Enums
Date-Time APIは曜日と月を指定するためのenumを提供しています。
DayOfWeek
DayOfWeek enumは、週の曜日を意味する、MONDAY
からSUNDAY
の7個の定数を持っています。DayOfWeek
の整数値の範囲は1(Monday)から7(Sunday)です。定義済みの定数値(DayOfWeek.FRIDAY
)は可読性が高いです。
また、このenumはtemporal-basedクラスで提供されるメソッドと似ているメソッドをいくつか持っています。たとえば、以下のコードは"Monday"に3日を足して結果を出力しています。出力は"THURSDAY"になります。
System.out.printf("%s%n", DayOfWeek.MONDAY.plus(3));
getDisplayName(TextStyle, Locale)メソッドを使用することで、ユーザーのロケールで曜日を指定する文字列を検索できます。TextStyle enumで表示したい文字列の種類を指定できます。FULL
, NARROW
(一般的には一文字), SHORT
(省略形)。STANDALONE TextStyle
定数値はいくつかの言語で使用されるもので、単独で用いる場合より日付の一部として使用される場合に出力が異なってきます。以下の例は、"Monday"をTextStyle
の三つの主要な形式で表示するものです。
DayOfWeek dow = DayOfWeek.MONDAY; Locale locale = Locale.getDefault(); System.out.println(dow.getDisplayName(TextStyle.FULL, locale)); System.out.println(dow.getDisplayName(TextStyle.NARROW, locale)); System.out.println(dow.getDisplayName(TextStyle.SHORT, locale));
このコードはen
ロケールでは以下の出力をします。
Monday M Mon
Month
Month enumは、JANUARY
からDECEMBER
の12か月の定数値を持っています。DayOfWeek
enumと同様に、Month
enumは強く型付がされており、各定数の整数値はISOの1(一月January)から12(十二月:December)の範囲と一致しています。定義済みの定数((Month.SEPTEMBER
)はコードの可読性を高めます。
また、Month
enumはいくつかのメソッドを持ちます。以下のコードはmaxLength
メソッドを使用して二月の日数の最大値を表示しており、出力は"29"になります。
System.out.printf("%d%n", Month.FEBRUARY.maxLength());
Month
enumは、getDisplayName(TextStyle, Locale)メソッドを実装しており、これは指定されたTextStyle
を使用して、ユーザーのロケールでの月を表す文字列を取得するためのものです。もしTextStyle
が定義されていない場合、定数の整数が文字列として返されます。以下のコードは主な三つのテキストスタイルを使用して、八月(August)を表示するものです。
Month month = Month.AUGUST; Locale locale = Locale.getDefault(); System.out.println(month.getDisplayName(TextStyle.FULL, locale)); System.out.println(month.getDisplayName(TextStyle.NARROW, locale)); System.out.println(month.getDisplayName(TextStyle.SHORT, locale));
このコードはen
ロケールでは下記の出力になります。
August A Aug
Date Classes
Date-Time APIは、時間もしくはタイムゾーンを除外して、日付情報を扱う4つのクラスを提供します。これらのクラスの使用法は、クラス名(LocalDate
, YearMonth
, MonthDay
, Year
)が示唆しています。
LocalDate
LocalDateはISOカレンダーの年・月・日を表現しており、時間を除いた日付を表現することに使用できます。LocalDate
は、誕生日や結婚記念日のような、特定のイベントを記録することに使用できます。以下の例は、LocalDate
インスタンスを作成するために、of
とwith
メソッドを使用しています。
LocalDate date = LocalDate.of(2000, Month.NOVEMBER, 20); LocalDate nextWed = date.with(TemporalAdjusters.next(DayOfWeek.WEDNESDAY));
TemporalAdjuster
インタフェースの詳細については、Temporal Adjusterを参照してください。
通常のメソッドに加えて、LocalDate
クラスは、与えられた日付に関する情報を取得するためのgetterメソッドを提供しています。getDayOfWeekメソッドは、その日付に対応する曜日を返します。たとえば、以下のコード例は"MONDAY"を返します。
DayOfWeek dotw = LocalDate.of(2012, Month.JULY, 9).getDayOfWeek();
以下の例は、指定された日付以降で最初の水曜日(Wednesday)を検索するために、TemporalAdjuster
を使用しています。
LocalDate date = LocalDate.of(2000, Month.NOVEMBER, 20); TemporalAdjuster adj = TemporalAdjusters.next(DayOfWeek.WEDNESDAY); LocalDate nextWed = date.with(adj); System.out.printf("For the date of %s, the next Wednesday is %s.%n", date, nextWed);
このコードの実行結果は以下のようになります。
For the date of 2000-11-20, the next Wednesday is 2000-11-22.
また、Period and DurationセクションでもLocalDate
クラスの使用例を紹介します。
YearMonth
YearMonthクラスは、特定の年月を表現するものです。以下の例では、YearMonth.lengthOfMonth()
メソッドを使用して、ある年のある月における日数を取得する例を示しています。
YearMonth date = YearMonth.now(); System.out.printf("%s: %d%n", date, date.lengthOfMonth()); YearMonth date2 = YearMonth.of(2010, Month.FEBRUARY); System.out.printf("%s: %d%n", date2, date2.lengthOfMonth()); YearMonth date3 = YearMonth.of(2012, Month.FEBRUARY); System.out.printf("%s: %d%n", date3, date3.lengthOfMonth());
このコードの出力は以下のようになります。
2013-06: 30 2010-02: 28 2012-02: 29
MonthDay
MonthDayクラスは、新年である1月1日のような、特定の月・日を表現するものです。
以下の例はMonthDay.isValidYearメソッドを使用して、2/29が2010年において正しいかどうかを確認しています。この呼び出しはfalse
を返し、2010年はうるう年ではないことを確認しています。
MonthDay date = MonthDay.of(Month.FEBRUARY, 29); boolean validLeapYear = date.isValidYear(2010);
Year
Yearクラスは年を表現するものです。以下の例は、Year.isLeadメソッドを使用して、与えられた年がうるう年かどうかを確認しています。この呼び出しはtrue
を返し、2012年はうるう年であることを確認しています。
boolean validLeapYear = Year.of(2012).isLeap();
Date and Time Classes
LocalTime
LocalTimeクラスは、他のLocal
プレフィクスを持つクラスと似ていますが、時間のみを扱います。このクラスが役に立つのはhuman-basedな時間を表現するときで、例えば、映画の上映時間や、地域図書館の開館と閉館時間などです。また、デジタル時計を作成するのにも使用でき、以下の例のようになります。
LocalTime thisSec; for (;;) { thisSec = LocalTime.now(); // displayの実装は課題として残してあります。 display(thisSec.getHour(), thisSec.getMinute(), thisSec.getSecond()); }
LocalTime
クラスはタイムゾーンやサマータイムの情報を持ちません。
LocalDateTime
このクラスは日付と時間の両方を扱いますが、タイムゾーンは持ちません、
Date-Time APIのコアクラスの一つであるLocalDateTimeクラスは、日付と時間の両方を扱います。ただし、タイムゾーンは持ちません。このクラスは、日付(年・月・日*6)と時間(時・分・秒・ナノ秒)を表現するために使用するもので、実質的には、LocalDate
とLocalTime
を組み合わせたものです。このクラスはある特定の出来事を表現するために使用でき、たとえば、Louis Vuitton Cup Finals in the America's Cup Challenger Series*7の最初のレースの開始時刻の2013年8月17日 午後1:10、などです。注意点として、午後1:10はローカル時間です。タイムゾーンを含めるためには、ZonedDateTime
もしくはOffsetDateTime
使用する必要があります。詳細については、Time Zone and Offset Classesで解説します。
どのtemporal-basedクラスでも提供しているnow
メソッドに加えて、LocalDateTime
クラスは様々なof
メソッド(ないしof
プレフィクスのメソッド)を持っており、これらはLocalDateTime
のインスタンスを生成します。from
メソッドは、インスタンスを別のtemporalフォーマットからLocalDateTime
インスタンスへと変換します。また、時・分・日・週・月を加算・減算するためのメソッドもあり、以下にその例を示しています。date-timeのところは大文字にしています*8。
System.out.printf("now: %s%n", LocalDateTime.now()); System.out.printf("Apr 15, 1994 @ 11:30am: %s%n", LocalDateTime.of(1994, Month.APRIL, 15, 11, 30)); System.out.printf("now (from Instant): %s%n", LocalDateTime.ofInstant(Instant.now(), ZoneId.systemDefault())); System.out.printf("6 months from now: %s%n", LocalDateTime.now().plusMonths(6)); System.out.printf("6 months ago: %s%n", LocalDateTime.now().minusMonths(6));
このコードは以下のような出力となります。
now: 2013-07-24T17:13:59.985 Apr 15, 1994 @ 11:30am: 1994-04-15T11:30 now (from Instant): 2013-07-24T17:14:00.479 6 months from now: 2014-01-24T17:14:00.480 6 months ago: 2013-01-24T17:14:00.481
Time Zone and Offset Classes
タイムゾーン(time zone)とは、同一の標準時間が使用されている地球の地域、のことです。各タイムゾーンは識別子によって指定され、通常はregion/city(Asia/Tokyo
)のフォーマットで、Greenwich/UTCからのオフセットを持ちます。たとえば、Tokyoのオフセットは+09:00
です。
ZoneId and ZoneOffset
Date-Time APIには、タイムゾーンもしくはオフセットを指定するためのクラスが二つがあります。
ZoneId
はタイムゾーン識別子で、Instant
とLocalDateTime
間の変換ルールを提供します。LocalDateTime
はGreenwich/UTCからのタイムゾーンオフセットを指定します。
Greenwich/UTCからのオフセットは時間単位*9で定義されますが、例外があります。以下のコードはTimeZoneIdの例からの抜粋で、Greenwich/UTCからのオフセットを使用しているすべてのタイムゾーンのうち、時間単位で定義されていないものをリストアップするものです。
Set<String> allZones = ZoneId.getAvailableZoneIds(); LocalDateTime dt = LocalDateTime.now(); // Create a List using the set of zones and sort it. List<String> zoneList = new ArrayList<String>(allZones); Collections.sort(zoneList); ... for (String s : zoneList) { ZoneId zone = ZoneId.of(s); ZonedDateTime zdt = dt.atZone(zone); ZoneOffset offset = zdt.getOffset(); int secondsOfHour = offset.getTotalSeconds() % (60 * 60); String out = String.format("%35s %10s%n", zone, offset); // 時間単位オフセットを持たないタイムゾーンのみを標準出力する。 if (secondsOfHour != 0) { System.out.printf(out); } ... }
この例は以下のリストを出力します。
America/Caracas -04:30 America/St_Johns -02:30 Asia/Calcutta +05:30 Asia/Colombo +05:30 Asia/Kabul +04:30 Asia/Kathmandu +05:45 Asia/Katmandu +05:45 Asia/Kolkata +05:30 Asia/Rangoon +06:30 Asia/Tehran +04:30 Australia/Adelaide +09:30 Australia/Broken_Hill +09:30 Australia/Darwin +09:30 Australia/Eucla +08:45 Australia/LHI +10:30 Australia/Lord_Howe +10:30 Australia/North +09:30 Australia/South +09:30 Australia/Yancowinna +09:30 Canada/Newfoundland -02:30 Indian/Cocos +06:30 Iran +04:30 NZ-CHAT +12:45 Pacific/Chatham +12:45 Pacific/Marquesas -09:30 Pacific/Norfolk +11:30
また、上記のTimeZoneId
のコード例はすべてのタイムゾーンIDをファイルに出力します。
The Date-Time Classes
Date-Time APIは、タイムゾーンと共に動作する、3つのtemporal-basedクラスを提供します。
ZonedDateTime
は、Greenwich/UTCからのオフセットを持つ、タイムゾーンで、日付と時間を扱います。OffsetDateTime
は、Greenwich/UTCからのタイムゾーン・オフセットで、日付と時間を扱います。ただし、タイムゾーンIDは持ちません。OffsetTime
は、Greenwich/UTCからのタイムゾーン・オフセットで、時間を扱います。ただし、タイムゾーンIDは持ちません。*10
いつ、ZonedDateTime
の代わりにOffsetDateTime
を使えば良いのでしょうか? もし、地理的な位置情報に基づく日付と時間計算のために自前のルールをモデル化する複雑なソフトウェアを記述しようとしてたり、データベースにGreenwich/UTCからの絶対オフセットのみを記録するタイムスタンプ(time-stamps)を格納しようとしている場合、OffsetDateTime
を使用するのが良いでしょう。また、XMLその他の通信フォーマットはOffsetDateTime
もしくはOffsetTime
としてdate-timeトランスファーをフォーマットします。
この三つのクラスはGreenwich/UTCからのオフセットを維持しますが、ZonedDateTime
のみ、java.time.zone
パッケージのZoneRulesを使用して、どのようにオフセットが特定のタイムゾーンごとに異なるかを決定します。たとえば、多くのタイムゾーンは、時計をサマータイムに進めるとき(一般的には1時間)にはギャップを生じ、そして、標準時に戻すとき最後の1時間は重なり合うことになります。ZonedDateTime
クラスはこうしたシナリオに適応するもので、OffsetDateTime
とOffsetTime
クラスは、ZoneRules
にはアクセスしません。
ZonedDateTime
ZonedDateTimeクラスは、実質的には、LocalDateTimeクラスとZoneIdクラスを組み合わせたものです。このクラスは、日付(年・月・日)、時間(時・分・秒・ナノ秒)、タイムゾーン(Europe/Paris
のようなregion/city)、を表現するために使用します。
以下のコードは、Flight例からの抜粋で、サンフランシスコから東京への旅客機の出発時刻を、America/Los AngelesタイムゾーンのZonedDateTime
で定義したものです。withZoneSameInstant
とplusMinutes
メソッドはZonedDateTime
インスタンスを生成するために使用するもので、このインスタンスは650分飛行後の東京予定到着時刻を表現しています。ZoneRules.isDaylightSavings
メソッドはフライトが東京に到着するときサマータイムかどうかを調べます。
DateTimeFormatter
オブジェクトはZonedDateTime
インスタンスの出力をフォーマットするために使用します。
DateTimeFormatter format = DateTimeFormatter.ofPattern("MMM d yyyy hh:mm a"); //出発時刻: San Francisco on July 20, 2013, at 7:30 p.m. LocalDateTime leaving = LocalDateTime.of(2013, Month.JULY, 20, 19, 30); ZoneId leavingZone = ZoneId.of("America/Los_Angeles"); ZonedDateTime departure = ZonedDateTime.of(leaving, leavingZone); try { String out1 = departure.format(format); System.out.printf("LEAVING: %s (%s)%n", out1, leavingZone); } catch (DateTimeException exc) { System.out.printf("%s can't be formatted!%n", departure); throw exc; } // フライトは10時間50分もしくは650分 ZoneId arrivingZone = ZoneId.of("Asia/Tokyo"); ZonedDateTime arrival = departure.withZoneSameInstant(arrivingZone) .plusMinutes(650); try { String out2 = arrival.format(format); System.out.printf("ARRIVING: %s (%s)%n", out2, arrivingZone); } catch (DateTimeException exc) { System.out.printf("%s can't be formatted!%n", arrival); throw exc; } if (arrivingZone.getRules().isDaylightSavings(arrival.toInstant())) System.out.printf(" (%s daylight saving time will be in effect.)%n", arrivingZone); else System.out.printf(" (%s standard time will be in effect.)%n", arrivingZone);
このコードは以下のような出力になります。
LEAVING: Jul 20 2013 07:30 PM (America/Los_Angeles) ARRIVING: Jul 21 2013 10:20 PM (Asia/Tokyo) (Asia/Tokyo standard time will be in effect.)
OffsetDateTime
OffsetDateTimeクラスは、実質的に、LocalDateTimeクラスとZoneOffsetクラスを組み合わせたものです。このクラスを使用して表現できるのは、日付(年・月・日)と時間(時・分・秒・ナノ秒)とGreenwich/UTCからのオフセット(+06:00
や-08:00
のような、+/-hh:mm)です。
以下の例は、2013年7月の最後の木曜日を取得するために、TemporalAdjuster.lastDay
メソッドと共にOffsetDateTime
を使用しています。
// 2013年7月最終木曜日を取得する。 LocalDateTime date = LocalDateTime.of(2013, Month.JULY, 20, 19, 30); ZoneOffset offset = ZoneOffset.of("-08:00"); OffsetDateTime date = OffsetDateTime.of(date, offset); OffsetDateTime lastThursday = date.with(TemporalAdjuster.lastInMonth(DayOfWeek.THURSDAY)); System.out.printf("The last Thursday in July 2013 is the %sth.%n", lastThursday.getDayOfMonth());
このコードの実行結果は以下になります。
The last Thursday in July 2013 is the 25th.
OffsetTime
OffsetTimeクラスは、実質的には、LocalTimeクラスとZoneOffsetクラスを組み合わせたものです。このクラスを使用して表現できるのは、時間(時・分・秒・ナノ秒)とGreenwich/UTCからのオフセット(+06:00
や-08:00
のような、+/-hh:mm)です。
OffsetTime
クラスはOffsetDateTime
クラスと同様な状況で使用出来ますが、こちらは日付を持つ必要が無いときに使用します。
Instant Class
Date-Time APIのコアクラスの一つがInstantクラスで、時間の流れ(timeline)における、ある地点からの経過ナノ秒を表現するものです。このクラスはmachine timeを表現するタイムスタンプの生成に使用できます。
import java.time.Instant;
Instant timestamp = Instant.now();
Instant
クラスが返す値は、EPOCHとも呼ばれる1970年1月1日(1970-01-01T00:00:00Z
)を起点としてカウントするものです。エポック以前のinstantは負の値となり、エポック以降のinstantは正の値となります。
Instant
クラスで提供されるその他の定数値にはMINがあり、instantが取り得る最小(遥か過去)の値を意味し、MAXは最大(遠い未来)のinstantの値を意味します。
Instant
のtoString
を呼び出すと以下のような出力になります。
2013-05-30T23:38:23.085Z
このフォーマットは日付と時間を表現するISO-8601標準に従うものです。
Instant
クラスは、Instant
を操作するためのメソッドを豊富に備えています。plus
とminus
メソッドで時間の加算・減算ができます。以下のコードは現在時刻に1時間を加算します。
Instant oneHourLater = Instant.now().plusHours(1);
instantの比較のためにはisAfterやisBeforeメソッドが存在します。untilメソッドは二つのInstant
オブジェクト間にどのくらいの時間差があるかを返します。以下のコード例は、Javaエポックの起点から現在時刻までの差を返します。
long secondsFromEpoch = Instant.ofEpochSecond(0L).until(Instant.now(), ChronoUnit.SECONDS);
Instant
クラスは、年・月・日などのhuman timeの単位のようには動作しません。もし、そうした単位で計算が必要な場合、Instant
を別のLocalDateTime
やZonedDateTime
クラスなどに、タイムゾーンをバインドし、変換することができます。以下のコードは、ofInstantメソッドを使用してInstant
をLocalDateTime
オブジェクトへデフォルトのタイムゾーンで変換し、読みやすい形式で日付と時間を出力しています。
Instant timestamp; ... LocalDateTime ldt = LocalDateTime.ofInstant(timestamp, ZoneId.systemDefault()); System.out.printf("%s %d %d at %d:%d%n", ldt.getMonth(), ldt.getDayOfMonth(), ldt.getYear(), ldt.getHour(), ldt.getMinute());
出力は以下のような結果になります。
MAY 30 2013 at 18:21
ZonedDateTime
もOffsetTimeZone
オブジェクトも、時間の流れにおけるある瞬間を示すInstant
オブジェクトへと変換できます。しかし、逆は真ではありません。Instant
オブジェクトをZonedDateTime
やOffsetDateTime
に変換するには、タイムゾーンかタイムゾーンオフセットの情報が必要です。
Parsing and Formatting
Date-Time APIのtemporal-basedクラスは、日付と時間の情報を含む文字列をパースするためのparse
メソッドを持っています。また、これらのクラスはtemporal-basedオブジェクトをフォーマットして表示するためのformat
メソッドも持っています。双方とも使い方は似ています。formatterオブジェクトを生成するには、DateTimeFormatter
へパターンを渡します。このformatterはparse
やformat
メソッドの引数となります。
DateTimeFormatter
クラスは、多数の定義済formatterを持っており、あるいは、自前のクラスを定義できます。
parse
とformat
メソッドは変換処理中に問題が発生した場合に例外をスローします。そのため、パースするコードはDateTimeParseException
、フォーマットはDateTimeException
をキャッチすべきです。より詳細な例外処理の情報については、Catching and Handling Exceptionsを参照してください。
DateTimeFormatter
クラスはイミュータブルかつスレッドセーフです。必要に応じてstatic定数として宣言できます(あるいは、そうすべきです)。
Version Note:java.time
date-timeオブジェクトは、java.util.Formatter
とレガシーなjava.util.Date
とjava.util.Calendar
で使われていたなじみ深いパターンベースフォーマットによるString.format
、を直接使用することができます。
Parsing
LocalDate
クラスの単一の引数を持つparse(CharSequence)メソッドはISO_LOCAL_DATE
formatterを使用します。異なるformatterを指定するには、二つの引数を持つparse(CharSequence, DateTimeFormatter)メソッドを使用します。以下の例は定義済みのBASIC_ISO_DATE
formatterを使用して、19590709
のフォーマットを1959年7月9日にパースします。
String in = ...;
LocalDate date = LocalDate.parse(in, DateTimeFormatter.BASIC_ISO_DATE);
自前のパターンを使用するformatterを定義することもできます。以下のコードは、Parseの例の抜粋で、"MMM d yyyy"のフォーマットを適用するformatterを生成しています。このフォーマットは、3文字の月・1文字の日・4文字の年、を表現しています。このパターンを使用して生成されたformatterは"Jan 3 2003"や"Mar 23 1994"のような文字列を受け入れます。しかし、"MMM dd yyyy"のように日を二文字のフォーマットを指定すると、"Jun 03 2003"のようにゼロパディングをして日を常に二文字で指定しなければなりません。
String input = ...; try { DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMM d yyyy"); LocalDate date = LocalDate.parse(input, formatter); System.out.printf("%s%n", date); } catch (DateTimeParseException exc) { System.out.printf("%s is not parsable!%n", input); throw exc; // Rethrow the exception. } // 'date' has been successfully parsed
DateTimeFormatter
クラスのドキュメントには、フォーマットやパースのパターンに指定可能なすべてのシンボルのリストが定義されています。
Non-ISO Date ConversionページのStringConverter
の例はdate formatterの別の例を示しています。
Formatting
format(DateTimeFormatter)メソッドは、temporal-basedオブジェクトを、指定されたフォーマットを使用して文字列へと変換します。以下のコードは、Flightサンプルの抜粋で、ZonedDateTime
インスタンスを"MMM d yyy hh:mm a"フォーマットを使用して変換しています。日付はパースの例で使用したのと同様な方法で定義されていますが、このパターンは時・分・a.m./p.m要素を含んでいます。
ZoneId leavingZone = ...; ZonedDateTime departure = ...; try { DateTimeFormatter format = DateTimeFormatter.ofPattern("MMM d yyyy hh:mm a"); String out = departure.format(format); System.out.printf("LEAVING: %s (%s)%n", out, leavingZone); } catch (DateTimeException exc) { System.out.printf("%s can't be formatted!%n", departure); throw exc; }
この例の出力は、以下のように出発と到着時刻を表示します。
LEAVING: Jul 20 2013 07:30 PM (America/Los_Angeles) ARRIVING: Jul 21 2013 10:20 PM (Asia/Tokyo)
The Temporal Package
java.time.temporalパッケージは、インターフェース、クラス、日付と時間のコードをサポートするenum、日付と時間計算、を提供します。
これらのインタフェースは最も低いレベルで使用されることを意図しています。一般的なアプリケーションコードは、LocalDate
やZonedDateTime
のような、変数と特定の型の引数を宣言すべきですが、Temporal
インタフェースはそうではありません。同様なこととしては、String
は変数を宣言しますが、CharSequence
はそうではありません。
Temporal and TemporalAccessor
Temporalインタフェースはtemporal-basedオブジェクトにアクセスするためのフレームワークを提供し、また、このインタフェースはInstant
, LocalDateTime
, ZonedDateTime
などのtemporal-basedクラスによって実装されています。このインタフェースは単位時間の加算・減算をするためのメソッドを提供しており、これらは様々な日付と時間のクラスに一貫性を保ち、時間ベースの算術が容易になります。TemporalAccessorインタフェースはTemporal
インタフェースのリードオンリーバージョンです。
Temporal
とTemporalAccessor
オブジェクトは、TemporalFieldインタフェースで指定される一単位としてのフィールドとして定義されています。ChronoField enumはTemporalField
インタフェースの実装で、DAY_OF_WEEK
, MINUTE_OF_HOUR
, MONTH_OF_YEAR
のような、定数値のセットを提供します。
それらのフィールドのための単位(units)はTemporalUnitインターフェースによって定義されます。ChronoUnit
enumはTemporalUnit
インタフェースを実装します。ChronoField.DAY_OF_WEEK
フィールドはChronoUnit.DAYS
とChronoUnit.WEEKS
の組み合わせです。ChronoField
とChronoUnit
enumは以降のセクションで扱います。
Temporal
インタフェースの算術ベース(arithmetic-based)メソッドはTemporalAmountを単位として定義されるパラメータを要求します。Period
とDuration
クラスがTemporalAmount
インタフェースを実装しています(Period and Duration)で解説します)。
ChronoField and IsoFields
ChronoField enumはTemporalField
インタフェースを実装しており、日付と時間の値を取得するための定数値を提供しています。CLOCK_HOUR_OF_DAY
, NANO_OF_DAY
, DAY_OF_YEAR
の例をいくつか示します。このenumは時間の概念的な捉え方を表現するために使用でき、たとえば、ある年の第三週、11時*11、ある月の最初の月曜日、などが挙げられます。良く知らないTemporal
に遭遇した場合、TemporalAccessor.isSupported(TemporalField)メソッドを使用して、個々のフィールドをTemporal
がサポートするかどうか判定できます。以下のコードはfalse
を返し、LocalDate
はChronoField.CLOCK_HOUR_OF_DAY
をサポートしないことを意味しています。
boolean isSupported = LocalDate.now().isSupported(ChronoField.CLOCK_HOUR_OF_DAY);
ISO-8601カレンダーシステムに固有の追加フィールドがIsoFieldsに定義されています。以下の例はChronoField
とIsoFields
の両方を使用するフィールド値を取得する方法を示しています。
time.get(ChronoField.MILLI_OF_SECOND)
int qoy = date.get(IsoFields.QUARTER_OF_YEAR);
二つの異なるクラスが役に立ちそうな追加的なフィールドを定義しており、それらはWeekFieldsとJulianFieldsです。
ChronoUnit
ChronoUnit enumはTemporalUnit
インタフェースを実装するもので、ミリ秒から世紀までの日付と時間に基づく標準的な単位を提供します。注意点として、ChronoUnit
オブジェクトはすべてのクラスでサポートされているわけではありません。たとえば、Instant
クラスはChronoUnit.MONTHS
やChronoUnit.YEARS
をサポートしません。TemporalAccessor.isSupported(TemporalField)*12メソッドで、あるクラスが個々の時間単位をサポートするかどうかを検証することができます。以下のisSupported
呼び出しはfalse
を返し、Instant
クラスはChronoUnit.DAYS
をサポートしないことを確認できます。
boolean isSupported = instant.isSupported(ChronoUnit.DAYS);
Temporal Adjuster
java.time.temporal
パッケージのTemporalAdjusterインタフェースは、引数にTemporal
を取り調整された値(adjusted value)を返します。adjustersは任意のtemporal-based型と共に使用します。
Predefined Adjusters
TemporalAdjustersクラス(複数形なことに注意)は定義済みのadjustersを提供しており、数例を上げると、ある月の最初の日や最後の日、ある年の最初の日や最後の日、ある月の最後の水曜日、指定日後での最初の火曜日、などが取得できます。定義済みadjustersはstaticメソッドとして定義されており、static importを使うように設計されています。
以下の例はTemporalAdjusters
メソッドのいくつかを使用したもので、temporal-basedクラスに定義されたwith
メソッドと共に使うことで、計算元の2000/10/15から新しい日付を算出しています。
LocalDate date = LocalDate.of(2000, Month.OCTOBER, 15); DayOfWeek dotw = date.getDayOfWeek(); System.out.printf("%s is on a %s%n", date, dotw); System.out.printf("first day of Month: %s%n", date.with(TemporalAdjusters.firstDayOfMonth())); System.out.printf("first Monday of Month: %s%n", date.with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY))); System.out.printf("last day of Month: %s%n", date.with(TemporalAdjusters.lastDayOfMonth())); System.out.printf("first day of next Month: %s%n", date.with(TemporalAdjusters.firstDayOfNextMonth())); System.out.printf("first day of next Year: %s%n", date.with(TemporalAdjusters.firstDayOfNextYear())); System.out.printf("first day of Year: %s%n", date.with(TemporalAdjusters.firstDayOfYear()));
この例は以下の出力になります。
2000-10-15 is on a SUNDAY first day of Month: 2000-10-01 first Monday of Month: 2000-10-02 last day of Month: 2000-10-31 first day of next Month: 2000-11-01 first day of next Year: 2001-01-01 first day of Year: 2000-01-01
Custom Adjusters
自前のカスタムadjusterを作ることもできます。そのためには、adjustInto(Temporal)メソッドを持つTemporalAdjuster
インタフェースを実装するクラスを用意します。NextPayDayサンプルからの抜粋したPaydayAdjusterはカスタムadjusterです。PaydayAdjuster
は引数の日付を評価して次の支払日を返します。支払日は月二回で毎月15日と月の最終日と仮定します。もし算出された日付が土日な場合は、直前の金曜日にします。年は今年*13と仮定します。
/** * The adjustInto method accepts a Temporal instance * and returns an adjusted LocalDate. If the passed in * parameter is not a LocalDate, then a DateTimeException is thrown. */ public Temporal adjustInto(Temporal input) { LocalDate date = LocalDate.from(input); int day; if (date.getDayOfMonth() < 15) { day = 15; } else { day = date.with(TemporalAdjusters.lastDayOfMonth()).getDayOfMonth(); } date = date.withDayOfMonth(day); if (date.getDayOfWeek() == DayOfWeek.SATURDAY || date.getDayOfWeek() == DayOfWeek.SUNDAY) { date = date.with(TemporalAdjusters.previous(DayOfWeek.FRIDAY)); } return input.with(date); }
このadjusterは定義済みadjusterと同様な方法で、with
メソッドを使用するときに呼び出すことができます。以下のコード例はNextPayday
サンプルからのものです。
LocalDate nextPayday = date.with(new PaydayAdjuster());
2013年では、6月15日と6月30日は両方とも土日です。NextPayday
サンプルをそれぞれ2013年6月3日と2013年6月18日で実行すると、以下の結果が得られます。
Given the date: 2013 Jun 3 the next payday: 2013 Jun 14 Given the date: 2013 Jun 18 the next payday: 2013 Jun 28
Temporal Query
TemporalQueryはtemporal-basedオブジェクトから情報を検索するために使用します。
Predefined Queries
TemporalQueries(複数形なことに注意)クラスはいくつかの定義済みクエリーを提供しており、これにはアプリケーションがtemporal-basedオブジェクトの型を指定できないときに役立つメソッドが含まれています。adjustersと同様に、定義済みクエリーはstaticメソッドとして定義されており、static importを使うように設計されています。
precisionクエリーは、例えば、特定のtemporal-basedオブジェクトが返すことのできる最小のChronoUnit
を返します。以下の例はprecision
クエリーをいくつかのtemporal-basedオブジェクトの型に対して使用しています。
TemporalQueries query = TemporalQueries.precision(); System.out.printf("LocalDate precision is %s%n", LocalDate.now().query(query)); System.out.printf("LocalDateTime precision is %s%n", LocalDateTime.now().query(query)); System.out.printf("Year precision is %s%n", Year.now().query(query)); System.out.printf("YearMonth precision is %s%n", YearMonth.now().query(query)); System.out.printf("Instant precision is %s%n", Instant.now().query(query));
出力は以下のようになります。
LocalDate precision is Days LocalDateTime precision is Nanos Year precision is Years YearMonth precision is Months Instant precision is Nanos
Custom Queries
自前のカスタムクエリーを作ることもできます。その方法は、TemporalQuery
インタフェースを実装してqueryFrom(TemporalAccessor) を作成することです。CheckDateサンプルは二つのカスタムクエリを実装しています。一つ目のカスタムクエリはFamilyVacationsクラスで、TemporalQueryインタフェースを実装しています。queryFrom
メソッドは渡された日付と休暇スケジュールを比較し、もし範囲内であればTRUE
を返します。
// 渡された日付が家族の休暇日に含まれていればtrueを返します。 // クエリは月日のみ比較するので、Temporal型が異なっていても // チェックは成功します。 public Boolean queryFrom(TemporalAccessor date) { int month = date.get(ChronoField.MONTH_OF_YEAR); int day = date.get(ChronoField.DAY_OF_MONTH); // 春休みのディズニーランド if ((month == Month.APRIL.getValue()) && ((day >= 3) && (day <= 8))) return Boolean.TRUE; // ソーガタック湖でのスミス家親睦会 if ((month == Month.AUGUST.getValue()) && ((day >= 8) && (day <= 14))) return Boolean.TRUE; return Boolean.FALSE; }
次のカスタムクエリーはFamilyBirthdaysクラスで実装されているものです。このクラスはisFamilyBirthday
メソッドを提供しており、渡された日付といくつかの誕生日を比較し、もし一致すればTRUE
を返します。
// 渡された日付が家族の誕生日のいずれか一つと一致すればtrueを返します。 // クエリは月日のみ比較するので、Temporal型が異なっていても // チェックは成功します。 public static Boolean isFamilyBirthday(TemporalAccessor date) { int month = date.get(ChronoField.MONTH_OF_YEAR); int day = date.get(ChronoField.DAY_OF_MONTH); // Angie's birthday is on April 3. if ((month == Month.APRIL.getValue()) && (day == 3)) return Boolean.TRUE; // Sue's birthday is on June 18. if ((month == Month.JUNE.getValue()) && (day == 18)) return Boolean.TRUE; // Joe's birthday is on May 29. if ((month == Month.MAY.getValue()) && (day == 29)) return Boolean.TRUE; return Boolean.FALSE; }
FamilyBirthday
クラスはTemporalQuery
インターフェースを実装していませんが、ラムダ式の一部として使用できます。以下のコードは、CheckDate
サンプルからのもので、二つのカスタムクエリの呼び出し方を示しています。
// ラムダ式を使用しないクエリの呼び出し Boolean isFamilyVacation = date.query(new FamilyVacations()); // ラムダ式を使用したクエリの呼び出し Boolean isFamilyBirthday = date.query(FamilyBirthdays::isFamilyBirthday); if (isFamilyVacation.booleanValue() || isFamilyBirthday.booleanValue()) System.out.printf("%s is an important date!%n", date); else System.out.printf("%s is not an important date.%n", date);
Period and Duration
時間の合計を指定するコードを書こうとするとき、最良のクラスとメソッドを、Durationクラス、Periodクラス、ChronoUnit.between メソッド、などに見出せるでしょう。Duration
は時間ベース(time-based)の値(秒、ナノ秒)を使用して時間の合計を計測します。Period
は日付ベース(date-based)の値(年、月、日)を使用します。
Note:一日のDuration
は正確に(exactly)24時間のlongです。一日のPeriod
は、ZonedDateTime
が追加されたとき、タイムゾーンに従って変化します。たとえば、サマータイムの最初と最後の日では変化します。
Duration
Duration
は、Instant
オブジェクトを使用するコードのような、machine-based時間を計測する状況で最も役立ちます。Duration
オブジェクトは秒あるいはナノ秒で計測され、日数・時間・分を変換するメソッドは持ちますが、年・月・日のような日付ベースで生成する仕組みは持ちません。Duration
はマイナス値を持つことができ、これは開始ポイントの前に終了ポイントを指定する場合に発生します。
以下のコードでは、ナノ秒で、二つのinstant間の時間を計算します。
Instant t1, t2; ... long ns = Duration.between(t1, t2).toNanos();
以下のコードはInstant
に10秒を足します。
Instant start; ... Duration gap = Duration.ofSeconds(10); Instant later = start.plus(gap);
Duration
は実世界の時間とは切り離されており、従ってタイムゾーンやサマータイムを追従しません。一日と等しいDuration
をZonedDateTime
に加えることは、サマータイムやその他の異なる結果を返す時差かどうかにかかわらず、24時間を加えることと正確に等しいです。
ChronoUnit
ChronoUnit
enumは、The Temporal Packageで説明したように、時間計測のためのユニットを定義しています。ChronoUnit.between
メソッドは、日数や秒数のような、時間単位のみで合計を計測する場合に役立ちます。between
メソッドはすべてのtemporal-basedオブジェクトで動作しますが、単一単位での合計のみ返します。以下のコードは、二つのタイムスタンプ間のミリ秒でのギャップを計算しています。
import java.time.Instant; import java.time.temporal.Temporal; import java.time.temporal.ChronoUnit; Instant previous, current, gap; ... current = Instant.now(); if (previous != null) { gap = ChronoUnit.MILLIS.between(previous,current); } ...
Period
日付ベース値(年・月・日)で時間の合計を定義するには、Periodクラスを使用します。Period
クラスは様々なget
メソッドを提供しており、たとえばgetMonths、getDays、getYearsなどで、これらを使用することでピリオドから時間の合計を抽出できます。
時間の総合計(total period of time)は年・月・日の三つの単位によって表現されます。日数のような単一の単位で計測される時間の合計を表現するには、ChronoUnit.between
メソッドを使用します。
以下のコードは、あなたが1960年1月1日に生まれたと仮定して、年齢を計算します。Period
クラスは年・月・日単位で計算を行うために使用します。合計日数は、同一のピリオドをChronoUnit.between
メソッドを使用することで決定でき、括弧内に表示しています。
LocalDate today = LocalDate.now(); LocalDate birthday = LocalDate.of(1960, Month.JANUARY, 1); Period p = Period.between(birthday, today); long p2 = ChronoUnit.DAYS.between(birthday, today); System.out.println("You are " + p.getYears() + " years, " + p.getMonths() + " months, and " + p.getDays() + " days old. (" + p2 + " days total)");
このコードは以下のような出力になります。
You are 53 years, 4 months, and 29 days old. (19508 days total)
次の誕生日までの日数を計算するには、Birthdayサンプルの以下のコードのようになります。Period
クラスは月数と日数を計算するのに使用します。ChronoUnit.between
メソッドは合計日数を返すので、それを括弧内に表示しています。
LocalDate birthday = LocalDate.of(1960, Month.JANUARY, 1); LocalDate nextBDay = birthday.withYear(today.getYear()); //もし今日が今年の誕生日以降なら、1年足す。 if (nextBDay.isBefore(today) || nextBDay.isEqual(today)) { nextBDay = nextBDay.plusYears(1); } Period p = Period.between(today, nextBDay); long p2 = ChronoUnit.DAYS.between(today, nextBDay); System.out.println("There are " + p.getMonths() + " months, and " + p.getDays() + " days until your next birthday. (" + p2 + " total)");
このコード以下のような結果になります。
There are 7 months, and 2 days until your next birthday. (216 total)
これらの計算はタイムゾーンの違いを解釈しません。たとえば、もしあなたがオーストラリアで生まれて、現在はバンガロール*15在住の場合、実際の年齢計算にわずかな影響を与えます。このような場合では、ZonedDateTime
クラスと共にPeriod
を使用します。Period
にZonedDateTime
を加えるとき、時差が考慮されます。
Clock
多くのtemporal-basedオブジェクトは引数無しのnow()
メソッドを備えており、このメソッドはシステムクロックとデフォルトタイムゾーンを使用して現在の日付と時刻を返します。また、これらのtemporal-basedオブジェクトは一つの引数を受け取るnow(Clock)
メソッドを備えており、これには別のClockを渡せます。
現在の日付と時刻は、国際化を必要とするアプリケーションでは、タイムゾーンに依存し、Clock
は正しいタイムゾーンに基づいて日付/時刻が生成されることを保障する必要があります。Clock
クラスの使用はオプションですが、この機能はあなたのコードを別のタイムゾーンや、時刻を固定した時計(fixed clock)で、テストできるようになります。
Clock
クラスは抽象クラスなので、インスタンスを生成することは出来ません。以下のファクトリーメソッドがテストのために使用できます。
- Clock.offset(Clock, Duration)は、指定された
Duration
でオフセットされるクロックを返します。 - Clock.systemUTC()は、Greenwich/UTCタイムゾーンを表現するクロックを返します。
- Clock.fixed(Instant, ZoneId)は、常に同一の
Instant
を返します。このクロックは時間が止まっています。
Non-ISO Date Conversion
このチュートリアルはjava.time.chronoパッケージの詳細については解説しません。しかしながら、いくつかの非ISOベースの定義済みの暦、たとえば和暦、ヒジュラ(Hijrah)、民国紀元(Minguo)*16、仏滅紀元(Thai Buddhist)を知っておくのは時に有用となるでしょう。このパッケージを使用して環境ごとの暦を使用することができます。
このセクションはISOベースの日付とその他の定義済みの暦との変換方法について示します。
Converting to a Non-ISO-Based Date
ISOベースの日付を別の暦に変更するには、JapaneseDate.from(TemporalAccessor)などのfrom(TemporalAccessor)
メソッドを使用します。このメソッドは、もし日付を妥当なインスタンスに変換できない場合はDateTimeException
をスローします。以下のコードはLocalDateTime
インスタンスをいくつかの定義済み非ISOカレンダーの日付に変換しています。
LocalDateTime date = LocalDateTime.of(2013, Month.JULY, 20, 19, 30); JapaneseDate jdate = JapaneseDate.from(date); HijrahDate hdate = HijrahDate.from(date); MinguoDate mdate = MinguoDate.from(date); ThaiBuddhistDate tdate = ThaiBuddhistDate.from(date);
StringConverterサンプルでは、LocalDate
をChronoLocalDate
に変換したあとString
を返します。toString
メソッドは引数にLocalDate
とChronology
のインスタンスを取り、引数Chronology
を使用して変換した文字列を返します。DateTimeFormatterBuilder
は、日付表示のための文字列を組み立てるために使用されています。
/** * 引数のChronologyを使用してLocalDate (ISO)をChronoLocalDateに変換 * してから、Chronologyと現在のロケールに基づく短いパターンのDateTimeFormatterを * 使用して、ChronoLocalDateを文字列にフォーマットします。 * * @param localDate - このISOベースの日付を変換してフォーマットします。 * @param chrono - オプションの暦。nullの場合はIsoChronologyを使用します。 */ public static String toString(LocalDate localDate, Chronology chrono) { if (localDate != null) { Locale locale = Locale.getDefault(Locale.Category.FORMAT); ChronoLocalDate cDate; if (chrono == null) { chrono = IsoChronology.INSTANCE; } try { cDate = chrono.date(localDate); } catch (DateTimeException ex) { System.err.println(ex); chrono = IsoChronology.INSTANCE; cDate = localDate; } DateTimeFormatter dateFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT) .withLocale(locale) .withChronology(chrono) .withDecimalStyle(DecimalStyle.of(locale)); String pattern = "M/d/yyyy GGGGG"; return dateFormatter.format(cDate); } else { return ""; } }
定義済みの暦でメソッドを呼び出すのは下記のようになります。
LocalDate date = LocalDate.of(1996, Month.OCTOBER, 29); System.out.printf("%s%n", StringConverter.toString(date, JapaneseChronology.INSTANCE)); System.out.printf("%s%n", StringConverter.toString(date, MinguoChronology.INSTANCE)); System.out.printf("%s%n", StringConverter.toString(date, ThaiBuddhistChronology.INSTANCE)); System.out.printf("%s%n", StringConverter.toString(date, HijrahChronology.INSTANCE));
出力は以下のようになります。
10/29/0008 H 10/29/0085 1 10/29/2539 B.E. 6/16/1417 1
Converting to an ISO-Based Date
非ISOベースの日付をLocalDate.fromstatic メソッドを使用してLocalDate
インスタンスに変換でき、以下がそのサンプルコードになります。
LocalDate date = LocalDate.from(JapaneseDate.now());
また、他のtemporal-basedクラスもこのメソッドを提供しており、もし変換不能な場合にはDateTimeException
をスローします。
StringConverterサンプルから抜粋したfromString
メソッドは、非ISOベースの日付を含むString
をパースしてLocalDate
インスタンスを返します。
/** * 現在のロケールと引数の暦に基づく短いパターンのDateTimeFormatterを * 使用して文字列をChronoLocalDateにパースしたあと、LocalDate (ISO)に変換します。 * * @param text - 現在のロケールと引数のChronologyで解釈可能なフォーマット * の文字列を入力値として渡します。 * * @param chrono - オプションのChronology。もしnullならIsoChronologyを使用します。 */ public static LocalDate fromString(String text, Chronology chrono) { if (text != null && !text.isEmpty()) { Locale locale = Locale.getDefault(Locale.Category.FORMAT); if (chrono == null) { chrono = IsoChronology.INSTANCE; } String pattern = "M/d/yyyy GGGGG"; DateTimeFormatter df = new DateTimeFormatterBuilder().parseLenient() .appendPattern(pattern) .toFormatter() .withChronology(chrono) .withDecimalStyle(DecimalStyle.of(locale)); TemporalAccessor temporal = df.parse(text); ChronoLocalDate cDate = chrono.date(temporal); return LocalDate.from(cDate); } return null; }
以下のような文字列でこのメソッドを呼び出すことが出来ます。
System.out.printf("%s%n", StringConverter.fromString("10/29/0008 H", JapaneseChronology.INSTANCE)); System.out.printf("%s%n", StringConverter.fromString("10/29/0085 1", MinguoChronology.INSTANCE)); System.out.printf("%s%n", StringConverter.fromString("10/29/2539 B.E.", ThaiBuddhistChronology.INSTANCE)); System.out.printf("%s%n", StringConverter.fromString("6/16/1417 1", HijrahChronology.INSTANCE));
すべて1996年10月29日に変換された結果の文字列が表示されます。
Legacy Date-Time Code
Java SE 8 releaseより以前では、Javaの日付と時間のメカニズムは、java.util.Date、java.util.Calendarのサブクラスであるjava.util.GregorianCalendar、によって提供されてきました。これらのクラスは以下のようないくつかの欠点を抱えていました。
Calendar
がタイプセーフでない。- クラスがミュータブルなので、マルチスレッドアプリケーションで使用できない。
- アプリケーションコードのバグの原因は共通して奇妙な月のナンバリングと型安全の不足によるものです。
Interoperability with Legacy Code
おそらく大抵の人はjava.util
の日付と時間のクラスを使うレガシーコードを持っており、最小の手間でレガシーコードをjava.time
の機能を生かせるように変更したいことでしょう。
JDK 8リリースにはjava.util
とjava.time
オブジェクト間の変更を行うメソッドが追加されています。
- Calendar.toInstant()は
Calendar
オブジェクトをInstant
に変換します。 - GregorianCalendar.toZonedDateTime()は
GregorianCalendar
インスタンスをZonedDateTime
に変換します。 - GregorianCalendar.from(ZonedDateTime)は
ZonedDateTime
インスタンスからデフォルトロケールのGregorianCalendar
オブジェクトを生成します。 - Date.from(Instant)は
Instant
からDate
オブジェクトを生成します。 - Date.toInstant()は
Date
オブジェクトをInstant
に変換します。 - TimeZone.toZoneId() は
TimeZone
オブジェクトをZoneId
に変換します。
以下の例はCalendar
インスタンスをZonedDateTime
インスタンスに変換します。注意点として、Instant
からZonedDateTime
の変換にはタイムゾーンが必須です。
Calendar now = Calendar.getInstance();
ZonedDateTime zdt = ZonedDateTime.ofInstant(now.toInstant(), ZoneId.systemDefault()));
以下の例はDate
とInstant
間の変換を示しています。
Instant inst = date.toInstant(); Date newDate = Date.from(inst);
以下の例はGregorianCalendar
からZonedDateTime
への変換をしたあと、ZonedDateTime
からGregorianCalendar
に変換しています。その他のtemporal-basedクラスはZonedDateTime
インスタンスを使用して生成されています。
GregorianCalendar cal = ...; TimeZone tz = cal.getTimeZone(); int tzoffset = cal.get(Calendar.ZONE_OFFSET); ZonedDateTime zdt = cal.toZonedDateTime(); GregorianCalendar newCal = GregorianCalendar.from(zdt); LocalDateTime ldt = zdt.toLocalDateTime(); LocalDate date = zdt.toLocalDate(); LocalTime time = zdt.toLocalTime();
Mapping java.util Date and Time Functionality to java.time
Java SE 8リリースでは日付と時間の実装は完全に再設計されたので、あるメソッドを別のメソッドへと単純に入れ替えすることは出来ません。もしjava.time
パッケージが提供する豊富な機能を使いたい場合、最も簡単な解決策は以下のセクションでリストアップしているtoInstant
やtoZonedDateTime
メソッドを使用することです。しかし、このアプローチを望まない場合や、ニーズを満たさない場合には、日付と時間を扱うコードを書き直さなければなりません。
java.time
クラスがニーズに合うかどうかを評価し始めるには、Overviewページで紹介した表を起点にするのが良いでしょう。
二つのAPI間は一対一にマッピングできませんが、以下の表にjava.util
の日付と時間のクラスの機能をjava.time
APIにマッピングする際の一般的な考え方を示します。
java.util Functionality | java.time Functionality | Comments |
---|---|---|
java.util.Date |
java.time.Instant |
Instant とDate クラスは似た物同士です。各クラスは、・時間軸(UTL)上におけるある一瞬を表現する。 ・タイムゾーンの独立した時間を持つ。 ・ナノ秒を足したエポック秒(1970-01-01T00:00:00Z以降)として表現される。 Date.from(Instant) とDate.toInstant() メソッドが両クラス間の変換を担当します。 |
java.util.GregorianCalendar |
java.time.ZonedDateTime |
ZonedDateTime クラスはGregorianCalendar を置き換えるもので、以下のような似た機能を有しています。human timeの表現は以下のように行います。 LocalDate : 年、月、日LocalTime :時、分、秒、ナノ秒ZoneId タイムゾーンZoneOffset :GMTからの現在のオフセットGregorianCalendar.from(ZonedDateTime) とGregorianCalendar.to(ZonedDateTime) メソッドが両クラス間の変換を調整します。 |
java.util.TimeZone |
java.time.ZoneId もしくはjava.time.ZoneOffset |
ZoneId クラスはタイムゾーン識別子を指定し、使用する各タイムゾーンのルールにアクセスできます。ZoneOffset クラスはGreenwich/UTCからのオフセットにみを指定します。より詳細な情報については、Time Zone and Offset Classesを参照してください。 |
1970-01-01 のGregorianCalendar |
java.time.LocalTime |
timeコンポーネントを使用するには、GregorianCalendar インスタンスに1970-01-01をセットするコードを、LocalTime インスタンスで置き換えます。 |
00:00 のGregorianCalendar |
java.time.LocalDate |
dateコンポーネントを使用するには、GregorianCalendar に00:00をセットするコードを、LocalDate インスタンスで置き換えます。(このGregorianCalendar のアプローチは欠陥があります。国によってはサマータイム移行時に年一回00:00が発生しないためです。) |
Date and Time Formatting
java.time.format.DateTimeFormatter
は日付と時間のフォーマットのための強力なメカニズムを持ちますが、java.time
のtemporal-basedクラスをjava.util.Formatter
とString.format
で使うこともでき、これらはjava.util
の日付と時間クラスで使用しているパターンベースのフォーマットと同様なやり方が使えます。
Summary
java.time
パッケージは、日付と時間を表現するために使用可能な多くのクラスが含まれています。これらは豊富なAPIから成ります。ISOベースの日付のキー要素となるのは以下の通りです。
Instant
クラスは時間軸上における機械時間表現(machine view)を提供します。LocalDate
,LocalTime
,LocalDateTime
クラスは日付と時間の人的時間表現(human view)をタイムゾーン参照無しで提供します。ZoneId
,ZoneOffset
,ZoneRules
クラスは、タイムゾーン・タイムゾーンオフセット・タイムゾーンルールを定義します。ZonedDateTime
クラスは日付と時間をタイムゾーン付きで表現します。OffsetDateTime
とOffsetTime
クラスはそれぞれ、、日付と時間ないし時間、を表現します。これらのクラスはタイムゾーンオフセットをアカウントに取ります。Duration
クラスは秒とナノ秒単位での時間の合計を計測します。Period
クラスは年・月・日単位での時間の合計を計測します。
他の非ISOカレンダーシステムはjava.time.chrono
パッケージを使用して表現可能です。このパッケージはこのチュートリアルの範囲を超えてはいますが、Non-ISO Date ConversionページでISOベースの日付を別のカレンダーシステムに変換するための情報を提供しています。
Date Time APIはJSR 310の設計下でJava community processの一部として開発されました。詳細な情報については、JSR 310: Date and Time APIを参照してください。
Questions and Exercises: Date-Time API
(省略)
Date Time: End of Trail
"Date Time"トレイルはここで終わりです。
もしコメントや質問がある場合、フィードバックページ*17からおしらせください。
Essential Classes:こちらには文字列とプロパティについての情報があり、どちらも国際化対応プログラムで使用されます。
*3:http://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar
*4:1582年以前の日付も扱える、という意味
*5:上手く訳せない…
*6:原文ではmonth-day-year
*7:詳細はぐぐって欲しいし俺も良くしらないかそういうヨットレースの大会があるらしい
*8:シンタックスハイライトの中を大文字にする方法ワカランのでやってないです
*9:whole hours
*10:このへん大変日本語がビミョーなので原文を参照願いたい…
*11:原文はthe 11th hour of the dayで、11th hourで最後の~とかぎりぎりの~などの意味があるようなので、単なる11時以上の意味がある気がする
*12:TemporalUnitを引数に取るメソッドが無いけど、何がどう変わったかまでは調べてない
*13:2013年
*14:Lake Saugatuck - ソーガタック湖はミシガン湖のリゾート地らしい。海かよっていうくらいデカイらしいので、湖っていう字面以上のスケールと思われる
*15:インド南部・カルナータカ州の州都
*16:民国紀元(みんこくきげん、正体字中国語:民國紀元・民國紀年)は、中華民国が成立した1912年を紀元(元年)とする紀年法である。参照元:http://ejje.weblio.jp/content/Minguo+calendar
*17:言うまでもなくOracleに対するものなので、俺の訳文エントリに対するものはこのページのコメント欄や https://twitter.com/kagamihoge からどうぞ