ในjava.util.Calendar
เดือนมกราคมหมายถึงเดือนที่ 0 ไม่ใช่เดือนที่ 1 มีเหตุผลเฉพาะเจาะจงหรือไม่
ฉันเห็นหลายคนสับสนเกี่ยวกับเรื่องนี้ ...
ในjava.util.Calendar
เดือนมกราคมหมายถึงเดือนที่ 0 ไม่ใช่เดือนที่ 1 มีเหตุผลเฉพาะเจาะจงหรือไม่
ฉันเห็นหลายคนสับสนเกี่ยวกับเรื่องนี้ ...
คำตอบ:
มันเป็นเพียงส่วนหนึ่งของระเบียบที่น่ากลัวซึ่งเป็น API วันที่ / เวลาของ Java การระบุสิ่งผิดปกติใช้เวลานานมาก (และฉันแน่ใจว่าฉันไม่ทราบปัญหาครึ่งหนึ่ง) การทำงานกับวันและเวลาที่เป็นที่ยอมรับนั้นเป็นเรื่องยุ่งยาก
ตัวเองไม่ชอบและใช้Joda เวลาแทนหรืออาจจะเป็นJSR-310
แก้ไข: สำหรับเหตุผลที่ - ตามที่ระบุไว้ในคำตอบอื่น ๆ อาจเป็นเพราะ C APIs เก่าหรือเพียงแค่ความรู้สึกทั่วไปของการเริ่มต้นทุกอย่างตั้งแต่ 0 ... ยกเว้นว่าวันนั้นเริ่มต้นด้วย 1 แน่นอน ฉันสงสัยว่าใครก็ตามที่อยู่นอกทีมเริ่มต้นใช้งานจริงสามารถระบุเหตุผลได้ - แต่อีกครั้งฉันขอให้ผู้อ่านไม่ต้องกังวลมากนักเกี่ยวกับสาเหตุของการตัดสินใจที่ไม่java.util.Calendar
ดี
จุดหนึ่งซึ่งเป็นในความโปรดปรานของการใช้ 0 ตามดัชนีก็คือว่ามันทำให้สิ่งที่ชอบ "อาร์เรย์ของชื่อ" ง่ายขึ้น:
// I "know" there are 12 months
String[] monthNames = new String[12]; // and populate...
String name = monthNames[calendar.get(Calendar.MONTH)];
แน่นอนว่าสิ่งนี้จะล้มเหลวทันทีที่คุณได้รับปฏิทินที่มี 13 เดือน ... แต่อย่างน้อยขนาดที่ระบุคือจำนวนเดือนที่คุณคาดหวัง
นี่ไม่ใช่เหตุผลที่ดีแต่เป็นเหตุผล ...
แก้ไข: ในฐานะที่เป็นประเภทความคิดเห็นขอความคิดบางอย่างเกี่ยวกับสิ่งที่ฉันคิดว่าผิดกับวันที่ / ปฏิทิน:
Date
และCalendar
เป็นสิ่งที่แตกต่างกัน แต่การแยกค่า "ท้องถิ่น" vs "ส่วน" หายไปเช่นเดียวกับวันที่ / เวลาเทียบกับวันที่เวลาDate.toString()
การดำเนินงานซึ่งมักจะใช้เขตเวลาท้องถิ่นระบบ (ว่าสับสนผู้ใช้กองมากเกินจำนวนมากก่อนหน้านี้)เพราะการทำคณิตศาสตร์กับเดือนนั้นง่ายกว่ามาก
1 เดือนหลังจากเดือนธันวาคมคือเดือนมกราคม แต่หากต้องการคิดออกตามปกติคุณจะต้องใช้หมายเลขเดือนและทำคณิตศาสตร์
12 + 1 = 13 // What month is 13?
ฉันรู้ว่า! ฉันสามารถแก้ไขได้อย่างรวดเร็วโดยใช้โมดูลัส 12
(12 + 1) % 12 = 1
ใช้งานได้ดี 11 เดือนจนถึงพฤศจิกายน ...
(11 + 1) % 12 = 0 // What month is 0?
คุณสามารถทำให้งานทั้งหมดนี้อีกครั้งโดยการลบ 1 ก่อนที่จะเพิ่มเดือนจากนั้นทำโมดูลัสของคุณและในที่สุดก็เพิ่ม 1 กลับมาอีกครั้ง ... aka การแก้ไขปัญหาพื้นฐาน
((11 - 1 + 1) % 12) + 1 = 12 // Lots of magical numbers!
ทีนี้ลองคิดถึงปัญหาของเดือนที่ 0 - 11
(0 + 1) % 12 = 1 // February
(1 + 1) % 12 = 2 // March
(2 + 1) % 12 = 3 // April
(3 + 1) % 12 = 4 // May
(4 + 1) % 12 = 5 // June
(5 + 1) % 12 = 6 // July
(6 + 1) % 12 = 7 // August
(7 + 1) % 12 = 8 // September
(8 + 1) % 12 = 9 // October
(9 + 1) % 12 = 10 // November
(10 + 1) % 12 = 11 // December
(11 + 1) % 12 = 0 // January
ทุกเดือนทำงานเหมือนเดิมและไม่จำเป็นต้องแก้ไขอีกต่อไป
((11 - 1 + 1) % 12) + 1 = 12
เป็น(11 % 12) + 1
เช่นสำหรับเดือน 1..12 คุณเพียงแค่ต้องเพิ่ม 1 หลังจากทำ modulo ไม่ต้องใช้เวทมนต์
ภาษาที่ใช้ภาษา C คัดลอก C ในระดับหนึ่ง tm
โครงสร้าง (ที่กำหนดไว้ในtime.h
) มีข้อมูลจำนวนเต็มtm_mon
กับ (ความเห็น) ช่วง 0-11
ภาษาที่ใช้ภาษา C เริ่มต้นอาร์เรย์ที่ดัชนี 0 ดังนั้นสิ่งนี้จึงสะดวกสำหรับการแสดงผลสตริงในอาร์เรย์ของชื่อเดือนพร้อมกับtm_mon
ดัชนี
มีคำตอบมากมายเกี่ยวกับเรื่องนี้ แต่ฉันจะให้ความเห็นเกี่ยวกับเรื่องนี้ต่อไป เหตุผลที่อยู่เบื้องหลังพฤติกรรมแปลก ๆ ดังที่ระบุไว้ก่อนหน้านี้มาจาก POSIX C time.h
ซึ่งเดือนที่จัดเก็บใน int ด้วยช่วง 0-11 เพื่ออธิบายว่าทำไมดูที่นี่ ปีและวันถือเป็นตัวเลขในภาษาพูด แต่เดือนมีชื่อของตัวเอง ดังนั้นเนื่องจากมกราคมเป็นเดือนแรกมันจะถูกเก็บเป็นออฟเซ็ต 0 องค์ประกอบอาร์เรย์แรก จะเป็นmonthname[JANUARY]
"January"
เดือนแรกในปีคือองค์ประกอบอาร์เรย์เดือนแรก
ตัวเลขในทางกลับกันเนื่องจากพวกเขาไม่มีชื่อเก็บไว้ใน int อย่างที่ 0-30 จะทำให้สับสนเพิ่มday+1
คำแนะนำจำนวนมากสำหรับการแสดงผลและแน่นอนมีแนวโน้มที่จะมีข้อบกพร่องมากมาย
ที่ถูกกล่าวว่าไม่สอดคล้องกันมีความสับสนโดยเฉพาะอย่างยิ่งในจาวาสคริปต์ (ซึ่งยังได้รับมรดก "คุณสมบัติ") นี้เป็นภาษาสคริปต์ที่สิ่งนี้ควรจะใจลอยห่างจาก langague
TL; DR : เนื่องจากเดือนมีชื่อและวันในเดือนนั้นไม่
ใน Java 8 มีวันที่ / เวลา API JSR 310 ใหม่ที่มีสติมากกว่า ข้อมูลจำเพาะนั้นเหมือนกับผู้เขียนหลักของ JodaTime และพวกเขาแบ่งปันแนวคิดและรูปแบบที่คล้ายคลึงกันจำนวนมาก
ฉันจะบอกว่าขี้เกียจ อาร์เรย์เริ่มต้นที่ 0 (ทุกคนรู้ว่า) เดือนของปีเป็นอาร์เรย์ซึ่งทำให้ฉันเชื่อว่าวิศวกรบางคนของ Sun ไม่ได้สนใจที่จะใส่รหัสนี้ลงในโค้ด Java
อาจเป็นเพราะ "struct tm" ของ C ทำแบบเดียวกัน
เนื่องจากโปรแกรมเมอร์กำลังหมกมุ่นอยู่กับดัชนี 0-based ตกลงมันซับซ้อนกว่านั้นเล็กน้อย: มันสมเหตุสมผลกว่าเมื่อคุณทำงานกับตรรกะระดับล่างเพื่อใช้การจัดทำดัชนีแบบ 0 แต่โดยมากฉันจะยังคงยึดติดกับประโยคแรกของฉัน
โดยส่วนตัวแล้วฉันใช้ความแปลกประหลาดของ API ปฏิทิน Java เป็นตัวบ่งชี้ว่าฉันต้องการหย่าตัวเองจากความคิดที่เป็นศูนย์กลางแบบคริสต์ศักราชและพยายามที่จะเขียนโปรแกรม agnostically ให้มากขึ้น โดยเฉพาะฉันเรียนรู้อีกครั้งเพื่อหลีกเลี่ยงค่าคงที่ฮาร์ดโค้ดสำหรับสิ่งต่าง ๆ เช่นเดือน
ข้อใดต่อไปนี้มีแนวโน้มที่จะถูกต้องมากขึ้น
if (date.getMonth() == 3) out.print("March");
if (date.getMonth() == Calendar.MARCH) out.print("March");
สิ่งนี้แสดงให้เห็นสิ่งหนึ่งที่ทำให้ฉันรู้สึกแย่เกี่ยวกับ Joda Time - มันอาจกระตุ้นให้โปรแกรมเมอร์คิดในแง่ของค่าคงที่ฮาร์ดโค้ด (มีเพียงเล็กน้อยเท่านั้นไม่ใช่ว่า Joda บังคับให้โปรแกรมเมอร์เขียนโปรแกรมไม่ดี)
สำหรับฉันไม่มีใครอธิบายได้ดีไปกว่าmindpro.com :
gotchas
java.util.GregorianCalendar
มีข้อบกพร่องและ gotchas น้อยกว่ามากในold java.util.Date
ชั้นเรียน แต่ก็ยังไม่มีการปิกนิกหากมีผู้เขียนโปรแกรมเมื่อเสนอการปรับเวลาตามฤดูกาลครั้งแรกพวกเขาจะคัดค้านว่าเป็นคนบ้า ด้วยการปรับเวลาตามฤดูกาลมีความกำกวมพื้นฐาน ในฤดูใบไม้ร่วงเมื่อคุณตั้งค่านาฬิกาของคุณกลับหนึ่งชั่วโมงเวลา 02.00 น. มีเวลาที่แตกต่างกันสองแบบในเวลาทั้งสองเรียกว่าเวลาท้องถิ่น 1:30 น. คุณสามารถแยกแยะความแตกต่างได้เฉพาะเมื่อคุณบันทึกว่าคุณต้องการให้ปรับเวลาตามฤดูกาลหรือเวลามาตรฐานด้วยการอ่าน
น่าเสียดายที่ไม่มีวิธีที่จะบอก
GregorianCalendar
ว่าคุณตั้งใจไว้ คุณต้องหันไปบอกเวลาท้องถิ่นกับเขตเวลา UTC จำลองเพื่อหลีกเลี่ยงความคลุมเครือ โปรแกรมเมอร์มักจะหลับตากับปัญหานี้และหวังว่าจะไม่มีใครทำอะไรเลยในช่วงเวลานี้บั๊กสหัสวรรษ ข้อบกพร่องยังคงไม่ได้อยู่ในคลาสปฏิทิน แม้แต่ใน JDK (Java Development Kit) 1.3 ยังมีข้อบกพร่องในปี 2001 พิจารณารหัสต่อไปนี้:
GregorianCalendar gc = new GregorianCalendar(); gc.setLenient( false ); /* Bug only manifests if lenient set false */ gc.set( 2001, 1, 1, 1, 0, 0 ); int year = gc.get ( Calendar.YEAR ); /* throws exception */
ข้อผิดพลาดจะหายไปเวลา 7:00 น. ในวันที่ 2001/01/01 สำหรับ MST
GregorianCalendar
ถูกควบคุมโดยกองยักษ์แห่งค่าคงที่เวทมนต์ int ที่ยังไม่ได้พิมพ์ เทคนิคนี้ทำลายความหวังทั้งหมดของการตรวจสอบข้อผิดพลาดเวลาคอมไพล์ ตัวอย่างเช่นเพื่อให้ได้เดือนที่คุณใช้GregorianCalendar. get(Calendar.MONTH));
GregorianCalendar
มีการประหยัดแบบดิบGregorianCalendar.get(Calendar.ZONE_OFFSET)
และเวลากลางวันGregorianCalendar. get( Calendar. DST_OFFSET)
แต่ไม่มีวิธีการชดเชยเวลาตามจริงที่ใช้ คุณต้องแยกสองอย่างนี้ออกจากกันและเพิ่มเข้าด้วยกัน
GregorianCalendar.set( year, month, day, hour, minute)
ไม่ตั้งค่าวินาทีเป็น 0
DateFormat
และGregorianCalendar
ไม่ได้ตาข่ายอย่างถูกต้อง คุณต้องระบุปฏิทินสองครั้งครั้งเดียวโดยอ้อมเป็นวันที่หากผู้ใช้ไม่ได้กำหนดค่าเขตเวลาของเขาอย่างถูกต้องผู้ใช้จะเริ่มต้นอย่างเงียบ ๆ กับ PST หรือ GMT
ใน GregorianCalendar เดือนจะมีหมายเลขเริ่มต้นที่มกราคม = 0 แทนที่จะเป็น 1 เหมือนกับคนอื่น ๆ บนโลก ยังวันเริ่มต้นที่ 1 เช่นเดียวกับวันของสัปดาห์กับวันอาทิตย์ = 1, วันจันทร์ = 2, …วันเสาร์ = 7 ยัง DateFormat แยกวิเคราะห์พฤติกรรมในแบบดั้งเดิมกับมกราคม = 1
java.util.Month
Java เป็นอีกวิธีหนึ่งในการใช้ดัชนีตามดัชนี 1 เดือน ใช้java.time.Month
enum วัตถุหนึ่งถูกกำหนดไว้ล่วงหน้าสำหรับแต่ละสิบสองเดือน พวกเขามีหมายเลขที่กำหนดให้แต่ละ 1-12 สำหรับเดือนมกราคมถึงธันวาคม โทรgetValue
หาหมายเลข
ใช้ประโยชน์จากMonth.JULY
(ให้คุณ 7) แทนCalendar.JULY
(ให้คุณ 6)
(import java.time.*;)
Month.FEBRUARY.getValue() // February → 2.
2
คำตอบโดยจอนสกีตที่ถูกต้อง
ตอนนี้เรามีการแทนที่ที่ทันสมัยสำหรับคลาสวัน - เวลาเก่าที่ยุ่งยากเหล่านั้น: คลาสjava.time
java.time.Month
ในบรรดาผู้เรียนเป็นenum Enum ดำเนินการหนึ่งหรือมากกว่าวัตถุที่กำหนดไว้ล่วงหน้าวัตถุที่มีอินสแตนซ์โดยอัตโนมัติเมื่อชั้นโหลด ในวันที่เรามีโหลวัตถุดังกล่าวแต่ละที่กำหนดชื่อ: , , และอื่น ๆ แต่ละคนเป็นค่าคงที่ระดับ คุณสามารถใช้และส่งผ่านวัตถุเหล่านี้ได้ทุกที่ในรหัสของคุณ ตัวอย่าง:Month
Month
JANUARY
FEBRUARY
MARCH
static final public
someMethod( Month.AUGUST )
โชคดีที่พวกเขามีเลขมีสติ 1-12 โดยที่ 1 คือมกราคมและ 12 คือธันวาคม
รับMonth
วัตถุสำหรับหมายเลขเดือนหนึ่ง ๆ (1-12)
Month month = Month.of( 2 ); // 2 → February.
ไปอีกทางหนึ่งถามMonth
วัตถุสำหรับหมายเลขเดือน
int monthNumber = Month.FEBRUARY.getValue(); // February → 2.
หลายวิธีที่มีประโยชน์อื่น ๆ ในชั้นนี้เช่นรู้จำนวนวันในแต่ละเดือน ชั้นสามารถสร้างชื่อเฉพาะของเดือน
คุณสามารถรับชื่อที่แปลเป็นภาษาท้องถิ่นของเดือนความยาวหรือตัวย่อต่างๆ
String output =
Month.FEBRUARY.getDisplayName(
TextStyle.FULL ,
Locale.CANADA_FRENCH
);
กุมภาพันธ์
นอกจากนี้คุณควรผ่านวัตถุของ enum นี้รอบ ๆ ฐานรหัสของคุณแทนที่จะเป็นตัวเลขจำนวนเต็มเท่านั้น การทำเช่นนี้ให้ความปลอดภัยประเภทมั่นใจช่วงที่ถูกต้องของค่าและทำให้รหัสของคุณเอกสารด้วยตนเอง ดูบทช่วยสอนของ Oracleหากไม่คุ้นเคยกับสิ่งอำนวยความสะดวกด้าน enum ที่ทรงพลังในจาวา
คุณอาจพบว่ามีประโยชน์Year
และYearMonth
คลาส
java.timeกรอบถูกสร้างขึ้นใน Java 8 และต่อมา ชั้นเรียนเหล่านี้แย่งลำบากเก่ามรดกเรียนวันที่เวลาเช่นjava.util.Date
, และ.Calendar
java.text.SimpleDateFormat
Joda เวลาโครงการขณะนี้อยู่ในโหมดการบำรุงรักษาให้คำแนะนำแก่การโยกย้ายไปยัง java.time
ต้องการเรียนรู้เพิ่มเติมโปรดดูที่ออราเคิลกวดวิชา และค้นหา Stack Overflow สำหรับตัวอย่างและคำอธิบายมากมาย สเปกJSR 310
จะรับคลาส java.time ได้ที่ไหน?
โครงการThreeTen-Extraขยาย java.time ด้วยคลาสเพิ่มเติม โครงการนี้เป็นพื้นฐานที่พิสูจน์ได้สำหรับการเพิ่มเติมในอนาคตไปยัง java.time คุณอาจพบว่าการเรียนที่มีประโยชน์บางอย่างที่นี่เช่นInterval
, YearWeek
, YearQuarter
และอื่น ๆ อีกมากมาย
มันไม่ได้กำหนดว่าเป็นศูนย์ต่อ se มันถูกกำหนดให้เป็นปฏิทินมกราคม มันเป็นปัญหาของการใช้ ints เป็นค่าคงที่แทนที่จะเป็น enums ปฏิทิน. มกราคม == 0
นอกจากนี้ในการตอบ DannySmurf Calendar.JANUARY
ของความเกียจคร้านฉันจะเพิ่มว่ามันเป็นขอแนะนำให้คุณใช้ค่าคงที่เช่น
เพราะทุกอย่างเริ่มต้นด้วย 0 นี่คือข้อเท็จจริงพื้นฐานของการเขียนโปรแกรมใน Java หากมีสิ่งใดที่เบี่ยงเบนไปจากสิ่งนั้นสิ่งนั้นก็จะนำไปสู่ความสับสนอย่างสิ้นเชิง อย่าเถียงการก่อตัวของพวกเขาและรหัสกับพวกเขา