DateFormatter เป็นคลาส Foundation ที่ออกแบบมาเพื่อการแปลงแบบสองทิศทางระหว่างวัตถุ Date และการแสดงเป็นสตริง คลาสนี้คำนึงถึงภาษาท้องถิ่น เขตเวลา และปฏิทินของผู้ใช้ เพื่อให้แน่ใจว่าการแสดงวันที่ถูกต้องในทุกภูมิภาคของโลก ตามเอกสารสำหรับนักพัฒนา Apple (2025) DateFormatter รองรับสไตล์วันที่และเวลาที่กำหนดไว้ล่วงหน้าสี่แบบ รวมถึงรูปแบบที่กำหนดเองได้อย่างสมบูรณ์ผ่านสตริงเทมเพลต หากไม่มี DateFormatter จะไม่สามารถแสดงวันที่ให้ผู้ใช้อย่างถูกต้องในแอปพลิเคชันที่เป็นสากลได้
ประเด็นสำคัญ
DateFormatter เป็นคลาสจากเฟรมเวิร์ก Foundation ที่ทำการแปลงแบบสองทิศทางระหว่าง Date และสตริง ปรากฏครั้งแรกใน OpenStep ในชื่อ NSDateFormatter และตั้งแต่นั้นมาก็ยังคงเป็นเครื่องมือหลักสำหรับการจัดรูปแบบวันที่บนแพลตฟอร์ม Apple ทั้งหมด คลาสนี้สืบทอดจาก Formatter และให้ API ที่สะดวกสำหรับการแสดงวันที่ที่แปลเป็นภาษาท้องถิ่น
DateFormatter ทำงานบนพื้นฐานของรูปแบบ Unicode LDML — เดียวกับที่ใช้ใน ICU (International Components for Unicode) รูปแบบถูกกำหนดผ่านคุณสมบัติ dateFormat โดยสัญลักษณ์ y, M, d, H, m, s สอดคล้องกับปี, เดือน, วัน, ชั่วโมง, นาที, วินาที การทำซ้ำสัญลักษณ์จะกำหนดรูปแบบ: “y” — ปีสองหลัก, “yyyy” — ปีสี่หลัก
การสร้าง DateFormatter เป็น การดำเนินการที่มีค่าใช้จ่ายสูง เนื่องจากข้อมูลภาษาท้องถิ่นและปฏิทินถูกโหลดระหว่างการเริ่มต้น Apple แนะนำให้สร้างตัวจัดรูปแบบหนึ่งครั้งสำหรับการจัดรูปแบบแต่ละประเภทและนำกลับมาใช้ใหม่ ใน SwiftUI และ UIKit ตัวจัดรูปแบบมักถูกเก็บในแคชในคุณสมบัติแบบสแตติกหรือถูกสร้างแบบขี้เกียจเมื่อมีการเข้าถึงครั้งแรก
DateFormatter ถูกใช้ในส่วนประกอบระบบ iOS หลายตัว UIDatePicker ใช้ DateFormatter ภายในเพื่อแสดงวันที่ในโหมด countDownTimer TextField ที่มีตัวจัดรูปแบบสามารถตรวจสอบวันที่ที่ผู้ใช้ป้อนได้โดยอัตโนมัติ Core Data รองรับแอตทริบิวต์ประเภท Date แต่การแสดงเป็นสตริงจะดำเนินการผ่าน DateFormatter เสมอ
ความปลอดภัยของเธรด — DateFormatter ไม่ปลอดภัยต่อเธรด การแก้ไขคุณสมบัติของตัวจัดรูปแบบจากเธรดต่างกันทำให้เกิดพฤติกรรมที่ไม่ได้กำหนดไว้ สำหรับการใช้งานหลายเธรด ให้สร้างอินสแตนซ์ตัวจัดรูปแบบแยกต่างหากสำหรับแต่ละเธรด หรือใช้การซิงโครไนซ์ผ่าน NSLock หรือ คิวแบบอนุกรม
dateStyle และ timeStyle เป็นวิธีที่ง่ายที่สุดในการกำหนดค่าการแสดงวันที่ แต่ละสไตล์มีสี่รูปแบบ: .short, .medium, .long, .full การรวมกันของ dateStyle และ timeStyle ช่วยให้กำหนดค่ารูปแบบวันที่และเวลาได้อย่างอิสระ และคุณสมบัติ .none จะปิดการใช้งานส่วนที่เกี่ยวข้อง
สำหรับภาษาท้องถิ่นของสหรัฐอเมริกา .short จัดรูปแบบวันที่เป็น “7/21/26” และสำหรับภาษาท้องถิ่นของรัสเซียเป็น “21.07.2026” สไตล์ .long สำหรับภาษาท้องถิ่นของรัสเซียแสดง “21 กรกฎาคม 2026” และ .full แสดง “วันอังคารที่ 21 กรกฎาคม 2026” พร้อมวันในสัปดาห์ ทั้งสี่สไตล์ปรับเปลี่ยนตามมาตรฐานภูมิภาคโดยอัตโนมัติ รวมถึงลำดับของส่วนประกอบและตัวคั่น
SFDateFormatter ใน iOS 15+ มีแนวทางทางเลือกผ่าน RelativeDateFormatter และ DateIntervalFormatter RelativeDateFormatter แสดง “วันนี้”, “เมื่อวาน”, “ใน 3 วัน” สำหรับบริบททันที DateIntervalFormatter แสดงช่วงวันที่: “21–25 กรกฎาคม 2026” — สำหรับการจองและการวางแผน
| สไตล์ | ตัวอย่าง (ru_RU) | ตัวอย่าง (en_US) |
|---|---|---|
| .short | 21.07.2026 | 7/21/26 |
| .medium | 21 ก.ค. 2026 | Jul 21, 2026 |
| .long | 21 กรกฎาคม 2026 | July 21, 2026 |
| .full | วันอังคารที่ 21 กรกฎาคม 2026 | Tuesday, July 21, 2026 |
เมื่อรวมสไตล์ DateFormatter จะเลือกตัวคั่นโดยอัตโนมัติ: สำหรับ .short.date + .short.time ผลลัพธ์อาจเป็น “21.07.2026, 14:30” สำหรับ .full.date + .full.time — “วันอังคารที่ 21 กรกฎาคม 2026, 14:30:00 MSK” ตัวคั่นถูกจัดการโดยภาษาท้องถิ่น ไม่ใช่นักพัฒนา — ซึ่งรับประกันความสอดคล้องกับความคาดหวังในภูมิภาคของผู้ใช้
dateFormat อนุญาตให้ตั้งค่ารูปแบบการจัดรูปแบบตามอำเภอใจโดยใช้สัญลักษณ์ข้อกำหนด Unicode LDML ซึ่งให้การควบคุมการแสดงผลอย่างสมบูรณ์: คุณสามารถแสดงเฉพาะปีและเดือน หรือวันในสัปดาห์โดยไม่มีวันที่ หรือเวลาโดยไม่มีวินาที รูปแบบที่กำหนดเองเป็นสิ่งที่ขาดไม่ได้สำหรับข้อกำหนดการออกแบบเฉพาะ
สัญลักษณ์หลัก — yyyy (ปี: 2026), MM (เดือน: 07), dd (วัน: 21), HH (ชั่วโมง: 14), mm (นาที: 30), ss (วินาที: 00) สำหรับชื่อเดือนเต็มใช้ MMMM (กรกฎาคม), สำหรับแบบย่อ — MMM (ก.ค.) วันในสัปดาห์ — EEEE (วันอังคาร), แบบย่อ — E (อ.)
เมื่อใช้ dateFormat สิ่งสำคัญคือต้องตั้ง ภาษาท้องถิ่น ของตัวจัดรูปแบบ หากไม่ได้ตั้งค่าภาษาท้องถิ่น ตัวจัดรูปแบบจะใช้ภาษาท้องถิ่นของระบบ ซึ่งอาจไม่เป็นที่ต้องการสำหรับรูปแบบคงที่ใน API Apple แนะนำให้ตั้ง locale = Locale(identifier: “en_US_POSIX”) สำหรับรูปแบบคงที่ระหว่างภูมิภาค โดยเฉพาะเมื่อแยกวิเคราะห์วันที่จากการตอบกลับของเซิร์ฟเวอร์
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "ru_RU")
formatter.dateFormat = "d MMMM yyyy"
let customString = formatter.string(from: Date())
// "21 July 2026"
// การแยกวิเคราะห์สตริงที่กำหนดเอง
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let date = formatter.date(from: "2026-07-21 14:30:00")!
ข้อผิดพลาดใน dateFormat เป็นหนึ่งในสาเหตุทั่วไปของแอปคร্যช หากรูปแบบไม่ตรงกับสตริง เมธอด date(from:) จะคืนค่า nil ใช้ guard let หรือ ?? เพื่อแกะ optional อย่างปลอดภัย ในการตรวจสอบรูปแบบ ให้ทดสอบในทุกภาษาที่รองรับ — สัญลักษณ์ LDML บางตัวทำงานแตกต่างกันในภาษาท้องถิ่นต่างกัน
Locale กำหนดวิธีการแสดงชื่อเดือน ชื่อวันในสัปดาห์ และตัวคั่น DateFormatter ใช้ Locale.current โดยค่าเริ่มต้น แต่ในบางสถานการณ์จำเป็นต้องระบุภาษาท้องถิ่นเฉพาะ: สำหรับรูปแบบคงที่ในบันทึกใช้ en_US_POSIX, สำหรับวันที่ของเซิร์ฟเวอร์ — ภาษาท้องถิ่นที่ตรงกับเซิร์ฟเวอร์
คุณสมบัติ TimeZone กำหนดเขตเวลาสำหรับการแสดงผล โดยค่าเริ่มต้นจะใช้เขตเวลาของระบบ แต่สำหรับแอปพลิเคชันที่มีผู้ชมต่างประเทศ มักจำเป็นต้องแสดงวันที่ในเขตเวลาของผู้ใช้หรือใน UTC การเปลี่ยน timeZone มีผลต่อการแสดงผลเท่านั้น — ค่า Date ยังคงไม่เปลี่ยนแปลง
คุณสมบัติที่สำคัญ: หาก DateFormatter ถูกใช้ในการแยกวิเคราะห์สตริงและสตริงมีการระบุเขตเวลา (เช่น “2026-07-21T14:30:00Z” โดยมี Z สำหรับ UTC) คุณสมบัติ timeZone จะถูกเพิกเฉย — ตัวจัดรูปแบบใช้เขตเวลาจากสตริง หากไม่มีเขตเวลาในสตริง timeZone ของตัวจัดรูปแบบจะถูกนำไปใช้
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "ru_RU")
formatter.timeZone = TimeZone(identifier: "Europe/Moscow")
formatter.dateStyle = .long
formatter.timeStyle = .short
let moscowTime = formatter.string(from: Date())
// "21 July 2026, 14:30"
// การแยกวิเคราะห์โดยไม่มีเขตเวลาในสตริง
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.dateFormat = "yyyy-MM-dd HH:mm"
let utcDate = formatter.date(from: "2026-07-21 10:30")!
AutoupdatingCurrentLocale — ชนิดภาษาท้องถิ่นพิเศษที่อัปเดตโดยอัตโนมัติเมื่อการตั้งค่าระบบของผู้ใช้เปลี่ยนไป DateFormatter รองรับโดยค่าเริ่มต้น หากแอปพลิเคชันทำงานในพื้นหลังและผู้ใช้เปลี่ยนภาษาของระบบ ตัวจัดรูปแบบที่สร้างก่อนการเปลี่ยนแปลงจะยังคงใช้ภาษาท้องถิ่นเก่า — ในการอัปเดตจำเป็นต้องสร้างอินสแตนซ์ใหม่
ISO8601DateFormatter เป็นตัวจัดรูปแบบเฉพาะสำหรับการทำงานกับวันที่ในรูปแบบ ISO 8601 รูปแบบนี้เป็นมาตรฐานโดยพฤตินัยสำหรับ REST API, JSON และการแลกเปลี่ยนข้อมูล ISO8601DateFormatter ทำงานเร็วกว่า DateFormatter อย่างมีนัยสำคัญเนื่องจากไม่ขึ้นอยู่กับภาษาท้องถิ่นและใช้ไวยากรณ์การแยกวิเคราะห์แบบคงที่
ตัวเลือกหลักของตัวจัดรูปแบบ — .withInternetDateTime (2026-07-21T14:30:00Z), .withFractionalSeconds (เพิ่มมิลลิวินาที), .withTimeZone (รวมค่าชดเชยเขตเวลา) การรวมตัวเลือกต่าง ๆ เข้าด้วยกัน คุณจะได้รับ ISO 8601 ทุกรูปแบบ: มีมิลลิวินาที มีเขตเวลา มีเฉพาะวันที่
JSONEncoder.DateEncodingStrategy ช่วยให้กำหนดค่าการเข้ารหัสวันที่ทั่วโลกสำหรับโมเดล Codable ทั้งหมด ตัวเลือก — .iso8601 (ใช้ ISO8601DateFormatter), .formatted(DateFormatter), .millisecondsSince1970, .secondsSince1970 การเลือกกลยุทธ์ส่งผลกระทบต่อวงจรชีวิตการซีเรียลไลซ์ทั้งหมดและควรสอดคล้องกันในทุกจุดสิ้นสุด API
// ISO8601DateFormatter
let isoFormatter = ISO8601DateFormatter()
isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
let isoString = isoFormatter.string(from: Date())
// "2026-07-21T14:30:00.000Z"
// JSONEncoder กับ ISO8601
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
// ทางเลือก: JSONEncoder กับตัวจัดรูปแบบที่กำหนดเอง
let customEncoder = JSONEncoder()
customEncoder.dateEncodingStrategy = .formatted(myFormatter)
DateFormatter เทียบกับ ISO8601DateFormatter — เลือก ISO8601DateFormatter สำหรับการซีเรียลไลซ์และแยกวิเคราะห์วันที่ใน API เนื่องจากเร็วกว่า DateFormatter 5-10 เท่าและไม่เกิดข้อผิดพลาดจากการแปลเป็นภาษาท้องถิ่น ใช้ DateFormatter สำหรับอินเทอร์เฟซผู้ใช้ที่ต้องการการแสดงผลที่แปลเป็นภาษาท้องถิ่นพร้อมชื่อเดือนและวันในภาษาพื้นเมืองของผู้ใช้
มาดูสถานการณ์จริงของการใช้ DateFormatter ในแอป iOS: การแสดงวันที่ในฟีดข่าว การป้อนวันเกิด และการส่งออกรายงานที่มีวันที่ในเขตเวลาต่างกัน
RelativeDateFormatter เหมาะที่สุดสำหรับฟีดข่าว โดยแสดง “เมื่อสักครู่”, “5 นาทีที่แล้ว”, “เมื่อวาน” สำหรับข่าวล่าสุดและเปลี่ยนเป็นวันที่เต็มสำหรับข่าวเก่า เกณฑ์การสลับถูกกำหนดค่าผ่านปฏิทิน: สำหรับข่าวใช้เกณฑ์ 24 ชั่วโมง, สำหรับแอปส่งข้อความ — หนึ่งสัปดาห์
func formatRelativeDate(_ date: Date) -> String {
let relative = RelativeDateFormatter()
relative.unitsStyle = .full
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .short
let daysDiff = Calendar.current.dateComponents(
[.day], from: date, to: Date()
).day ?? 0
return daysDiff < 1
? relative.localizedString(for: date, relativeTo: Date())
: formatter.string(from: date)
}
การป้อนวันเกิด — อีกสถานการณ์ทั่วไป DateFormatter ถูกกำหนดค่าด้วย dateFormat เฉพาะ “dd.MM.yyyy” และ locale “ru_RU” เมื่อแยกวิเคราะห์สตริงที่ป้อน สิ่งสำคัญคือต้องจัดการข้อผิดพลาดที่อาจเกิดขึ้น: ตัวจัดรูปแบบคืนค่า nil สำหรับสตริงที่ไม่ถูกต้อง หลังจากการแยกวิเคราะห์สำเร็จ วันที่จะถูกตรวจสอบว่าอยู่ในช่วงที่ยอมรับได้ — ไม่ก่อนปี 1900, ไม่เกินวันนี้
การส่งออกรายงานที่มีวันที่ ต้องใช้รูปแบบคงที่ที่ไม่ขึ้นอยู่กับภาษาท้องถิ่นของผู้ใช้ ใช้ dateFormat “yyyy-MM-dd HH:mm:ss” กับ locale en_US_POSIX และเขตเวลา UTC วิธีการนี้รับประกันว่าไฟล์จะเปิดอย่างถูกต้องในทุกประเทศโดยไม่ขึ้นอยู่กับการตั้งค่าภูมิภาคของระบบ
| สถานการณ์ | ตัวจัดรูปแบบ | การตั้งค่าหลัก |
|---|---|---|
| ฟีดข่าว | RelativeDateFormatter | unitsStyle = .full |
| การป้อนวันที่ | DateFormatter | dateFormat + ทางเลือกสำรอง |
| การซีเรียลไลซ์ API | ISO8601DateFormatter | withInternetDateTime |
| การส่งออกรายงาน | DateFormatter | en_US_POSIX + UTC |
คำถามที่พบบ่อย
สาเหตุที่พบบ่อยที่สุด — ความไม่ตรงกันระหว่าง dateFormat กับรูปแบบสตริง ตัวอย่างเช่น รูปแบบ “dd.MM.yyyy” จะไม่แยกวิเคราะห์สตริง “2026-07-21” สาเหตุที่สอง — ความไม่ตรงกันของภาษาท้องถิ่น: สตริง “July 21, 2026” จะไม่ถูกแยกวิเคราะห์ด้วยภาษาท้องถิ่น ru_RU สาเหตุที่สาม — การพิมพ์ผิดในสัญลักษณ์ LDML: ใช้ yyyy ไม่ใช่ YYYY (ความหมายต่างกัน)
ไม่ DateFormatter เป็นวัตถุหนัก การเริ่มต้นรวมถึงการโหลดข้อมูลภาษาท้องถิ่น สร้างหนึ่งอินสแตนซ์ต่อประเภทการจัดรูปแบบและนำกลับมาใช้ใหม่ ในสภาพแวดล้อมแบบหลายเธรด ใช้พื้นที่จัดเก็บเฉพาะเธรดหรือพูลตัวจัดรูปแบบที่มีคิวแบบอนุกรมสำหรับการซิงโครไนซ์
DateFormatter แสดงวันที่แบบสัมบูรณ์ (21 กรกฎาคม 2026) ในขณะที่ RelativeDateFormatter แสดงวันที่แบบสัมพัทธ์ (วันนี้ เมื่อวาน ใน 3 วัน) RelativeDateFormatter ถูกนำมาใช้ใน iOS 15+ และใช้เทมเพลต LDML เดียวกัน แต่เลือกการแสดงแบบสัมพัทธ์โดยอัตโนมัติ
ตั้งค่า timeZone ของตัวจัดรูปแบบ เป็น UTC ก่อนแยกวิเคราะห์ หากเซิร์ฟเวอร์คืนวันที่ในเวลาท้องถิ่นโดยไม่ระบุเขตเวลา ให้ตรวจสอบข้อกำหนดของ API — ส่วนใหญ่หมายถึง UTC สำหรับ ISO 8601 ที่ลงท้ายด้วย Z ไม่จำเป็นต้องใช้ timeZone — ตัวจัดรูปแบบแยกวิเคราะห์ค่าชดเชยจากสตริง
อย่าใช้อินสแตนซ์เดียวจากเธรดต่างกัน โดยไม่มีการซิงโครไนซ์ สร้างอินสแตนซ์ใหม่ในแต่ละเธรดหรือใช้ Thread.current.threadDictionary สำหรับจัดเก็บ ทางเลือกคือ NSLock ที่ล็อกระหว่างการทำงานของ string(from:) และ date(from:)
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม