/ BasicKnowledge

[365 วันแห่งโปรแกรม #day59] Date/Time (ตอนที่ 3)

วันที่ห้าสิบเก้าของ ‪#‎365วันแห่งโปรแกรม ยังขอหากินด้วยเรื่องเดิมต่อไปครับ สัญญาว่านี่จะเป็นครั้งสุดท้ายที่เขียนบทความเกี่ยวกับ Date/Time ในบทความชุดนี้ ><


Timestamp

ผมขอเริ่มบทความในวันนี้ด้วยเรื่อง Timestamp ก่อนละกันครับ Timestamp คือ string หรือข้อมูลอะไรก็ตามที่ใช้บอกว่าเหตุการณ์นั้นๆ เกิดขึ้นเมื่อไหร่ เช่น เวลาเราไปส่งจดหมายหรือพัสดุที่ไปรษณีย์เค้าก็จะมีการปั้มบอกเวลาว่ารับเข้าระบบเมื่อไหร่ ถ้าเอาเป็นตัวอย่างด้าน IT ก็เวลาเราบันทึกข้อมูลลงในฐานข้อมูลเราก็มักจะเก็บด้วยว่าข้อมูลนี้ถูกสร้างเมื่อไหร่ แก้ไขครั้งสุดท้ายเมื่อไหร่ นี่แหละครับ Timestamp โดยทั่วไปแล้วเราจะเก็บเป็นวันที่และเวลา ส่วนเรื่องที่ว่าจะต้องเก็บละเอียดแค่ไหน ก็อยู่ที่ requirement ของงานนั้นๆ ครับ

ตัวอย่างของ Timestamp

Tue 01-01-2009 6:00
2005-10-30 T 10:45 UTC
2007-11-09 T 11:20 UTC
Sat Jul 23 02:16:57 2005
12569537329
(1969-07-21 T 02:56 UTC)
07:38, 11 December 2012 (UTC)

System time

ในระบบคอมพิวเตอร์นั้น ไม่ได้มีนาฬิกาที่จับต้องได้เป็นชิ้นเป็นอัน แต่ก็มีสิ่งหนึ่งที่เรียกว่า System clock ที่มีหน้าที่ในการนับไปเรื่อยๆ แล้วนับจากไหนล่ะ? เราไม่มีทางรู้ได้เลยว่าตอนนี้เป็นวันที่เท่าไหร่ เวลาเท่าไหร่ ถ้าไม่มีจุดอ้างอิงซึ่งในแต่ละระบบก็จะอ้างจากเวลาเริ่มต้นที่ต่างกันไป จำนวนที่นับได้จาก System clock นี้เราเรียกว่า System time ครับ System time ที่เราพูดถึงกันบ่อยๆ ก็น่าจะเป็น Unix time ครับ เดี๋ยวเรามาดูกันดีกว่าว่า Unix time คืออะไร และหน้าตาเป็นอย่างไร

Unix time

Unix time หรือ POSIX time เป็นวิธีการนับ System time ของระบบที่เป็น Unix Based หรือระบบที่เข้ากันได้กับ POSIX การนับเวลาของระบบนี้จะมีจุดอ้างอิงจาก วันพฤหัสบดีที่ 1 มกราคม 1970 เวลา 00:00:00 ตามเขตเวลา UTC แล้วก็นับไปเรื่อยๆ

1432878739 (ISO 8601:2015-05-29T05:52:19Z)

ระบบเวลาส่วนใหญ่รวมไปถึง Unix time นั้น ยังไม่รองรับเรื่องของ leap second (เพิ่มวินาทีเข้าไปเพื่อชดเชยเวลาที่ไม่พอดีกับการหมุนของโลก) ดังนั้นระบบอาจจะมีปัญหาเมื่อต้องมีการเพิ่มเวลาเข้ามา แต่ในปัจจุบันปัญหานี้ไม่ใช่เรื่องใหญ่อะไรมากเนื่องจากเราผ่านการใส่เวลาเพิ่มกันมาแล้ว 25 ครั้ง ซึ่งวิธีการรับมือกับปัญหานี้ก็ทำประมาณว่าวนการนับวินาทีสุดท้ายของวันที่มีการเพิ่มเวลาซ้ำอีกรอบ

อีกปัญหาหนึ่งที่ชาวโลกวิตกกันก็คือ Year 2038 เนื่องจากตัวเลขที่ System Clock นับจะ overflow เมื่อเลยเวลา 03:14:07 UTC on 19 January 2038 ไป เพราะว่าแต่เดิมนั้น Unix time เก็บข้อมูลเป็น signed 32-bit integer

Network Time Protocol

อย่างที่ผมเคยพูดไปแล้วว่าเวลาไม่เคยเท่ากันจริงๆ เลย ความต่างของเวลานั้นเป็นปัญหากับระบบคอมพิวเตอร์มากๆ ดังนั้นจึงมีผู้คิดค้นวิธีแก้ปัญหานี้มากมายเพื่อทำให้เวลาในคอมพิวเตอร์แต่ละเครื่องนั้นใกล้เคียงกันมากที่สุดเท่าที่จะทำได้ Network Time Protocol ก็เป็นโปรโตคอลหนึ่งที่เกิดขึ้นมาเพื่อแก้ปัญหานี้ผ่านระบบเครือข่ายคอมพิวเตอร์ โดยใช้สถาปัตยกรรมแบบ client-server หรือ peer-to-peer ในการ sync ก็ได้ นอกจากที่เราใช้โปรโตคอลนี้เพื่อตั้งเวลาอัตโนมัติแล้ว โปรโตคอลนี้เป็นหนึ่งในวิธีแก้ปํญหา leap second อีกด้วย

Date/Time implementation in Programming languages

หัวข้อนี้คือประเด็นสำคัญ เพราะนี่คือบล็อกเขียนโปรแกรม >< เราจะมาดูกันว่าในแต่ละภาษาโปรแกรมนั้น implement เรื่องเกี่ยวกับ Date/Time อย่างไรกันบ้าง

เช่นเดิมครับผมขอยกตัวอย่างด้วย C# และ Java เนื่องจากมี source code ให้อ้างอิงได้ เข้าใจง่าย และต่างกันยอย่างชัดเจน

C# Date/Time implementation

ผมขออ้างอิงจาก source code ของคลาส DateTime ที่นำมาจากเว็บ dotnetframework.org

C# มีคำสั่งสำหรับขอค่าวันเวลาปัจจุบันคือ DateTime.Now ครับ เมื่อดูที่โค้ดจะพบว่า implement ไว้ดังนี้

public static DateTime Now {
    get {
        return UtcNow.ToLocalTime();
    } 
}

จะเห็นว่า Now จะไปเรียกเมธอด ToLocalTime() ของ UtcNow อีกที งั้นเราก็ต้องไปดูว่า UtcNow คืออะไร

public static DateTime UtcNow { 
    get {
        long ticks = 0;
        ticks = GetSystemTimeAsFileTime();
        return new DateTime( ((UInt64)(ticks + FileTimeOffset)) | KindUtc);
    } 
}

ที่น่าสนใจอยู่ตรงนี้ครับเมธอดนี้เรียก GetSystemTimeAsFileTime() แล้วก็เอามาสร้างเป็น DateTime object ใหม่แล้วส่งกลับไป

[MethodImplAttribute(MethodImplOptions.InternalCall)]
internal static extern long GetSystemTimeAsFileTime(); 

สรุปแล้วคือมันไปเรียกเมธอดใน CLR (Common Runtime Language) ครับ เข้าใจว่าจริงๆ แล้วเป็นการเรียก System time ของ Windows API อีกที

เมื่อพิจารณาทั้งหมดแล้วพบว่าค่าที่ได้มาจาก GetSystemTimeAsFileTime() นั้นถูกนำไปบวกด้วย FileTimeOffset (ค่าเริ่มต้นของเวลา ในระบบ Windows ก็คือ 1 มกราคม 1601) ก่อนที่จะส่งไปเพื่อสร้าง object และเนื่องจาก OS เก็บค่า System time ในเขตเวลา UTC ดังนั้นจึงมีการระบุตอนสร้าง object ด้วยว่าให้สร้างด้วยเขตเวลา UTC นั่นแสดงให้เห็นว่าคลาส DateTime นี้เก็บเวลาตั้งแต่ 1/1/0001 12:00am UTC เลย

Java Date/Time implementation

ในการทดลองต่อไปผมขออ้างอิงจากโค้ดของคลาส java.util.Date ใน OpenJDK ครับ เอามาจากเว็บ grepcode.com

ในจาวาเราสามารถสร้าง Date object ที่มีข้อมูลเป็นวันเวลาปัจจุบันได้ด้วยการ new Date() แบบไม่มีพารามิเตอร์ครับ

 public Date() {
     this(System.currentTimeMillis());
 }

ดีครับดี เปิดมาก็เรียกใช้ System.currentTimeMillis() เลย (System.currentTimeMillis() คือเมธอดสำหรับขอ System time จากระบบ) เพื่อให้แน่ใจ เราไปดูในคลาส System ด้วยดีกว่า

public static native long currentTimeMillis();

เป็นไปตามที่คาดไว้ครับ currentTimeMillis() จะไปขอค่า System time จาก OS อีกที หมดข้อสงสัยแล้วครับ แต่เอ๊ะ แล้ว java.util.Date นี่นับเวลาเริ่มจากเมื่อไหร่ล่ะ? เท่าที่ผมอ่าน Document ดู ค่า System time ที่ Constructor ของคลาสนี้รับจะนับตั้งแต่ 1 มกราคม 1970 เวลา 00:00:00 ตามเขตเวลา GMT ครับ ซึ่งก็คือ Unix time นั่นเอง แสดงว่า System.currentTimeMillis() ไม่ใช่แค่ดึง System time มาจาก OS เฉยๆ แบบที่ ทำใน GetSystemTimeAsFileTime() ของ C# แต่มีการแปลงเพื่อให้เข้ากันได้ในทุก OS ครับ

สรุปคือในการ implement Date/Time ในแต่ละภาษาโปรแกรมนั้นน่าจะมีโครงสร้างที่ใกล้เคียงกันคือดึงค่าจาก System time แต่การเก็บข้อมูลนั้นอาจจะต่างกันบ้าง เช่น Java อิงตามวิธีของ Unix time ส่วน C# เก็บเป็น tick ตั้วแต่ 1/1/0001 12:00am (1 tick บน Windows คือ 1 นาโนวินาที) ส่วนเรื่อง Timezone นั้นผมฝากไปดูหน่อยนะครับ ว่าแต่ระภาษามันเก็บต่างกันมั้ย (ใบ้ให้ว่าต่าง แต่ต่างยังไงต้องอ่านเอง)

References

Timestamp - Wikipedia

System time - Wikipedia

Unix time - Wikipedia

Network Time Protocol - Wikipedia

DateTime.cs source code in C# .NET - dotnetframework.org

java.util.Date - grepcode.com

#‎day59 #365วันแห่งโปรแกรม ‪#‎โครงการ365วันแห่ง‬...