ZonedDateTime — isang immutable na klase mula sa package na java.time na nag-iimbak ng petsa at oras kasama ng impormasyon tungkol sa time zone (ZoneId). Hindi tulad ng LocalDateTime, ang ZonedDateTime ay natatanging tumutukoy sa sandali sa timeline. Ayon sa detalye ng Oracle Java 17 (2024), wastong pinoproseso ng klase ang paglipat sa daylight saving time (DST) sa pamamagitan ng mga panuntunan ng zone mula sa IANA Time Zone Database.
Mga Pangunahing
ZonedDateTime — isa sa mga pangunahing klase ng package na java.time, na kumakatawan sa petsa at oras na may kumpletong impormasyon tungkol sa time zone. Pinagsasama nito ang tatlong bahagi: LocalDateTime (petsa at oras), ZoneId (identifier ng zone) at ZoneOffset (offset na may kaugnayan sa UTC).
Hindi tulad ng LocalDateTime, na nag-iimbak lamang ng wall-clock time na walang koneksyon sa zone, ang ZonedDateTime ay natatanging tumutukoy sa sandali. Dalawang magkaparehong LocalDateTime sa iba't ibang time zone ay kumakatawan sa magkaibang sandali ng oras. Dalawang magkaparehong ZonedDateTime — parehong sandali.
Ang klase ay ganap na immutable at thread-safe. Lahat ng aritmetikong operasyon ay nagbabalik ng bagong bagay. Ang ZonedDateTime ay nagpapatupad ng interface na ChronoZonedDateTime at maaaring gamitin saanman kung saan kinakailangan ang pagtatrabaho sa zonal time sa Java.
Ayon sa detalye ng Oracle Java 17, sinusuportahan ng ZonedDateTime ang pagtatrabaho sa anumang zone mula sa IANA Time Zone Database, na sumasaklaw sa mahigit 600 time zone.
Ang pangunahing pagkakaiba — ang ZonedDateTime ay naglalaman ng time zone, habang ang LocalDateTime ay hindi. Ang pangunahing pagkakaibang ito ay tumutukoy sa saklaw ng paggamit ng bawat klase.
Ang LocalDateTime ay ginagamit para sa mga lokal na kaganapan: oras ng konsiyerto, iskedyul ng klase, petsa ng kapanganakan. Kung ang kaganapan ay magaganap sa Moscow sa 15:00, itatala ng LocalDateTime ang 15:00 na walang koneksyon. Kung ililipat mo ang server sa New York, mananatiling 15:00 ang oras — ngunit ito ay ibang pisikal na sandali na.
Ang ZonedDateTime ay ginagamit para sa pandaigdigang data: mga server log, mga timestamp sa API, mga internasyonal na pagpupulong. Kung ang pulong ay nakatakda sa 15:00 MSK, iimbak ng ZonedDateTime ang parehong oras at zone. Sa New York ito ay maipapakita nang tama bilang 8:00 EST. Ayon sa Baeldung (2024), ang pagpili sa pagitan ng LocalDateTime at ZonedDateTime ay ang pinakakaraniwang desisyon sa arkitektura kapag nagtatrabaho sa mga petsa.
Praktikal na panuntunan: kung ang data ay nakaimbak para sa isang rehiyon — gamitin ang LocalDateTime. Kung ang data ay tumatawid sa mga hangganan ng time zone — gamitin ang ZonedDateTime. Kung kailangan mong magpasa ng ganap na sandali — gamitin ang Instant.
Ang time zone sa java.time ay kinakatawan ng klase na ZoneId. Ang ZoneId ay isang identifier ng zone sa format na “continent/region”, halimbawa “Europe/Moscow”, “America/New_York”, “Asia/Tokyo”. Ang ZoneId ay nakukuha sa pamamagitan ng static na pamamaraan na of(String zoneId) o sa pamamagitan ng default na system time zone.
Ang ZoneId ay nahahati sa dalawang uri: fixed offset (fixed offset, halimbawa “+03:00”) at region-based (mga regional zone, halimbawa “Europe/London”). Ang mga regional zone ay naglalaman ng mga panuntunan sa paglipat sa daylight saving time at mga makasaysayang pagbabago. Ang fixed offset — fixed offset lang.
Upang makuha ang kasalukuyang offset ng ZoneId sa isang partikular na sandali, ginagamit ang pamamaraang getRules(), na nagbabalik ng ZoneRules. Ang ZoneRules ay naglalaman ng lahat ng transition at offset para sa zone na iyon. Ito ang pangunahing mekanismo para sa tamang pagproseso ng DST.
Lahat ng time zone ay ibinibigay kasama ng JDK sa pamamagitan ng mga file na tzdata (IANA Time Zone Database) at regular na ina-update. Sa Android, ang bersyon ng tzdata ay nakadepende sa mga update ng system sa pamamagitan ng Google Play Services.
Mayroong ilang mga paraan upang lumikha ng ZonedDateTime. Ang pinakasimple — now(), na nagbabalik ng kasalukuyang oras sa system time zone. Ang variant na now(ZoneId) ay nagbibigay-daan upang makuha ang kasalukuyang oras sa isang tinukoy na zone.
Ang pamamaraang of(LocalDateTime, ZoneId) ay lumilikha ng ZonedDateTime mula sa lokal na oras at zone. Ang variant na of(int year, int month, int dayOfMonth, int hour, int minute, int second, int nanoOfSecond, ZoneId zone) — mula sa mga bahagi.
Ang LocalDateTime ay maaaring i-convert sa ZonedDateTime sa pamamagitan ng pamamaraang atZone(ZoneId). Instant — sa pamamagitan ng Instant.atZone(ZoneId). Date — sa pamamagitan ng Date.toInstant().atZone(ZoneId).
val moscowZone = ZoneId.of("Europe/Moscow")
val nowInMoscow = ZonedDateTime.now(moscowZone)
val fromComponents = ZonedDateTime.of(
2026, 7, 21, 15, 30, 0, 0, moscowZone
)
val fromLocal = LocalDateTime.now().atZone(moscowZone)
val fromInstant = Instant.now().atZone(moscowZone)
Ang pangunahing pamamaraan ng conversion — withZoneSameInstant(ZoneId). Kino-convert ang ZonedDateTime sa ibang time zone, pinapanatili ang parehong sandali ng oras. Halimbawa, 15:00 MSK → 8:00 EST. Ang pamamaraang withZoneSameLocal(ZoneId) ay nagbabago ng zone, pinapanatili ang lokal na oras — ito ay nagbibigay ng ibang sandali.
Upang makuha ang offset na may kaugnayan sa UTC, ginagamit ang pamamaraang getOffset(), na nagbabalik ng ZoneOffset. Ang ZoneOffset ay isang subclass ng ZoneId na kumakatawan sa isang fixed offset sa format na “+HH:mm” o “-HH:mm”.
Ang conversion sa Instant ay ginagawa sa pamamagitan ng pamamaraang toInstant(). Ang Instant ay isang ganap na sandali ng oras, hindi nakadepende sa time zone. Ang baligtad na conversion — Instant.atZone(ZoneId).
val moscow = ZonedDateTime.of(
2026, 7, 21, 15, 0, 0, 0,
ZoneId.of("Europe/Moscow")
)
val newYork = moscow.withZoneSameInstant(
ZoneId.of("America/New_York")
)
val utcInstant = moscow.toInstant()
val backToMoscow = utcInstant.atZone(ZoneId.of("Europe/Moscow"))
Ang paglipat sa daylight saving time ay lumilikha ng dalawang problema: gap at overlap. Ang gap ay nangyayari sa tagsibol kapag ang orasan ay isinulong — ang isang tiyak na oras ay hindi umiiral. Ang overlap — sa taglagas kapag ang oras ay ibinalik — ang parehong oras ay umiiral nang dalawang beses.
Pinoproseso ng ZonedDateTime ang mga sitwasyong ito sa pamamagitan ng estratehiyang resolve. Kapag lumilikha ng bagay sa panahon ng gap, awtomatikong isinusulong ng java.time ang oras ng halaga ng offset. Kapag lumilikha sa panahon ng overlap, ang unang variant (bago ang pagbabago) ang pipiliin. Ang pag-uugali ay maaaring baguhin sa pamamagitan ng withZoneSameInstant.
Maaaring suriin kung ang oras ay nasa DST zone sa pamamagitan ng zone.getRules().isDaylightSavings(instant). Ang pamamaraang getOffset() ay nagpapakita ng kasalukuyang offset para sa sandaling iyon, at getRules().getDaylightSavings(instant) — ang halaga ng DST correction sa millisecond.
fun checkDST(zdt: ZonedDateTime) {
val rules = zdt.getZone().getRules()
val instant = zdt.toInstant()
if (rules.isDaylightSavings(instant)) {
val dstAmount = rules.getDaylightSavings(instant)
Log.d("DST", "DST offset: $dstAmount")
}
}
Para sa pag-format ng ZonedDateTime, ginagamit ang DateTimeFormatter. Ang karaniwang ISO format ay may kasamang petsa, oras at offset: “2026-07-21T15:30:00+03:00[Europe/Moscow]”. Mga paunang natukoy na format: ISO_ZONED_DATE_TIME, ISO_OFFSET_DATE_TIME, ISO_INSTANT.
Gumamit ng DateTimeFormatter.ofLocalizedDateTime(FormatStyle) para sa localized na pag-format. Ang FormatStyle ay maaaring SHORT, MEDIUM, LONG, FULL. Ang LONG ay may kasamang pangalan ng zone (“MSK”), FULL — buong pangalan (“Moscow Standard Time”).
Mahalaga: kapag nagpa-parse ng string na may ZonedDateTime, ang format ay dapat maglaman ng impormasyon tungkol sa zone o offset. Kung hindi tinukoy ang zone, gamitin ang LocalDateTime.parse() at pagkatapos ay atZone().
val zdt = ZonedDateTime.now(ZoneId.of("Europe/Moscow"))
val iso = zdt.format(DateTimeFormatter.ISO_ZONED_DATE_TIME)
val custom = DateTimeFormatter
.ofPattern("dd.MM.yyyy HH:mm z")
val formatted = zdt.format(custom)
val parsed = ZonedDateTime.parse(
"2026-07-21T15:30:00+03:00",
DateTimeFormatter.ISO_OFFSET_DATE_TIME
)
Unang halimbawa — pagpapakita ng oras ng pulong para sa user sa kanyang sariling time zone. Ibinabalik ng server ang ZonedDateTime sa UTC, kino-convert ng client sa lokal na time zone ng device.
fun displayMeetingTime(
serverUtc: ZonedDateTime
): String {
val deviceZone = ZoneId.systemDefault()
val localTime = serverUtc.withZoneSameInstant(deviceZone)
val formatter = DateTimeFormatter
.ofPattern("dd.MM.yyyy HH:mm z")
return localTime.format(formatter)
}
Pangalawang halimbawa — pagkalkula ng oras hanggang sa susunod na kaganapan na isinasaalang-alang ang time zone. Ginagamit namin ang ZonedDateTime para sa oras ng server at Duration.between() para sa pagkalkula ng pagkakaiba.
fun timeUntilEvent(eventTime: ZonedDateTime): String {
val now = ZonedDateTime.now()
val duration = Duration.between(now, eventTime)
val hours = duration.toHours()
val minutes = duration.toMinutes() % 60
return "Remaining $hours h $minutes min"
}
Pangatlong halimbawa — pagtatrabaho sa Retrofit API. Ibinabalik ng server ang isang string sa ISO-8601 na may zone. Gumagamit kami ng custom na deserializer para sa conversion sa ZonedDateTime.
data class EventResponse(
@JsonAdapter(ZonedDateTimeAdapter::class)
val eventTime: ZonedDateTime
)
class ZonedDateTimeAdapter : JsonAdapter<ZonedDateTime>() {
override fun fromJson(reader: JsonReader): ZonedDateTime? {
return ZonedDateTime.parse(
reader.nextString()
)
}
}
Unang error — paggamit ng ZoneId.systemDefault() sa server code. Ang time zone ng server ay maaaring naiiba sa client, at ang paggamit ng system zone sa server ay humahantong sa maling kalkulasyon. Palaging tukuyin nang tahasan ang zone o gamitin ang UTC bilang sanggunian.
Pangalawang error — pagbalewala sa DST kapag kinakalkula ang tagal. Ang Duration.between() ay wastong pinoproseso ang mga transition, ngunit kung manu-mano mong ibawas ang mga timestamp, ang paglipat sa daylight saving time ay maaaring magbigay ng 1 oras na error. Gamitin ang mga pamamaraan ng ChronoUnit.HOURS.between() sa halip na manu-manong matematika.
Pangatlong error — pagkalito sa pagitan ng withZoneSameInstant at withZoneSameLocal. Ang una ay nagbabago ng zone, pinapanatili ang sandali — ang oras ay nagbabago. Ang pangalawa ay nagbabago ng zone, pinapanatili ang lokal na oras — ang sandali ay nagbabago. Ang pagpili ng maling pamamaraan ay isa sa mga pinakakaraniwang error ayon sa SonarSource (2024).
Pang-apat na error — pag-aakalang ang time zone ng device ay laging pareho sa time zone ng user. Ang user ay maaaring maglakbay at asahan na ang app ay magpapakita ng oras sa kanyang “tahanan” na zone, hindi sa kasalukuyang zone. Sa kasong ito, kailangan mong magbigay ng pagpili ng zone sa pamamagitan ng interface.
Mga Madalas Itanong
Ang ZonedDateTime ay naglalaman ng regional zone identifier (halimbawa “Europe/Moscow”) at nagpoproseso ng DST. Ang OffsetDateTime ay nag-iimbak lamang ng fixed offset (+03:00) nang walang regional rules. Para sa pag-iimbak sa database, inirerekomenda ang OffsetDateTime.
Gamitin ang ZonedDateTime.now(ZoneOffset.UTC) o Instant.now().atZone(ZoneOffset.UTC). Ang parehong variant ay nagbabalik ng kasalukuyang sandali na may zero offset. Para sa simpleng timestamp, gamitin ang Instant.now() nang walang koneksyon sa zone.
Oo, ngunit kinakailangan ang custom na adapter. Hindi sinusuportahan ng Gson ang ZonedDateTime bilang default. Sinusuportahan ito ng Moshi — sa pamamagitan ng Rfc3339DateJsonAdapter adapter. Para sa Jackson, inirerekomenda ang paggamit ng Kotlinx Serialization o library na JavaTimeModule.
Awtomatikong isinusulong ng java.time ang oras ng halaga ng offset. Halimbawa, kung ang oras na 02:30 ay hindi umiiral sa transition sa 03:00, ang ZonedDateTime ay lilikha ng bagay sa 03:30. Ang pagkakaroon ng gap ay maaaring suriin sa pamamagitan ng ZoneRules.getTransition(instant).
Sinusuportahan ng JDBC 4.2 ang OffsetDateTime, ngunit hindi direktang ZonedDateTime. Ang ZonedDateTime ay naglalaman ng regional zone na walang katumbas sa SQL. Inirerekomenda na mag-imbak ng OffsetDateTime o Instant, at ang zone ay iimbak sa isang hiwalay na column.
Buod
Gagawa kami ng mobile application na turnkey
Gumagawa ang IT Sectr ng mga iOS at Android application para sa mga startup at negosyo mula noong 2017. Magpapayo kami sa iyo at magmumungkahi ng pinakamahusay na solusyon.
Basahin din