在java.util.Calendar
中,1月定义为第0个月,而不是第1个月。是否有任何具体原因?
我看到很多人对此感到困惑......
答案 0 :(得分:310)
这只是Java日期/时间API的可怕混乱的一部分。列出它的错误需要很长时间(而且我确定我不知道一半的问题)。无论如何,处理日期和时间都很棘手,但无论如何都是aaargh。
帮自己一个忙,改为使用Joda Time,或者JSR-310。
编辑:至于原因 - 正如其他答案所述,很可能是由于旧的C API,或者只是从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
作为不同的东西很好,
但是缺少“本地”与“分区”值的分离,日期/时间与日期对时间的分离Date.toString()
实现(此前许多Stack Overflow用户感到困惑)答案 1 :(得分:39)
因为用数月做数学会容易得多。
12月份是1月份后的1个月,但通常你需要拿出月份数并做数学
12 + 1 = 13 // What month is 13?
我知道!我可以通过使用12的模数来快速解决这个问题。
(12 + 1) % 12 = 1
直到十一月才能正常工作11个月......
(11 + 1) % 12 = 0 // What month is 0?
你可以通过在添加月份之前减1来再次使所有这些工作,然后做你的模数,最后再添加1 ...也就是解决潜在的问题。
((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
所有月份的工作都相同,并且没有必要进行解决。
答案 2 :(得分:34)
基于C语言在某种程度上复制C. tm
结构(在time.h
中定义)具有整数字段tm_mon
,其(注释)范围为0-11。
基于C语言在索引0处启动数组。因此,这对于在月份名称数组中输出字符串很方便,tm_mon
为索引。
答案 3 :(得分:22)
对此有很多答案,但无论如何我都会就这个问题发表看法。
如前所述,这种奇怪行为背后的原因来自POSIX C time.h
,其中存储在0-11范围内的int的月份。
为了解释原因,请这样看;年和日被认为是口语中的数字,但是几个月都有自己的名字。因此,1月是第一个月,它将被存储为第一个数组元素offset 0。 monthname[JANUARY]
将是"January"
。一年中的第一个月是第一个月的数组元素。
另一方面,由于日期数字没有名称,将它们存储在0到30的int中会让人感到困惑,添加了很多day+1
输出指令,当然也很容易很多错误。
话虽如此,这种不一致性令人困惑,特别是在javascript(也继承了这个“功能”)中,这是一种脚本语言,应该远离langague抽象。
TL; DR :因为月份有名称,而月份则没有。
答案 4 :(得分:12)
在Java 8中,有一个更健全的新日期/时间API JSR 310。规范领导与JodaTime的主要作者相同,他们共享许多类似的概念和模式。
答案 5 :(得分:9)
我会说懒惰。数组从0开始(每个人都知道);一年中的几个月是一个阵列,这让我相信Sun的一些工程师只是不愿意把这一点放在Java代码中。
答案 6 :(得分:9)
可能是因为C的“struct tm”也是如此。
答案 7 :(得分:6)
因为程序员痴迷于基于0的索引。好吧,它比这更复杂:当你使用低级逻辑来使用基于0的索引时,它更有意义。但总的来说,我仍然坚持我的第一句话。
答案 8 :(得分:4)
就个人而言,我把Java日历API的陌生感作为一种迹象,表明我需要将自己与以格列高利为中心的思维模式脱离,并试图在这方面更加非常地编程。具体来说,我再次学会了避免像数月这样的事情的硬编码常量。
以下哪项更可能是正确的?
if (date.getMonth() == 3) out.print("March");
if (date.getMonth() == Calendar.MARCH) out.print("March");
这说明了一件让我想起Joda Time的事情 - 它可能会鼓励程序员用硬编码的常量来思考。 (但是,只有一点点。并不是说Joda强迫程序员编程很糟糕。)
答案 9 :(得分:4)
java.util.Month
Java为您提供了另一种使用基于1的索引的方法。使用java.time.Month
枚举。为十二个月中的每一个预定义一个对象。他们在1月至12月期间为每个1-12分配了数字;请拨打getValue
以获取该号码。
利用Month.JULY
(给你7)
而不是Calendar.JULY
(给你6)。
(import java.time.*;)
答案 10 :(得分:2)
您可能会认为,当我们弃用 Date 的大部分内容并添加新的 日历类,我们将解决 Date 最大的烦恼:事实 一月是 0 月。我们当然应该有,但不幸的是 我们没有。我们担心如果 Date 使用从零开始的月份,日历使用从一开始的月份。还有一些 程序员可能会。但事后看来,事实是 日历仍然是零基础已经造成了大量的 混乱,这可能是 Java 中最大的单一错误 国际 API。
引自 Laura Werner 的Java 国际日历,链接在底部。
这可能只是在重复其他人所说的,抛弃旧的和设计糟糕的 Calendar
类,并使用 java.time,现代 Java 日期和时间 API。从 1 月份的 1 到 12 月份的 12,月份的编号一直很合理。
如果您从尚未升级到 java.time 的遗留 API 获得 Calendar
,首先要做的是转换为现代 ZonedDateTime
。根据您的需要,您可以从那里进行进一步的转换。在世界上的大多数情况下,您获得的 Calendar
对象实际上总是 GregorianCalendar
子类的实例(因为 Calendar
类本身是抽象的)。驱魔:
Calendar oldfashionedCalendarObject = Calendar.getInstance();
ZonedDateTime zdt
= ((GregorianCalendar) oldfashionedCalendarObject).toZonedDateTime();
System.out.println(zdt);
System.out.format("Month is %d or %s%n", zdt.getMonthValue(), zdt.getMonth());
我刚刚在我的时区运行时的输出:
<块引用>2021-03-17T23:18:47.761+01:00[Europe/Copenhagen]
Month is 3 or MARCH
答案 11 :(得分:2)
Month.FEBRUARY.getValue() // February → 2.
2
Answer by Jon Skeet是正确的。
现在,我们可以替换那些麻烦的旧遗留日期时间类:java.time类。
java.time.Month
这些课程中有Month
enum。枚举携带一个或多个预定义对象,即在加载类时自动实例化的对象。在Month
我们有十几个这样的对象,每个对象都有一个名称:JANUARY
,FEBRUARY
,MARCH
,依此类推。每个都是static final public
类常量。您可以在代码中的任何位置使用并传递这些对象。示例:someMethod( Month.AUGUST )
幸运的是,他们有正确的编号,1-12,其中1是1月,12是12月。
获取特定月份编号(1-12)的Month
对象。
Month month = Month.of( 2 ); // 2 → February.
转向另一个方向,向Month
对象询问其月份编号。
int monthNumber = Month.FEBRUARY.getValue(); // February → 2.
此课程中有许多其他方便的方法,例如知道the number of days in each month。该课程甚至可以generate a localized name个月。
您可以使用各种长度或缩写获取月份的本地化名称。
String output =
Month.FEBRUARY.getDisplayName(
TextStyle.FULL ,
Locale.CANADA_FRENCH
);
FEVRIER
此外,你应该在你的代码库周围传递这个枚举的对象,而不仅仅是整数。这样做可以提供类型安全性,确保有效的值范围,并使您的代码更加自我记录。如果不熟悉Java中令人惊讶的强大枚举功能,请参阅Oracle Tutorial。
java.time框架内置于Java 8及更高版本中。这些类取代了麻烦的旧legacy日期时间类,例如java.util.Date
,.Calendar
和&amp; java.text.SimpleDateFormat
现在位于Joda-Time的maintenance mode项目建议迁移到java.time。
要了解详情,请参阅Oracle Tutorial。并搜索Stack Overflow以获取许多示例和解释。规范是JSR 310。
从哪里获取java.time类?
ThreeTen-Extra项目使用其他类扩展java.time。该项目是未来可能添加到java.time的试验场。您可以在此处找到一些有用的课程,例如Interval
,YearWeek
,YearQuarter
和more。
答案 12 :(得分:1)
对我来说,没有人比mindpro.com更好地解释:
<强>陷阱强>
java.util.GregorianCalendar
的错误和陷阱要少得多old java.util.Date
课程,但仍然没有野餐。当夏令时首次出现时,有程序员吗? 提议,他们会否认它是疯狂和难以处理的。同 夏令时,存在着根本的模糊性。在秋天的时候 你在凌晨2点将时钟设置回一小时有两种不同 当地时间凌晨1点30分。你可以告诉他们 只有当你记录你是否打算夏令时或 标准时间与阅读。
不幸的是,没有办法告诉
GregorianCalendar
你是谁 意。你必须诉诸于假人的当地时间 UTC TimeZone可以避免歧义。程序员通常关闭他们的 注意这个问题,希望没有人在这期间做任何事情 小时。千年虫。这些错误仍然不在Calendar类之外。 即使在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 */
该错误在2001年1月1日上午7点消失,为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中,月份从1月开始编号= 0, 而不是像地球上的其他人一样。然而,几天从1开始 星期日= 1,星期一= 2,星期六= 7,星期几也是如此。然而 日期格式。解析用January = 1的传统方式表现。
答案 13 :(得分:0)
它本身并没有精确定义为零,它被定义为Calendar.January。这是使用int作为常量而不是枚举的问题。 Calendar.January == 0。
答案 14 :(得分:0)
因为语言写作比看起来更难,特别是处理时间比大多数人想象的要困难得多。对于问题的一小部分(实际上,不是Java),请参阅YouTube视频&#34;时间问题&amp;时区 - Computerphile&#34;在https://www.youtube.com/watch?v=-5wpm-gesOY。如果你的头脑在混乱中脱落,不要感到惊讶。
答案 15 :(得分:-1)
除了DannySmurf对懒惰的回答之外,我还要补充说,鼓励你使用常量,例如Calendar.JANUARY
。
答案 16 :(得分:-2)
因为一切都以0开头。这是Java编程的基本事实。如果有一件事情与此有所不同,那么这将导致混乱的全部线索。我们不要争论它们的形成和代码。