บทนำ
SimpleDateFormat จัดรูปแบบและแปลงค่า java.util.Date โดยใช้สตริงรูปแบบ (pattern) ที่กำหนดเอง ในขณะที่ DateFormat ให้ API แบบ abstract สำหรับการจัดรูปแบบวันที่และเวลาที่รองรับ locale ควรใช้ API เหล่านี้เมื่อดูแลรักษาโค้ด Java เก่า (legacy) ที่ยังพึ่งพา java.util.Date สำหรับโค้ดใหม่ ควรเลือก DateTimeFormatter จาก Java 8+ date/time API เพราะ SimpleDateFormat ไม่ปลอดภัยต่อเธรด (not thread-safe)
บทช่วยสอนนี้แสดงวิธีใช้ DateFormat และ SimpleDateFormat สำหรับการจัดรูปแบบ การแปลง (parsing) การแปลงโซนเวลา และการแสดงผลตาม locale ยังครอบคลุมการตรวจสอบแบบเข้มงวดด้วย setLenient(false) วิธีแก้ปัญหา thread safety และเส้นทางการย้ายไปใช้ DateTimeFormatter อย่างเป็นประโยชน์
สรุปประเด็นสำคัญ
DateFormatเป็น API จัดรูปแบบแบบ abstract และSimpleDateFormatเป็นการ implement แบบ concrete สำหรับจัดรูปแบบและแปลงด้วย patternSimpleDateFormatรองรับ pattern tokens อุดมสมบูรณ์สำหรับปี เดือน วัน ฟิลด์เวลา และการแสดงผลโซนเวลา- อินสแตนซ์
SimpleDateFormatที่แชร์ร่วมกันไม่ปลอดภัยในโค้ด multithreaded เพราะสถานะภายใน (internal state) เป็น mutable ThreadLocal<SimpleDateFormat>แยกอินสแตนซ์ formatter ต่อเธรด เมื่อโค้ดเก่ายังย้ายไม่ได้ทันทีDateTimeFormatterเป็น immutable และ thread-safe จึงเป็นตัวเลือกแทนที่ที่แนะนำสำหรับโค้ด Java ใหม่.parse()อาจโยนParseExceptionดังนั้นโค้ดแปลงควรใช้ try-catch และข้อความผิดพลาดที่มีประโยชน์setLenient(false)ป้องกันการปรับวันที่ไม่ถูกต้องโดยเงียบๆ เช่น2024-13-45- ควรส่ง
Localeโดยเฉพาะเจาะจงเสมอ เพื่อหลีกเลี่ยงผลลัพธ์ที่ขึ้นกับ environment ในการปรับใช้เซิร์ฟเวอร์
SimpleDateFormat ใน Java คืออะไร?
SimpleDateFormat เป็น subclass ของ DateFormat ที่ให้คุณกำหนด pattern วันที่/เวลาโดยตรงสำหรับการจัดรูปแบบและแปลงใน Java
ใช้ DateFormat เมื่อต้องการ output แบบ style-based ที่รองรับ locale ผ่าน factory methods และใช้ SimpleDateFormat เมื่อต้องการควบคุม pattern แน่นอน เช่น "dd-MM-yyyy" หรือ "yyyy-MM-dd'T'HH:mm:ssZ" ค่าคงที่ style ของ DateFormat (SHORT, MEDIUM, LONG, FULL) ให้ output ที่เหมาะสมกับ locale โดยไม่ต้องรู้ลำดับวันที่ของภูมิภาค ทำให้เหมาะสำหรับการแสดงผลสำหรับผู้ใช้ที่รูปแบบควรปฏิบัติตาม locale ของผู้ใช้ ใช้ SimpleDateFormat เมื่อรูปแบบ output ถูกกำหนดโดยข้อกำหนดภายนอก เช่น สัญญา API, รูปแบบ log หรือข้อตกลงการตั้งชื่อไฟล์ เพราะกรณีเหล่านั้นต้องการ 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) ถูก deprecated ตั้งแต่ Java 19 ควรใช้ Locale.Builder แทน
Locale locale = new Locale.Builder()
.setLanguage("en")
.setRegion("US")
.build();
เพื่อจัดรูปแบบเวลาแทนวันที่ ใช้ DateFormat.getTimeInstance() กับ locale และ style parameters เดียวกัน
Locale locale = Locale.US;
DateFormat timeFormat = DateFormat.getTimeInstance(DateFormat.MEDIUM, locale);
System.out.println(timeFormat.format(new Date()));
Output depends on the current time and timezone, for example:
2:30:45 PM
สำหรับภาพรวมกว้างขึ้นของ Java date/time APIs สมัยใหม่ ดูได้ที่ Java 8 Date, LocalDate, LocalDateTime, Instant และ Java 8 Features with Examples
อ้างอิงไวยากรณ์ Pattern ของ SimpleDateFormat
ตัวอักษร pattern ของ SimpleDateFormat จับคู่กับส่วนประกอบวันที่/เวลาโดยตรง และการเลือก pattern ที่ถูกต้องควบคุมทั้งรูปแบบ output และพฤติกรรมการแปลง
| Symbol | ความหมาย | ตัวอย่าง Pattern | ตัวอย่าง Output |
|---|---|---|---|
G | Era designator | G | AD |
y | Year | yyyy | 2026 |
M | Month in year | MMMM | January |
w | Week in year | w | 3 |
W | Week in month | W | 1 |
D | Day in year | D | 15 |
d | Day in month | dd | 05 |
F | Day of week in month | F | 1 |
E | Day name in week | EEE | Mon |
u | Day number in week (1 = Monday) | u | 1 |
a | AM/PM marker | a | PM |
H | Hour in day (0-23) | HH | 14 |
k | Hour in day (1-24) | k | 14 |
K | Hour in am/pm (0-11) | K | 2 |
h | Hour in am/pm (1-12) | hh | 02 |
m | Minute in hour | mm | 30 |
s | Second in minute | ss | 45 |
S | Millisecond | SSS | 123 |
z | General timezone | z | IST |
Z | RFC 822 timezone offset | Z | +0530 |
X | ISO 8601 timezone offset | XXX | +05:30 |
การทำซ้ำ Pattern และความกว้างของ Output
การทำซ้ำตัวอักษร pattern จะเปลี่ยนความกว้าง การเติมศูนย์นำหน้า (numeric padding) หรือสไตล์ข้อความ
| Field | Pattern | ตัวอย่าง Output |
|---|---|---|
| Month | M | 1 |
| Month | MM | 01 |
| Month | MMM | Jan |
| Month | MMMM | January |
| Day of month | d | 5 |
| Day of month | dd | 05 |
Hour (0-23) | H | 7 |
Hour (0-23) | HH | 07 |
| Year | yy | 26 |
| Year | yyyy | 2026 |
ตัวอย่าง Pattern พוצעיםและ Output ที่คาดหวัง
Pattern พוצעיםเหล่านี้มีประโยชน์สำหรับ logs, APIs และวันที่ที่แสดงให้ผู้ใช้เห็น
| Pattern | ตัวอย่าง Output |
|---|---|
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 |
วิธีจัดรูปแบบวันที่ด้วย SimpleDateFormat
เพื่อจัดรูปแบบ 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 จากอินสแตนซ์ formatter ดังนั้น ตั้งค่าเหล่านี้อย่างชัดเจนเมื่อ output ต้องสม่ำเสมอ
วิธีแปลงสตริงเป็นวันที่ด้วย SimpleDateFormat
เพื่อแปลงสตริงเป็น Date ให้สร้าง formatter ด้วย pattern ที่ตรงกับ input แล้วเรียก .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
หากให้เวลาเพียงอย่างเดียว Java จะใช้ epoch date เป็นส่วนของวันที่
การจัดการ ParseException อย่างถูกต้อง
parse() โยน ParseException เมื่อ input ไม่ตรงกับ pattern ที่ตั้งค่าไว้ ดังนั้นโค้ด production ควร catch และ log ทั้ง raw input และ expected 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) {
// รวมทั้งค่า input และ pattern เพื่อ debug ง่ายขึ้น
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"
สำหรับรูปแบบการจัดการ exception ที่นำไปใช้ซ้ำได้ทั่ว services ดูได้ที่ Exception Handling in Java และ Java 8 Date, LocalDate, LocalDateTime, Instant
การตรวจสอบสตริงวันที่ก่อนแปลง
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 mode ปรับค่าที่ผิดปกติโดยเงียบๆ แทนที่จะโยน exception
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 บน formatter ก่อน output อ็อบเจกต์ Date เก็บแค่จำนวนมิลลิวินาที UTC และไม่มี timezone ของตัวเอง Timezone เป็นเรื่องของการจัดรูปแบบ (formatting concern) ไม่ใช่การเก็บข้อมูล (storage concern) Date เดียวกันจะผลิต output ที่มนุษย์อ่านต่างกันไป ขึ้นอยู่กับ timezone ที่ formatter ใช้ นั่นหมายความ setTimeZone() ต้องเรียกบน formatter ก่อน format() การตั้งค่าหลังจากนั้นไม่มีผลต่อ output ที่ผลิตแล้ว
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
Date timestamp = new Date(1700000000000L); // Fixed instant สำหรับ output ที่ซ้ำได้
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
ดูรายละเอียดเพิ่มเติมที่ How to Convert Java Date into Specific TimeZone format
ข้อผิดพลาด Timezone ที่พบบ่อยและวิธีหลีกเลี่ยง
บั๊ก timezone มักมาจากการสับสนระหว่าง instants ที่เก็บกับ local time ที่แสดง
Dateเก็บมิลลิวินาที UTC และ timezone ถูกนำมาใช้เฉพาะตอนจัดรูปแบบ- การเรียก
setTimeZone()บน formatter instance ที่แชร์ร่วมในสภาพแวดล้อม multithreaded อาจเปลี่ยน timezone ระหว่างการทำงานของเธรดอื่น ทำให้ output อยู่ใน timezone ผิดโดยไม่มี exception - การ reuse
SimpleDateFormatinstance เดียวข้าม HTTP requests ใน servlet หรือ Spring controller อาจทำให้การเรียกsetTimeZone()ของ request หนึ่งส่งผลต่อ output ของ request อื่น ควรสร้าง instance ใหม่ต่อ request หรือใช้ThreadLocal<SimpleDateFormat>เพื่อแยก state TimeZone.getTimeZone("Invalid/Zone")fallback ไป GMT แบบเงียบๆ เพราะสัญญา method ระบุ GMT เป็นค่า default สำหรับ IDs ที่ไม่รู้จัก แทนที่จะโยน exception- ควร validate zone IDs ด้วย
ZoneId.of()จากjava.timeก่อนส่งให้TimeZone.getTimeZone()ZoneId.of()โยนZoneRulesExceptionบน ID ที่ไม่ถูกต้อง ทำให้ปัญหาชัดเจนขึ้น
การจัดรูปแบบวันที่ที่รองรับ Locale
เพื่อผลิต output วันที่เฉพาะ locale เช่น ชื่อเดือนภาษาฝรั่งเศส หรือชื่อวันภาษาเยอรมัน ให้ส่ง Locale ให้ constructor ของ SimpleDateFormat ใช้ค่าคงที่ built-in เช่น Locale.US, Locale.UK, Locale.FRENCH, Locale.GERMAN สำหรับภูมิภาคทั่วไป หรือสร้าง custom locale ด้วย Locale.Builder เมื่อต้องการภูมิภาคที่ไม่มีในค่าคงที่ Locale ควบคุมภาษาที่ใช้สำหรับฟิลด์ข้อความ เช่น ชื่อเดือน ชื่อวัน และเครื่องหมาย AM/PM
การระบุ Locale ใน Constructor ของ SimpleDateFormat
ส่ง Locale เป็น argument ที่สองของ constructor หากละเลย SimpleDateFormat จะเรียก Locale.getDefault() ภายใน ซึ่งผูก formatter กับ locale รันไทม์ของ JVM ค่าที่แตกต่างกันระหว่างเครื่อง developer, CI environments และ production servers ที่รันภายใต้ OS locale configurations ที่ต่างกัน
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
การจัดรูปแบบชื่อเดือนและวันตามภูมิภาค
Pattern เดียวกันผลิต output ข้อความที่ต่างกันตาม locale
| Locale | Pattern | Output |
|---|---|---|
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 ที่รันในสภาพแวดล้อมเซิร์ฟเวอร์อาจให้ output ไม่สม่ำเสมอ เพราะ OS locale settings แตกต่างกันระหว่างโฮสต์ ควรส่ง Locale โดยเฉพาะเจาะจงเสมอ
ปัญหา Thread Safety กับ SimpleDateFormat
ทำไม SimpleDateFormat จึงไม่ thread-safe
SimpleDateFormat เปลี่ยนแปลงสถานะ Calendar และ NumberFormat ภายในระหว่างการ parse/format หากหลายเธรดใช้ instance เดียวกัน ฟิลด์ mutable เหล่านี้อาจถูกเขียนทับระหว่างการทำงาน ทำให้เกิด race conditions และ output ที่คาดการณ์ไม่ได้ ในทางปฏิบัติจะพบว่า วันที่จากเธรดหนึ่งปรากฏในผลลัพธ์ของอีกเธรด .parse() คืนค่าผิดปกติโดยไม่มี exception หรือ ArrayIndexOutOfBoundsException ถูกโยนจากภายใน formatter เอง ความล้มเหลวเหล่านี้เป็น intermittent และขึ้นกับ load ทำให้ยากที่จะ reproduce ในสภาพแวดล้อม single-threaded test
เมื่อต้องการ formatting ที่ปลอดภัยต่อ concurrency ในโค้ดเก่า ให้แยก instances ต่อเธรดหรือต่อ request scope สำหรับพื้นหลังพฤติกรรมเธรด Java ดูได้ที่ Java Thread Example
การใช้ ThreadLocal เพื่อแยกอินสแตนซ์ SimpleDateFormat
ThreadLocal<SimpleDateFormat> ให้แต่ละเธรดมี formatter instance ของตัวเอง และหลีกเลี่ยง shared mutable state ในสภาพแวดล้อม thread pool เช่น servlet containers และ Spring applications เธรดถูกนำกลับมาใช้ข้าม requests และไม่มีการ terminated จนหมายความว่า ThreadLocal values ไม่ถูกลบอัตโนมัติ ควรเรียก FORMATTER.remove() หลัง operation เสร็จ มิฉะนั้น formatter instance จะคงอยู่ตลอดอายุของเธรดและสะสมเป็น memory leak ภายใต้โหลดต่อเนื่อง
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class ThreadSafeLegacyFormatter {
// แต่ละเธรดได้ formatter instance 專屬
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 {
// ไม่มี cross-thread mutation เพราะแต่ละเธรดอ่าน formatter ของตัวเอง
return FORMATTER.get().format(date);
} finally {
// ลบ instance หลังใช้เพื่อป้องกัน memory leak ใน thread pools
FORMATTER.remove();
}
}
}
การเปลี่ยนไปใช้ DateTimeFormatter เป็นทางเลือกที่ thread-safe
DateTimeFormatter เป็น immutable และ thread-safe ดังนั้น static instance แชร์ร่วมได้ปลอดภัย
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.Locale;
// ไม่ thread-safe เมื่อแชร์ข้ามเธรด
SimpleDateFormat unsafe = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US);
System.out.println(unsafe.format(new Date()));
// Thread-safe: DateTimeFormatter เป็น immutable และเก็บเป็น static field ได้
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 สามารถดำรงอยู่ร่วมกันระหว่างการย้ายแบบค่อยเป็นค่อยไป และคุณไม่ต้องแปลงทุกอย่างพร้อมกัน ความเสี่ยงหลักคือสัญลักษณ์ u ซึ่งหมายถึง day-of-week ใน SimpleDateFormat แต่หมายถึง year ใน DateTimeFormatter pattern ที่คัดลอกโดยตรงระหว่าง API โดยไม่ตรวจสอบสิ่งนี้จะผลิต output ผิดโดยเงียบๆ
เปรียบเทียบโค้ด Side-by-side
โค้ด side-by-side นี้แสดงพฤติกรรมการจัดรูปแบบและแปลงที่เทียบเท่าใน legacy และ modern APIs
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 (legacy java.util.Date API)
SimpleDateFormat legacyFormatter = new SimpleDateFormat(pattern, Locale.US);
Date legacyParsed = legacyFormatter.parse(input);
String legacyFormatted = legacyFormatter.format(new Date());
// DateTimeFormatter (modern java.time API)
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 ส่วนใหญ่คล้ายกัน แต่บางสัญลักษณ์แตกต่างและอาจทำลายการย้ายหากคัดลอกโดยตรง
| งาน | SimpleDateFormat Pattern | DateTimeFormatter Pattern | หมายเหตุ |
|---|---|---|---|
| Year | yyyy | uuuu หรือ yyyy | เลือก uuuu ใน java.time สำหรับ proleptic year consistency |
| Timezone offset/name | z, Z, X | z, Z, x, X | java.time แยก offset variants มากกว่า (x และ X) |
| Literal text | 'T' | 'T' | ทั้งสอง API ใช้ single quotes สำหรับ literals |
u symbol | Day-of-week number | Year | Known gotcha: u เปลี่ยนความหมายระหว่าง APIs |
Checklist การย้ายทีละขั้นตอน
ใช้ checklist นี้เพื่อแปลง 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()ขึ้นกับความแม่นยำของ input - แทนที่
.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 เช่นจาก legacy ORM หรือ JDBC driver ให้แปลงก่อนด้วย date.toInstant() ก่อนส่งให้ DateTimeFormatter สอง API สามารถดำรงอยู่ร่วมกันใน codebase เดียวกันระหว่างการย้ายแบบเพิ่มเติม คุณไม่ต้องแปลงทุกอย่างพร้อมกัน DateTimeFormatter ยังแทนที่ ParseException ด้วย java.time.format.DateTimeParseException ซึ่งเป็น unchecked exception ดังนั้น try-catch blocks กลายเป็น optional แต่ยังแนะนำใน production parsing code
สำหรับบริบทการย้ายและการเลือกประเภท อ้างอิงได้ที่ Java 8 Date, LocalDate, LocalDateTime, Instant
คำถามที่พบบ่อย
ความแตกต่างระหว่าง SimpleDateFormat และ DateFormat ใน Java คืออะไร?
DateFormat เป็น abstract class ที่กำหนดสัญญาสำหรับการจัดรูปแบบวันที่/เวลา SimpleDateFormat เป็น concrete subclass ที่อนุญาตให้จัดรูปแบบแบบ pattern-based คุณใช้ SimpleDateFormat โดยตรงในโค้ด ใช้ DateFormat เป็น reference type เมื่อต้องการรับ implementation ใดๆ ก็ได้
SimpleDateFormat เป็น thread-safe ไหม?
ไม่ SimpleDateFormat instances ไม่ thread-safe การแชร์ instance เดียวข้ามเธรดโดยไม่มี synchronization ทำให้ parse และ format ให้ผลลัพธ์ที่คาดการณ์ไม่ได้ ควรใช้ ThreadLocal<SimpleDateFormat> หรือแทนที่ด้วย java.time.format.DateTimeFormatter ซึ่งเป็น immutable และ thread-safe
วิธีแปลงสตริงวันที่รูปแบบ dd-MM-yyyy ด้วย SimpleDateFormat?
สร้าง SimpleDateFormat ด้วย pattern "dd-MM-yyyy" แล้วเรียก .parse(yourString) ห่อหุ้มการเรียกใน try-catch block สำหรับ ParseException ตรวจสอบว่า input string ตรงกับ pattern ทุกประการ รวมถึงตัวคั่น
วิธีแปลง Date ไป timezone อื่นด้วย SimpleDateFormat?
เรียก .setTimeZone(TimeZone.getTimeZone("America/New_York")) บน instance SimpleDateFormat ของคุณ ก่อนเรียก .format(date) อ็อบเจกต์ Date ด้านล่างเก็บมิลลิวินาที UTC formatter ใช้ timezone offset ระหว่าง output
Pattern "yyyy-MM-dd'T'HH:mm:ssZ" ผลิตอะไร?
ผลิต timestamp ที่เข้ากันได้กับ ISO 8601 เช่น 2024-06-15T14:30:00+0530 T เป็น literal character ห่อด้วย single quotes Z แทน RFC 822 timezone offset
วิธีย้ายจาก SimpleDateFormat ไป DateTimeFormatter?
แทนที่ new SimpleDateFormat("yyyy-MM-dd") ด้วย DateTimeFormatter.ofPattern("yyyy-MM-dd") แทนที่การเรียก .parse() ด้วย LocalDate.parse() หรือ LocalDateTime.parse() แทนที่การเรียก .format(date) ด้วย formatter.format(localDate) โปรดทราบว่า DateTimeFormatter ใช้ u สำหรับ year ในบางบริบทแทน y
ทำไม SimpleDateFormat แปลงวันที่ไม่ถูกต้องโดยไม่โยน exception?
โดยค่าเริ่มต้น SimpleDateFormat ใช้ lenient parsing mode ซึ่งปรับค่าที่อยู่นอกช่วงแทนที่จะปฏิเสธ เรียก .setLenient(false) ก่อนแปลง เพื่อบังคับการตรวจสอบแบบเข้มงวดและโยน ParseException บน input ที่ไม่ถูกต้อง
วิธีจัดรูปแบบวันที่ด้วย locale เฉพาะด้วย SimpleDateFormat?
ส่ง Locale เป็น argument ที่สองของ constructor: new SimpleDateFormat("dd MMMM yyyy", Locale.FRENCH) ทำให้ชื่อเดือนและวันแสดงเป็นภาษาที่ระบุ ซึ่งสำคัญสำหรับแอปพลิเคชันที่บริการหลายภูมิภาค
สรุป
คู่มือนี้ครอบคลุมพื้นฐาน DateFormat และ SimpleDateFormat ไวยากรณ์ pattern ขั้นตอนการจัดรูปแบบและแปลง พฤติกรรม timezone การแสดงผลตาม locale ปัญหา thread safety และการย้ายไป DateTimeFormatter
ตอนนี้คุณสามารถเลือก formatter ที่เหมาะสมสำหรับโค้ดเก่าและใหม่ แปลง input อย่างป้องกันกันด้วยการตรวจสอบเข้มงวด หลีกเลี่ยง locale และ timezone drift ในสภาพแวดล้อมเซิร์ฟเวอร์ และกำจัดความเสี่ยง concurrency จาก date formatters ที่แชร์ร่วมกัน
สำหรับขั้นตอนต่อไป ทบทวน Java 8 Date, LocalDate, LocalDateTime, Instant, Java 8 Features with Examples, Exception Handling in Java และ How to Convert Java Date into Specific TimeZone format เพื่อต่อยอดการทำให้การจัดการวันที่/เวลาในแอปพลิเคชัน Java ทันสมัย
อ้างอิง:
- SimpleDateFormat API Doc
- DateFormat API Doc
- DateTimeFormatter API Doc
งานนี้ได้รับอนุญาตภายใต้ Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.