TimeZone คือคลาส Foundation ใน iOS และ macOS ที่สรุปข้อมูลโซนเวลาเพื่อการแปลงเวลาที่ถูกต้องระหว่างภูมิภาคทางภูมิศาสตร์ ตาม Apple Developer Documentation, 2024 TimeZone มีวิธีการสำหรับทำงานกับตัวระบุโซนเวลา (IANA Time Zone Database) ค่าชดเชยเทียบกับ UTC และกฎการเปลี่ยนไปใช้เวลาออมแสง คลาสนี้รวมเข้ากับ DateFormatter และ Calendar เพื่อให้แน่ใจว่ามีการใช้โซนเวลาที่ถูกต้องโดยอัตโนมัติเมื่อจัดรูปแบบวันที่ แตกต่างจากการคำนวณค่าชดเชยด้วยตนเอง TimeZone จะอัปเดตข้อมูลโดยอัตโนมัติเมื่อโซนเวลาของอุปกรณ์เปลี่ยนแปลง
ประเด็นสำคัญ
TimeZone คือชนิดค่าใน Swift ที่ให้ข้อมูลเกี่ยวกับโซนเวลาทางภูมิศาสตร์: ค่าชดเชย UTC ชื่อ ตัวย่อ และกฎเวลาออมแสง ใน Objective-C คลาสนี้เรียกว่า NSTimeZone ทั้งสองคลาสอาศัย IANA Time Zone Database (หรือที่รู้จักในชื่อฐานข้อมูล Olson) ซึ่งมีประวัติการเปลี่ยนแปลงโซนเวลาตั้งแต่ปี 1970
แต่ละอินสแตนซ์ของ TimeZone จะเก็บตัวระบุโซนเวลา (เช่น Europe/Moscow) ค่าชดเชยปัจจุบันเป็นวินาทีจาก UTC แฟล็ก isDaylightSavingTime และวันที่ของการเปลี่ยนครั้งถัดไป ตัวระบุเป็นคีย์หลัก: เมื่อเริ่มต้น TimeZone(identifier:) ระบบจะโหลดเรกคอร์ดที่เกี่ยวข้องจากฐานข้อมูลโซนเวลาของอุปกรณ์
ตาม IANA (2024) ฐานข้อมูลมีตัวระบุโซนเวลาที่ไม่ซ้ำกันมากกว่า 600 รายการ Apple จัดทำสแนปชอตของฐานข้อมูลนี้ในทุก ๆ รุ่นของ iOS และ macOS เพื่อให้แน่ใจว่ามีการคำนวณที่สอดคล้องกันในทุกอุปกรณ์โดยไม่ต้องใช้คำขอเครือข่าย
สถาปัตยกรรม ของ TimeZone ใน Foundation สร้างขึ้นบนระบบสองระดับ: ตัวระบุโซนเวลา (ชื่อที่มนุษย์อ่านได้) และการแสดงเป็นตัวเลข (ค่าชดเชย UTC) ระบบจะเลือกโซนเวลาปัจจุบันจากการตั้งค่าอุปกรณ์โดยอัตโนมัติ แต่ผู้พัฒนาสามารถแทนที่สำหรับการดำเนินการจัดรูปแบบเฉพาะได้
TimeZone เชื่อมโยงอย่างใกล้ชิดกับ Calendar และ DateFormatter เมื่อจัดรูปแบบวันที่ DateFormatter จะใช้คุณสมบัติ timeZone ของอินสแตนซ์ TimeZone เพื่อแปลงช่วงเวลาสัมบูรณ์ (Date) เป็นการแสดงสตริงในโซนเวลาที่ต้องการ หากไม่ได้ตั้งค่า timeZone จะใช้โซนเวลาระบบเริ่มต้น — TimeZone.current
| ประเภท | การเริ่มต้น | คุณสมบัติ |
|---|---|---|
| ปัจจุบัน | TimeZone.current | อัปเดตโดยอัตโนมัติเมื่อเปลี่ยนภูมิภาคในการตั้งค่า ติดตามเวลาออมแสง |
| คงที่ | TimeZone(identifier:) | ไม่ขึ้นกับภูมิภาคของอุปกรณ์ ใช้ตัวระบุที่เลือกอย่างสม่ำเสมอ |
| UTC | TimeZone(secondsFromGMT: 0) | โซนเวลาไม่มีการแก้ไข ตัวระบุ: GMT |
| ค่าชดเชยตามอำเภอใจ | TimeZone(secondsFromGMT: 10800) | ค่าชดเชยคงที่เป็นวินาที ไม่คำนึงถึงเวลาออมแสง |
หมายเหตุสำคัญ: TimeZone(identifier:) จะคืนค่า nil สำหรับตัวระบุที่ไม่รู้จัก นี่เป็นสาเหตุทั่วไปของแอปคราช — นักพัฒนาลืมจัดการค่า optional เมื่อส่งตัวระบุที่ไม่ถูกต้องจากอินพุตของผู้ใช้ สำหรับตัวระบุ IANA ตัวพิมพ์ใหญ่/เล็กมีความสำคัญ: Europe/Moscow ใช้ได้, europe/moscow คืนค่า nil
IANA Time Zone Database ใช้รูปแบบ “ภูมิภาค/เมือง” (ทวีป/เมือง) โดยที่ภูมิภาคคือหนึ่งในทวีป (Africa, America, Asia, Atlantic, Australia, Europe, Indian, Pacific) หรือมหาสมุทร และเมืองคือท้องที่ที่มีประชากรมากที่สุดในพื้นที่ครอบคลุมของโซนเวลา รูปแบบนี้รับประกันความเป็นเอกลักษณ์และความสามารถในการอ่านของตัวระบุ
นอกจากรูปแบบหลักแล้ว TimeZone ยังรองรับวิธีการระบุเพิ่มเติมอีกสามวิธี: ตัวย่อ (MSK, EST, PST) รหัสโซนเวลาสามตัวอักษร (GMT, UTC) และค่าชดเชยตัวเลข (+0300, -0500) อย่างไรก็ตาม ตัวย่อมีความกำกวม: EST อาจหมายถึง Eastern Standard Time (GMT-5) หรือ Eastern Summer Time (GMT+10) ในออสเตรเลีย Apple แนะนำให้ใช้เฉพาะตัวระบุ IANA เท่านั้น
import Foundation
// รับตัวระบุโซนเวลาที่รู้จักทั้งหมด
let allIdentifiers: [String] = TimeZone.knownTimeZoneIdentifiers
print("โซนเวลาทั้งหมด: \(allIdentifiers.count)")
// กรองตามภูมิภาค
let europeZones = allIdentifiers.filter { $0.hasPrefix("Europe/") }
print("โซนเวลายุโรป: \(europeZones)")
// ตัวย่อ (ไม่แนะนำสำหรับการใช้งานจริง)
if let moscowTimeZone = TimeZone(abbreviation: "MSK") {
print("วินาที MSK จาก GMT: \(moscowTimeZone.secondsFromGMT())")
}
// ค้นหาตัวระบุตามค่าชดเชย
let utcPlus3 = TimeZone(secondsFromGMT: 10800)
print("ตัวระบุ: \(utcPlus3.identifier)")
ตัวย่อ ใน TimeZone.abbreviationDictionary มีตัวย่อสำหรับโซนเวลาที่รู้จักทั้งหมด แต่พจนานุกรมนี้ไม่รับประกันความเป็นเอกลักษณ์: คีย์ PST อาจตรงกับ America/Los_Angeles หรือ Pacific/Pago_Pago สำหรับโค้ดที่ใช้งานจริง ให้ใช้ตัวระบุ IANA เสมอ
TimeZone จะพิจารณาการเปลี่ยนไปใช้เวลาออมแสง (DST) โดยอัตโนมัติสำหรับทุกภูมิภาคที่มีการปฏิบัติตาม ระบบใช้ข้อมูลทางประวัติศาสตร์จาก IANA Time Zone Database ซึ่งรวมถึงวันที่เปลี่ยนที่แม่นยำสำหรับแต่ละโซนเวลา คุณสมบัติ isDaylightSavingTime จะคืนค่า true หากโซนเวลาอยู่ในเวลาออมแสงในปัจจุบัน
เมธอด nextDaylightSavingTimeTransition ช่วยให้ทราบวันที่ของการเปลี่ยนครั้งถัดไป ซึ่งมีประโยชน์สำหรับการวางแผนเหตุการณ์ในอนาคต ฟังก์ชันนี้สำคัญโดยเฉพาะสำหรับภูมิภาคที่มีการเปลี่ยนแปลงกฎ DST บ่อยครั้ง เช่น บราซิลหรือโมร็อกโก — จนถึงปี 2024 บราซิลเปลี่ยนวันที่เปลี่ยนทุกปี และการคำนวณด้วยตนเองนำไปสู่ข้อผิดพลาดในแอปพลิเคชัน
ตาม Apple WWDC 2023 ไลบรารี ICU (International Components for Unicode) ซึ่งเป็นพื้นฐานของ Foundation จะอัปเดตข้อมูล DST ทุกครั้งที่มีการอัปเดต iOS แอปพลิเคชันไม่ควรแคชข้อมูลเวลาออมแสงนานกว่าหนึ่งวันหลังจากการอัปเดตระบบ — ฐานข้อมูล IANA อาจเปลี่ยนแปลงได้แม้ไม่มีการอัปเดตเวอร์ชัน OS ผ่านการปรับโซนเวลา
import Foundation
// ตรวจสอบ DST สำหรับ Europe/Moscow
let moscow = TimeZone(identifier: "Europe/Moscow")!
let now = Date()
let isMoscowDST = moscow.isDaylightSavingTime(for: now)
print("มอสโกปัจจุบันอยู่ใน DST: \(isMoscowDST)")
// รับวันที่เปลี่ยน DST ถัดไป
if let nextTransition = moscow.nextDaylightSavingTimeTransition(
after: now
) {
let dstOffset = moscow.daylightSavingTimeOffset(
for: nextTransition
)
print("การเปลี่ยนถัดไป: \(nextTransition), ค่าชดเชย DST: \(dstOffset)s")
}
// การแปลงอย่างปลอดภัยด้วยการรับรู้ DST
let newYork = TimeZone(identifier: "America/New_York")!
let offsetNY = newYork.secondsFromGMT(for: now)
print("ค่าชดเชยปัจจุบันของ NY: \(offsetNY / 3600)h")
ความแตกต่างที่สำคัญ: secondsFromGMT(for:) จะพิจารณา DST สำหรับวันที่ที่ระบุ ในขณะที่ secondsFromGMT() ใช้กับเวลาปัจจุบันเท่านั้น เมื่อจัดรูปแบบวันที่ในอดีต ให้ใช้เวอร์ชันที่มีพารามิเตอร์ Date เสมอ: secondsFromGMT(for: someHistoricalDate) ความแตกต่างอาจสูงถึง 1–2 ชั่วโมง ซึ่งสำคัญสำหรับล็อกหรือข้อมูลในอดีต
การจัดรูปแบบวันที่ ด้วยโซนเวลาเฉพาะเป็นงานที่พบบ่อยที่สุดเมื่อทำงานกับ TimeZone DateFormatter ใช้คุณสมบัติ timeZone เพื่อแปลง Date เป็นสตริง หากไม่ได้ตั้งค่า timeZone อย่างชัดเจน ตัวจัดรูปแบบจะใช้ TimeZone.current — โซนเวลาที่ตั้งบนอุปกรณ์ของผู้ใช้ ซึ่งอาจนำไปสู่ผลลัพธ์ที่ไม่คาดคิดสำหรับข้อมูลเซิร์ฟเวอร์
import Foundation
// จัดรูปแบบวันที่ในโซนเวลาเฉพาะ
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let tokyo = TimeZone(identifier: "Asia/Tokyo")!
formatter.timeZone = tokyo
let tokyoTime = formatter.string(from: Date())
print("เวลาโตเกียว: \(tokyoTime)")
// ตัวระบุที่พร้อมใช้งานสำหรับการเลือกผู้ใช้
let displayNames: [(String, String)] = TimeZone.knownTimeZoneIdentifiers
.prefix(20)
.map { ($0, TimeZone(identifier: $0)!.localizedName(
for: .generic, locale: .current
)) }
// เปรียบเทียบสองโซนเวลา
let london = TimeZone(identifier: "Europe/London")!
let difference = tokyo.secondsFromGMT(for: Date())
- london.secondsFromGMT(for: Date())
print("ความแตกต่างโตเกียว-ลอนดอน: \(difference / 3600)h")
// ทำงานกับพจนานุกรมตัวย่อ
let knownAbbrevs = TimeZone.abbreviationDictionary
for (abbr, ident) in knownAbbrevs.sorted(by: { $0.key < $1.key }).prefix(5) {
print("\(abbr) -> \(ident)")
}
ชื่อที่แปลเป็นภาษา local ของโซนเวลาผ่าน localizedName(for:locale:) จะคืนชื่อที่มนุษย์อ่านได้ในภาษาที่ระบุ ตัวอย่างเช่น สำหรับ Europe/Moscow กับ locale รัสเซีย เมธอดจะคืนชื่อภาษารัสเซีย “Moskva” และกับ locale อังกฤษ — “Moscow Time” สไตล์ที่ใช้ได้: .standard (ชื่อมาตรฐาน), .daylightSaving (เวลาออมแสง) และ .shortGeneric (สั้น)
import Foundation
let paris = TimeZone(identifier: "Europe/Paris")!
let nameRU = paris.localizedName(
for: .standard,
locale: Locale(identifier: "ru_RU")
)
print("ชื่อภาษารัสเซีย: \(nameRU)")
// ตรวจสอบว่าภูมิภาคอยู่ในวันเดียวกันหรือไม่
let isSameDay = Calendar.current.isDate(
Date(),
equalTo: Date(),
toGranularity: .day
)
print("วันเดียวกันในโซนเวลาต่างกัน: \(isSameDay)")
การทำให้เป็นลำดับ ของตัวระบุโซนเวลาเป็นแนวทางปฏิบัติที่ดีที่สุดสำหรับการจัดเก็บ TimeZone ในฐานข้อมูลหรือ UserDefaults บันทึกตัวระบุ (สตริงเช่น Europe/Moscow) ไม่ใช่ค่าชดเชยเป็นวินาทีหรือตัวย่อ ค่าชดเชยสามารถเปลี่ยนแปลงได้ตามการเปลี่ยนแปลง DST และตัวย่อก็กำกวม การกู้คืน: TimeZone(identifier: savedString)
การใช้ค่าชดเชยคงที่ แทนตัวระบุโซนเวลาเป็นข้อผิดพลาดที่พบบ่อยที่สุด TimeZone(secondsFromGMT: 10800) ไม่พิจารณา DST ดังนั้นสำหรับ Europe/Moscow ในฤดูร้อน โครงสร้างนี้ให้ค่าชดเชยที่ผิดพลาด 1 ชั่วโมง ให้ใช้ตัวระบุ IANA สำหรับภูมิภาคที่มีเวลาออมแสงเสมอ
การจัดการ nil ที่ถูกลืม เมื่อเริ่มต้น TimeZone(identifier:) เป็นข้อผิดพลาดที่พบบ่อยเป็นอันดับสอง หากผู้ใช้ป้อนตัวระบุที่ไม่ถูกต้อง (เช่น “moscow” แทน “Europe/Moscow”) ตัวสร้างจะคืนค่า nil หากไม่จัดการค่า optional แอปจะคราชด้วยข้อผิดพลาดขณะรันไทม์ ใช้ guard let หรือ TimeZone(identifier:) กับ fallback ที่รู้จัก
การละเลย DST เมื่อทำงานกับวันที่ในอนาคต TimeZone.secondsFromGMT(for:) เป็นวิธีที่ถูกต้องเท่านั้นในการรับค่าชดเชยสำหรับวันที่เฉพาะ การใช้ secondsFromGMT() โดยไม่มีพารามิเตอร์สำหรับวันที่ในอดีตหรืออนาคตจะให้ค่าชดเชยสำหรับช่วงเวลาปัจจุบัน ซึ่งอาจไม่ตรงกับค่าชดเชยจริงในวันที่ระบุ โดยเฉพาะสำหรับภูมิภาคที่ยกเลิกหรือเริ่มใช้ DST
ตาม Stack Overflow (2024) ประมาณ 15% ของคำถามเกี่ยวกับ DateFormatter เกี่ยวข้องกับการกำหนดค่า timeZone ที่ไม่ถูกต้อง สถานการณ์ทั่วไป: เซิร์ฟเวอร์ส่งวันที่ใน UTC นักพัฒนาจัดรูปแบบโดยไม่ตั้งค่า timeZone ของตัวจัดรูปแบบ และวันที่แสดงในโซนเวลาของอุปกรณ์ ทำให้เกิดความสับสนในหมู่ผู้ใช้จากภูมิภาคต่าง ๆ กฎ: ตั้งค่า timeZone ของตัวจัดรูปแบบอย่างชัดเจนเสมอสำหรับข้อมูลเซิร์ฟเวอร์
คำถามที่พบบ่อย
TimeZone คือคลาส Foundation สำหรับทำงานกับโซนเวลาใน iOS และ macOS โดยให้ข้อมูลเกี่ยวกับค่าชดเชย UTC กฎเวลาออมแสง และตัวระบุโซนเวลาตาม IANA Time Zone Database
สามรูปแบบ: ตัวระบุ IANA (Europe/Moscow) ตัวย่อ (MSK, EST) และค่าชดเชยตัวเลข (+0300) Apple แนะนำให้ใช้ตัวระบุ IANA เป็นรูปแบบที่ชัดเจนเท่านั้นสำหรับโค้ดที่ใช้งานจริง
โดยอัตโนมัติ ผ่านเมธอด secondsFromGMT(for:) และ isDaylightSavingTime(for:) TimeZone ใช้ข้อมูล IANA ในอดีตที่อัปเดตทุก iOS รุ่น เพื่อให้แน่ใจว่ามีการเปลี่ยน DST ที่ถูกต้องสำหรับวันที่ใด ๆ
TimeZone.current คืนค่าโซนเวลาที่ผู้ใช้เลือกในการตั้งค่า (อาจแตกต่างจากโซนเวลาทางภูมิศาสตร์) TimeZone.system คืนค่าโซนเวลาของอุปกรณ์ซึ่งถูกกำหนดโดยอัตโนมัติตามตำแหน่งทางภูมิศาสตร์และไม่สามารถแทนที่โดยผู้ใช้
TimeZone.current คืนค่าโซนเวลาปัจจุบันของอุปกรณ์ หากต้องการรับตัวระบุ ให้ใช้คุณสมบัติ identifier: TimeZone.current.identifier สำหรับชื่อที่แปลเป็นภาษา local ให้เรียก localizedName(for:locale:)
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม