ZonedDateTime java.time প্যাকেজের একটি অপরিবর্তনীয় (immutable) ক্লাস যা টাইম জোন তথ্য (ZoneId) সহ তারিখ এবং সময় সংরক্ষণ করে। LocalDateTime-এর বিপরীতে, ZonedDateTime টাইমলাইনে একটি মুহূর্তকে দ্ব্যর্থহীনভাবে শনাক্ত করে। Oracle Java 17 (2024) স্পেসিফিকেশন অনুসারে, ক্লাসটি IANA Time Zone Database-এর জোন নিয়মের মাধ্যমে ডেলাইট সেভিং টাইম (DST) ট্রানজিশন সঠিকভাবে পরিচালনা করে।
মূল বিষয়
ZonedDateTime java.time প্যাকেজের মূল ক্লাসগুলির মধ্যে একটি, যা সম্পূর্ণ টাইম জোন তথ্য সহ তারিখ এবং সময় উপস্থাপন করে। এটি তিনটি উপাদান একত্রিত করে: LocalDateTime (তারিখ এবং সময়), ZoneId (জোন শনাক্তকারী), এবং ZoneOffset (UTC-এর সাপেক্ষে অফসেট)।
LocalDateTime-এর বিপরীতে, যা টাইম জোন বাঁধাই ছাড়া শুধুমাত্র ওয়াল-ক্লক টাইম সংরক্ষণ করে, ZonedDateTime একটি মুহূর্তকে দ্ব্যর্থহীনভাবে শনাক্ত করে। বিভিন্ন টাইম জোনে দুটি অভিন্ন LocalDateTime ইনস্ট্যান্স সময়ের ভিন্ন ভিন্ন মুহূর্ত উপস্থাপন করে। দুটি অভিন্ন ZonedDateTime ইনস্ট্যান্স — একই মুহূর্ত।
ক্লাসটি সম্পূর্ণরূপে অপরিবর্তনীয় এবং থ্রেড-সেফ। সমস্ত গাণিতিক অপারেশন একটি নতুন অবজেক্ট ফেরত দেয়। ZonedDateTime ChronoZonedDateTime ইন্টারফেস বাস্তবায়ন করে এবং Java-তে জোনাল সময় পরিচালনার প্রয়োজন যেকোনো জায়গায় ব্যবহার করা যেতে পারে।
Oracle Java 17 স্পেসিফিকেশন অনুসারে, ZonedDateTime IANA Time Zone Database-এর যেকোনো জোনের সাথে কাজ করার সমর্থন করে, যাতে 600টির বেশি টাইম জোন রয়েছে।
প্রধান পার্থক্য — ZonedDateTime-এ টাইম জোন রয়েছে, যেখানে LocalDateTime-এ নেই। এই মৌলিক পার্থক্য প্রতিটি ক্লাসের ব্যবহারের পরিধি নির্ধারণ করে।
LocalDateTime স্থানীয় ইভেন্টের জন্য ব্যবহৃত হয়: কনসার্টের সময়, ক্লাসের সময়সূচী, জন্ম তারিখ। যদি মস্কোতে 15:00 টায় একটি ইভেন্ট ঘটে, তাহলে LocalDateTime কোনো বাঁধাই ছাড়াই 15:00 রেকর্ড করবে। আপনি যদি সার্ভারটি নিউইয়র্কে সরিয়ে নেন, সময় 15:00-ই থাকবে — তবে এটি একটি ভিন্ন ভৌত মুহূর্ত হবে।
ZonedDateTime গ্লোবাল ডেটার জন্য ব্যবহৃত হয়: সার্ভার লগ, API-তে টাইমস্ট্যাম্প, আন্তর্জাতিক মিটিং। যদি একটি মিটিং 15:00 MSK-এ নির্ধারিত হয়, তাহলে ZonedDateTime সময় এবং জোন উভয়ই সংরক্ষণ করবে। নিউইয়র্কে, এটি 8:00 EST হিসাবে সঠিকভাবে প্রদর্শিত হবে। Baeldung (2024) অনুসারে, LocalDateTime এবং ZonedDateTime-এর মধ্যে নির্বাচন তারিখ নিয়ে কাজ করার সময় সবচেয়ে সাধারণ স্থাপত্যগত সিদ্ধান্ত।
ব্যবহারিক নিয়ম: যদি ডেটা একটি অঞ্চলের জন্য সংরক্ষিত হয় — LocalDateTime ব্যবহার করুন। যদি ডেটা টাইম জোন সীমানা অতিক্রম করে — ZonedDateTime ব্যবহার করুন। যদি আপনার একটি পরম মুহূর্ত উপস্থাপন করার প্রয়োজন হয় — Instant ব্যবহার করুন।
java.time-এ টাইম জোন ZoneId ক্লাস দ্বারা উপস্থাপিত হয়। ZoneId হল “মহাদেশ/অঞ্চল” ফর্ম্যাটে একটি জোন শনাক্তকারী, উদাহরণস্বরূপ “Europe/Moscow”, “America/New_York”, “Asia/Tokyo”। ZoneId স্ট্যাটিক পদ্ধতি of(String zoneId) অথবা সিস্টেম ডিফল্ট টাইম জোনের মাধ্যমে প্রাপ্ত করা হয়।
ZoneId দুই প্রকারে বিভক্ত: fixed offset (নির্দিষ্ট অফসেট, যেমন “+03:00”) এবং region-based (আঞ্চলিক জোন, যেমন “Europe/London”)। আঞ্চলিক জোনে ডেলাইট সেভিং টাইম ট্রানজিশন নিয়ম এবং ঐতিহাসিক পরিবর্তন রয়েছে। Fixed offset শুধুমাত্র একটি নির্দিষ্ট অফসেট।
একটি নির্দিষ্ট মুহূর্তে ZoneId-এর বর্তমান অফসেট পেতে, getRules() পদ্ধতি ব্যবহার করা হয়, যা ZoneRules ফেরত দেয়। ZoneRules-এ একটি নির্দিষ্ট জোনের জন্য সমস্ত ট্রানজিশন এবং অফসেট থাকে। এটি সঠিক DST পরিচালনার জন্য মূল প্রক্রিয়া।
সমস্ত টাইম জোন tzdata ফাইল (IANA Time Zone Database) এর মাধ্যমে JDK-এর সাথে সরবরাহ করা হয় এবং নিয়মিত আপডেট করা হয়। Android-এ, tzdata সংস্করণ Google Play Services-এর মাধ্যমে সিস্টেম আপডেটের উপর নির্ভর করে।
ZonedDateTime বিভিন্ন উপায়ে তৈরি করা যেতে পারে। সবচেয়ে সহজ হল now(), যা সিস্টেম টাইম জোনে বর্তমান সময় ফেরত দেয়। now(ZoneId) ভেরিয়েন্ট আপনাকে নির্দিষ্ট জোনে বর্তমান সময় পেতে দেয়।
of(LocalDateTime, ZoneId) পদ্ধতি স্থানীয় সময় এবং জোন থেকে ZonedDateTime তৈরি করে। of(int year, int month, int dayOfMonth, int hour, int minute, int second, int nanoOfSecond, ZoneId zone) ভেরিয়েন্ট উপাদানগুলি থেকে তৈরি করে।
LocalDateTime atZone(ZoneId) পদ্ধতির মাধ্যমে ZonedDateTime-এ রূপান্তরিত করা যেতে পারে। Instant — Instant.atZone(ZoneId)-এর মাধ্যমে। Date — 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)
প্রধান রূপান্তর পদ্ধতি হল withZoneSameInstant(ZoneId)। এটি একই মুহূর্ত বজায় রেখে ZonedDateTime-কে অন্য টাইম জোনে রূপান্তর করে। উদাহরণস্বরূপ, 15:00 MSK → 8:00 EST। withZoneSameLocal(ZoneId) পদ্ধতি স্থানীয় সময় বজায় রেখে জোন পরিবর্তন করে — এটি একটি ভিন্ন মুহূর্ত দেয়।
UTC-এর সাপেক্ষে অফসেট পেতে, getOffset() পদ্ধতি ব্যবহার করা হয়, যা ZoneOffset ফেরত দেয়। ZoneOffset হল ZoneId-এর একটি উপশ্রেণী যা “+HH:mm” বা “-HH:mm” ফর্ম্যাটে একটি নির্দিষ্ট অফসেট উপস্থাপন করে।
Instant-এ রূপান্তর toInstant() পদ্ধতির মাধ্যমে করা হয়। Instant হল সময়ের একটি পরম মুহূর্ত, টাইম জোন থেকে স্বাধীন। বিপরীত রূপান্তর হল 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"))
ডেলাইট সেভিং টাইম ট্রানজিশন দুটি সমস্যা তৈরি করে: ফাঁক (gaps) এবং ওভারল্যাপ (overlaps)। বসন্তে যখন ঘড়ি এগিয়ে দেওয়া হয় তখন ফাঁক হয় — একটি নির্দিষ্ট সময় থাকে না। শরতে যখন ঘড়ি পিছিয়ে দেওয়া হয় তখন ওভারল্যাপ হয় — একই সময় দুবার ঘটে।
ZonedDateTime একটি সমাধান (resolve) কৌশলের মাধ্যমে এই পরিস্থিতিগুলি পরিচালনা করে। ফাঁকের সময় একটি অবজেক্ট তৈরি করার সময়, java.time স্বয়ংক্রিয়ভাবে অফসেট পরিমাণ দ্বারা সময় স্থানান্তর করে। ওভারল্যাপের সময় তৈরি করার সময়, প্রথম বিকল্পটি (ট্রানজিশনের আগে) নির্বাচিত হয়। এই আচরণ withZoneSameInstant-এর মাধ্যমে পরিবর্তন করা যেতে পারে।
আপনি zone.getRules().isDaylightSavings(instant) এর মাধ্যমে একটি সময় DST-তে আছে কিনা তা পরীক্ষা করতে পারেন। getOffset() পদ্ধতি একটি নির্দিষ্ট মুহূর্তের জন্য প্রকৃত অফসেট দেখায়, এবং getRules().getDaylightSavings(instant) মিলিসেকেন্ডে DST সমন্বয়ের পরিমাণ দেখায়।
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 অফসেট: $dstAmount")
}
}
ZonedDateTime ফর্ম্যাট করার জন্য DateTimeFormatter ব্যবহার করা হয়। স্ট্যান্ডার্ড ISO ফর্ম্যাটে তারিখ, সময় এবং অফসেট অন্তর্ভুক্ত: “2026-07-21T15:30:00+03:00[Europe/Moscow]”। পূর্বনির্ধারিত ফর্ম্যাট: ISO_ZONED_DATE_TIME, ISO_OFFSET_DATE_TIME, ISO_INSTANT।
স্থানীয়কৃত আউটপুটের জন্য, DateTimeFormatter.ofLocalizedDateTime(FormatStyle) ব্যবহার করুন। FormatStyle SHORT, MEDIUM, LONG, FULL হতে পারে। LONG-এ জোনের নাম (“MSK”) অন্তর্ভুক্ত, FULL-এ সম্পূর্ণ নাম (“Moscow Standard Time”) অন্তর্ভুক্ত।
গুরুত্বপূর্ণ: ZonedDateTime-এর সাথে একটি স্ট্রিং পার্স করার সময়, ফর্ম্যাটে জোন বা অফসেট তথ্য থাকতে হবে। যদি জোন নির্দিষ্ট না থাকে, তাহলে LocalDateTime.parse() এবং তারপর 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
)
প্রথম উদাহরণ — ব্যবহারকারীকে তার স্থানীয় টাইম জোনে মিটিংয়ের সময় দেখানো। সার্ভার UTC-তে ZonedDateTime পাঠায়, ক্লায়েন্ট ডিভাইসের স্থানীয় টাইম জোনে রূপান্তর করে।
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)
}
দ্বিতীয় উদাহরণ — টাইম জোন বিবেচনা করে পরবর্তী ইভেন্ট পর্যন্ত সময় গণনা করা। আমরা সার্ভার সময়ের জন্য ZonedDateTime এবং পার্থক্য গণনার জন্য Duration.between() ব্যবহার করি।
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"
}
তৃতীয় উদাহরণ — Retrofit API নিয়ে কাজ করা। সার্ভার জোন সহ ISO-8601 স্ট্রিং ফেরত দেয়। আমরা 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()
)
}
}
প্রথম ভুল — সার্ভার কোডে ZoneId.systemDefault() ব্যবহার করা। সার্ভারের টাইম জোন ক্লায়েন্টের থেকে আলাদা হতে পারে, এবং সার্ভারে সিস্টেম জোন ব্যবহার করলে ভুল গণনা হয়। সর্বদা স্পষ্টভাবে জোন নির্দিষ্ট করুন বা রেফারেন্স হিসেবে UTC ব্যবহার করুন।
দ্বিতীয় ভুল — সময়কাল গণনা করার সময় DST উপেক্ষা করা। Duration.between() ট্রানজিশনগুলি সঠিকভাবে পরিচালনা করে, কিন্তু আপনি যদি ম্যানুয়ালি টাইমস্ট্যাম্প বিয়োগ করেন, তাহলে ডেলাইট সেভিং টাইম 1 ঘন্টার ত্রুটি সৃষ্টি করতে পারে। ম্যানুয়াল গণিতের পরিবর্তে ChronoUnit.HOURS.between() ব্যবহার করুন।
তৃতীয় ভুল — withZoneSameInstant এবং withZoneSameLocal-এর মধ্যে বিভ্রান্তি। প্রথমটি মুহূর্ত বজায় রেখে জোন পরিবর্তন করে — সময় স্থানান্তরিত হয়। দ্বিতীয়টি স্থানীয় সময় বজায় রেখে জোন পরিবর্তন করে — মুহূর্ত পরিবর্তিত হয়। SonarSource (2024) অনুসারে, ভুল পদ্ধতি নির্বাচন করা সবচেয়ে সাধারণ ভুলগুলির মধ্যে একটি।
চতুর্থ ভুল — ধরে নেওয়া যে ডিভাইসের টাইম জোন সর্বদা ব্যবহারকারীর টাইম জোনের মতোই। ব্যবহারকারী ভ্রমণ করতে পারেন এবং আশা করতে পারেন যে অ্যাপটি বর্তমান জোনের পরিবর্তে তার “গৃহ” টাইম জোনে সময় দেখাবে। এই ক্ষেত্রে, ইন্টারফেসের মাধ্যমে জোন নির্বাচন প্রদান করুন।
প্রায়শই জিজ্ঞাসিত প্রশ্ন
ZonedDateTime-এ একটি আঞ্চলিক জোন শনাক্তকারী (যেমন, “Europe/Moscow”) রয়েছে এবং এটি DST পরিচালনা করে। OffsetDateTime আঞ্চলিক নিয়ম ছাড়া শুধুমাত্র একটি নির্দিষ্ট অফসেট (+03:00) সংরক্ষণ করে। ডেটাবেস সংরক্ষণের জন্য, OffsetDateTime সুপারিশ করা হয়।
ZonedDateTime.now(ZoneOffset.UTC) বা Instant.now().atZone(ZoneOffset.UTC) ব্যবহার করুন। উভয় বিকল্প শূন্য অফসেট সহ বর্তমান মুহূর্ত ফেরত দেয়। একটি সাধারণ টাইমস্ট্যাম্পের জন্য, জোন বাঁধাই ছাড়া Instant.now() ব্যবহার করুন।
হ্যাঁ, তবে একটি কাস্টম অ্যাডাপ্টার প্রয়োজন। Gson ডিফল্টরূপে ZonedDateTime সমর্থন করে না। Moshi Rfc3339DateJsonAdapter-এর মাধ্যমে এটি সমর্থন করে। Jackson-এর জন্য Kotlinx Serialization বা JavaTimeModule লাইব্রেরি ব্যবহার করার সুপারিশ করা হয়।
java.time স্বয়ংক্রিয়ভাবে অফসেট পরিমাণ দ্বারা সময় এগিয়ে নিয়ে যায়। উদাহরণস্বরূপ, যদি 02:30 বিদ্যমান না থাকে যখন ঘড়ি 03:00-তে এগিয়ে দেওয়া হয়, তাহলে ZonedDateTime 03:30-এ একটি অবজেক্ট তৈরি করবে। আপনি ZoneRules.getTransition(instant)-এর মাধ্যমে ফাঁক পরীক্ষা করতে পারেন।
JDBC 4.2 OffsetDateTime সমর্থন করে কিন্তু সরাসরি ZonedDateTime নয়। ZonedDateTime-এ একটি আঞ্চলিক জোন রয়েছে যার SQL-এ কোনো সমতুল্য নেই। OffsetDateTime বা Instant সংরক্ষণ করার এবং জোনটি একটি আলাদা কলামে সংরক্ষণ করার সুপারিশ করা হয়।
সারসংক্ষেপ
আমরা একটি মোবাইল অ্যাপ্লিকেশন টার্নকি তৈরি করব
IT Sectr 2017 সাল থেকে স্টার্টআপ এবং ব্যবসার জন্য iOS এবং Android অ্যাপ্লিকেশন তৈরি করে। আমরা আপনাকে পরামর্শ দেব এবং সেরা সমাধান প্রস্তাব করব।
আরও পড়ুন