SimpleDateFormat و DateFormat در جاوا: راهنمای کامل

مقدمه
SimpleDateFormat مقادیر java.util.Date را با استفاده از رشتههای Pattern سفارشی Format و Parse میکند؛ و DateFormat هم API پایه انتزاعی را برای Format کردن آگاه-از-Localeِ تاریخ و زمان فراهم میکند. از این APIها وقتی استفاده کنید که کد Legacy جاوا را نگه میدارید که همچنان به java.util.Date وابسته است؛ برای کد جدید، DateTimeFormatter از API تاریخ/زمان جاوا ۸+ را ترجیح دهید—چون SimpleDateFormat امن-برای-Thread نیست.
این آموزش نشان میدهد چطور از DateFormat و SimpleDateFormat برای Format کردن، Parse کردن، تبدیل Timezone و خروجی خاص-Locale استفاده کنید. همچنین اعتبارسنجی سختگیرانه با setLenient(false)، راهحلهای Thread Safety و مسیر مهاجرت عملی به DateTimeFormatter را پوشش میدهد.
نکات کلیدی
- DateFormat، API انتزاعیِ Format کردن است؛ و SimpleDateFormat پیادهسازی عینیِ آن برای Format و Parse کردن مبتنی بر Pattern.
- SimpleDateFormat، Tokenهای غنیِ Pattern را برای سالها، ماهها، روزها، فیلدهای زمان و خروجی Timezone پشتیبانی میکند.
- Instanceِ اشتراکیِ SimpleDateFormat در کد Multi-Threaded ناامن است—چون وضعیت داخلیاش Mutable است.
- ThreadLocal وقتی کد Legacy نمیتواند فوراً Migrate شود، Instanceهای Formatter را بهازای هر Thread ایزوله میکند.
- DateTimeFormatter Immutable و امن-برای-Thread است؛ پس جایگزین ترجیحدادهشده برای کد جدید جاواست.
.parse()میتواند ParseException پرتاب کند؛ پس کد Parse کردن باید از try-catch و پیامهای خطای مفید استفاده کند.- setLenient(false) از تاریخهای نامعتبری که بیصدا تنظیم میشوند—مانند 2024-13-45—جلوگیری میکند.
- همیشه Localeای صریح پاس بدهید تا از خروجی وابسته-به-محیط در دیپلویهای سروری اجتناب شود.
SimpleDateFormat در جاوا چیست؟
SimpleDateFormat، زیرکلاسِ DateFormat است که به شما اجازه تعریف Patternهای صریح تاریخ/زمان برای Format و Parse کردن در جاوا را میدهد.
از DateFormat وقتی استفاده کنید که خروجی مبتنی-بر-سبک و آگاه-از-Locale را—from طریق متدهای Factory—میخواهید؛ و از SimpleDateFormat وقتی که به کنترل دقیقِ Patternهایی مانند “dd-MM-yyyy” یا “yyyy-MM-dd’T’HH:mm:ssZ” نیاز دارید. ثابتهای سبکِ DateFormat یعنی (SHORT، MEDIUM، LONG، FULL) خروجی مناسب-Locale تولید میکنند—بدون اینکه لازم باشد ترتیب منطقهای دقیق تاریخ را بدانید؛ که برای رشتههای نمایشی رو-به-کاربر—which در آنها Format باید از قراردادهای Locale کاربر پیروی کند—مفید است. از SimpleDateFormat وقتی استفاده کنید که خروجیِ Format با الزام بیرونیای مانند قرارداد API، Format لاگ یا قرارداد نامگذاری فایل ثابت شده باشد؛ چون این موارد Pattern دقیقی فارغ از Locale لازم دارند.
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
Locale locale = Locale.US;
DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.MEDIUM, locale);
String styleBased = dateFormat.format(new Date());
SimpleDateFormat patternBased = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss", locale);
String patternOutput = patternBased.format(new Date());
System.out.println(styleBased);
System.out.println(patternOutput);
Jan 5, 2026
05-01-2026 14:30:22
new Locale(String, String) از جاوا ۱۹ منسوخ شده. بهجایش از Locale.Builder استفاده کنید:
Locale locale = new Locale.Builder()
.setLanguage("en")
.setRegion("US")
.build();
برای Format کردن زمان بهجای تاریخ، از DateFormat.getTimeInstance() با همان پارامترهای Locale و سبک استفاده کنید:
Locale locale = Locale.US;
DateFormat timeFormat = DateFormat.getTimeInstance(DateFormat.MEDIUM, locale);
System.out.println(timeFormat.format(new Date()));
خروجی به زمان و Timezone فعلی وابسته است؛ مثلاً:
2:30:45 PM
مرجع سینتکس Pattern مربوط به SimpleDateFormat
حروف Pattern مربوط به SimpleDateFormat مستقیماً به اجزای تاریخ/زمان نگاشت میشوند؛ و انتخاب درست Pattern، هم خروجی Format و هم رفتار Parse را کنترل میکند.
| نماد | معنی | Pattern نمونه | خروجی نمونه |
|---|---|---|---|
| G | نشانگر دوره (Era) | G | AD |
| y | سال | yyyy | 2026 |
| M | ماه در سال | MMMM | January |
| w | هفته در سال | w | 3 |
| W | هفته در ماه | W | 1 |
| D | روز در سال | D | 15 |
| d | روز در ماه | dd | 05 |
| F | روز هفته در ماه | F | 1 |
| E | نام روز در هفته | EEE | Mon |
| u | شماره روز در هفته (۱ = دوشنبه) | u | 1 |
| a | نشانگر AM/PM | a | PM |
| H | ساعت در روز (۰-۲۳) | HH | 14 |
| k | ساعت در روز (۱-۲۴) | k | 14 |
| K | ساعت در am/pm (۰-۱۱) | K | 2 |
| h | ساعت در am/pm (۱-۱۲) | hh | 02 |
| m | دقیقه در ساعت | mm | 30 |
| s | ثانیه در دقیقه | ss | 45 |
| S | میلیثانیه | SSS | 123 |
| z | Timezone عمومی | z | IST |
| Z | آفست Timezone مربوط به RFC 822 | Z | +0530 |
| X | آفست Timezone مربوط به ISO 8601 | XXX | +05:30 |
تکرار Pattern و عرض خروجی
تکرار حروف Pattern، عرض، Padding عددی یا سبک متن را تغییر میدهد.
| فیلد | Pattern | خروجی نمونه |
|---|---|---|
| ماه | M | 1 |
| ماه | MM | 01 |
| ماه | MMM | Jan |
| ماه | MMMM | January |
| روز ماه | d | 5 |
| روز ماه | dd | 05 |
| ساعت (۰-۲۳) | H | 7 |
| ساعت (۰-۲۳) | HH | 07 |
| سال | yy | 26 |
| سال | yyyy | 2026 |
مثالهای Pattern رایج با خروجی مورد انتظار
این Patternهای رایج برای لاگها، APIها و تاریخهای رو-به-کاربر مفیدند.
| Pattern | خروجی نمونه |
|---|---|
| MM/dd/yyyy | 01/05/2026 |
| dd-M-yyyy hh:mm:ss | 05-1-2026 02:30:45 |
| dd MMMM yyyy | 05 January 2026 |
| dd MMMM yyyy zzzz | 05 January 2026 India Standard Time |
| E, dd MMM yyyy HH:mm:ss z | Mon, 05 Jan 2026 14:30:45 IST |
نحوه Format کردن تاریخ با SimpleDateFormat
برای Format کردن Date، SimpleDateFormatای با Pattern بسازید و .format(date) را صدا بزنید:
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
Date now = new Date();
SimpleDateFormat basic = new SimpleDateFormat("MM-dd-yyyy", Locale.US);
SimpleDateFormat withTime = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss", Locale.US);
SimpleDateFormat isoLike = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US);
System.out.println(basic.format(now));
System.out.println(withTime.format(now));
System.out.println(isoLike.format(now));
01-05-2026
05-01-2026 14:30:45
2026-01-05T14:30:45+0000
SimpleDateFormat قوانین Timezone و Locale را از Instance مربوط به Formatter اعمال میکند؛ پس وقتی خروجی باید سازگار باشد، این مقادیر را صریح تنظیم کنید.
نحوه Parse کردن رشته به تاریخ با SimpleDateFormat
برای Parse کردن رشتهای به Date، Formatterای با Patternِ دقیق ورودی بسازید و .parse(input) را صدا بزنید:
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
try {
SimpleDateFormat dateParser = new SimpleDateFormat("dd-MM-yyyy", Locale.US);
Date parsedDate = dateParser.parse("05-01-2026");
System.out.println(parsedDate);
SimpleDateFormat timeParser = new SimpleDateFormat("HH:mm:ss", Locale.US);
Date parsedTime = timeParser.parse("22:15:09");
System.out.println(parsedTime);
} catch (ParseException e) {
e.printStackTrace();
}
Mon Jan 05 00:00:00 UTC 2026
Thu Jan 01 22:15:09 UTC 1970
اگر فقط زمان فراهم شده باشد، جاوا از تاریخ Epoch بهعنوان بخش تاریخ استفاده میکند.
مدیریت صحیح ParseException
parse() وقتی ورودی با Patternِ پیکربندیشده مطابقت ندارد، ParseException پرتاب میکند؛ پس کد پروداکشن باید آن را بگیرد و هم ورودی خام و هم Pattern مورد انتظار را لاگ کند:
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Locale;
String input = "31/01/2026";
String pattern = "dd-MM-yyyy";
SimpleDateFormat parser = new SimpleDateFormat(pattern, Locale.US);
try {
parser.parse(input);
System.out.println("Parsed successfully");
} catch (ParseException e) {
// برای دیباگ آسانتر، هم مقدار ورودی و هم pattern را شامل شوید.
System.err.println("Failed to parse date string: " + input);
System.err.println("Expected pattern: " + pattern);
System.err.println("Parser error: " + e.getMessage());
}
Failed to parse date string: 31/01/2026
Expected pattern: dd-MM-yyyy
Parser error: Unparseable date: "31/01/2026"
اعتبارسنجی رشتههای تاریخ قبل از Parse کردن
SimpleDateFormat بهطور پیشفرض Lenient است؛ پس مقادیر نامعتبر میتوانند بهجای شکست خوردن، Roll Over شوند. برای اعمال اعتبارسنجی سختگیرانه، setLenient(false) را صدا بزنید:
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Locale;
String invalid = "2024-13-45";
SimpleDateFormat lenientParser = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
SimpleDateFormat strictParser = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
strictParser.setLenient(false);
System.out.println("Lenient parse result:");
try {
// حالت lenient بهجای پرتاب، مقادیر نامعتبر را بیصدا roll over میکند.
System.out.println(lenientParser.parse(invalid));
} catch (ParseException e) {
System.out.println("ParseException: " + e.getMessage());
}
System.out.println("Strict parse result:");
try {
System.out.println(strictParser.parse(invalid));
} catch (ParseException e) {
System.out.println("ParseException: " + e.getMessage());
}
Lenient parse result:
Fri Feb 14 00:00:00 UTC 2025
Strict parse result:
ParseException: Unparseable date: "2024-13-45"
مدیریت Timezone در SimpleDateFormat
SimpleDateFormat میتواند همان Date را در Timezoneهای مختلف Render کند—با تنظیم Timezoneای روی Formatter قبل از خروجی. شیء Date فقط شمارش میلیثانیه UTC را ذخیره کرده و Timezone خودش را ندارد. Timezone دغدغه Format کردن است—نه ذخیرهسازی؛ همان مقدار Date خروجیهای قابل-خواندن-انسانی متفاوتی تولید میکند بسته به اینکه Formatter کدام Timezone را اعمال کند. یعنی setTimeZone() باید روی Formatter قبل از صدا زدن format() صدا زده شود؛ تنظیمش بعداً هیچ اثری بر خروجیِ از-پیش-تولیدشده ندارد.
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
Date timestamp = new Date(1700000000000L); // لحظه ثابت برای خروجی تکرارپذیر
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z", Locale.US);
formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
System.out.println("UTC: " + formatter.format(timestamp));
formatter.setTimeZone(TimeZone.getTimeZone("America/New_York"));
System.out.println("New York: " + formatter.format(timestamp));
formatter.setTimeZone(TimeZone.getTimeZone("Asia/Kolkata"));
System.out.println("Kolkata: " + formatter.format(timestamp));
UTC: 2023-11-14 22:13:20 +0000
New York: 2023-11-14 17:13:20 -0500
Kolkata: 2023-11-15 03:43:20 +0530
اشتباهات رایج Timezone و نحوه اجتناب از آنها
باگهای Timezone اغلب از قاطی کردن Instantهای ذخیرهشده با زمان محلیِ Render-شده میآیند.
- Date میلیثانیههای UTC را ذخیره میکند؛ و Timezone فقط هنگام Format اعمال میشود.
- صدا زدن
setTimeZone()روی Instanceِ Formatterِ اشتراکی در محیط Multi-Threaded میتواند وسط عملیاتِ فراخوانیِ در-جریانِ Thread دیگری، Timezone را تغییر دهد؛ و خروجی را—بدون پرتاب هیچ Exceptionای—در Timezone اشتباه تولید کند. - استفاده مجدد از Instance منفرد SimpleDateFormat بین درخواستهای HTTP در Servlet یا Controller مربوط به Spring میتواند باعث شود فراخوانی
setTimeZone()یک درخواست، بر خروجی درخواست دیگری اثر بگذارد؛ بهازای هر درخواست Instance جدیدی بسازید یا ازThreadLocal<SimpleDateFormat>برای ایزوله کردن وضعیت استفاده کنید. TimeZone.getTimeZone("Invalid/Zone")بیصدا به GMT برمیگردد—چون قرارداد متد، GMT را بهعنوان پیشفرض برای IDهای ناشناخته تعیین میکند؛ نه اینکه Exception پرتاب کند.- شناسههای Zone را قبل از پاس دادن به
TimeZone.getTimeZone()باZoneId.of()از java.time اعتبارسنجی کنید؛ZoneId.of()روی ID نامعتبر، ZoneRulesException پرتاب میکند—which مسئله را صریح میسازد.
Format کردن تاریخ آگاه-از-Locale
برای تولید خروجی تاریخِ خاص-Locale—مانند نامهای ماه فرانسوی یا نام روزهای آلمانی—Localeای به Constructor مربوط به SimpleDateFormat پاس بدهید. از ثابتهای داخلی یعنی Locale.US، Locale.UK، Locale.FRENCH، Locale.GERMAN برای مناطق رایج استفاده کنید؛ یا با Locale.Builder وقتی منطقهای لازم دارید که ثابتها پوششش نمیدهند، Locale سفارشی بسازید. Locale تعیین میکند کدام زبان برای فیلدهای متنی مانند نام ماهها، نام روزها و نشانگرهای AM/PM استفاده شود.
تعیین Locale در Constructor مربوط به SimpleDateFormat
Locale را بهعنوان آرگومان دوم Constructor پاس بدهید. اگر حذفش کنید، SimpleDateFormat درون خود Locale.getDefault() را صدا میزند—which Formatter را به Locale زمان-اجرای JVM مقید میکند؛ مقداری که بین ماشینهای توسعهدهنده، محیطهای CI و سرورهای پروداکشنِ اجراشده زیر پیکربندیهای Locale متفاوتِ سیستمعامل، متفاوت است.
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
Date sample = new Date(1700000000000L);
String pattern = "EEEE, dd MMMM yyyy";
SimpleDateFormat usFormatter = new SimpleDateFormat(pattern, Locale.US);
SimpleDateFormat frFormatter = new SimpleDateFormat(pattern, Locale.FRENCH);
String usDate = usFormatter.format(sample);
String frDate = frFormatter.format(sample);
System.out.println("US: " + usDate);
System.out.println("French: " + frDate);
US: Tuesday, 14 November 2023
French: mardi, 14 novembre 2023
Format کردن نام ماه و روز بر اساس منطقه
همان Pattern، خروجی متنی متفاوتی بر اساس Locale تولید میکند.
| Locale | Pattern | خروجی |
|---|---|---|
| Locale.US | EEEE, dd MMMM yyyy | Tuesday, 14 November 2023 |
| Locale.FRENCH | EEEE, dd MMMM yyyy | mardi, 14 novembre 2023 |
| Locale.GERMAN | EEEE, dd MMMM yyyy | Dienstag, 14. November 2023 |
تکیه بر Locale.getDefault() در JVMای که در محیط سروری اجرا میشود، میتواند خروجی ناسازگار تولید کند—چون تنظیمات Locale سیستمعامل بین هاستها متفاوت است. همیشه Locale صریحی پاس بدهید.
مسائل Thread Safety با SimpleDateFormat
چرا SimpleDateFormat امن-برای-Thread نیست
SimpleDateFormat حین عملیاتهای Parse/Format، وضعیت داخلیِ Calendar و NumberFormat را Mutate میکند. اگر چند Thread از یک Instance استفاده کنند، این فیلدهای Mutable میتوانند وسط عملیات Overwrite شوند؛ که به Race Condition و خروجی غیرقابل-پیشبینی منجر میشود. در عمل، این به شکل تاریخهایی از یک Thread که در نتیجه Thread دیگری ظاهر میشوند، .parse()ای که—بدون هیچ Exceptionای—تاریخ کاملاً اشتباهی برمیگرداند، یا ArrayIndexOutOfBoundsExceptionای که از درون خود Formatter پرتاب میشود، بروز میکند. این خرابیها متناوب و وابسته-به-بار هستند؛ که بازتولیدشان را در محیط تست تک-Threaded دشوار میسازد.
وقتی به Format کردن امن-برای-همزمانی در کد Legacy نیاز دارید، Instanceها را بر اساس Thread یا Request Scope ایزوله کنید.
استفاده از ThreadLocal برای ایزوله کردن Instanceهای SimpleDateFormat
ThreadLocal<SimpleDateFormat> به هر Thread، Formatter مخصوص خودش را میدهد و از وضعیتِ Mutable مشترک اجتناب میکند. در محیطهای Thread-Pool مانند کانتینرهای Servlet و اپلیکیشنهای Spring، Threadها بین درخواستها بازاستفاده شده و هرگز Terminate نمیشوند؛ یعنی مقادیر ThreadLocal هرگز خودکار حذف نمیشوند. همیشه بعد از تکمیل عملیات FORMATTER.remove() را صدا بزنید؛ وگرنه Instance مربوط به Formatter برای عمرِ Thread باقی میماند و زیر بارِ پیوسته، بهصورت نشت حافظه انباشته میشود.
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class ThreadSafeLegacyFormatter {
// هر thread یک instance اختصاصی formatter میگیرد.
private static final ThreadLocal<SimpleDateFormat> FORMATTER =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US));
public static String format(Date date) {
try {
// هیچ mutation بین-threadای نیست؛ چون هر thread فرمتکننده خودش را میخواند.
return FORMATTER.get().format(date);
} finally {
// برای جلوگیری از نشت حافظه در thread poolها، instance را بعد از استفاده حذف کن.
FORMATTER.remove();
}
}
}
سوییچ به DateTimeFormatter بهعنوان جایگزین امن-برای-Thread
DateTimeFormatter، Immutable و امن-برای-Thread است؛ پس یک Instanceِ Static مشترک امن است:
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.Locale;
// هنگام اشتراک بین threadها امن-برای-thread نیست
SimpleDateFormat unsafe = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US);
System.out.println(unsafe.format(new Date()));
// امن-برای-thread: DateTimeFormatter است immutable و میتواند بهعنوان فیلد static ذخیره شود
DateTimeFormatter safe = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss", Locale.US);
System.out.println(safe.format(LocalDateTime.now()));
2026-01-05 14:30:22
2026-01-05 14:30:22
مهاجرت از SimpleDateFormat به DateTimeFormatter
مهاجرت به DateTimeFormatter، ریسکهای Thread-Safety را حذف، Workaroundهای ThreadLocal را از بین برده و با انواع java.time تمیز یکپارچه میشود. برای بیشتر Codebaseها، مهاجرت مکانیکی است: سینتکس Pattern تقریباً یکسان است، دو API میتوانند حین Rollout افزایشی همزیستی کنند و لازم نیست همهچیز را یکجا تبدیل کنید. ریسک اصلی نماد u است—که در SimpleDateFormat یعنی روز-هفته و در DateTimeFormatter یعنی سال؛ Patternهایی که مستقیم بین APIها کپی میشوند—بدون چک کردن این—بیصدا خروجی اشتباه تولید میکنند.
مقایسه کد کنار-هم
این قطعه کنار-هم، رفتار Format و Parse معادل را در APIهای Legacy و مدرن نشان میدهد:
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.Locale;
String pattern = "yyyy-MM-dd";
String input = "2026-01-05";
// SimpleDateFormat (API قدیمی java.util.Date)
SimpleDateFormat legacyFormatter = new SimpleDateFormat(pattern, Locale.US);
Date legacyParsed = legacyFormatter.parse(input);
String legacyFormatted = legacyFormatter.format(new Date());
// DateTimeFormatter (API مدرن java.time)
DateTimeFormatter modernFormatter = DateTimeFormatter.ofPattern(pattern, Locale.US);
LocalDate modernParsed = LocalDate.parse(input, modernFormatter);
String modernFormatted = modernFormatter.format(LocalDate.now());
System.out.println(legacyParsed);
System.out.println(legacyFormatted);
System.out.println(modernParsed);
System.out.println(modernFormatted);
Mon Jan 05 00:00:00 UTC 2026
2026-01-05
2026-01-05
2026-01-05
تفاوتهای سینتکس Pattern بین دو API
بیشتر حروف Pattern مشابهاند؛ اما برخی نمادها متفاوتند و—if مستقیم کپی شوند—میتوانند مهاجرت را بشکنند.
| کار | Pattern مربوط به SimpleDateFormat | Pattern مربوط به DateTimeFormatter | نکته |
|---|---|---|---|
| سال | yyyy | uuuu یا yyyy | در java.time برای سازگاری سالِ Proleptic، uuuu را ترجیح بدهید |
| آفست/نام Timezone | z، Z، X | z، Z، x، X | java.time بین variantهای آفست بیشتری تفکیک میکند (x و X) |
| متن Literal | ‘T’ | ‘T’ | هر دو API از تک-کوتیشن برای Literal استفاده میکنند |
| نماد u | شماره روز-هفته | سال | دام شناختهشده: u بین APIها معنایش عوض میشود |
چکلیست مهاجرت گامبهگام
از این چکلیست برای تبدیل یک Formatter در هر بار در Codebaseهای موجود استفاده کنید:
import java.text.SimpleDateFormat;را باimport java.time.format.DateTimeFormatter;جایگزین کنید.new SimpleDateFormat("yyyy-MM-dd")را باDateTimeFormatter.ofPattern("yyyy-MM-dd")جایگزین کنید..parse()را—بسته به دقت ورودی—باLocalDate.parse()یاLocalDateTime.parse()جایگزین کنید..format(date)را باformatter.format(localDate)یاformatter.format(localDateTime)جایگزین کنید.new Date()را تا جای ممکن حذف کرده و ازLocalDate.now()یاInstant.now()استفاده کنید.
DateTimeFormatter منحصراً با انواع java.time کار میکند (یعنی LocalDate، LocalDateTime، ZonedDateTime، Instant). java.util.Date را مستقیم نمیپذیرد. اگر کدی دارید که هنوز مقادیر java.util.Date تولید میکند—مثلاً از ORM یا JDBC Driver قدیمی—ابتدا با date.toInstant() تبدیل کنید قبل از پاس دادن به DateTimeFormatter. دو API میتوانند حین مهاجرت افزایشی در همان Codebase همزیستی کنند؛ لازم نیست همهچیز را یکجا تبدیل کنید. DateTimeFormatter هم ParseException را با java.time.format.DateTimeParseException جایگزین میکند—which یک Exception ازنوع Unchecked است؛ پس بلاکهای try-catch اختیاری میشوند اما همچنان در کد Parse کردنِ پروداکشن توصیه میشوند.
سوالات متداول
تفاوت SimpleDateFormat و DateFormat در جاوا چیست؟
DateFormat، کلاس انتزاعیای است که قرارداد Format کردن تاریخ/زمان را تعریف میکند. SimpleDateFormat زیرکلاس عینیِ آن است که Format کردن مبتنی بر Pattern سفارشی را اجازه میدهد. SimpleDateFormat را مستقیم در کد استفاده میکنید؛ DateFormat را بهعنوان نوع Referenceای وقتی استفاده میکنید که میخواهید هر پیادهسازیای را بپذیرید.
آیا SimpleDateFormat امن-برای-Thread است؟
نه. Instanceهای SimpleDateFormat امن-برای-Thread نیستند. اشتراک یک Instance بین Threadها—بدون Synchronization—به نتایج Parse و Format غیرقابل-پیشبینی منجر میشود. از ThreadLocal<SimpleDateFormat> استفاده کنید؛ یا آن را با java.time.format.DateTimeFormatter—که Immutable و امن-برای-Thread است—جایگزین نمایید.
چطور رشته تاریخ را با Format یعنی dd-MM-yyyy با SimpleDateFormat پارس کنم؟
SimpleDateFormatای با Pattern یعنی “dd-MM-yyyy” بسازید؛ سپس .parse(yourString) را صدا بزنید. فراخوانی را در بلاک try-catch برای ParseException بپیچید. مطمئن شوید رشته ورودی—including کاراکترهای جداکننده—دقیقاً با Pattern مطابقت دارد.
چطور Date را به Timezone متفاوتی با SimpleDateFormat تبدیل کنم؟
.setTimeZone(TimeZone.getTimeZone("America/New_York")) را روی Instance مربوط به SimpleDateFormat خودتان قبل از صدا زدن .format(date) صدا بزنید. شیء Date زیربنایی، میلیثانیههای UTC را نگه میدارد؛ Formatter آفست Timezone را حین خروجی اعمال میکند.
Pattern یعنی “yyyy-MM-dd’T’HH:mm:ssZ” چه چیزی تولید میکند؟
Timestampای سازگار با ISO 8601 تولید میکند؛ مثلاً 2024-06-15T14:30:00+0530. حرف T، کاراکتر Literalای است که در تک-کوتیشن پیچیده شده. Z هم آفست Timezone مربوط به RFC 822 را نشان میدهد.
چطور از SimpleDateFormat به DateTimeFormatter مigrate کنم؟
new SimpleDateFormat("yyyy-MM-dd") را با DateTimeFormatter.ofPattern("yyyy-MM-dd") جایگزین کنید. فراخوانیهای .parse() را با LocalDate.parse() یا LocalDateTime.parse() جایگزین کنید. فراخوانیهای .format(date) را با formatter.format(localDate) جایگزین کنید. دقت کنید که DateTimeFormatter در برخی زمینهها—toجای y—از u برای سال استفاده میکند.
چرا SimpleDateFormat تاریخ نامعتبر را—بدون پرتاب Exception—پارس میکند؟
بهطور پیشفرض، SimpleDateFormat از حالت Parse کردنِ Lenient استفاده میکند؛ که مقادیر خارج-از-بازه را—toجای رد کردن—تنظیم میکند. قبل از Parse کردن، .setLenient(false) را صدا بزنید تا اعتبارسنجی سختگیرانه اعمال شده و روی ورودی نامعتبر، ParseException پرتاب شود.
چطور تاریخ را با Locale خاصی با SimpleDateFormat فرمت کنم؟
Locale را بهعنوان آرگومان دوم به Constructor پاس بدهید: new SimpleDateFormat("dd MMMM yyyy", Locale.FRENCH). این، نامهای ماه و روز را به زبان مشخصشده Render میکند؛ که برای اپلیکیشنهایی که به مناطق متعددی سرویس میدهند حیاتی است.
نتیجهگیری
این راهنما مبانی DateFormat و SimpleDateFormat، سینتکس Pattern، روندهای کاری Format و Parse، رفتار Timezone، Render کردن آگاه-از-Locale، مسائل Thread Safety و مهاجرت به DateTimeFormatter را پوشش داد.
حالا میتوانید Formatter درست را برای کد Legacy و مدرن انتخاب کنید، ورودی را با اعتبارسنجی سختگیرانه دفاعی Parse کنید، از Drift مربوط به Locale و Timezone در محیطهای سروری اجتناب کنید؛ و ریسکهای همزمانی را از Formatterهای تاریخ اشتراکی حذف نمایید.



