การจัดเก็บการนัดหมายในฐานข้อมูล SQL เช่น Postgres สำหรับใช้กับ java.time framework
สมมติว่าเรามีนัดที่มิลานอิตาลีในวันที่ 23/01/2021 21:00 "ยุโรป / โรม" นัดนี้จะถูกบันทึกลงฐานข้อมูลใน UTC TIMESTAMP WITH TIME ZONEในคอลัมน์ชนิดหนึ่งที่คล้ายกับชนิด
ตอนนี้ผู้ใช้ที่อาศัยอยู่ในนิวยอร์กสหรัฐอเมริกาจำเป็นต้องเข้าใจว่าการนัดหมายนี้จะเกิดขึ้นเมื่อใด เราสามารถแสดงให้ผู้ใช้ทราบว่าวันที่ - เวลาแปลงเป็นเขตเวลา "America / New_York" หรือแทนใน TZ "Europe / Rome" เมื่อผู้ใช้บินจากนิวยอร์กไปมิลานเขาจะพบว่าข้อมูลทั้งสองมีประโยชน์
ประเด็นคือการจัดเก็บทุกอย่างที่แปลงเป็นข้อมูลอ้างอิง TZ (UTC) เดียวกันและจัดการวันที่ - เวลาขึ้นอยู่กับเป้าหมายที่คุณใช้กรอบงานjava.time ที่มาพร้อมกับ Java สมัยใหม่
เข้าท่าหรือมีบางอย่างผิดปกติ / ขาดหายไป?
คำตอบ
เข้าท่าหรือมีบางอย่างผิดปกติ / ขาดหายไป?
ขึ้นอยู่กับชนิดของการนัดหมาย
การนัดหมายมีสองประเภท:
- ครู่หนึ่งจุดใดจุดหนึ่งบนไทม์ไลน์โดยไม่สนใจการเปลี่ยนแปลงใด ๆ กับกฎเขตเวลา
ตัวอย่าง: การปล่อยจรวด - วันที่และเวลาของวันที่ควรปรับเปลี่ยนตามการเปลี่ยนแปลงกฎเขตเวลา
ตัวอย่าง: การไปพบแพทย์ / ทันตกรรม
ช่วงเวลา
ตัวอย่างเช่นหากเราจองการปล่อยจรวดเราไม่สนใจวันที่และเวลาของวัน เราสนใจเฉพาะช่วงเวลาที่ (ก) สวรรค์จัดแนวและ (ข) เราคาดหวังว่าจะมีอากาศเอื้ออำนวย
หากในช่วงเวลาที่มีการแทรกแซงนักการเมืองเปลี่ยนกฎของเขตเวลาที่ใช้ที่ไซต์เปิดตัวของเราหรือที่สำนักงานของเรานั่นจะไม่มีผลต่อการนัดหมายเปิดตัวของเรา หากนักการเมืองที่ควบคุมไซต์เปิดตัวของเราใช้เวลาออมแสง (DST)ช่วงเวลาของการเปิดตัวจะยังคงเหมือนเดิม หากนักการเมืองที่ควบคุมสำนักงานของเราตัดสินใจที่จะเปลี่ยนนาฬิกาเร็วขึ้นครึ่งชั่วโมงเนื่องจากความสัมพันธ์ทางการทูตกับประเทศเพื่อนบ้านช่วงเวลาแห่งการเปิดตัวของเราก็ยังคงเหมือนเดิม
สำหรับการนัดหมายเช่นนี้ใช่แนวทางของคุณจะถูกต้อง คุณจะได้รับการแต่งตั้งบันทึกในUTCTIMESTAMP WITH TIME ZONEโดยใช้คอลัมน์ชนิด เมื่อดึงข้อมูลแล้วให้ปรับเปลี่ยนเป็นเขตเวลาใดก็ได้ที่ผู้ใช้ต้องการ
ฐานข้อมูลเช่นPostgresใช้ข้อมูลโซนเวลาใด ๆ ที่มาพร้อมกับอินพุตเพื่อปรับเป็น UTC จากนั้นจึงทิ้งข้อมูลโซนเวลานั้น เมื่อคุณดึงค่าจาก Postgres ค่านี้จะแสดงวันที่พร้อมเวลาของวันตามที่เห็นใน UTC เสมอ ระวังเครื่องมือหรือมิดเดิลแวร์บางตัวอาจมีคุณสมบัติต่อต้านการใช้เขตเวลาเริ่มต้นระหว่างการดึงข้อมูลจากฐานข้อมูลและการส่งมอบให้กับคุณโปรแกรมเมอร์ แต่ชัดเจน: Postgres จะบันทึกและดึงค่าประเภทTIMESTAMP WITH TIME ZONEใน UTC เสมอ UTC เสมอและชดเชยจาก UTCของศูนย์ชั่วโมงนาที - วินาที
นี่คือตัวอย่างโค้ด Java
LocalDate launchDateAsSeenInRome = LocalDate.of( 2021 , 1 , 23 ) ;
LocalTime launchTimeAsSeenInRome = LocalTime.of( 21 , 0 ) ;
ZoneId zoneEuropeRome = ZoneId.of( "Europe/Rome" ) ;
// Assemble those three parts to determine a moment.
ZonedDateTime launchMomentAsSeenInRome = ZonedDateTime.of( launchDateAsSeenInRome , launchTimeAsSeenInRome , zoneEuropeRome ) ;
LaunchMomentAsSeenInRome.toString (): 2021-01-23T21: 00 + 01: 00 [ยุโรป / โรม]
หากต้องการดูช่วงเวลาเดียวกันใน UTC ให้แปลงเป็นInstantไฟล์. Instantวัตถุเสมอหมายถึงช่วงเวลาที่เท่าที่เห็นใน UTC
Instant launchInstant = launchMomentAsSeenInRome.toInstant() ; // Adjust from Rome time zone to UTC.
LaunchInstant.toString (): 2021-01-23T20: 00: 00Z
Zที่สิ้นสุดของสตริงตัวอย่างข้างต้นเป็นสัญกรณ์มาตรฐาน UTC และเด่นชัดคือ“ซูลู”
น่าเสียดายที่ทีมงาน JDBC 4.2 ละเลยที่จะต้องการการสนับสนุนสำหรับอย่างใดอย่างหนึ่งInstantหรือZonedDateTimeประเภท ดังนั้นไดรเวอร์ JDBCของคุณอาจหรือไม่สามารถอ่าน / เขียนวัตถุดังกล่าวไปยังฐานข้อมูลของคุณได้ ถ้าไม่เพียงแค่แปลงเป็นOffsetDateTimeไฟล์. ทั้งสามประเภทแสดงถึงช่วงเวลาหนึ่งซึ่งเป็นจุดที่เฉพาะเจาะจงบนไทม์ไลน์ แต่OffsetDateTimeมีการสนับสนุนที่จำเป็นโดยJDBC 4.2สำหรับเหตุผลที่หนีฉัน
OffsetDateTime odtLaunchAsSeenInRome = launchMomentAsSeenInRome.toOffsetDateTime() ;
การเขียนลงฐานข้อมูล
myPreparedStatement.setObject( … , odtLaunchAsSeenInRome ) ;
การดึงข้อมูลจากฐานข้อมูล
OffsetDateTime launchMoment = myResultSet.getObject( … , OffsetDateTime.class ) ;
ปรับเป็นเขตเวลานิวยอร์กที่ผู้ใช้ของคุณต้องการ
ZoneId zoneAmericaNewYork = ZoneId.of( "America/New_York" ) ;
ZonedDateTime launchAsSeenInNewYork = launchMoment.atZoneSameInstant( zoneAmericaNewYork ) ;
launchAsSeenInNewYork.toString (): 2021-01-23T15: 00-05: 00 [อเมริกา / New_York]
ท่านสามารถเข้าดูทั้งหมดข้างต้นรหัสวิ่งอยู่ที่ IdeOne.com
อย่างไรก็ตามการติดตามเหตุการณ์ที่ผ่านมาถือเป็นช่วงเวลาหนึ่งเช่นกัน ผู้ป่วยมาถึงนัดจริงเมื่อใดลูกค้าชำระใบแจ้งหนี้เมื่อใดผู้ว่าจ้างรายใหม่เซ็นเอกสารเมื่อใดเซิร์ฟเวอร์ขัดข้องเมื่อใด…ทั้งหมดนี้จะถูกติดตามเป็นช่วงเวลาใน UTC ตามที่กล่าวไว้ข้างต้นโดยปกติแล้วจะเป็นInstantแม้ว่าZonedDateTime& OffsetDateTimeยังแสดงถึงช่วงเวลาหนึ่ง สำหรับฐานข้อมูลใช้TIMESTAMP WITH TIME ZONE(ไม่WITHOUT)
เวลาของวัน
ฉันคาดว่าแอปที่มุ่งเน้นธุรกิจส่วนใหญ่จะเน้นไปที่การนัดหมายประเภทอื่นซึ่งเราตั้งเป้าหมายไปที่วันที่ด้วยช่วงเวลาของวันแทนที่จะเป็นช่วงเวลาที่เฉพาะเจาะจง
หากผู้ใช้นัดหมายกับผู้ให้บริการด้านการดูแลสุขภาพเพื่อตรวจสอบผลการทดสอบผู้ใช้จะดำเนินการในช่วงเวลาหนึ่งของวันในวันนั้น หากในระหว่างนี้นักการเมืองเปลี่ยนกฎของเขตเวลาของตนเลื่อนนาฬิกาไปข้างหน้าหรือข้างหลังหนึ่งชั่วโมงครึ่งชั่วโมงหรือระยะเวลาอื่น ๆ วันที่และเวลาของวันของการนัดพบแพทย์นั้นยังคงเหมือนเดิม ตามความเป็นจริงจุดของเส้นเวลาของการนัดหมายเดิมจะเปลี่ยนไปหลังจากที่นักการเมืองเปลี่ยนเขตเวลาโดยเปลี่ยนไปยังจุดก่อนหน้า / ในภายหลังบนไทม์ไลน์
สำหรับการนัดหมายดังกล่าวเราจะไม่จัดเก็บวันที่และเวลาของวันตามที่เห็นใน UTC เราไม่ได้TIMESTAMP WITH TIME ZONEใช้ฐานข้อมูลชนิดคอลัมน์
สำหรับการนัดหมายดังกล่าวเราจะจัดเก็บวันที่พร้อมเวลาของวันโดยไม่คำนึงถึงเขตเวลาใด ๆ เราใช้คอลัมน์ประเภทฐานข้อมูลTIMESTAMP WITHOUT TIME ZONE(สังเกตWITHOUTมากกว่าWITH) ประเภทที่ตรงกันใน Java LocalDateTimeเป็น
LocalDate medicalApptDate = LocalDate.of( 2021 , 1 , 23 ) ;
LocalTime medicalApptTime = LocalTime.of( 21 , 0 ) ;
LocalDateTime medicalApptDateTime = LocalDateTime.of( medicalApptDate , medicalApptTime ) ;
เขียนลงในฐานข้อมูล
myPreparedStatement.setObject( … , medicalApptDateTime ) ;
มีความชัดเจนในเรื่องนี้: LocalDateTimeวัตถุไม่ได้แสดงถึงช่วงเวลาไม่ใช่จุดเฉพาะบนไทม์ไลน์ LocalDateTimeวัตถุหมายถึงช่วงของความเป็นไปได้ในช่วงเวลาที่พร้อมประมาณ 26-27 ชั่วโมงระยะเวลา (ช่วงของโซนเวลาทั่วโลก) ในการให้ความหมายที่แท้จริงกับ a LocalDateTimeเราต้องเชื่อมโยงเขตเวลาที่ต้องการ
สำหรับเขตเวลาที่ต้องการนั้นให้ใช้คอลัมน์ที่สองเพื่อจัดเก็บตัวระบุโซน ตัวอย่างเช่นสตริงEurope/RomeหรือAmerica/New_York. ดูรายชื่อโซน
ZoneId zoneEuropeRome = ZoneId.of( "Europe/Rome" ) ;
เขียนลงในฐานข้อมูลเป็นข้อความ
myPreparedStatement.setString( … , zoneEuropeRome ) ;
การดึงข้อมูล ดึงชื่อโซนเป็นข้อความและสร้างอินสแตนซ์ZoneIdวัตถุ
LocalDateTime medicalApptDateTime = myResultSet.getObject( … , LocalDateTime.class ) ;
ZoneId medicalApptZone = ZoneId.of( myResultSet.getString( … ) ) ;
รวมทั้งสองชิ้นเข้าด้วยกันเพื่อกำหนดช่วงเวลาที่แสดงเป็นZonedDateTimeวัตถุ ทำสิ่งนี้แบบไดนามิกเมื่อคุณต้องการกำหนดเวลาปฏิทิน แต่ไม่ได้เก็บช่วงเวลาที่ หากนักการเมืองกำหนดเขตเวลาใหม่ในอนาคตจะต้องคำนวณช่วงเวลาที่แตกต่างออกไป
ZonedDateTime medicalApptAsSeenInCareProviderZone = ZonedDateTime.of( medicalApptDateTime , medicalApptZone ) ;
ผู้ใช้กำลังเดินทางไปนิวยอร์กสหรัฐอเมริกา พวกเขาจำเป็นต้องรู้ว่าเมื่อใดควรโทรหาผู้ให้บริการด้านการดูแลสุขภาพในมิลานอิตาลีตามนาฬิกาบนผนังในที่ตั้งชั่วคราวของนิวยอร์ก ดังนั้นปรับเปลี่ยนจากโซนเวลาหนึ่งไปยังอีกโซนหนึ่ง ช่วงเวลาเดียวกันเวลานาฬิกาแขวนต่างกัน
ZoneId zoneAmericaNewYork = ZoneId.of( "America/New_York" ) ;
ZonedDateTime medicalApptAsSeenInNewYork = medicalApptAsSeenInCareProviderZone.withZoneSameInstant( zoneAmericaNewYork ) ;
tzdata
โปรดทราบว่าหากกฎของเขตเวลาที่คุณต้องการอาจเปลี่ยนแปลงคุณต้องอัปเดตสำเนาของข้อกำหนดเขตเวลาบนคอมพิวเตอร์ของคุณ
Java มีสำเนาของtzdataเช่นเดียวกับกลไกจัดการฐานข้อมูล Postgres และระบบปฏิบัติการโฮสต์ของคุณด้วย โค้ดเฉพาะที่แสดงที่นี่ต้องการเฉพาะ Java เท่านั้นที่จะเป็นปัจจุบัน หากคุณใช้ Postgres เพื่อทำการปรับเปลี่ยนเขตเวลาtzdataของมันจะต้องเป็นข้อมูลล่าสุดด้วย และสำหรับการบันทึกดังนั้นระบบปฏิบัติการโฮสต์ของคุณควรได้รับการอัปเดตอยู่เสมอ เพื่อให้ผู้ใช้รับชมนาฬิกาได้อย่างเหมาะสมระบบปฏิบัติการของเครื่องไคลเอ็นต์จะต้องเป็นปัจจุบันด้วย
ระวัง: นักการเมืองทั่วโลกแสดงความชื่นชอบในการเปลี่ยนเขตเวลาด้วยความถี่ที่น่าประหลาดใจและมักจะมีการเตือนล่วงหน้าเพียงเล็กน้อย
เกี่ยวกับjava.time
java.timeกรอบถูกสร้างขึ้นใน Java 8 และต่อมา ชั้นเรียนเหล่านี้แย่งลำบากเก่ามรดกเรียนวันที่เวลาเช่นjava.util.Date, และCalendarSimpleDateFormat
ต้องการเรียนรู้เพิ่มเติมโปรดดูที่ออราเคิลกวดวิชา และค้นหา Stack Overflow สำหรับตัวอย่างและคำอธิบายมากมาย สเปกJSR 310
โครงการJoda-Timeขณะนี้อยู่ในโหมดการบำรุงรักษาแนะนำการโยกย้ายไปยังคลาสjava.time
คุณสามารถแลกเปลี่ยนวัตถุjava.timeโดยตรงกับฐานข้อมูลของคุณ ใช้ไดรเวอร์ JDBC ที่สอดคล้องกับJDBC 4.2หรือใหม่กว่า ไม่จำเป็นต้องมีสตริงไม่จำเป็นต้องมีjava.sql.*คลาส Hibernate 5 & JPA 2.2 สนับสนุนjava.time
จะหาคลาส java.time ได้ที่ไหน?
- Java SE 8 , Java SE 9 , Java SE 10 , Java SE 11และใหม่กว่า - เป็นส่วนหนึ่งของ Java API มาตรฐานพร้อมการใช้งานแบบรวม
- Java 9นำเสนอคุณสมบัติและการแก้ไขเล็กน้อย
- Java SE 6และ Java SE 7
- ส่วนใหญ่ของjava.timeการทำงานจะกลับรังเพลิง Java 6 และ 7 ในThreeTen-ย้ายกลับ
- Android
- การใช้งานบันเดิล Android (26+) เวอร์ชันที่ใหม่กว่าของคลาสjava.time
- สำหรับ Android รุ่นก่อนหน้า (<26) กระบวนการที่เรียกว่าAPI desugaringจะนำส่วนย่อยของฟังก์ชันjava.time ที่ไม่ได้ติดตั้งไว้ใน Android
- หาก desugaring ไม่ได้มีสิ่งที่คุณต้องการที่ThreeTenABPโครงการปรับThreeTen-ย้ายกลับ (ดังกล่าวข้างต้น) เพื่อ Android ดูวิธีใช้ ThreeTenABP … .
วิธีแก้ปัญหาที่ง่ายมากคือปล่อยให้การแปลงเป็น PostgreSQL หากคุณตั้งค่าtimezoneพารามิเตอร์อย่างถูกต้องสำหรับแต่ละเซสชันและใช้timestamp with time zonePostgreSQL จะแสดงการประทับเวลาให้กับผู้ใช้นิวยอร์กในเวลานิวยอร์กโดยอัตโนมัติ