Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> การเขียนโปรแกรม

เชี่ยวชาญการจัดรูปแบบวันที่ใน Java: SimpleDateFormat และ DateFormat อย่างละเอียด

บทนำ

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 สำหรับจัดรูปแบบและแปลงด้วย pattern
  • SimpleDateFormat รองรับ 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
GEra designatorGAD
yYearyyyy2026
MMonth in yearMMMMJanuary
wWeek in yearw3
WWeek in monthW1
DDay in yearD15
dDay in monthdd05
FDay of week in monthF1
EDay name in weekEEEMon
uDay number in week (1 = Monday)u1
aAM/PM markeraPM
HHour in day (0-23)HH14
kHour in day (1-24)k14
KHour in am/pm (0-11)K2
hHour in am/pm (1-12)hh02
mMinute in hourmm30
sSecond in minutess45
SMillisecondSSS123
zGeneral timezonezIST
ZRFC 822 timezone offsetZ+0530
XISO 8601 timezone offsetXXX+05:30

การทำซ้ำ Pattern และความกว้างของ Output

การทำซ้ำตัวอักษร pattern จะเปลี่ยนความกว้าง การเติมศูนย์นำหน้า (numeric padding) หรือสไตล์ข้อความ

FieldPatternตัวอย่าง Output
MonthM1
MonthMM01
MonthMMMJan
MonthMMMMJanuary
Day of monthd5
Day of monthdd05
Hour (0-23)H7
Hour (0-23)HH07
Yearyy26
Yearyyyy2026

ตัวอย่าง Pattern พוצעיםและ Output ที่คาดหวัง

Pattern พוצעיםเหล่านี้มีประโยชน์สำหรับ logs, APIs และวันที่ที่แสดงให้ผู้ใช้เห็น

Patternตัวอย่าง Output
MM/dd/yyyy01/05/2026
dd-M-yyyy hh:mm:ss05-1-2026 02:30:45
dd MMMM yyyy05 January 2026
dd MMMM yyyy zzzz05 January 2026 India Standard Time
E, dd MMM yyyy HH:mm:ss zMon, 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 SimpleDateFormat instance เดียวข้าม 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

LocalePatternOutput
Locale.USEEEE, dd MMMM yyyyTuesday, 14 November 2023
Locale.FRENCHEEEE, dd MMMM yyyymardi, 14 novembre 2023
Locale.GERMANEEEE, dd MMMM yyyyDienstag, 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 PatternDateTimeFormatter Patternหมายเหตุ
Yearyyyyuuuu หรือ yyyyเลือก uuuu ใน java.time สำหรับ proleptic year consistency
Timezone offset/namez, Z, Xz, Z, x, Xjava.time แยก offset variants มากกว่า (x และ X)
Literal text'T''T'ทั้งสอง API ใช้ single quotes สำหรับ literals
u symbolDay-of-week numberYearKnown gotcha: u เปลี่ยนความหมายระหว่าง APIs

Checklist การย้ายทีละขั้นตอน

ใช้ checklist นี้เพื่อแปลง formatter ทีละตัวใน codebase ที่มีอยู่

  1. แทนที่ import java.text.SimpleDateFormat; ด้วย import java.time.format.DateTimeFormatter;
  2. แทนที่ new SimpleDateFormat("yyyy-MM-dd") ด้วย DateTimeFormatter.ofPattern("yyyy-MM-dd")
  3. แทนที่ .parse() ด้วย LocalDate.parse() หรือ LocalDateTime.parse() ขึ้นกับความแม่นยำของ input
  4. แทนที่ .format(date) ด้วย formatter.format(localDate) หรือ formatter.format(localDateTime)
  5. ลบ 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

เชี่ยวชาญการจัดรูปแบบวันที่ใน Java: SimpleDateFormat และ DateFormat อย่างละเอียดงานนี้ได้รับอนุญาตภายใต้ Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.