Whenever – ไลบรารีจัดการวันที่และเวลาที่ปลอดภัยด้านชนิดข้อมูลและ DST สำหรับ Python
(github.com/ariebovenberg)- Whenever เป็นไลบรารีวันที่และเวลาสำหรับ Python ที่ใช้ชนิดข้อมูลช่วยป้องกันการปะปนกันของ
datetimeแบบ naive/aware และความผิดพลาดในการคำนวณ DST โดยสามารถใช้งานได้ทั้งแบบอิมพลีเมนต์ด้วย Rust หรือแบบ Python ล้วน datetimeในไลบรารีมาตรฐานไม่ได้สะท้อน DST เสมอไป และมีการยกตัวอย่างว่าdatetime(2023, 3, 25, 22, tzinfo=ZoneInfo("Europe/Paris")) + timedelta(hours=8)คืนค่าเป็น 6 นาฬิกา แทนที่จะเป็น 7 นาฬิกา ทั้งที่มีการข้ามไปหนึ่งชั่วโมงเพราะ DST- ระบบชนิดข้อมูลไม่สามารถแยก naive และ aware ได้จาก
datetimeเพียงตัวเดียว แต่ Whenever แยกเป็น ชนิดข้อมูลตามการใช้งาน เช่นInstant,ZonedDateTime,PlainDateTimeเพื่อให้ตัวตรวจสอบชนิดข้อมูลจับความผิดพลาดได้ - ในตารางเปรียบเทียบ Arrow และ Pendulum ยังไม่สามารถตอบโจทย์ครบทั้งความปลอดภัยด้าน DST, typed aware/naive และประสิทธิภาพ ขณะที่ Whenever ระบุว่ารองรับครบทั้ง DST-safe, typed aware/naive และ fast
- โครงการยัง ไม่ถึงเวอร์ชัน 1.0 จึงอาจมีการเปลี่ยน API ใน minor release โดยจะอธิบายการเปลี่ยนแปลงไว้ใน changelog และตัวตรวจสอบชนิดข้อมูลหรือ IDE สามารถช่วยปรับโค้ดได้
ปัญหาที่ Whenever พยายามแก้
- Whenever เป็นไลบรารีที่ช่วยให้เขียนโค้ดวันที่และเวลาบน Python ได้อย่างถูกต้องและตรวจสอบชนิดข้อมูลได้
- มีอิมพลีเมนต์ด้วย Rust และยังมี เวอร์ชัน Python ล้วน สำหรับผู้ใช้ที่ไม่ต้องการ Rust
- ออกแบบบนพื้นฐานของแนวคิดที่ผ่านการพิสูจน์แล้วจากไลบรารีวันที่และเวลาของภาษาอื่นยุคใหม่
- ระบุว่าเร็วกว่าไลบรารี third-party อื่นมาก และโดยทั่วไปยังเร็วกว่ามาตรฐานไลบรารีด้วย
- การออกเวอร์ชัน 1.0 ถูกชะลอไว้เพื่อขัดเกลา API ระยะยาวเพิ่มเติม โดยเฉพาะกำลังรับฟังความเห็นเกี่ยวกับ duration
ข้อจำกัดของ datetime ในไลบรารีมาตรฐาน
datetimeของ Python เติบโตมากว่า 20 ปี จนมีบางส่วนที่ไม่สอดคล้องกับสิ่งที่คาดหวังจากไลบรารีวันที่และเวลาแบบสมัยใหม่-
ปัญหาการคำนวณ DST
datetimeไม่ได้นำ Daylight Saving Time (DST) มาพิจารณาเสมอไป- ในโค้ดตัวอย่าง เมื่อเพิ่มเวลา 8 ชั่วโมงให้กับเวลา 22:00 ของวันที่ 25 มีนาคม 2023 ในเขตเวลา
Europe/Parisผลลัพธ์ควรเป็น 7:00 เพราะมีการข้ามไปหนึ่งชั่วโมงจาก DST แต่กลับคืนค่าเป็น 6:00 - นี่ไม่ใช่บั๊ก แต่เป็นการตัดสินใจด้านการออกแบบที่ให้พิจารณา DST เฉพาะการคำนวณที่เกี่ยวข้องกับสองเขตเวลา
-
ปัญหาการแยกชนิด naive/aware
- ด้วยชนิด
datetimeเพียงอย่างเดียว ระบบชนิดข้อมูลไม่สามารถแยก naive datetime กับ aware datetime ได้ - ฟังก์ชันซิกเนเจอร์อย่าง
def schedule_meeting(at: datetime) -> None: ...ไม่สามารถบอกได้ว่าคาดหวังค่าแบบ naive หรือ aware
- ด้วยชนิด
เปรียบเทียบกับ Arrow·Pendulum
- ตามเกณฑ์ในตารางเปรียบเทียบ Whenever ตอบโจทย์ครบทั้ง DST-safe, typed aware/naive และ fast
datetimeตอบโจทย์เฉพาะ fast เท่านั้น และไม่ผ่านทั้ง DST-safe กับ typed aware/naive- Arrow พยายามนำเสนอ API ที่ใช้ง่ายกว่ามาตรฐานไลบรารี แต่ไม่ได้แก้ปัญหาหลัก
- ยังคงมี footgun แบบเดิม
- การตัดสินใจยุบชนิดข้อมูลให้เหลือเพียง
arrow.Arrowเดียว ทำให้ตัวตรวจสอบชนิดข้อมูลจับความผิดพลาดได้ยากขึ้น
- Pendulum เคยชูจุดเด่นเรื่องการจัดการ DST ที่ดีกว่าและประสิทธิภาพที่ดีขึ้นในปี 2016 แต่แก้ได้เพียงกับดักบางส่วนที่เกี่ยวข้องกับ DST
- ประสิทธิภาพลดลงมากเมื่อเวลาผ่านไป
- ช่วง 4 ปีหลังสุดมีออกรีลีสเพียง 2 ครั้ง อยู่ในภาวะซบเซาด้านการบำรุงรักษาเป็นเวลานาน และยังมีปัญหาร้ายแรงเก่าค้างอยู่จำนวนมาก
ความสามารถหลัก
- รองรับ DST-safe arithmetic
- API ที่ปลอดภัยด้านชนิดข้อมูลเพื่อป้องกันบั๊กที่พบบ่อย
- แก้ปัญหาที่ Arrow และ Pendulum ยังแก้ไม่ได้
- อิงกับแนวคิดที่ผ่านการพิสูจน์แล้วและคุ้นเคย
- ให้ประสิทธิภาพสูง
- มีการทดสอบและเอกสารประกอบอย่างครบถ้วน
- รองรับ date arithmetic
- รองรับ ความละเอียดระดับนาโนวินาที
- มี การรองรับ SQLAlchemy
- การรองรับ Pydantic ยังอยู่ในสถานะเบตา
- มีทั้งอิมพลีเมนต์ด้วย Rust และ ตัวเลือกแบบ Python ล้วน
- การรองรับ free-threading ยังอยู่ในสถานะเบตา
- รองรับ per-interpreter GIL
ลำดับตัวอย่างการใช้งาน
- นำเข้าชนิดข้อมูลแบบ ชัดเจน อย่าง
Instant,ZonedDateTime,PlainDateTimeเพื่อแทนกรณีใช้งานที่ต่างกัน Instant.now()ใช้ระบุช่วงเวลาหนึ่งโดยไม่ต้องยุ่งกับความซับซ้อนของเขตเวลาหรือปฏิทิน- แปลงเขตเวลาอย่างชัดเจนด้วย
now.to_tz("Europe/Paris") PlainDateTime("2023-10-28 22:00")จะไม่ถูกนำไปปะปนกับชนิดอื่นโดยไม่ตั้งใจ- เมื่อเรียก
add(hours=6)จะเกิดNaiveArithmeticWarningเพื่อเตือนว่าการปรับเวลาแบบ local time นี้มองข้าม DST - ต้องระบุเขตเวลาอย่างชัดเจนด้วย
assume_tz("Europe/Amsterdam")
- เมื่อเรียก
- เมื่อเรียก
add(hours=6)บนZonedDateTimeจะสะท้อนการเปลี่ยนผ่าน DST และคืนค่า2023-10-29 03:00:00+01:00[Europe/Amsterdam] - รองรับการเปรียบเทียบ การปัดเศษและการตัดทอน การพาร์สฟอร์แมต ISO8601·RFC3339·RFC2822 และการฟอร์แมตด้วยแพตเทิร์นแบบกำหนดเอง
- หากจำเป็น สามารถแปลงเป็น
datetimeในไลบรารีมาตรฐาน หรือแปลงกลับกันได้ - ดูวิธีใช้งานเพิ่มเติมได้จาก feature overview และ API reference
ข้อจำกัดและนโยบายด้านเสถียรภาพ
- ขอบเขตที่รองรับคือ ปี 1 ถึง 9999 ของ proleptic Gregorian calendar
- ค่า offset ของเขตเวลาถูกจำกัดให้เป็นจำนวนเต็มระดับวินาทีให้สอดคล้องกับ IANA TZ DB
- Whenever ปฏิบัติตาม semantic versioning
- ก่อนเวอร์ชัน 1.0 อาจมีการเปลี่ยน API ใน minor release
- การเปลี่ยนแบบ breaking change จะมีอธิบายรายละเอียดไว้ใน changelog
- API ถูกทำให้มีชนิดข้อมูลครบถ้วน จึงช่วยให้ตัวตรวจสอบชนิดข้อมูลหรือ IDE รองรับการปรับตามการเปลี่ยนแปลงของ API ได้
ไลเซนส์และอิทธิพลด้านการออกแบบ
- Whenever เผยแพร่ภายใต้ MIT License
- binary wheel มี Rust dependency ที่ใช้ไลเซนส์แบบผ่อนปรนคล้ายกัน เช่น MIT และ Apache-2.0 รวมอยู่ด้วย
- Noda Time และ Joda Time เป็นผู้บุกเบิกแนวทางใช้ชนิดข้อมูลแยกตามแนวคิด และลำดับชั้นชนิดข้อมูลของ Noda Time เป็นแรงบันดาลใจโดยตรงต่อการออกแบบ Whenever
- Temporal เป็นแรงบันดาลใจในการจัดการกรณีซับซ้อนเกี่ยวกับความกำกวมของ DST และการปัดเศษ
- โมดูล
datetimeของ Python ถูกใช้อย่างกว้างขวางในการจัดการวันที่และเวลาระดับล่างในอิมพลีเมนต์แบบ Python ล้วนของ Whenever - ยืมบางแนวคิดมาจาก Jiff ด้วย
- กราฟเปรียบเทียบ benchmark ดัดแปลงมาจากโปรเจกต์ Ruff
ยังไม่มีความคิดเห็น