LocalDate، LocalTime و LocalDateTime — کلاسهای اصلی بسته java.time هستند که کار با تاریخ و زمان را بدون وابستگی به منطقه زمانی فراهم میکنند. به استناد مستندات Oracle (Java 17, 2024)، این انواع به صورت immutable و thread-safe طراحی شدهاند که آنها را برای برنامههای چندرشتهای ایمن میسازد. آنها در Android از طریق desugaring از API 26 به بعد، و برای نسخههای کهنتر از طریق کتابخانه ThreeTenABP در دسترس شدند.
نکات کلیدی
LocalDate — کلاسی که تاریخ را به صورت سال-ماه-روز بدون اطلاعات زمان و منطقه زمانی نمایش میدهد. از آن برای ذخیره دادههایی مانند تاریخ تولد، تاریخ رویداد یا تاریخ انقضا استفاده میشود.
LocalDate سال را در محدوده -999999999 تا +999999999، ماه را از 1 تا 12 و روز ماه را با توجه به سالهای کبیسه ذخیره میکند. کلاس کاملاً immutable است — هر عملیات یک شیئ جدید بازمیگرداند.
LocalTime زمان شبانهروز را نمایش میدهد: ساعتها، دقیقهها، ثانیهها و نانوثانیهها. حداکثر دقت — تا نانوثانیه. LocalTime حاوی اطلاعاتی درباره تاریخ و منطقه زمانی نیست، که آن را برای ذخیره ساعت باز شدن فروشگاه یا مدت زمان پروسه مناسب میکند.
LocalDateTime LocalDate و LocalTime را در یک شیئ ترکیب میکند. این پرکاربردترین نوع است که هنگامی که نیاز به ذخیره هم تاریخ و هم زمان داریم اما وابستگی به منطقه زمانی نمورد نیاز نیست، استفاده میشود. به عنوان مثال، تاریخ و ساعت کنسرت به فرمت محلی.
به استناد Oracle Java Documentation (2024)، هر سه کلاس بر اساس ایدههای کتابخانه Joda-Time طراحی شدهاند، اما با معماری بهبودیافته و ادغام کامل در کتابخانه استاندارد.
بسته java.time در Java 8 به عنوان جایگزین کلاسهای منسوخی Date، Calendar و SimpleDateFormat ایجاد شد. معماری آن بر اصول اشیاء immutable و رابط fluent ساخته شده است.
ویژگی کلیدی — همه کلاسهای اصلی value-based هستند. این به معناست که نمونههای آنها بر اساس مقدار مقایسه میشوند نه ارجاع، و نمیتوان آنها را به ارث برد. برای مقایسه دو شیئ از روش equals استفاده میشود نه از اپراتور ==.
بسته به چند دسته تقسیم میشود. انواع بدون منطقه زمانی — LocalDate، LocalTime، LocalDateTime — برای تاریخ و زمان محلی استفاده میشوند. انواع با منطقه زمانی — ZonedDateTime، OffsetDateTime، OffsetTime — اطلاعات انحراف یا منطقه را اضافه میکنند. انواع لحظهای — Instant — نقطهای را در خط زمان در UTC نمایش میدهند.
این تقسیم مشکل مخصوص API قدیمی را حل میکند: برنامهنویس هرگز نمیدانست که آیا شیئ Date حاوی اطلاعات منطقه زمانی است یا خیر. در java.time هر نوع صریحاً سمانتیک خود را اعلام میکند.
کلاس LocalDate روشهای زیادی برای ایجاد، خواندن و تغییر تاریخ فراهم میکند. تاریخ جاری را میتوان از طریق روش استاتیک now() به دست آورد. تاریخ مشخص را از طریق روش of(int year, int month, int dayOfMonth).
برای خواندن جزء تاریخ از getterها استفاده میشود: getYear()، getMonthValue()، getDayOfMonth()، getDayOfWeek()، getDayOfYear(). روش getMonth() انوم Month و getDayOfWeek() انوم DayOfWeek را بازمیگرداند.
LocalDate از بررسی تاریخ پشتیبانی میکند. روشهای isBefore()، isAfter() و isEqual() امکان مقایسه تاریخها را فراهم میکنند. روش isLeapYear() بررسی میکند که آیا سال کبیسه است. روش lengthOfMonth() تعداد روزهای ماه و lengthOfYear() تعداد روزهای سال را بازمیگرداند.
برای تغییر از روشهای withYear()، withMonth()، withDayOfMonth() استفاده میشود که شیئی جدید با جزء تغییریافته بازمیگردانند. روشهای plusDays()، minusMonths() و مشابه حسابات تاریخ را انجام میدهند.
LocalTime زمان شبانهروز را با دقت تا نانوثانیه نمایش میدهد. فرمت استاندارد — ISO-8601 (HH:mm:ss.nnnnnnnnn). حداقل مقدار — 00:00، حداکثر — 23:59:59.999999999.
میتوان شیئ LocalTime را از طریق now() برای زمان جاری، of(int hour, int minute)، of(int hour, int minute, int second) یا of(int hour, int minute, int second, int nanoOfSecond) ایجاد کرد. روش parse(CharSequence text) سترونگ را در فرمات ISO-8601 تجزیه و تحلیل میکند.
Getterها شامل getHour()، getMinute()، getSecond()، getNano() هستند. روش toSecondOfDay() تعداد ثانیه از آغاز روز را و toNanoOfDay() نانوثانیه را بازمیگرداند. این برای محاسبات مدت زمان در طول یک روز مفید است.
LocalTime همان عملیات مقایسه و تغییر را مانند LocalDate پشتیبانی میکند: plusHours()، minusMinutes()، withHour()، withMinute(). روشهای isBefore() و isAfter() با توجه به چرخشی بودن زمان در طول شبانهروز کار میکنند.
LocalDateTime قابلیتهای LocalDate و LocalTime را در یک کلاس ترکیب میکند. هم تاریخ و هم زمان را ذخیره میکند اما بدون منطقه زمانی. این انعطافپذیرترین نوع از انواع محلی است، اما در سیستمهای توزیعشده نیازمند احتیاط است.
میتوان LocalDateTime را از طریق روشهای استاتیک now()، of(LocalDate date, LocalTime time)، of(int year, Month month, int dayOfMonth, int hour, int minute) و سایر انواع ساخت. همچنین میتوان LocalDate و LocalTime را از طریق روش atTime() ترکیب کرد.
LocalDateTime از طریق getterهای مناسب به تمامی فیلدهای تاریخ و زمان دسترسی فراهم میکند: toLocalDate() و toLocalTime() جزءات جداگانه را بازمیگردانند. روش truncatedTo(TemporalUnit unit) امکان گرد کردن زمان را به دقت مورد نظر فراهم میکند — مثلاً، تا دقیقه.
برای تبدیل به منطقه زمانی از روش atZone(ZoneId zone) استفاده میشود که ZonedDateTime را بازمیگرداند. این تنها راه اضافه کردن منطقه زمانی به LocalDateTime است.
هر سه کلاس از یک الگوی یکسان ایجاد از طریق روشهای استاتیک کارخانه استفاده میکنند. سازندههای کلاسها به صورت private اعلام شدهاند — نمیتوان شیئی را مستقیماً از طریق new ایجاد کرد.
روشهای اصلی ایجاد:
روش of چندین انواع ساخت دارد. برای LocalDate سال، ماه و روز مورد نیاز است. برای LocalTime — ساعت و دقیقه (اختیاری ثانیه و نانوثانیه). برای LocalDateTime — سال، ماه، روز، ساعت، دقیقه. ماه را میتوان به صورت int (1-12) یا انوم Month ارسال کرد.
val today = LocalDate.now()
val specificDate = LocalDate.of(2026, Month.JULY, 21)
val parsedDate = LocalDate.parse("2026-07-21")
val currentTime = LocalTime.now()
val lunchTime = LocalTime.of(13, 30, 0)
val parsedTime = LocalTime.parse("13:30:00")
val now = LocalDateTime.now()
val meeting = LocalDateTime.of(2026, 7, 21, 15, 0)
کلاسهای java.time برای تبدیل راحت به یکدیگر طراحی شدهاند. LocalDate میتواند از طریق روش atTime(LocalTime) یا atStartOfDay() به LocalDateTime تبدیل شود. LocalTime — از طریق atDate(LocalDate).
LocalDateTime میتواند از طریق toLocalDate() به LocalDate و از طریق toLocalTime() به LocalTime بازگردانده شود. برای تبدیل به ZonedDateTime از روش atZone(ZoneId) استفاده میشود.
تبدیل به java.util.Date (برای سازگاری با کد قدیمی) نیازمند یک مرحله میانی از طریق Instant و منطقه زمانی است. به استناد Baeldung (2024)، این عملیات از طریق Date.from(instant) انجام میشود.
val date = LocalDate.of(2026, 7, 21)
val dateTime = date.atTime(LocalTime.of(10, 30))
val time = LocalTime.of(14, 0)
val dateTimeFromTime = time.atDate(date)
val extractedDate = dateTime.toLocalDate()
val extractedTime = dateTime.toLocalTime()
val zoned = dateTime.atZone(ZoneId.of("Europe/Moscow"))
برای فرمتبندی و تجزیه و تحلیل از کلاس DateTimeFormatter استفاده میشود. آن فرمتهای از پیش تعریف شده را از طریق ثابتها (ISO_LOCAL_DATE، ISO_LOCAL_TIME، ISO_LOCAL_DATE_TIME) و امکان ایجاد فرمتهای خود را از طریق الگوها فراهم میکند.
الگوهای فرمتبندی از سمبلها استفاده میکنند: yyyy — سال، MM — ماه (دو رقمی)، dd — روز، HH — ساعت (0-23)، mm — دقیقه، ss — ثانیه. روش format() بر روی شیئ تاریخ-زمان یا از طریق DateTimeFormatter فراخوانی میشود.
DateTimeFormatter همچنین از طریق روشهای استاتیک ofLocalizedDate(FormatStyle)، ofLocalizedTime(FormatStyle) و ofLocalizedDateTime(FormatStyle) لوکالیسازی را پشتیبانی میکند. سبکهای SHORT، MEDIUM، LONG و FULL موجود است.
val formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm")
val formatted = LocalDateTime.now().format(formatter)
val parsed = LocalDate.parse(
"21.07.2026",
DateTimeFormatter.ofPattern("dd.MM.yyyy")
)
هر سه کلاس رابط Comparable را پیاده سازی میکنند که امکان مقایسه طبیعی آنها را فراهم میکند. روش compareTo() بسته به ترتیب عدد منفی، صفر یا مثبت بازمیگرداند. روشهای isBefore()، isAfter() و isEqual() boolean بازمیگردانند.
برای LocalDate مقایسه به صورت زمانشناختی انجام میشود — تاریخ زودتر کوچکتر است. برای LocalTime — به استناد زمان شبانهروز. برای LocalDateTime — ابتدا به استناد تاریخ، سپس زمان. تمام مقایسهها سالهای کبیسه و تعداد روزهای ماهها را به درستی مد نظر قرار میدهند.
تفاوت مهم با API قدیمی: equals() برای LocalDate، LocalTime و LocalDateTime مقادیر را مقایسه میکند نه ارجاعات را. این به معناست که دو شیئ با فیلدهای یکسان برابر خواهند بود، حتی اگر نمونههای مختلفی باشند.
val d1 = LocalDate.of(2026, 7, 21)
val d2 = LocalDate.of(2026, 12, 25)
if (d1.isBefore(d2)) {
Log.d("تاریخ", "d1 قبل از d2 است")
}
val sortedDates = listOf(d2, d1).sorted()
هر سه کلاس از عملیات حسابی از طریق روشهای plus و minus پشتیبانی میکنند. برای LocalDate روشهای plusDays()، plusWeeks()، plusMonths()، plusYears() و روشهای minus مشابه موجود است. LocalTime از plusHours()، plusMinutes()، plusSeconds()، plusNanos() پشتیبانی میکند.
LocalDateTime تمام عملیات حسابی هر دو نوع را به ارث میبرد. ویژگی LocalDate: هنگام افزودن ماه، نتایج طول مختلف ماهها را به درستی مدیریت میکند. به عنوان مثال، 31 ژانویه + 1 ماه = 28 (و در سال کبیسه 29) فوریه.
برای عملیات پیچیدهتر کلاسهای Period (برای تاریخ) و Duration (برای زمان) وجود دارند. روشهای plus(TemporalAmount) و minus(TemporalAmount) این اشیاء را میپذیرند.
val today = LocalDate.now()
val nextWeek = today.plusDays(7)
val nextMonth = today.plusMonths(1)
val lastYear = today.minusYears(1)
val now = LocalTime.now()
val inTwoHours = now.plusHours(2)
val halfHourAgo = now.minusMinutes(30)
یک مثال عملی را در نظر بگیرید: برنامه ثبت شیفتهای کاری. باید مدت زمان شیفت را محاسبه کرده و مشخص کرد که آیا در ساعت شب قرار میگیرد. از LocalTime برای زمان شروع و پایان، LocalDate برای تاریخ و LocalDateTime برای محاسبات شیفتهایی که از نیمه شب گذر میکنند استفاده میکنیم.
data class Shift(
val startTime: LocalTime,
val endTime: LocalTime,
val date: LocalDate
) {
fun isOvernight(): Boolean = endTime.isBefore(startTime)
fun durationInMinutes(): Long {
val start = LocalDateTime.of(date, startTime)
val end = LocalDateTime.of(
if (isOvernight()) date.plusDays(1) else date,
endTime
)
return Duration.between(start, end).toMinutes()
}
}
مثال دوم — محاسبه سن کاربر. از LocalDate برای تاریخ تولد استفاده میکنیم و با توجه به روز و ماه تولد با تاریخ جاری مقایسه میکنیم.
fun calculateAge(birthDate: LocalDate): Int {
val today = LocalDate.now()
val period = Period.between(birthDate, today)
return period.years
}
مثال سوم — کار با اعلانها. LocalDateTime برای زمانبندی یادآوریها استفاده میشود. بررسی میکنیم که آیا زمان زمانبندی شده فرا رسیده است.
data class Reminder(
val id: Long,
val scheduledAt: LocalDateTime
) {
fun isDue(): Boolean =
LocalDateTime.now().isAfter(scheduledAt)
}
پشتیبانی داخلی java.time در Android از API 26 (Android 8.0 Oreo) به بعد موجود شد. برای دستگاههایی با نسخههای کهنتر Android باید از desugaring استفاده کرد — مکانیزمی که پشتیبانی APIهای جدید Java را در نسخههای قدیمی اضافه میکند.
Desugaring در Android Gradle Plugin از طریق compileOptions در build.gradle پیکربندی میشود. کافی است isCoreLibraryDesugaringEnabled = true را تنظیم کرده و کتابخانه desugar_jdk_libs را اضافه کنید. پس از آن java.time برای تمام سطوح API از 14 قابل دسترس خواهد بود.
برای پروژههایی که نمیتوانند از desugaring استفاده کنند (مثلاً پروژههای قدیمی با AGP پایینتر از 4.0)، کتابخانه ThreeTenABP — backport java.time وجود دارد. آن همان کلاسها (LocalDate، LocalTime، LocalDateTime) را ارائه میدهد، اما در بسته org.threeten.bp.
@Suppress("UnstableApiUsage")
android {
compileOptions {
isCoreLibraryDesugaringEnabled = true
}
}
dependencies {
"coreLibraryDesugaring"("com.android.tools:desugar_jdk_libs:2.1.4")
}
خطای اول و رایج — استفاده از LocalDateTime در سیستمهای توزیعشده بدون در نظر گرفتن منطقه زمانی. اگر سرور در Europe/Moscow و مشتری در Asia/Tokyo باشد، LocalDateTime به طور متفاوت تفسیر خواهد شد. راه حل: برای دادههای جهانی از Instant یا ZonedDateTime استفاده کنید.
خطای دوم — تجزیه و تحلیل نادرست سترونگها. LocalDate.parse() به صورت پیشفرض فرمات ISO-8601 (yyyy-MM-dd) را انتظار دارد. اگر سترونگ در فرمات دیگری است، باید DateTimeFormatter را صریحاً ارسال کنید. همچنین باید DateTimeParseException را مدیریت کرد تا برنامه در ورودی نامناسب کراش نکند.
خطای سوم — نادیده گرفتن امنیت null. LocalDate، LocalTime و LocalDateTime اشیائی هستند که میتوانند null باشند. در Kotlin توصیه میشود از انواع nullable با بررسی صریح یا اپراتور الویس استفاده کنید. در Java — قبل از فراخوانی روشها null بودن را بررسی کنید.
خطای چهارم — اشتباه گرفتن LocalDateTime با ZonedDateTime. LocalDateTime هیچ اطلاعاتی درباره منطقه زمانی ندارد. اگر نیاز به ارسال یک لحظه زمانی مطلق دارید — از انواع منطقهای استفاده کنید. اگر زمان محلی کافی است — از انواع محلی.
سوالات متداول
Date تعداد میلیثانیه از 1970-01-01 UTC را ذخیره میکند، در حالی که LocalDate سال، ماه و روز را بدون وابستگی به منطقه زمانی ذخیره میکند. Date قابل تغییر است و thread-safe نیست، LocalDate — immutable و thread-safe. Date از Java 8 منسوخ شده است.
بله، LocalDateTime به خوبی به نوع SQL TIMESTAMP WITHOUT TIME ZONE نمایش میشود. JPA و Room آن را از طریق TypeConverter پشتیبانی میکنند. برای TIMESTAMP WITH TIME ZONE از ZonedDateTime یا OffsetDateTime استفاده کنید.
از ChronoUnit.DAYS.between(startDate, endDate) استفاده کنید. این روش long — تفاوت به روز را بازمیگرداند. برای محاسبه جزئیاتر از Period.between() استفاده کنید که Period را با سالها، ماهها و روزها بازمیگرداند.
LocalTime از دقت تا نانوثانیه (9 رقم بعد از اعشار) پشتیبانی میکند. اگر دقت تا میلیثانیه مورد نیاز است، قبل از ذخیره از truncateTo(ChronoUnit.MILLIS) استفاده کنید. این از مشکلات گرد کردن در سریالیسازی جلوگیری میکند.
روش now() از ساعت سیستم دستگاه و منطقه زمانی پیشفرض استفاده میکند. اگر دستگاهها در مناطق زمانی مختلفی قرار داشته باشند، تاریخ میتواند متفاوت باشد. برای یک نشانگر زمانی واحد از Instant.now() استفاده کنید که همیشه زمان را در UTC بازمیگرداند.
نتیجه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید