モバイル開発における日付と時刻 — モバイルエンジニアリングの重要なトピックの一つです。iOSはDateFormatterとISO8601DateFormatterを使用し、Androidはjava.timeのDateTimeFormatterとLocalDateを使用します。Android Developer Docsによると、日付と時刻のフォーマットに適切なモバイルAPIを選択することは、異なるタイムゾーンでのパフォーマンスと表示の正確さに直接影響します。
重要なポイント
iOSでは、日付と時刻は伝統的にFoundationのDateFormatterを使用してフォーマットされます。このクラスは、指定されたパターン、ロケール、タイムゾーンを使用してDateを文字列に変換したり戻したりします。DateFormatterの主な問題 — スレッドセーフではなく、作成に時間がかかるため、モバイルコードではインスタンスをキャッシュすることが必須です。iOSのモバイル開発における日付には、適切なフォーマッタが必要です。
DateFormatterの作成には約1ミリ秒かかります。これはdateFormatパターンの解析、ロケールの読み込み、タイムゾーンの決定によるものです。数百の日付のリストをフォーマットする際、繰り返し作成するとUIに顕著なラグが生じます。解決策 — フォーマッタを画面全体で一度だけ作成し再利用します。マルチスレッドアクセスには、DispatchQueueによる同期を使用します。
import Foundation
// キャッシュされたDateFormatter — 一度だけ作成
private let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "dd.MM.yyyy HH:mm"
formatter.locale = Locale(identifier: "ru_RU")
formatter.timeZone = TimeZone(identifier: "Europe/Moscow")
return formatter
}()
// ISO8601DateFormatter — 軽量な代替
private let isoFormatter: ISO8601DateFormatter = {
let formatter = ISO8601DateFormatter()
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
return formatter
}()
let now = Date()
let formattedDate = dateFormatter.string(from: now)
// "01.07.2026 14:30"
let isoDate = isoFormatter.string(from: now)
// "2026-07-01T14:30:00.000Z"
ISO 8601形式には、ISO8601DateFormatterを使用してください — パターンの指定が不要で、DateFormatterより高速に動作します。iOS 10+では、このクラスがサーバーの日付を扱うのに適しています。古い形式にはDateFormatterを引き続き使用しますが、インスタンスを静的プロパティまたはシングルトンとして必ずキャッシュしてください。
Androidでは、日付と時刻はAPI 26以降で利用可能なjava.timeパッケージを使用して処理されます。主要なクラス:LocalDate(日付のみ)、LocalTime(時刻のみ)、LocalDateTime(タイムゾーンなしの日付と時刻)、ZonedDateTime(タイムゾーン付き)。DateTimeFormatter — Androidモバイルプラットフォーム向けのスレッドセーフなフォーマッタです。モバイルアプリケーションの日付は、DateTimeFormatterとLocalDateを使用してパフォーマンスの低下なくフォーマットされます。
LocalDateは時刻なしで日付を保存します — 誕生日に最適です。ZonedDateTimeはタイムゾーン付きの完全な情報を保存します。DateTimeFormatterは定義済みパターン(ISO_LOCAL_DATE、ISO_DATE_TIME)とofPatternによるカスタムパターンをサポートします。すべてのjava.timeクラスは不変かつスレッドセーフであり、複数のスレッドで動作する際の競合状態を排除します。
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.time.ZoneId
// 現在の日付の作成とフォーマット
val today = LocalDate.now()
val formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy")
val formatted = today.format(formatter)
// タイムゾーンの操作
val utcTime = ZonedDateTime.now(ZoneId.of("UTC"))
val moscowTime = utcTime.withZoneSameInstant(ZoneId.of("Europe/Moscow"))
DateTimeFormatterは、呼び出しのたびにパターンを解析しないため、SimpleDateFormatよりも大幅に効率的です。フォーマッタ定数はクラスまたはコンパニオンオブジェクトのレベルで作成することをお勧めします。Unix Timestampを変換するには、Instant.ofEpochSecondを使用し、タイムゾーンを介してLocalDateTimeまたはZonedDateTimeに変換します。
ユーザーの画面に正しい日付と時刻を表示するには、タイムゾーンの適切な処理が不可欠です。Android向けモバイル開発における日付と時刻は、デバイスのタイムゾーンを考慮する必要があります。黄金律:時刻はUTCで保存し、表示時のみローカルタイムゾーンに変換します。iOSはFoundationのTimeZoneを提供し、Androidはjava.timeのZoneIdを提供します。両方のモバイルプラットフォームAPIは、デバイスの現在のタイムゾーンを自動検出します。モバイルアプリケーションの日付はZoneIdとTimeZoneに依存します。
夏時間は曖昧さを生み出します:一つの時点が二つの異なるローカル時刻で表現される可能性があります。java.timeとFoundationはDSTを自動的に処理します。IANA識別子(Europe/Moscow、America/New_York)を使用し、略称(MSK、EST)は使用しないでください — 略称は曖昧で夏時間への移行を無視します。
import Foundation
// UTCからiOSのローカル時刻への変換
func formatToLocalTime(utcDate: Date, timeZoneId: String) -> String {
let formatter = DateFormatter()
formatter.dateFormat = "dd.MM.yyyy HH:mm"
formatter.timeZone = TimeZone(identifier: timeZoneId)
return formatter.string(from: utcDate)
}
// デバイスのタイムゾーンを特定
let localTimeZone = TimeZone.current
let isDST = localTimeZone.isDaylightSavingTime()
let secondsFromGMT = localTimeZone.secondsFromGMT()
Androidでは、ZoneId.systemDefault()を使用してデバイスのタイムゾーンを取得します。UTCをローカル時刻に変換する際は、ZoneOffset.UTCを使用してZonedDateTimeを作成し、withZoneSameInstantを適用します。java.timeがない古いAPIでは、TimeZone.getDefault()とCalendarを使用しますが、直接ThreeTenABPを追加することをお勧めします。
import java.time.Instant
import java.time.ZonedDateTime
import java.time.ZoneId
// Unix Timestampをローカル時刻に変換
fun toLocalDisplay(unixSeconds: Long): String {
val instant = Instant.ofEpochSecond(unixSeconds)
val local = instant.atZone(ZoneId.systemDefault())
return local.format(DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm"))
}
ThreeTenABP — java.timeをAndroid API 26未満に移植するアダプターライブラリです。同じクラスを提供します:LocalDate、LocalTime、ZonedDateTime、DateTimeFormatter、Instant。アプリケーションがAndroid 5または6(API 21-25)をサポートする場合、ThreeTenABPは非推奨のCalendarに移行せずに最新のモバイルAPIを使用する唯一の方法です。古いデバイス向けモバイル開発における日付と時刻は、このライブラリを通じて実装されます。古いデバイスでは、日付と時刻はThreeTenABPを通じて実装されます。
このライブラリは、Application.onCreate()メソッドでAndroidThreeTen.init()を呼び出して初期化する必要があります。これにより、assetsからタイムゾーンデータが読み込まれます。初期化後、APIはjava.timeと完全に同一になります — API 26+とAPI 21+のコードは同じになります。ライブラリのサイズは約400KBで、ほとんどのモバイルアプリケーションにとって許容範囲です。
import org.threeten.bp.LocalDate
import org.threeten.bp.format.DateTimeFormatter
// Applicationでの初期化
class App : Application() {
override fun onCreate() {
super.onCreate()
AndroidThreeTen.init(this)
}
}
// APIはjava.timeと同一
val today = LocalDate.now()
val formatted = today.format(DateTimeFormatter.ISO_LOCAL_DATE)
ThreeTenABPのパフォーマンスは、最新デバイスでは元のjava.timeと同等です。古いデバイスでは、タイムゾーンデータの読み込みにより、最初の呼び出しで遅延が発生する可能性があります。既存のコードをCalendarからThreeTenABPに段階的に移行します:新しいモジュールではDateとCalendarをInstantとLocalDateに置き換え、古いコードは変更せずに残します。
Unix Timestamp — 1970年1月1日00:00 UTCからの経過秒数です。これはサーバーとクライアント間で時刻を保存および交換するための汎用形式です。ただし、デバイスの時刻はユーザー設定により実際の時刻と異なる場合があります。同期にはNTP — UDPを介した正確な時刻プロトコルが使用されます。Unix Timestampはサーバーで日付と時刻が保存される形式です。
iOSのDate().timeIntervalSince1970は1970年からの秒数を返します。AndroidではSystem.currentTimeMillis()がミリ秒を返します。精度を高めるにはNTPを使用します:iOS用のTrueTimeライブラリとAndroid用のAndroidNtpがNTPサーバーにリクエストを送信し、時刻のオフセットを計算します。NTPの精度はローカルネットワークで1〜10ミリ秒、インターネット経由で10〜100ミリ秒です。
import Foundation
// iOSでUnix Timestampを取得
let seconds = Date().timeIntervalSince1970
let milliseconds = Int64(seconds * 1000)
// TrueTimeでNTP同期
TrueTime.shared.start { result in
switch result {
case .success:
let ntpTime = TrueTime.shared.now()
print("正確な時刻:\(ntpTime)")
case .failure(let error):
print("NTPエラー:\(error)")
}
}
モバイルアプリケーションで同期がない場合、誤ったイベントタイムスタンプ、サーバーとの非同期などのエラーが発生する可能性があります。モバイル開発における時刻は、アプリケーション起動時にNTPを介して一度同期され、その後は計算されたオフセットが使用されます。精度が重要なモバイルアプリケーション(金融、物流)では、サーバーにデータを送信する前にNTPプロトコルが必須です。
よくある質問
DateFormatterはインスタンスが作成されるたびにフォーマットパターンを解析し、ロケールを読み込み、タイムゾーンを決定します。フォーマッタを静的定数としてキャッシュするか、ISO 8601にはISO8601DateFormatterを使用してください — より軽量で高速です。
java.timeパッケージのDateTimeFormatter — スレッドセーフで効率的です。API 26から利用可能です。古いバージョンでは、同一のAPIを提供するThreeTenABPを使用してください。
ThreeTenABPを追加します — Android API 21+向けのjava.timeのバックポートです。Application.onCreate()でAndroidThreeTen.init()を呼び出して初期化します。APIは元のjava.timeと完全に同一です。
はい、サーバーとローカルデータベースではUTCで時刻を保存してください。表示段階でのみユーザーのローカルタイムゾーンに変換します。これにより、タイムゾーンの変更や旅行時のエラーが排除されます。
Date().timeIntervalSince1970は1970年1月1日からの秒数を返します。ミリ秒の場合は1000を掛けてInt64に変換します。正確な同期には、NTPサーバーとTrueTimeライブラリを使用してください。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。