ปัญหาเรื่องเวลากับ Java

คิดว่าปัญหามันเริ่มมาจากการที่เราเป็นคนไทย การนับปี พ.ศ. มันเลยไม่ตรงกับแบบฝรั่ง ที่ใช้ ค.ศ.

แถมยังมีปัญหาเรื่อง TimeZone เพิ่มเข้ามาอีก เพราะของไทยเราจะเป็น GMT+7 หรือ UTC+7

ความหมายคือ การแบ่ง TimeZone  ในโลกเราจะมี 2 แบบ GMT กับ UTC ซึ่งโดยปกติแล้วมันจะเหมือนๆกัน แต่อาจจะแตกต่างเล็กน้อยตรงการจัดโซนว่าประเทศไหนควรจะเป็นเวลาเท่าไหร่ อาจจะแตกต่างกันบางประเทศในยุโรปและแอฟฟริกา แต่ส่วนมากจะตรงกันไม่ว่าจะเป็น GMT หรือ UTC

โดย GMT เป็นตัวเก่าที่เกิดขึ้นมาก่อนแล้วเพิ่งมาเปลี่ยนมาตรฐานใหม่เป็น UTC คือเหมือนๆกับของเดิมแต่ปรับเล็กๆน้อยๆบางประเทศ แล้วกำหนดให้มันเป็นมาตรฐานใหม่ของทั่วโลกใช้ แต่ที่เห็นก็ยังเป็น GMT เยอะอยู่เหมือนกัน โดยไทยเราไม่ว่าจะเป็น GMT หรือ UTC ก็เป็น +7 เหมือนกัน

ส่วนประเทศที่ถือว่าเป็นตรงกลางคือ UTC+0 เลยก็คือที่แอฟฟริกา กาน่า เซเนกัล

ทีนี้ปัญหาแรกของ java กับเวลาที่เราต้องรู้ คือ

1.ตอนเรา เรียก System.currentTimeMillis() สิ่งที่เราได้มานั้นมันจะเป็น เสี้ยววินาทีของเวลามาตรฐาน คือ UTC+0 ดังนั้นถ้าเราอยากให้เป็นเวลาไทยเราต้อง+เพิ่มไปอีก 7 ชั่วโมงในหน่วย millisecond

2.ที่ต้องรู้คือเวลาเราสร้าง Calendar หรือ Date ขึ้นมา ถ้าเราไม่ได้กำหนด TimeZone หรือ Locale หรือประเภทของ Calendar ตัวโปรแกรมมันจะสร้างเองจาก default system ของระบบเรา

เช่น

Calendar c1 = Calendar.getInstance();
System.out.println(c1.get(Calendar.YEAR));//2556 ถ้าไม่กำหนดจะเป็นของไทย

Calendar gc1 = new GregorianCalendar();
System.out.println(gc1.get(Calendar.YEAR));//2013 พอกำหนดจะเป็นฝรั่ง

3.ถ้ากำหนด เป็น GregorianCalendar มันจะเป็น ปี 2013 ตลอดไป

แต่ถ้าไม่ได้กำหนด เราก็จะเปลี่ยนได้ตาม Locale

Calendar gc2 = new GregorianCalendar(new Locale(“en”, “US”));

System.out.println(gc2.get(Calendar.YEAR));//2013

Calendar gc2 = new GregorianCalendar(new Locale(“th”, “TH”));
System.out.println(gc2.get(Calendar.YEAR));//2013

Calendar c2 = Calendar.getInstance(new Locale(“en”, “US”));
System.out.println(c2.get(Calendar.YEAR));//2013

Calendar c2 = Calendar.getInstance(new Locale(“th”, “TH”));
System.out.println(c2.get(Calendar.YEAR));//2556

4.เราสามารถระบุ TimeZone ให้ Calendar ได้เลยมันจะได้เวลาปัจจุบัน แต่แตกต่างกันไปตามสถานที่

Calendar cal1 = Calendar.getInstance(TimeZone.getTimeZone(“Asia/Bangkok”));//GMT+7
System.out.println(cal1.get(Calendar.HOUR_OF_DAY));//11
Calendar cal2 = Calendar.getInstance(TimeZone.getTimeZone(“Asia/Tokyo”));//GMT+9
System.out.println(cal2.get(Calendar.HOUR_OF_DAY));//13

Calendar gcal1 = new GregorianCalendar(TimeZone.getTimeZone(“Asia/Bangkok”));//GMT+7
System.out.println(gcal1.get(Calendar.HOUR_OF_DAY));//11
Calendar gcal2 = new GregorianCalendar(TimeZone.getTimeZone(“Asia/Tokyo”));//GMT+9
System.out.println(gcal2.get(Calendar.HOUR_OF_DAY));//13

Calendar gc_utc = new GregorianCalendar(TimeZone.getTimeZone(“UTC”));//GMT+0
System.out.println(gc_utc.get(Calendar.HOUR_OF_DAY));//4

