携帯端末は使用する場所(国・地域)と時刻を選びません。
ゆえに、何時何処で携帯端末を使用したとしても、アプリの画面に表示される日付と時刻の間違いは許されません。
これらの課題はプログラムで対応します。
プログラムで日付・時刻を正しく扱うには、「世界標準時(GMTとUTC)」や「タイムゾーン」といった概念や、専用に用意されたクラスの理解が必要です。
ここでは、プログラム中で現在の日付と時刻を取得する方法をまとめます。
※環境:Android Studio Jellyfish | 2023.3.1
Kotlin 1.9.0
Compose Compiler 1.5.1
AGP 8.4.0
目次
世界標準時(GMTとUTC)
世界標準時というと、GMT(グリニッジ標準時:Greenwich Mean Time)とUTC(協定世界時:Coordinated Universal Time)の2つが挙げられます。
どちらも、時刻(時分秒)の表現を定義したものです。両者の違いは表の通りです。
基準 | 1秒の計測 | 正確性 | |
---|---|---|---|
GMT (UT0) | 英国のグリニッジ天文台(経緯0度)で平均太陽が南中する時を正午 ※グリニッジ子午線を太陽が通過する時 正午(昼の12時)から計測 | 地球の自転周期 (天文観測) | 一定でない (自転が一定でない) |
GMT (UT1) | 地球の自転周期 +軸のズレを考慮 (天文観測) |
||
GMT (UT2) | 地球の自転周期 +軸のズレを考慮 +季節変化を考慮 (天文観測) |
||
UTC | GMT(UT1)との差が±0.9秒以内 ※「うるう秒」の加減算で補正 午前0時から計測 | セシウム原子の振動数 (原子時計) | 一定である (超高精度) |
※GMT:グリニッジ標準時(Greenwich Mean Time) ※UTC:協定世界時(Coordinated Universal Time) ※UT0,1,2:世界時の型 |
最初の標準時はGMTです。
GMTは「地球の自転周期(1日≒24時間)」から1秒の長さを計測しています。自転周期は天文観測により求めているため、天体の事象の影響を受けます。従って、年間を通して一定では無く、正確性に劣るという問題がありました。そこで、UT0,1,2(世界時の型)と改良が進められました。
GMTに代わり、現在の主流はUTCです。
UTCは「セシウム原子の振動数(9192631770[Hz])」から1秒の長さを計測しています。非常に正確です。正確であるがゆえに、GMTと差が発生してしまいます。そこで、うるう秒の加減算を行って、GMTと同じ時刻を示すように補正されます。
GMTとUTCは同じ時刻を示すことから、国際協定により「GMTとUTCは等しい」とされています。厳密には1秒未満の差があり、違うのですが…
タイムゾーン(TimeZone)
朝に太陽は東から昇ります。場所を東に移動すると、東へ行くほど太陽は早い時刻に昇ります。朝をAM 6:00と定義した時、AM 6:00は「東に行くほど時間が進み、西に行くほど時間が遅れる」ことになります。
つまり、AM 6:00という時刻は場所によって異なります。
この事象をうまく表現するために、TimeZoneが設けられています。
1日は24時間です。地球を経度15°づつ24の区域(360°/24時間=15°)に分割して、各区域の時刻を「世界標準時の基準からの時差」という形で与えます。
例えば、UTC+0(またはGMT+0)はグリニッジ天文台(ロンドン)を含む区域の時刻です。また、日本はUTC+9となり、UTC+0から9時間進んだ時刻になります。
また、タイムゾーンを含めて日付と時刻を表現するときは、次のような書き方をします。
1900年1月1日0時0分0秒(UTC) ... 時差 0の場所(ロンドン)の時刻 1900年1月1日0時0分0秒(UTC-5) ... 時差-5の場所(ニューヨーク)の時刻 1900年1月1日0時0分0秒(UTC+9) ... 時差+9の場所(東京)の時刻 1900年1月1日0時0分0秒(UTC+09:00) ... 同上 1900年1月1日0時0分0秒(JST) ... 同上(JST:Japan Standard Time)
実際のタイムゾーンの境界は直線ではありません。国内が同じ時刻になるように折れ曲がっています。その方が都合の良いためです。
「時(とき)」を刻むカウンター
Androidシステム(UNIX系のOS)は、内部に「時(とき)」を刻むカウンターを持ちます。
このカウンターは、1970年1月1日0時0分0秒(UTC)を起点とした経過秒数をカウントしていて、カウント値から時刻が算出できます。実際のところ、1970年にAndroidシステムは存在していないので、カウントの起点は携帯端末へ時刻設定を行った時点(カウント値N)です。
カウンターのカウントはAndroidシステムが行いますが、原子時計のような正確性はありません。ですので、NTPサーバーと時刻同期を行わせるのが普通です。
また、カウンターの精度はシステムに依存します。現在は、64bitの整数(long)のカウンターで、ナノ秒単位のカウントが大多数です。
このように、コンピュータシステムで時刻を表現する方法を「POSIX Time」と呼びます。
現在時刻の取得 ~System.currentTimeMillis~
currentTimeMillis( )はPOSIX TimeのTimeカウンター値を直に読み出します。
読みだされる値は「1970年1月1日0時0分0秒(UTC)を起点とした経過秒数のカウント値」です。精度はミリ秒になります。
val _currentTime : Long = System.currentTimeMillis() System.out.println("カレントタイム : ${_currentTime} [ms]")
I/System.out: カレントタイム : 1679985063704 [ms]
似た関数(メソッド)にuptimeMillis( )があります。こちらは、「Androidシステムが稼働してからの経過秒数のカウント値」です。精度はミリ秒になります。
val _uptime : Long = SystemClock.uptimeMillis() System.out.println("アップタイム : ${_uptime} [ms]")
I/System.out: アップタイム : 546689 [ms]
現在時刻の取得 ~java.util~
java.utilパッケージはJavaの創成時から存在しているライブラリです。日付(年月日)と時刻(時分秒)を表すクラスを含みます。
Date
Dateクラスは日付(年月日)と時刻(時分秒)を表すクラスです。
Date( )でインスタンスを作成すると、POSIX TimeのTimeカウンター値とデフォルトのタイムゾーンを保持したインスタンスが作られます。
Dateは不変(Immutable object)クラスなので、インスタンス生成時に保持した値を変更できません。
toString( )やgetHours( )の出力は、保持したTimeカウンター値を保持したタイムゾーンに合わせて変換した値です。
System.out.println("カレントタイム = ${System.currentTimeMillis()}") System.out.println("--------\n") val _date = Date() System.out.println("カレントタイム = ${_date.time}"); System.out.println("Date = ${_date}"); System.out.println("日時 = %4d/%02d/%02d %02d:%02d:%02d".format( _date.year + 1900, // 非推奨 _date.month + 1, // 非推奨 _date.date, // 非推奨 _date.hours, // 非推奨 _date.minutes, // 非推奨 _date.seconds) // 非推奨 ) System.out.println("--------\n")
I/System.out: カレントタイム = 1680144306231 I/System.out: -------- I/System.out: カレントタイム = 1680144306231 I/System.out: Date = Thu Mar 30 11:45:06 GMT+09:00 2023 I/System.out: 日時 = 2023/03/30 11:45:06 I/System.out: --------
別のタイムゾーンに合わせた日付と時刻が欲しければ、SimpleDateFormatなどを使って変換します。
System.out.println("カレントタイム = ${System.currentTimeMillis()}") System.out.println("--------\n") val _date = Date() val _fmt = SimpleDateFormat() _fmt.timeZone = TimeZone.getTimeZone("Asia/Tokyo") // 東京 System.out.println("Date = ${_fmt.format(_date)}") _fmt.timeZone = TimeZone.getTimeZone("EST") // ニューヨーク System.out.println("Date = ${_fmt.format(_date)}") _fmt.timeZone = TimeZone.getTimeZone("Greenwich") // ロンドン System.out.println("Date = ${_fmt.format(_date)}") System.out.println("--------\n")
I/System.out: カレントタイム = 1680144306231 I/System.out: -------- I/System.out: Date = 2023/03/30 11:45 ... 東京 I/System.out: Date = 2023/03/29 21:45 ... ニューヨーク I/System.out: Date = 2023/03/30 2:45 ... ロンドン I/System.out: --------
現在、Dateクラスは、メソッドの一部が非推奨です。今後、Java8で追加されたjava.timeパッケージに含まれるLocalDateクラス、LocalTimeクラスなどの利用が推奨されます。
※図はApi 26の場合
Calendar
Calendarクラスは日付(年月日)と時刻(時分秒)を表すクラスです。
Calendar.getInstance()でインスタンスを作成すると、POSIX TimeのTimeカウンター値とデフォルトのタイムゾーンを保持し、タイムゾーンの現在の日付・時刻を初期値にしたインスタンスが作られます。
System.out.println("カレントタイム = ${System.currentTimeMillis()}") System.out.println("--------\n") val cal = Calendar.getInstance() System.out.println("カレントタイム : ${cal.timeInMillis}") System.out.println("タイムゾーンID : ${cal.timeZone.getID()}") System.out.println("タイムゾーンName : ${cal.timeZone.getDisplayName()}") System.out.println("日時 : %4d/%02d/%02d %02d:%02d:%02d".format( cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DAY_OF_MONTH), cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE), cal.get(Calendar.SECOND) )) System.out.println("--------\n")
I/System.out: カレントタイム = 1680151502714 I/System.out: -------- I/System.out: カレントタイム : 1680151502714 I/System.out: タイムゾーンID : Asia/Tokyo I/System.out: タイムゾーンName : 日本標準時 I/System.out: 日時 : 2023/03/30 13:45:02 I/System.out: --------
また、作成したインスタンスに対してタイムゾーンを変更すれば、その区域の現在の日付・時刻が取得できます。
System.out.println("カレントタイム = ${System.currentTimeMillis()}") System.out.println("--------\n") val cal = Calendar.getInstance() val tzn1 = TimeZone.getTimeZone("Asia/Tokyo") // 東京 cal.timeZone = tzn1 System.out.println("カレントタイム : ${cal.timeInMillis}") System.out.println("タイムゾーンID : ${tzn1.getID()}") System.out.println("タイムゾーンName : ${tzn1.getDisplayName()}") System.out.println("日時 : %4d/%02d/%02d %02d:%02d:%02d".format( cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DAY_OF_MONTH), cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE), cal.get(Calendar.SECOND) )) System.out.println("--------\n") val tzn2 = TimeZone.getTimeZone("EST") // ニューヨーク cal.setTimeZone(tzn2) System.out.println("カレントタイム : ${cal.timeInMillis}") System.out.println("タイムゾーンID : ${tzn2.getID()}") System.out.println("タイムゾーンName : ${tzn2.getDisplayName()}") System.out.println("日時 : %4d/%02d/%02d %02d:%02d:%02d".format( cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DAY_OF_MONTH), cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE), cal.get(Calendar.SECOND) )) System.out.println("--------\n") val tzn3 = TimeZone.getTimeZone("Greenwich") // ロンドン cal.timeZone = tzn3 System.out.println("カレントタイム : ${cal.timeInMillis}") System.out.println("タイムゾーンID : ${tzn3.getID()}") System.out.println("タイムゾーンName : ${tzn3.getDisplayName()}") System.out.println("日時 : %4d/%02d/%02d %02d:%02d:%02d".format( cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DAY_OF_MONTH), cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE), cal.get(Calendar.SECOND) )) System.out.println("--------\n")
I/System.out: カレントタイム = 1680151502714 I/System.out: -------- I/System.out: カレントタイム : 1680151502714 ... 東京 I/System.out: タイムゾーンID : Asia/Tokyo I/System.out: タイムゾーンName : 日本標準時 I/System.out: 日時 : 2023/03/30 13:45:02 I/System.out: -------- I/System.out: カレントタイム : 1680151502714 ... ニューヨーク I/System.out: タイムゾーンID : EST I/System.out: タイムゾーンName : GMT-05:00 I/System.out: 日時 : 2023/03/29 23:45:02 I/System.out: -------- I/System.out: カレントタイム : 1680151502714 ... ロンドン I/System.out: タイムゾーンID : Greenwich I/System.out: タイムゾーンName : GMT+00:00 I/System.out: 日時 : 2023/03/30 04:45:02 I/System.out: --------
現在時刻の取得 ~java.time~
java.timeパッケージはJava8での追加され、Androidにおいても使用可能になりました。
以前はAPI≧26より使用可能でしたが、AGP(Android Gradle Plugin)の機能により、古いAPIでもサポートされました。サポートの条件は次の2つです。
- AGP Ver 4.0.0以上の使用
- build.gradleへモジュールの読み込みを追加(下記の詳細を参照)
※現在(2024.05)、「desugar_jdk_libs:2.0.4」が最新
表に示すような、日付(年月日)と時刻(時分秒)を表すクラスを含みます。
クラス | 保持する値 | 付属する情報 | 不変(Immutable) |
---|---|---|---|
LocalDate | タイムゾーンの日付 | ||
LocalTime | タイムゾーンの時刻 | ||
LocalDateTime | タイムゾーンの日付・時刻 | ||
ZonedDateTime | タイムゾーンの日付・時刻 | タイムゾーン | |
※JAVA8で追加、AndroidにおいてはAPI≧26より使用可能 |
表のクラスは不変(Immutable object)クラスなので、インスタンス生成時に保持した値を変更できません。
ですので、保持する値の異なるインスタンスが必要な場合は、再生成を行わなければなりません。
LocaleDate
LocaleDateクラスは日付(年月日)を表すクラスです。
LocaleDate.now( )でインスタンスを作成すると、デフォルトのタイムゾーンの現在の日付を保持したインスタンスが作られます。
System.out.println("カレントタイム = ${System.currentTimeMillis()}") System.out.println("--------\n") if(Build.VERSION.SDK_INT >= 26) { val _localDate = LocalDate.now() System.out.println("LocalDate = ${_localDate}"); } System.out.println("--------\n")
I/System.out: カレントタイム = 1680139005357 I/System.out: -------- I/System.out: LocalDate = 2023-03-30 I/System.out: --------
LocalDate.now( )の引数にタイムゾーンを指定してインスタンスを作成すると、指定したタイムゾーンの現在の日付を保持したインスタンスが作られます。
System.out.println("カレントタイム = ${System.currentTimeMillis()}") System.out.println("--------\n") if(Build.VERSION.SDK_INT >= 26) { val _localDate1 = LocalDate.now(ZoneId.of("Asia/Tokyo")) // 東京 System.out.println("LocalDate1 = ${_localDate1}"); val _localDate2 = LocalDate.now(ZoneId.of("EST")) // ニューヨーク System.out.println("LocalDate2 = ${_localDate2}"); val _localDate3 = LocalDate.now(ZoneId.of("Greenwich")) // ロンドン System.out.println("LocalDate3 = ${_localDate3}"); } System.out.println("--------\n")
I/System.out: カレントタイム = 1680139005357 I/System.out: -------- I/System.out: LocalDate1 = 2023-03-30 ... 東京 I/System.out: LocalDate2 = 2023-03-29 ... ニューヨーク I/System.out: LocalDate3 = 2023-03-30 ... ロンドン I/System.out: --------
LocalTime
LocalTimeクラスは時刻(時分秒)を表すクラスです。
LocaleTime.now( )でインスタンスを作成すると、デフォルトのタイムゾーンの現在の時刻を保持したインスタンスが作られます。
System.out.println("カレントタイム = ${System.currentTimeMillis()}") System.out.println("--------\n") if(Build.VERSION.SDK_INT >= 26) { val _localTime = LocalTime.now() System.out.println("LocalTime = ${_localTime}"); } System.out.println("--------\n")
I/System.out: カレントタイム = 1680139005357 I/System.out: -------- I/System.out: LocalTime = 10:16:45.358 I/System.out: --------
LocalTime.now( )の引数にタイムゾーンを指定してインスタンスを作成すると、指定したタイムゾーンの現在の時刻を保持したインスタンスが作られます。
System.out.println("カレントタイム = ${System.currentTimeMillis()}") System.out.println("--------\n") if(Build.VERSION.SDK_INT >= 26) { val _localTime1 = LocalTime.now(ZoneId.of("Asia/Tokyo")) // 東京 System.out.println("LocalTime1 = ${_localTime1}"); val _localTime2 = LocalTime.now(ZoneId.of("EST")) // ニューヨーク System.out.println("LocalTime2 = ${_localTime2}"); val _localTime3 = LocalTime.now(ZoneId.of("Greenwich")) // ロンドン System.out.println("LocalTime3 = ${_localTime3}"); } System.out.println("--------\n")
I/System.out: カレントタイム = 1680139005357 I/System.out: -------- I/System.out: LocalTime1 = 10:16:45.359 ... 東京 I/System.out: LocalTime2 = 20:16:45.359 ... ニューヨーク I/System.out: LocalTime3 = 01:16:45.359 ... ロンドン I/System.out: --------
LocalDateTime
LocalDateTimeクラスは日付(年月日)と時刻(時分秒)を表すクラスです。
LocalDateTime.now( )でインスタンスを作成すると、デフォルトのタイムゾーンの現在の日付と時刻を保持したインスタンスが作られます。
System.out.println("カレントタイム = ${System.currentTimeMillis()}") System.out.println("--------\n") if(Build.VERSION.SDK_INT >= 26) { val _localDateTime = LocalDateTime.now() System.out.println("LocalDateTime = ${_localDateTime}"); } System.out.println("--------\n")
I/System.out: カレントタイム = 1680141436709 I/System.out: -------- I/System.out: LocalDateTime = 2023-03-30T10:57:16.710 I/System.out: --------
LocalDateTime.now( )の引数にタイムゾーンを指定してインスタンスを作成すると、指定したタイムゾーンの現在の日付と時刻を保持したインスタンスが作られます。
System.out.println("カレントタイム = ${System.currentTimeMillis()}") System.out.println("--------\n") if(Build.VERSION.SDK_INT >= 26) { val _localDateTime1 = LocalDateTime.now(ZoneId.of("Asia/Tokyo")) // 東京 System.out.println("LocalDateTime1 = ${_localDateTime1}"); val _localDateTime2 = LocalDateTime.now(ZoneId.of("EST")) // ニューヨーク System.out.println("LocalDateTime2 = ${_localDateTime2}"); val _localDateTime3 = LocalDateTime.now(ZoneId.of("Greenwich")) // ロンドン System.out.println("LocalDateTime3 = ${_localDateTime3}"); } System.out.println("--------\n")
I/System.out: カレントタイム = 1680141436709 I/System.out: -------- I/System.out: LocalDateTime1 = 2023-03-30T10:57:16.710 ... 東京 I/System.out: LocalDateTime2 = 2023-03-29T20:57:16.710 ... ニューヨーク I/System.out: LocalDateTime3 = 2023-03-30T01:57:16.710 ... ロンドン I/System.out: --------
ZonedDateTime
ZonedDateTimeクラスは日付(年月日)と時刻(時分秒)を表すクラスです。
LocalDateTimeクラスとの違いは、日付と時刻の保持とは別に、タイムゾーンの情報を持つことができる点です。
System.out.println("カレントタイム = ${System.currentTimeMillis()}") System.out.println("--------\n") if(Build.VERSION.SDK_INT >= 26) { val _zonedDateTime = ZonedDateTime.now() System.out.println("ZonedDateTime = ${_zonedDateTime}"); } System.out.println("--------\n")
I/System.out: カレントタイム = 1680141436709 I/System.out: -------- I/System.out: ZonedDateTime = 2023-03-30T10:57:16.711+09:00[Asia/Tokyo] I/System.out: --------
ZonedDateTime.now( )の引数にタイムゾーンを指定してインスタンスを作成すると、指定したタイムゾーンの現在の日付と時刻を保持したインスタンスが作られます。
System.out.println("カレントタイム = ${System.currentTimeMillis()}") System.out.println("--------\n") if(Build.VERSION.SDK_INT >= 26) { val _zonedDateTime1 = ZonedDateTime.now(ZoneId.of("Asia/Tokyo")) // 東京 System.out.println("ZonedDateTime1 = ${_zonedDateTime1}"); val _zonedDateTime2 = ZonedDateTime.now(ZoneId.of("EST")) // ニューヨーク System.out.println("ZonedDateTime2 = ${_zonedDateTime2}"); val _zonedDateTime3 = ZonedDateTime.now(ZoneId.of("Greenwich")) // ロンドン System.out.println("ZonedDateTime3 = ${_zonedDateTime3}"); } System.out.println("--------\n")
I/System.out: カレントタイム = 1680141436709 I/System.out: -------- I/System.out: ZonedDateTime1 = 2023-03-30T10:57:16.711+09:00[Asia/Tokyo] ... 東京 I/System.out: ZonedDateTime2 = 2023-03-29T20:57:16.711-05:00[EST] ... ニューヨーク I/System.out: ZonedDateTime3 = 2023-03-30T01:57:16.711Z[Greenwich] ... ロンドン I/System.out: --------
関連記事: