Trong java.util.Calendar
, tháng 1 được định nghĩa là tháng 0, không phải tháng 1. Có lý do cụ thể nào không?
Tôi đã thấy nhiều người bị nhầm lẫn về điều đó ...
Trong java.util.Calendar
, tháng 1 được định nghĩa là tháng 0, không phải tháng 1. Có lý do cụ thể nào không?
Tôi đã thấy nhiều người bị nhầm lẫn về điều đó ...
Câu trả lời:
Nó chỉ là một phần của mớ hỗn độn khủng khiếp đó là API ngày / giờ Java. Liệt kê những gì sai với nó sẽ mất một thời gian rất dài (và tôi chắc chắn rằng tôi không biết một nửa vấn đề). Phải thừa nhận làm việc với ngày tháng và thời gian là khó khăn, nhưng dù sao đi nữa.
Thay vào đó, hãy tạo cho mình một lợi ích và sử dụng Joda Time , hoặc có thể là JSR-310 .
EDIT: Về lý do tại sao - như đã lưu ý trong các câu trả lời khác, đó cũng có thể là do API C cũ hoặc chỉ là cảm giác chung về việc bắt đầu mọi thứ từ 0 ... ngoại trừ ngày bắt đầu bằng 1, tất nhiên. Tôi nghi ngờ liệu có ai ngoài nhóm thực hiện ban đầu có thể thực sự nêu lý do hay không - nhưng một lần nữa, tôi mong các độc giả đừng lo lắng quá nhiều về lý do tại sao các quyết định tồi tệ được đưa ra, như để xem xét toàn bộ vấn đề khó chịu java.util.Calendar
và tìm ra điều gì đó tốt hơn.
Một điểm mà là ủng hộ việc sử dụng 0 dựa trên chỉ số là nó làm cho những câu như "mảng tên" dễ dàng hơn:
// I "know" there are 12 months
String[] monthNames = new String[12]; // and populate...
String name = monthNames[calendar.get(Calendar.MONTH)];
Tất nhiên, điều này không thành công ngay khi bạn nhận được lịch với 13 tháng ... nhưng ít nhất kích thước được chỉ định là số tháng bạn mong đợi.
Đây không phải là một lý do tốt , nhưng đó là một lý do ...
EDIT: Là một loại bình luận yêu cầu một số ý tưởng về những gì tôi nghĩ là sai với Ngày / Lịch:
Date
vàCalendar
như những thứ khác nhau, nhưng việc tách các giá trị "cục bộ" và "khoanh vùng" bị thiếu, cũng như ngày / giờ so với ngày so với thời gianDate.toString()
triển khai luôn sử dụng múi giờ cục bộ của hệ thống (điều này làm bối rối nhiều người dùng Stack Overflow trước đây)Bởi vì làm toán với tháng dễ hơn nhiều.
1 tháng sau tháng 12 là tháng 1, nhưng để hiểu điều này một cách bình thường, bạn sẽ phải lấy số tháng và làm toán
12 + 1 = 13 // What month is 13?
Tôi biết! Tôi có thể khắc phục điều này một cách nhanh chóng bằng cách sử dụng mô-đun 12.
(12 + 1) % 12 = 1
Điều này chỉ hoạt động tốt trong 11 tháng cho đến tháng 11 ...
(11 + 1) % 12 = 0 // What month is 0?
Bạn có thể thực hiện lại tất cả công việc này bằng cách trừ đi 1 trước khi bạn thêm tháng, sau đó thực hiện mô đun của mình và cuối cùng thêm 1 lần nữa ... hay còn gọi là giải quyết vấn đề tiềm ẩn.
((11 - 1 + 1) % 12) + 1 = 12 // Lots of magical numbers!
Bây giờ hãy nghĩ về vấn đề với các tháng 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
Tất cả các tháng làm việc như nhau và một công việc xung quanh là không cần thiết.
((11 - 1 + 1) % 12) + 1 = 12
chỉ (11 % 12) + 1
trong tháng 1..12 bạn chỉ cần thêm số 1 sau khi thực hiện modulo. Không cần phép thuật.
Ngôn ngữ dựa trên C sao chép C ở một mức độ nào đó. Các tm
cấu trúc (quy định tại time.h
) có một trường số nguyên tm_mon
với (nhận xét) khoảng 0-11.
Các ngôn ngữ dựa trên C bắt đầu các mảng ở chỉ số 0. Vì vậy, điều này thuận tiện cho việc xuất một chuỗi trong một mảng các tên tháng, với tm_mon
tư cách là chỉ mục.
Đã có rất nhiều câu trả lời cho vấn đề này, nhưng dù sao tôi cũng sẽ đưa ra quan điểm của mình về chủ đề này. Lý do đằng sau hành vi kỳ quặc này, như đã nêu trước đây, xuất phát từ POSIX C time.h
trong đó các tháng được lưu trữ trong một int với phạm vi 0-11. Để giải thích tại sao, hãy nhìn nó như thế này; năm và ngày được coi là số trong ngôn ngữ nói, nhưng tháng có tên riêng. Vì vậy, vì tháng 1 là tháng đầu tiên, nó sẽ được lưu dưới dạng offset 0, phần tử mảng đầu tiên. monthname[JANUARY]
sẽ là "January"
. Tháng đầu tiên trong năm là yếu tố mảng tháng đầu tiên.
Mặt khác, các số ngày, vì chúng không có tên, lưu trữ chúng trong một số 0-30 sẽ gây nhầm lẫn, thêm rất nhiều day+1
hướng dẫn để xuất ra và tất nhiên, dễ bị lỗi.
Điều đó đang được nói, sự không nhất quán là khó hiểu, đặc biệt là trong javascript (cũng được thừa hưởng "tính năng" này), một ngôn ngữ kịch bản mà điều này nên được trừu tượng hóa xa khỏi langague.
TL; DR : Vì tháng có tên và ngày trong tháng không.
Tôi muốn nói sự lười biếng. Mảng bắt đầu từ 0 (mọi người đều biết điều đó); các tháng trong năm là một mảng, khiến tôi tin rằng một số kỹ sư tại Sun không bận tâm đưa một chút tốt đẹp này vào mã Java.
Có lẽ bởi vì "struct tm" của C cũng làm như vậy.
Bởi vì các lập trình viên bị ám ảnh bởi các chỉ số dựa trên 0. OK, nó phức tạp hơn thế một chút: sẽ hợp lý hơn khi bạn làm việc với logic cấp thấp hơn để sử dụng lập chỉ mục dựa trên 0. Nhưng nhìn chung, tôi vẫn sẽ gắn bó với câu đầu tiên của mình.
Cá nhân, tôi đã lấy sự kỳ lạ của API lịch Java như một dấu hiệu cho thấy tôi cần phải ly dị bản thân khỏi tư duy trung tâm của Gregorian và cố gắng lập trình một cách nông nghiệp hơn trong khía cạnh đó. Cụ thể, tôi đã học một lần nữa để tránh các hằng số được mã hóa cứng cho những thứ như tháng.
Điều nào sau đây có nhiều khả năng là chính xác?
if (date.getMonth() == 3) out.print("March");
if (date.getMonth() == Calendar.MARCH) out.print("March");
Điều này minh họa một điều khiến tôi bực mình một chút về Thời gian Joda - nó có thể khuyến khích các lập trình viên suy nghĩ về các hằng số được mã hóa cứng. (Tuy nhiên, chỉ một chút thôi. Không như thể Joda đang buộc các lập trình viên phải lập trình tồi.)
Đối với tôi, không ai giải thích điều đó tốt hơn mindpro.com :
Gotchas
java.util.GregorianCalendar
có ít lỗi và gotchas hơnold java.util.Date
lớp nhưng nó vẫn không có dã ngoại.Đã có những lập trình viên khi Giờ tiết kiệm ánh sáng ban ngày được đề xuất lần đầu tiên, họ sẽ phủ quyết nó là điên rồ và khó hiểu. Với tiết kiệm ánh sáng ban ngày, có một sự mơ hồ cơ bản. Vào mùa thu khi bạn đặt đồng hồ trở lại một giờ vào lúc 2 giờ sáng, có hai thời điểm khác nhau được gọi là 1:30 AM giờ địa phương. Bạn chỉ có thể phân biệt chúng nếu bạn ghi lại xem bạn dự định tiết kiệm ánh sáng ban ngày hay thời gian tiêu chuẩn với việc đọc.
Thật không may, không có cách nào để nói
GregorianCalendar
bạn dự định. Bạn phải dùng đến việc nói với nó giờ địa phương với UTC TimeZone giả để tránh sự mơ hồ. Các lập trình viên thường nhắm mắt trước vấn đề này và chỉ hy vọng không ai làm gì trong giờ này.Lỗi thiên niên kỷ. Các lỗi vẫn không nằm ngoài các lớp Lịch. Ngay cả trong JDK (Java Development Kit) 1.3 cũng có lỗi 2001. Hãy xem xét các mã sau đây:
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 */
Lỗi biến mất lúc 7 giờ sáng ngày 2001/01/01 đối với MST.
GregorianCalendar
được điều khiển bởi một đống khổng lồ các hằng số ma thuật chưa được kiểm tra. Kỹ thuật này hoàn toàn phá hủy mọi hy vọng kiểm tra lỗi thời gian biên dịch. Ví dụ để lấy tháng bạn sử dụngGregorianCalendar. get(Calendar.MONTH));
GregorianCalendar
cóGregorianCalendar.get(Calendar.ZONE_OFFSET)
tiền tiết kiệm thô và ánh sáng ban ngàyGregorianCalendar. get( Calendar. DST_OFFSET)
, nhưng không có cách nào để có được phần bù múi giờ thực tế đang được sử dụng. Bạn phải lấy hai cái này một cách riêng biệt và thêm chúng lại với nhau.
GregorianCalendar.set( year, month, day, hour, minute)
không đặt giây thành 0.
DateFormat
vàGregorianCalendar
không lưới đúng cách. Bạn phải chỉ định Lịch hai lần, một lần gián tiếp là Ngày.Nếu người dùng không cấu hình đúng múi giờ của mình, nó sẽ mặc định lặng lẽ theo PST hoặc GMT.
Trong GregorianCalWiki, Tháng được đánh số bắt đầu từ tháng 1 = 0, thay vì 1 như mọi người khác trên hành tinh này. Tuy nhiên, ngày bắt đầu từ 1 cũng như các ngày trong tuần với Chủ nhật = 1, Thứ hai = 2, Thứ bảy = 7. Chưa DateFormat. phân tích ứng xử theo cách truyền thống với tháng 1 = 1.
java.util.Month
Java cung cấp cho bạn một cách khác để sử dụng 1 chỉ mục dựa trên nhiều tháng. Sử dụng java.time.Month
enum. Một đối tượng được xác định trước cho mỗi mười hai tháng. Họ có các số được gán cho mỗi 1-12 cho tháng 1-12; gọi getValue
cho số.
Sử dụng Month.JULY
(Cung cấp cho bạn 7) thay vì Calendar.JULY
(Cung cấp cho bạn 6).
(import java.time.*;)
Month.FEBRUARY.getValue() // February → 2.
2
Câu trả lời của Jon Skeet là chính xác.
Bây giờ chúng ta có một sự thay thế hiện đại cho các lớp thời gian cũ kế thừa rắc rối đó: các lớp java.time .
java.time.Month
Trong số các lớp đó là enum . Một enum mang một hoặc nhiều đối tượng được xác định trước, các đối tượng được tự động khởi tạo khi lớp tải. Trên chúng ta có một chục đối tượng như vậy, mỗi một cái tên: , , , và vân vân. Mỗi cái là một hằng số lớp. Bạn có thể sử dụng và chuyển các đối tượng này bất cứ nơi nào trong mã của bạn. Thí dụ:Month
Month
JANUARY
FEBRUARY
MARCH
static final public
someMethod( Month.AUGUST )
May mắn thay, họ đã đánh số lành mạnh, 1-12 trong đó 1 là tháng 1 và 12 là tháng 12.
Lấy một Month
đối tượng cho một số tháng cụ thể (1-12).
Month month = Month.of( 2 ); // 2 → February.
Đi theo hướng khác, yêu cầu một Month
đối tượng cho số tháng của nó.
int monthNumber = Month.FEBRUARY.getValue(); // February → 2.
Nhiều phương pháp tiện dụng khác trên lớp này, chẳng hạn như biết số ngày trong mỗi tháng . Lớp thậm chí có thể tạo ra một tên địa phương của tháng.
Bạn có thể lấy tên địa phương của tháng, với nhiều độ dài hoặc viết tắt khác nhau.
String output =
Month.FEBRUARY.getDisplayName(
TextStyle.FULL ,
Locale.CANADA_FRENCH
);
yêu thích
Ngoài ra, bạn nên chuyển các đối tượng của enum này xung quanh cơ sở mã của bạn chứ không chỉ là số nguyên . Làm như vậy cung cấp loại an toàn, đảm bảo phạm vi giá trị hợp lệ và làm cho mã của bạn trở nên tự ghi lại hơn. Xem Hướng dẫn của Oracle nếu không quen thuộc với tiện ích enum mạnh mẽ đáng ngạc nhiên trong Java.
Bạn cũng có thể tìm thấy hữu ích Year
và YearMonth
các lớp.
Các java.time khung được xây dựng vào Java 8 và sau đó. Những lớp học thay thế cái cũ phiền hà di sản lớp học ngày thời gian như java.util.Date
, .Calendar
, & java.text.SimpleDateFormat
.
Các Joda thời gian dự án, bây giờ trong chế độ bảo trì , khuyên chuyển đổi sang java.time.
Để tìm hiểu thêm, xem Hướng dẫn Oracle . Và tìm kiếm Stack Overflow cho nhiều ví dụ và giải thích. Đặc điểm kỹ thuật là JSR 310 .
Nơi để có được các lớp java.time?
Các ThreeTen-Extra dự án mở rộng java.time với các lớp bổ sung. Dự án này là một nền tảng chứng minh cho các bổ sung có thể trong tương lai cho java.time. Bạn có thể tìm thấy một số các lớp học hữu ích ở đây chẳng hạn như Interval
, YearWeek
, YearQuarter
, và nhiều hơn nữa .
Nó không được định nghĩa chính xác là 0 mỗi se, nó được định nghĩa là Lịch. Đó là vấn đề sử dụng ints như hằng số thay vì enum. Lịch.Janemony == 0.
Bởi vì viết ngôn ngữ khó hơn vẻ ngoài của nó, và thời gian xử lý nói riêng khó hơn rất nhiều so với hầu hết mọi người nghĩ. Đối với một phần nhỏ của vấn đề (trong thực tế, không phải Java), hãy xem video YouTube "Vấn đề về thời gian và múi giờ - Máy tính" tại https://www.youtube.com/watch?v=-5wpm-gesOY . Đừng ngạc nhiên nếu đầu bạn rơi ra vì cười trong sự bối rối.
Ngoài câu trả lời về sự lười biếng của DannySmurf, tôi sẽ nói thêm rằng đó là để khuyến khích bạn sử dụng các hằng số, chẳng hạn như Calendar.JANUARY
.
Bởi vì mọi thứ bắt đầu bằng 0. Đây là một thực tế cơ bản của lập trình trong Java. Nếu một điều đi chệch khỏi đó, thì điều đó sẽ dẫn đến một sự nhầm lẫn hoàn toàn. Chúng ta đừng tranh luận về sự hình thành của chúng và mã với chúng.