TimeZone เป็นเวลาจริงๆของแต่ละสถานที่ แต่ Locale เป็นแค่การแสดงผลเท่านั้น

5.การแสดงผล ต้องแยกระหว่าง TimeZone กับ Locale ให้ออกจากกันก่อนมันคนละเรื่องกันต้องทำความเข้าใจให้ดี

TimeZone คือเวลา ที่เวลาเดียวกันเราแสดงผลออกมาได้ต่างกันตาม TimeZone คือเวลาที่แสดงออกมา ตี1 กับ 8โมงเช้า อันนี้เป็นเรื่องของ TimeZone

แต่ปี 2013 กับ 2556 เป็นการแสดงค่าของ Locale

Calendar กับ DateFormat ทั้งสอง Class จะมี TimeZone กับ Locale แยกกันของใครของมัน

ลองดูตัวอย่างแล้วทำความเข้าใจว่าทำไมผลมันจึงเป็นแบบนั้น

 

Calendar gcutc = new GregorianCalendar(TimeZone.getTimeZone(“GMT+0”));//Gregorian จะมี default locale ฝังมาเลยว่าเป็น “en”,”US”
String format = “yyyy-MM-dd HH:mm”;
Locale locale1 = new Locale(“en”, “US”);
Locale locale2 = new Locale(“th”, “TH”);
System.out.println(gcutc.get(Calendar.HOUR_OF_DAY));//10 เวลาตามมาตรฐาน +0
DateFormat dateFormat1 = new SimpleDateFormat(format, locale1);
DateFormat dateFormat2 = new SimpleDateFormat(format, locale2);
System.out.println(dateFormat1.format(gcutc.getTime()));//2013-11-11 17:41 ตัว Calendar มี timezone เป็น+0 locale เป็น en ส่วน DateFormat มี timezone เป็น default +7 เพราะเราไม่ได้กำหนดให้ และมี locale เป็น en ตามที่เรากำหนด
System.out.println(dateFormat2.format(gcutc.getTime()));//2556-11-11 17:41 DateFormat ตัวนี้มี locale เป็น th เพราะเรากำหนด

dateFormat1.setTimeZone(TimeZone.getTimeZone(“Asia/Tokyo”));
dateFormat2.setTimeZone(TimeZone.getTimeZone(“Asia/Tokyo”));
System.out.println(dateFormat1.format(gcutc.getTime()));//2013-11-11 19:41 ตัว DateFormat ถูกเปลี่ยน timezone เป็น+9 ส่วน locale เป็น en เหมือนเดิม
System.out.println(dateFormat2.format(gcutc.getTime()));//2556-11-11 19:41 ตัว DateFormat ถูกเปลี่ยน  timezone เป็น+9 ส่วน locale เป็น th เหมือนเดิม

dateFormat1.setCalendar(gcutc);
dateFormat2.setCalendar(gcutc);
System.out.println(dateFormat1.format(gcutc.getTime()));//2013-11-11 10:41 DateFormat ถูกเปลี่ยนทั้ง timezone และ locale เป็นแบบเดียวกับของ Calendar คือ timezone+0 และ locale en (default ของ Gregorian)
System.out.println(dateFormat2.format(gcutc.getTime()));//2013-11-11 10:41 DateFormat ถูกเปลี่ยนทั้ง timezone และ locale เป็นแบบเดียวกับของ Calendar คือ timezone+0 และ locale en (default ของ Gregorian)

ทุกอย่างชัดและตรง อธิบายได้ทั้งหมด ซึ่งกว่าจะทำความเข้าใจได้ก็ผ่านการปวดหัวมาหลายครั้งจนคิดว่าถึงเวลาต้องทำความเข้าใจจริงๆสักที คิดว่าจากนี้คงจะไม่มีเรื่องให้ต้องสับสนอีก ถ้าเจอเกี่ยวกับเวลาในภาษาจาวา

มาตรฐานวิธีการเรียกใช้งาน TimeZone ในภาษา java มีเยอะแต่เพื่อความเข้าใจ คิดว่าเขียนแบบนี้น่าจะดี

TimeZone.getTimeZone(“GMT+0”);
TimeZone.getTimeZone(“GMT+7”);

แล้วเรื่องการเก็บข้อมูลลง database เขาก็บอกว่าให้เก็บด้วยเวลา GMT+0 ไปให้หมดเป็นมาตรฐานแล้วเมื่อดึงข้อมูลออกมาใช้ จะแปลงไปอยู่ใน timezone ไหนก็ค่อยว่ากันอีกที ในเรื่องของการแสดงผล

สุดท้ายนี้อยากจะบอกว่าที่เล่ามาทั้งหมดนี้ เป็นการตีความเอาเอง อาจจะเข้าใจผิดในบางเรื่อง

อ้างอิง

1.http://en.wikipedia.org/wiki/Coordinated_Universal_Time

2.http://en.wikipedia.org/wiki/Time_zone

3.http://en.wikipedia.org/wiki/Greenwich_Mean_Time

4.https://www.codemagi.com/blog/post/192