はじめに (対象読者・この記事でわかること)

この記事は、Javaプログラミングにおいて、文字列(String型)で表現された日付や時刻を、Javaが扱う日付・時刻オブジェクト(Date型やLocalDateTime型など)に変換する必要がある、すべての開発者を対象としています。特に、外部からの入力、ファイルからの読み込み、APIレスポンスなどで文字列形式の日付を扱う場面で役立つでしょう。

この記事を読むことで、以下のことができるようになります。

  • Javaの標準API(SimpleDateFormatDateTimeFormatter)を使用したStringからDate型への変換方法を理解し、実装できるようになります。
  • 様々な日付フォーマットに対応するための具体的なコード例を習得できます。
  • 変換時に発生しがちなエラーとその対処法、および予期せぬ日付フォーマットへの対応方法を学びます。
  • よりモダンで扱いやすいjava.timeパッケージ(LocalDateTimeなど)への変換方法についても理解できます。
  • (応用)Apache Commons LangライブラリやJoda-Timeライブラリといった外部ライブラリを利用した、より簡潔で強力な変換方法についても触れます。

開発を進める上で、日付や時刻の扱いは避けて通れません。この記事を通して、安全かつ効率的にString型からDate型への変換を行えるようになり、より堅牢なJavaアプリケーション開発に繋げてください。

JavaにおけるStringからDate型への変換:基本から応用まで

Javaで文字列を日付型に変換する処理は、Webアプリケーション開発、バッチ処理、データ分析など、様々な場面で頻繁に登場します。しかし、日付のフォーマットは多様であり、変換処理を誤るとParseExceptionなどの例外が発生したり、意図しない日付に変換されてしまったりする可能性があります。

ここでは、JavaでString型からDate型(およびそれ以降のモダンな日付・時刻型)へ変換する際の主要な方法と、それぞれの特徴、注意点について詳しく解説していきます。

1. Java標準API(java.text.SimpleDateFormat)を用いた変換

最も古典的で基本的な方法として、java.text.SimpleDateFormatクラスを使用する方法があります。これは、日付と時刻のフォーマットとパース(文字列から日付オブジェクトへの変換)を行うためのクラスです。

1.1 SimpleDateFormatの基本的な使い方

SimpleDateFormatを利用するには、まず変換したい文字列の日付フォーマットを正確に指定したSimpleDateFormatオブジェクトを作成します。例えば、「2023-10-27」のような形式であれば、"yyyy-MM-dd"というパターンを指定します。

Java
import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; public class StringToDateExample { public static void main(String[] args) { String dateString = "2023-10-27"; SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); try { Date date = formatter.parse(dateString); System.out.println("変換されたDateオブジェクト: " + date); } catch (ParseException e) { System.err.println("日付のパースに失敗しました: " + e.getMessage()); e.printStackTrace(); } } }

解説:

  • SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); の部分で、変換したい文字列のフォーマットを指定しています。
    • yyyy: 4桁の年 (例: 2023)
    • MM: 2桁の月 (01~12)
    • dd: 2桁の日 (01~31)
  • formatter.parse(dateString) メソッドが、指定されたフォーマットに従って文字列をDateオブジェクトに変換します。
  • parseメソッドは、指定されたフォーマットに一致しない文字列が渡された場合にParseExceptionをスローする可能性があるため、try-catchブロックで囲むことが必須です。

1.2 よく使われる日付フォーマットパターン

SimpleDateFormatでは、以下のような様々なパターン文字を組み合わせて、柔軟なフォーマットを指定できます。

