为什么Java Calendar中的1月份为0?

Sté*_*iez 294 java calendar

java.util.Calendar,1月定义为第0个月,而不是第1个月.是否有任何具体原因?

我见过很多人对此感到困惑......

Jon*_*eet 317

它只是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)];
Run Code Online (Sandbox Code Playgroud)

当然,只要您获得13个月的日历,这就会失败......但至少指定的大小是您期望的月数.

这不是一个理由,但这是一个原因......

编辑:作为一种评论请求一些关于我认为日期/日历错误的想法:

  • 令人惊讶的基数(1900年作为Date的年份基础,不可否认的是被弃用的构造函数; 0作为两者的月基数)
  • 可变性 - 使用不可变类型可以简单地处理真正有效的
  • 一组不充分的类型:拥有DateCalendar不同的东西很好,但缺少"本地"与"分区"值的分离,日期/时间与日期与时间的关系也是如此
  • 一个API导致带有魔术常量的丑陋代码,而不是明确命名的方法
  • 一个非常难以推理的API - 关于重新计算事物的所有业务等
  • 使用无参数构造函数默认为"now",这导致难以测试的代码
  • Date.toString()它总是使用系统本地时区的实现(这是以前,现在弄得很多堆栈溢出的用户)

  • ...以及如何弃用所有有用的简单Date方法?现在我必须以复杂的方式使用那个可怕的Calendar对象来做一些过去简单的事情. (13认同)
  • 你没有回答这个问题. (8认同)
  • @Brian:我感觉到你的痛苦.同样,Joda Time更简单:)(不变性因素也使得使用起来更加愉快.) (3认同)
  • @ user443854:我在编辑中列出了一些要点 - 看看是否有帮助. (2认同)
  • 如果您正在使用Java 8,那么您可以抛弃Calendar类并切换到新的时尚[DateTime API](https://docs.oracle.com/javase/8/docs/api/java/time/package-summary的.html).新的API还包括一个不可变的/ threadsafe [DateTimeFormatter](https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html),这是对问题和昂贵的SimpleDateFormat. (2认同)
  • @DerickDaniel:不在 java.util.Calendar 中,不。但我强烈建议您改用 java.time,它*确实*使用基于 1 的月份。 (2认同)

小智 43

因为用数月做数学会容易得多.

12月之后的1个月是1月,但通常你需要拿出月份数并做数学

12 + 1 = 13 // What month is 13?
Run Code Online (Sandbox Code Playgroud)

我知道!我可以使用12的模数快速解决这个问题.

(12 + 1) % 12 = 1
Run Code Online (Sandbox Code Playgroud)

直到11月,这个工作正常11个月......

(11 + 1) % 12 = 0 // What month is 0?
Run Code Online (Sandbox Code Playgroud)

您可以通过在添加月份之前减去1来再次使所有这些工作,然后执行模数并最终再次添加1 ...也就是解决潜在问题.

((11 - 1 + 1) % 12) + 1 = 12 // Lots of magical numbers!
Run Code Online (Sandbox Code Playgroud)

现在让我们考虑一下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
Run Code Online (Sandbox Code Playgroud)

所有月份的工作都相同,并且不需要解决问题.

  • 这很令人满意.至少对这种疯狂有一些价值! (5认同)
  • 我不理解这些投票-`(((11-1 + 1)%12)+ 1 = 12`只是`(11%12)+ 1`,即对于1..12个月,您只需要添加1 *在*做模之后。不需要魔术。 (4认同)
  • “很多神奇的数字”——不,这只是一个出现了两次的数字。 (3认同)
  • 由于类似DateTime.AddMonths之类的理智功能很难在lib中正确实现,因此我们必须自己进行数学描述... Mmmmmkay (2认同)

ste*_*sch 35

基于C语言在某种程度上复制C. 的tm结构(在定义的time.h)具有一个整数字段tm_mon具有0-11的(注释)范围.

基于C语言在索引0处启动数组.因此,这对于输出月份名称数组中的字符串非常方便,并将其tm_mon作为索引.


pik*_*rks 22

对此有很多答案,但无论如何我都会就这个问题发表看法.如前所述,这种奇怪行为背后的原因来自POSIX C time.h,其中存储在0-11范围内的int的月份.为了解释原因,请这样看; 年和日被认为是口语中的数字,但是几个月都有自己的名字.因此,1月是第一个月,它将被存储为第一个数组元素offset 0.monthname[JANUARY]会的"January".一年中的第一个月是第一个月的数组元素.

另一方面,日期数字,因为它们没有名称,将它们存储在int中作为0-30会令人困惑,添加大量day+1输出指令,当然,容易出现很多错误.

话虽这么说,不一致是令人困惑的,特别是在javascript(它也继承了这个"功能"),一种脚本语言,这应该被抽象远离langague.

TL; DR:因为月份有名字,而月份没有.


Ale*_*ler 12

在Java 8中,有一个更新的日期/时间API JSR 310更加理智.spec规范与JodaTime的主要作者相同,它们共享许多类似的概念和模式.

  • 新的Date Time API现在是Java 8的一部分 (2认同)

The*_*urf 9

我会说懒惰.数组从0开始(每个人都知道); 一年中的几个月是一个阵列,这使我相信Sun的一些工程师只是不愿意把这一点放在Java代码中.

  • 不,我不会.优化一个客户的效率比一个程序员更重要.由于这位顾客在这里花时间问,他们就失败了. (9认同)
  • 你可以称之为效率. (3认同)
  • 它与效率完全无关 - 它不像数月存储在一个阵列中,你需要13代表12个月.这是一个问题,就是不要让API像用户一样友好,因为它们应该首先出现.Josh Bloch在"有效Java"中破坏日期和日历.很少有API是完美的,Java中的日期/时间API具有不幸的作用.这就是生活,但我们不要假装它与效率有任何关系. (2认同)

Pau*_*lin 9

可能是因为C的"struct tm"也是如此.


Din*_*nah 7

因为程序员痴迷于基于0的索引.好吧,它比这更复杂:当你使用低级逻辑来使用基于0的索引时,它更有意义.但总的来说,我仍然坚持我的第一句话.


Dig*_*ity 5

java.time.Month

Java 为您提供了另一种使用基于 1 的索引数月的方法。使用java.time.Month枚举。为十二个月中的每一个月预定义一个对象。1 月至 12 月的编号为 1-12;称呼getValue电话索取号码。

使用Month.JULY(Gives you 7) 而不是Calendar.JULY(Gives you 6)。

(import java.time.*;)
Run Code Online (Sandbox Code Playgroud)


Ole*_*.V. 5

真正的原因是

您可能会认为,当我们弃用 Date 的大部分内容并添加新的 Calendar 类时,我们就可以解决 Date 最大的烦恼:一月是第 0 个月。我们当然应该这样做,但不幸的是我们没有。我们担心如果 Date 使用从零开始的月份而 Calendar 使用从一开始的月份,程序员会感到困惑。一些程序员可能会这样。但事后看来,Calendar 仍然是从零开始的这一事实引起了巨大的混乱,这可能是 Java 国际 API 中最大的单一错误。

引用自Laura Werner 的Java 国际日历,链接位于底部。

更好的选择:java.time

这可能只是重复其他人所说的,抛弃旧的且设计不良的Calendar类并使用 java.time,现代 Java 日期和时间 API。月份的编号一致,从 1 月(1 月)到 12 月(12 月)。

如果您从尚未升级到 java.time 的旧版 API 获取 a 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());
Run Code Online (Sandbox Code Playgroud)

我刚才在我的时区跑步时的输出:

2021-03-17T23:18:47.761+01:00[Europe/Copenhagen]
Month is 3 or MARCH
Run Code Online (Sandbox Code Playgroud)

链接