การจัดเก็บการนัดหมายในฐานข้อมูล SQL เช่น Postgres สำหรับใช้กับ java.time framework

Oct 27 2020

สมมติว่าเรามีนัดที่มิลานอิตาลีในวันที่ 23/01/2021 21:00 "ยุโรป / โรม" นัดนี้จะถูกบันทึกลงฐานข้อมูลใน UTC TIMESTAMP WITH TIME ZONEในคอลัมน์ชนิดหนึ่งที่คล้ายกับชนิด

ตอนนี้ผู้ใช้ที่อาศัยอยู่ในนิวยอร์กสหรัฐอเมริกาจำเป็นต้องเข้าใจว่าการนัดหมายนี้จะเกิดขึ้นเมื่อใด เราสามารถแสดงให้ผู้ใช้ทราบว่าวันที่ - เวลาแปลงเป็นเขตเวลา "America / New_York" หรือแทนใน TZ "Europe / Rome" เมื่อผู้ใช้บินจากนิวยอร์กไปมิลานเขาจะพบว่าข้อมูลทั้งสองมีประโยชน์

ประเด็นคือการจัดเก็บทุกอย่างที่แปลงเป็นข้อมูลอ้างอิง TZ (UTC) เดียวกันและจัดการวันที่ - เวลาขึ้นอยู่กับเป้าหมายที่คุณใช้กรอบงานjava.time ที่มาพร้อมกับ Java สมัยใหม่

เข้าท่าหรือมีบางอย่างผิดปกติ / ขาดหายไป?

คำตอบ

4 BasilBourque Oct 27 2020 at 04:57

เข้าท่าหรือมีบางอย่างผิดปกติ / ขาดหายไป?

ขึ้นอยู่กับชนิดของการนัดหมาย

การนัดหมายมีสองประเภท:

  • ครู่หนึ่งจุดใดจุดหนึ่งบนไทม์ไลน์โดยไม่สนใจการเปลี่ยนแปลงใด ๆ กับกฎเขตเวลา
    ตัวอย่าง: การปล่อยจรวด
  • วันที่และเวลาของวันที่ควรปรับเปลี่ยนตามการเปลี่ยนแปลงกฎเขตเวลา
    ตัวอย่าง: การไปพบแพทย์ / ทันตกรรม

ช่วงเวลา

ตัวอย่างเช่นหากเราจองการปล่อยจรวดเราไม่สนใจวันที่และเวลาของวัน เราสนใจเฉพาะช่วงเวลาที่ (ก) สวรรค์จัดแนวและ (ข) เราคาดหวังว่าจะมีอากาศเอื้ออำนวย

หากในช่วงเวลาที่มีการแทรกแซงนักการเมืองเปลี่ยนกฎของเขตเวลาที่ใช้ที่ไซต์เปิดตัวของเราหรือที่สำนักงานของเรานั่นจะไม่มีผลต่อการนัดหมายเปิดตัวของเรา หากนักการเมืองที่ควบคุมไซต์เปิดตัวของเราใช้เวลาออมแสง (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 … .
LaurenzAlbe Oct 27 2020 at 14:09

วิธีแก้ปัญหาที่ง่ายมากคือปล่อยให้การแปลงเป็น PostgreSQL หากคุณตั้งค่าtimezoneพารามิเตอร์อย่างถูกต้องสำหรับแต่ละเซสชันและใช้timestamp with time zonePostgreSQL จะแสดงการประทับเวลาให้กับผู้ใช้นิวยอร์กในเวลานิวยอร์กโดยอัตโนมัติ