パターン 説明
yyyy 年 (4桁) 2023
yy 年 (下2桁) 23
MM 月 (2桁、01~12) 10
M 月 (1桁または2桁) 10
MMM 月の略称 (ロケール依存) Oct
MMMM 月のフルネーム (ロケール依存) October
dd 日 (2桁、01~31) 27
d 日 (1桁または2桁) 27
HH 時 (24時間表記、00~23) 14
hh 時 (12時間表記、01~12) 02
mm 分 (00~59) 30
ss 秒 (00~59) 15
SSS ミリ秒 (3桁) 456
a 午前/午後マーカー (ロケール依存) PM
z タイムゾーンの省略名 (例: PST) JST
Z タイムゾーンのオフセット (例: -0800) +0900
' リテラル文字の開始 'at'
'' 単一引用符 (') '' -> '

例:

  • "yyyy/MM/dd HH:mm:ss" -> "2023/10/27 14:30:15"
  • "yyyy年MM月dd日" -> "2023年10月27日"
  • "MM-dd-yyyy hh:mm:ss a" -> "10-27-2023 02:30:15 PM"

1.3 SimpleDateFormatの注意点

  • スレッドセーフではない: SimpleDateFormatはスレッドセーフではありません。複数のスレッドから同時に同じSimpleDateFormatインスタンスにアクセスすると、予期しない結果やエラーが発生する可能性があります。スレッドセーフを保証するためには、各スレッドで個別のインスタンスを作成するか、ThreadLocalを利用する、あるいは後述するjava.timeパッケージの使用を検討してください。
  • ロケール依存: 月の名称(MMM, MMMM)や午前/午後(a)、タイムゾーンの表示(z)などは、JavaVMのデフォルトロケールや、SimpleDateFormatコンストラクタで指定したロケールに依存します。意図しない表示になることを避けるため、必要に応じてロケールを指定したコンストラクタを使用しましょう。 java // 日本語ロケールでインスタンス化 SimpleDateFormat formatterJp = new SimpleDateFormat("yyyy/MM/dd EEEE", new Locale("ja", "JP"));
  • 厳密なフォーマット: parse()メソッドは、指定されたフォーマットに厳密に一致しない文字列をパースしようとするとParseExceptionをスローします。例えば、"yyyy-MM-dd"を指定しているのに"2023/10/27"のような文字列が渡された場合です。

2. Java 8以降の java.time パッケージを用いた変換

Java 8で導入されたjava.timeパッケージ(JSR 310)は、日付・時刻APIの設計を根本的に見直し、より強力で使いやすく、スレッドセーフなAPIを提供します。java.util.Datejava.util.Calendarの欠点を克服しており、新規開発ではこちらを利用することが強く推奨されます。

java.timeパッケージでは、主に以下のクラスがStringからの変換に利用されます。

  • LocalDate: 日付 (年、月、日) のみ
  • LocalTime: 時刻 (時、分、秒、ナノ秒) のみ
  • LocalDateTime: 日付と時刻 (年、月、日、時、分、秒、ナノ秒)
  • ZonedDateTime: 日付、時刻、タイムゾーン

2.1 DateTimeFormatterの基本的な使い方

java.timeパッケージでは、DateTimeFormatterクラスがStringと日付・時刻オブジェクト間の変換(パースとフォーマット)を担当します。SimpleDateFormatと同様に、フォーマットパターンを指定してDateTimeFormatterオブジェクトを作成します。

Java
import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; public class StringToLocalDateExample { public static void main(String[] args) { String dateString = "2023-10-27"; // 基本的なISOフォーマット (yyyy-MM-dd) はデフォルトで解釈されることが多いですが、明示的に指定すると安全です。 DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE; // または DateTimeFormatter.ofPattern("yyyy-MM-dd"); try { LocalDate date = LocalDate.parse(dateString, formatter); System.out.println("変換されたLocalDateオブジェクト: " + date); // LocalDateTimeへの変換例 String dateTimeString = "2023-10-27 14:30:15"; DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); // LocalDateTime.parse(dateTimeString, dateTimeFormatter); // もしくは DateTimeFormatter.ISO_LOCAL_DATE_TIME // System.out.println("変換されたLocalDateTimeオブジェクト: " + dateTime); } catch (DateTimeParseException e) { System.err.println("日付/時刻のパースに失敗しました: " + e.getMessage()); e.printStackTrace(); } } }

解説:

  • DateTimeFormatter.ISO_LOCAL_DATE は、"yyyy-MM-dd"というISO標準の日付フォーマットを扱います。
  • DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") のように、SimpleDateFormatと同様のパターン文字でカスタムフォーマットを指定できます。
  • LocalDate.parse(dateString, formatter) メソッドでパースを行います。
  • java.timeパッケージのパースメソッドはDateTimeParseExceptionをスローします。
  • DateTimeFormatterはスレッドセーフなので、共有して使用することができます。

2.2 java.timeパッケージの利点

  • スレッドセーフ: DateTimeFormatterはスレッドセーフです。
  • 不変性 (Immutability): LocalDateLocalDateTimeなどの日付・時刻オブジェクトは不変です。生成されたオブジェクトは変更できず、変更操作を行うと新しいオブジェクトが返されます。これにより、意図しない状態変化を防ぎ、コードの安全性が高まります。
  • 明確なAPI: DateCalendarに比べて、APIが直感的で分かりやすくなっています。
  • JavaBeansとの連携: java.util.Dateからjava.timeクラスへの変換は、Date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate()のように行うことができます。

3. 外部ライブラリを利用した変換 (Apache Commons Lang / Joda-Time)

標準APIで十分な場合も多いですが、より簡潔に、あるいは特殊なフォーマットに対応したい場合に、外部ライブラリの利用も有効な選択肢となります。

3.1 Apache Commons Langの DateUtils

Apache Commons Langは、Javaの標準ライブラリを補完する様々なユーティリティクラスを提供するライブラリです。その中のorg.apache.commons.lang3.time.DateUtilsクラスは、日付のパースを容易にします。

まず、MavenやGradleで依存関係を追加します。

Maven:

Xml
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.14.0</version> <!-- 最新バージョンを確認してください --> </dependency>

Gradle:

Gradle
implementation 'org.apache.commons:commons-lang3:3.14.0' // 最新バージョンを確認してください

コード例:

Java
import org.apache.commons.lang3.time.DateUtils; import java.util.Date; import java.text.ParseException; public class CommonsLangDateParseExample { public static void main(String[] args) { String dateString1 = "2023-10-27"; String dateString2 = "27/10/2023"; String dateString3 = "Oct 27, 2023"; // 複数のフォーマットを試す String[] parsePatterns = {"yyyy-MM-dd", "dd/MM/yyyy", "MMM dd, yyyy"}; try { Date date1 = DateUtils.parseDate(dateString1, parsePatterns); System.out.println("変換されたDate (String 1): " + date1); Date date2 = DateUtils.parseDate(dateString2, parsePatterns); System.out.println("変換されたDate (String 2): " + date2); Date date3 = DateUtils.parseDate(dateString3, parsePatterns); System.out.println("変換されたDate (String 3): " + date3); } catch (ParseException e) { System.err.println("日付のパースに失敗しました: " + e.getMessage()); e.printStackTrace(); } } }

解説:

  • DateUtils.parseDate(String str, String... parsePatterns) メソッドは、指定された文字列を、parsePatterns配列に定義されたフォーマットのいずれかでパースしようとします。最初に一致したフォーマットが使用されます。
  • これにより、複数の異なるフォーマットの文字列をまとめて処理したい場合に非常に便利です。
  • ParseExceptionをスローする点は標準APIと同様です。

3.2 Joda-Timeライブラリ (レガシーだが依然として利用されている場合も)

Joda-Timeは、Java 8のjava.timeパッケージが導入される前に、事実上の標準日付・時刻ライブラリとして広く使われていました。現在ではjava.timeが推奨されますが、古いプロジェクトや特定の理由でJoda-Timeが利用されている場合もあります。

Joda-TimeではDateTimeFormatterorg.joda.time.format.DateTimeFormatter)を使用します。

Maven:

Xml
<dependency> <groupId>joda-time</groupId> <artifactId>joda-time</artifactId> <version>2.12.7</version> <!-- 最新バージョンを確認してください --> </dependency>

Gradle:

Gradle
implementation 'joda-time:joda-time:2.12.7' // 最新バージョンを確認してください

コード例:

Java
import org.joda.time.DateTime; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; public class JodaTimeParseExample { public static void main(String[] args) { String dateString = "2023/10/27 14:30:15"; DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy/MM/dd HH:mm:ss"); try { DateTime dateTime = formatter.parseDateTime(dateString); System.out.println("変換されたDateTimeオブジェクト: " + dateTime); System.out.println("Joda-TimeのDateTime: " + dateTime.toString()); // java.util.Dateへの変換 java.util.Date utilDate = dateTime.toDate(); System.out.println("java.util.Dateに変換: " + utilDate); } catch (IllegalArgumentException e) { // Joda-TimeはDateTimeParseExceptionではなくIllegalArgumentExceptionをスロー System.err.println("日付のパースに失敗しました: " + e.getMessage()); e.printStackTrace(); } } }

4. ハマりやすい点とエラー対処法

4.1 ParseException / DateTimeParseException が発生する

  • 原因: 指定したフォーマットパターンと、変換したい文字列のフォーマットが一致しない場合。
  • 解決策:
    • 変換したい文字列のフォーマットを正確に確認し、SimpleDateFormatまたはDateTimeFormatterのパターンをそれに合わせます。
    • yyyyMM(月)、mm(分)の区別、HH(24時間)とhh(12時間)の区別、大文字・小文字の区別などに注意します。
    • Apache Commons Lang のDateUtils.parseDate()のように、複数のパターンを試す方法も有効です。

4.2 タイムゾーンに関する問題

java.util.Dateは内部的にはUTC(協定世界時)で時刻を保持しますが、SimpleDateFormatで表示する際はJVMのデフォルトタイムゾーンが使われがちです。これにより、意図しないタイムゾーンでの表示や解釈が発生する可能性があります。 * 解決策: java.timeパッケージのZonedDateTimeOffsetDateTimeを使用し、タイムゾーンを明示的に扱うことを強く推奨します。ZoneIdクラスを利用して、必要に応じたタイムゾーンを指定します。

4.3 曖昧なフォーマットへの対応

例えば「10-01-2023」のような文字列は、「10月1日」なのか「1月10日」なのか判断できません。 * 解決策: * 可能であれば、日付のフォーマットを統一するようにシステム設計を見直します。 * どうしても曖昧なフォーマットを扱う必要がある場合は、Localeを指定したり、DateUtils.parseDate()のように複数のパターンを定義したりして、最も可能性の高いフォーマットを優先的に試すロジックを実装します。 * あるいは、ユーザーにフォーマットを選択させるなどのUI上の工夫も考えられます。

4.4 java.util.Datejava.time の互換性

既存のコードベースにjava.util.Dateが使われている場合、java.timeパッケージへ移行するには、互換性のある変換が必要です。 * java.util.Dateからjava.timeへ: ```java import java.time.ZoneId; import java.util.Date;

Date legacyDate = new Date();
java.time.LocalDateTime dateTime = legacyDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
```
  • java.timeからjava.util.Dateへ: ```java import java.time.ZoneId; import java.util.Date; import java.time.LocalDateTime;

    LocalDateTime localDateTime = LocalDateTime.now(); Date legacyDate = Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant()); ```

まとめ

本記事では、JavaにおいてString型からDate型(およびjava.timeパッケージのモダンな日付・時刻型)への変換を行うための、主要な方法とその注意点について解説しました。

  • Java標準API (SimpleDateFormat): 古典的で広く使われていますが、スレッドセーフではない点に注意が必要です。
  • Java 8以降 (java.timeパッケージ): スレッドセーフで不変、APIも直感的であり、新規開発ではこちらを強く推奨します。DateTimeFormatterを使用します。
  • 外部ライブラリ (Apache Commons Lang): 複数のフォーマットをまとめて扱う場合に便利です。

これらの方法を理解し、状況に応じて適切なものを選ぶことで、安全かつ効率的に日付・時刻の変換処理を実装できるようになります。特に、java.timeパッケージの利用は、コードの可読性、保守性、そして堅牢性を向上させる上で非常に重要です。

今後は、日付の加算・減算、期間の計算、タイムゾーンを跨いだ日付処理など、より発展的な日付・時刻操作についても記事にする予定です。

参考資料