Javaカレンダーで1月の月が0なのはなぜですか?


300

ではjava.util.Calendar、1月は月1ではなく月0として定義されています。これに特別な理由はありますか?

多くの人が混乱しているのを見てきました...


4
定数JANUARY、FEBRUARYなどが存在するので、そのような実装の詳細ではありませんか?日付クラスは適切なjava enumサポートよりも前に存在します。
gnud 2008

6
さらに面倒です-なぜUndecemberがあるのですか?
matt b

40
@gnud:いいえ、実装の詳細ではありません。「自然な」基数(つまりJan = 1)で整数が与えられ、それをカレンダーAPIで使用する必要がある場合、これは面倒です。
Jon Skeet

1
@matt b:13か月のグレゴリオ暦以外の暦(月暦など)向けです。そのため、数字で考えないことが最善ですが、ローカライズはカレンダーに任せてください。
エリクソン2008

7
13か月の議論は意味がありません。その場合は、追加の月を0または13にしないでください。
Quinn Taylor、

回答:


323

これは、Javaの日付/時刻APIである恐ろしい混乱のほんの一部です。何が問題なのかをリストするのに非常に時間がかかります(そして、問題の半分を知らないと確信しています)。確かに日付と時刻を操作するのは難しいですが、とにかくそうです。

代わりにJoda Timeを使用するか、JSR-310を使用してください。

編集:理由について-他の回答で述べたように、それは古いC API、またはすべてが0から始まるという一般的な感覚による可能性があります...もちろん、1から始まる日を除きます。元の実装チームの外の誰かが本当に理由を述べることができるかどうか疑問です-繰り返しますが、私は読者に悪い決定がなされた理由についてそれほど心配しないようにしてくださいjava.util.Calendar

0ベースのインデックスを使用することの利点の1つ、「名前の配列」などを簡単にすることです。

// I "know" there are 12 months
String[] monthNames = new String[12]; // and populate...
String name = monthNames[calendar.get(Calendar.MONTH)];

もちろん、これは、13か月のカレンダーを取得するとすぐに失敗します...しかし、少なくとも指定されたサイズは、期待する月数です。

これは正当な理由ではありませんが、それ理由です...

編集:コメントの一種として、日付/カレンダーで私が間違っていると思うことについていくつかのアイデアを要求します:

  • 驚くべきベース(Dateの年ベースとして1900、非推奨のコンストラクターの場合は確かに、両方の月ベースとして0)
  • 可変性-不変タイプを使用すると、実際に有効なであるものを操作するのがはるかに簡単になります
  • 不十分な型のセット:持っていることのnice DateCalendar異なるものとしてではなく、日付/時刻が時間対日対であるような値を「ゾーニング」対「ローカル」の分離は、不足しています
  • 明確に名前が付けられたメソッドではなく、魔法の定数を持つ醜いコードにつながるAPI
  • 推論するのが非常に難しいAPI-物事がいつ再計算されるかに関するすべてのビジネスなど
  • パラメータなしのコンストラクタを使用してデフォルトで「今」に設定すると、テストが困難なコードになります。
  • Date.toString()いつも(のは、今までに多くのスタックオーバーフローのユーザーを混同していること)システムのローカルタイムゾーンを使用して実装

14
...そして、便利な単純なDateメソッドをすべて非推奨にするとどうなりますか?今、私はその恐ろしいCalendarオブジェクトを、以前は単純であったことを行うために複雑な方法で使用する必要があります。
Brian Knoblauch、

3
@ブライアン:私はあなたの痛みを感じます。繰り返しになりますが、Joda Timeの方が単純です:)(不変性の要素により、操作もずっと快適になります。)
Jon Skeet

8
あなたは質問に答えませんでした。
Zeemee

2
@ user443854:編集にいくつかのポイントを記載しました-それが役立つかどうかを確認してください。
Jon Skeet

2
Java 8を使用している場合は、Calendarクラスを破棄して、新しく洗練されたDateTime APIに切り替えることができます。新しいAPIには、不変でスレッドセーフなDateTimeFormatterも含まれています。これは、問題のある高価なSimpleDateFormatを大幅に改善したものです。
ccpizza 2015

43

数か月で計算する方がはるかに簡単だからです。

12月の1か月後は1月ですが、通常これを理解するには、月数を計算して計算する必要があります。

12 + 1 = 13 // What month is 13?

知っている!モジュラス12を使用すると、これをすばやく修正できます。

(12 + 1) % 12 = 1

これは11月までの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

すべての月が同じように機能し、回避策は必要ありません。


5
これは満足です。少なくともこの狂気にはいくつかの価値があります!
moljac024 2014

「たくさんの魔法の数字」-いや、それは2回現れる1つだけです。
user123444555621 2014

ただし、Cが「剰余」演算子ではなく「剰余」演算子を使用しているため、1か月前に戻るのはまだ少し厄介です。また、年を調整せずに月を実際にバンプする必要がある頻度もわかりません。月を1〜12にしても、「while(月> 12){月-= 12;
++++

2
DateTime.AddMonthsのような正気な関数はlibに適切に実装するには難しすぎるため、自分で説明した計算を実行する必要があります... Mmmmmkay
nsimeonov '30

8
私はこれらのupvotesを理解していない- ((11 - 1 + 1) % 12) + 1 = 12だけで(11 % 12) + 1あなただけの1を追加する必要がありヶ月1..12のために、すなわち後に剰余をやって。魔法は必要ありません。
mfitzp

35

Cベースの言語はCをある程度コピーします。tm(で定義された構造time.h)整数フィールド有するtm_mon0-11の(コメント)の範囲とします。

Cベースの言語は配列をインデックス0から開始します。これは、月の名前の配列に文字列をインデックスとして出力するのに便利でしたtm_mon


22

これに対する答えはたくさんありますが、私はとにかくこの問題について私の見解を述べます。前述のように、この奇妙な動作の背後にある理由time.hは、月が0〜11の範囲のintに格納されているPOSIX Cにあります。理由を説明するには、次のように見てください。年と日は話し言葉の数字と見なされますが、月には独自の名前があります。したがって、1月は最初の月なので、最初の配列要素であるオフセット0として格納されます。monthname[JANUARY]だろう"January"。年の最初の月は、最初の月の配列要素です。

一方、日番号には名前がないため、0〜30としてintに格納すると混乱を招き、day+1出力するための多くの指示が追加されます。もちろん、多くのバグが発生しやすくなります。

そうは言っても、特にJavaScript(この "機能"も継承している)の場合、言語から遠く離れた場所で抽象化する必要があるスクリプト言語では、矛盾がわかりにくくなっています。

TL; DR:月には名前があり、日にはないので。


1
「月には名前があり、日にはありません。」 「金曜日」を聞いたことがありますか?;)OK私はあなたが '..days of the month'を意味しないと思います-多分あなたの(そうでなければ良い)答えを編集するのはお金になるでしょう。:-)
Andrew Thompson

0/0/0000は "00-Jan-0000"または "00-XXX-0000"としてより適切にレンダリングされますか?私見では、「月」が13である場合、多くのコードはきれいでしたが、月0にダミーの名前が付けられました。
スーパーキャット2015

1
それは興味深いテイクですが、0/0/0000は有効な日付ではありません。どのように40/40/0000をレンダリングしますか?
ピクセルビットワークス

12

Java 8には、より健全な新しい日付/時刻API JSR 310があります。仕様リードはJodaTimeの主要な作成者と同じであり、多くの類似した概念とパターンを共有しています。


2
新しいDate Time APIがJava 8の一部になりました
mschenk74 '25年

9

私は怠惰だと思います。配列は0から始まります(誰もが知っています)。今年の月は配列です。Sunのエンジニアは、Javaコードにこれを少しも気にしないだけだと思いました。


9
いいえ、しません。プログラマーよりも顧客の効率を最適化することが重要です。この顧客はここで時間を費やしているため、失敗しました。
TheSmurf 2008

2
効率性とはまったく関係ありません。月が配列に格納されているようではなく、12か月を表すには13が必要です。APIを本来のユーザーフレンドリーにする必要があるので、APIをユーザーフレンドリーにする必要はありません。Jof Blochが「Effective Java」の日付とカレンダーをぼろぼろにします。完璧なAPIはほとんどありません。Javaの日付/時刻APIには、不適切な役割を果たしているという残念な役割があります。それは人生ですが、それが効率に関係しているとは思わないでください。
Quinn Taylor、

1
では、なぜ0から30までの日数を数えないのですか?それは一貫性がなく、ずさんだ。
JuanguiJordán2017年


8

なぜなら、プログラマーは0ベースのインデックスに夢中だからです。わかりました。それよりも少し複雑です。低レベルのロジックを使用して0ベースのインデックスを使用している場合は、より理にかなっています。しかし、概して、私はまだ最初の文に固執します。


1
これは、これらのイディオム/行く習慣の別のあるすべてがオフセットではなく、インデックスの観点から行われているアセンブラまたは機械語へ戻ります。配列表記はオフセット0から開始し、連続したブロックにアクセスするためのショートカットになっ
ケンジェントル

4

個人的には、JavaカレンダーAPIの奇妙さを、グレゴリオ中心の考え方から自分自身を離婚し、その点でより不可知論的にプログラミングしようとする必要があることを示していたと考えました。具体的には、月などの定数をハードコードしないようにもう一度学びました。

次のうちどれが正しい可能性が高いですか?

if (date.getMonth() == 3) out.print("March");

if (date.getMonth() == Calendar.MARCH) out.print("March");

これは、Joda Timeについて私を少しいらいらさせる1つのことを示しています-それはプログラマーがハードコードされた定数の観点から考えることを奨励するかもしれません。(ただし、ほんの少しです。Jodaがプログラマーに不適切なプログラミングを強いているわけではありません。)


1
しかし、コードに定数がない場合、どのスキームが頭痛の種になる可能性が高くなります-Webサービス呼び出しなどの結果である値があります。
Jon Skeet、

もちろん、そのWebサービス呼び出しでもその定数を使用する必要があります。:-)同じことが外部の呼び出し元にも当てはまります。複数の標準が存在することを確認したら、1つの標準を適用する必要性が明らかになります。(私は...私はあなたのコメントを理解願っています)
ポール・ブリンクリー

3
はい、私たちは、世界の他のほとんどすべてが月を表すときに使用する標準を強制する必要があります-1ベースの標準。
Jon Skeet、

ここでのキーワードは「ほぼ」です。明らかに、Jan = 1などは、非常に広く使用されている日付システムでは自然に感じられますが、この1つの場合でも、ハードコードされた定数を回避する例外を自分自身に許可するのはなぜですか?
ポールブリンクリー

3
生活が楽になるからです。それだけです。1 か月ベースのシステムで1つずれた問題に遭遇したことはありません。私が見てきたたくさんのJava APIを使用して、このようなバグを。世界中の誰もがしていることを無視しても意味がありません。
Jon Skeet、

4

私にとって、mindpro.comほど優れた説明はありません。

ガチャ

java.util.GregorianCalendarold java.util.Dateクラスよりバグと落とし穴ははるかに少ない ですが、それでもピクニックではありません。

サマータイムが最初に提案されたときにプログラマがいたとしたら、彼らはそれを狂気で扱いにくいものとして拒否したでしょう。夏時間には、根本的な曖昧さがあります。秋に時計を午前1時に1時間戻すと、現地時間の午前1時30分と呼ばれる2つの異なる瞬間があります。読書で夏時間か標準時間のどちらを意図したかを記録した場合にのみ、それらを区別することができます。

残念ながら、GregorianCalendarあなたがどちらを意図しているかを知る方法はありません。あいまいさを避けるために、ダミーのUTC TimeZoneを使用して現地時間を伝えることをお勧めします。プログラマは通常、この問題に目を閉じており、この時間は誰も何もしないことを望んでいます。

ミレニアムバグ。バグはまだカレンダークラスから出ていません。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 */

MSTの場合、バグは2001/01/01の午前7時に消えます。

GregorianCalendar型付けされていないintマジック定数の巨大な山によって制御されます。この手法は、コンパイル時エラーチェックの希望を完全に破壊します。たとえば、使用する月を取得するには GregorianCalendar. get(Calendar.MONTH));

GregorianCalendarには未加工 GregorianCalendar.get(Calendar.ZONE_OFFSET)と夏時間 GregorianCalendar. get( Calendar. DST_OFFSET)がありますが、使用されている実際のタイムゾーンオフセットを取得する方法はありません。これら2つを個別に取得して、一緒に追加する必要があります。

GregorianCalendar.set( year, month, day, hour, minute) 秒を0に設定しません。

DateFormatGregorianCalendar正しくメッシュしないでください。カレンダーを2回指定する必要があります。1回は間接的に日付として指定します。

ユーザーが自分のタイムゾーンを正しく設定していない場合、デフォルトで静かにPSTまたはGMTになります。

GregorianCalendarでは、地球上のすべての人がそうであるように、月は1ではなく、1月0から始まる番号が付けられます。しかし、日は1から始まり、曜日は日曜日= 1、月曜日= 2、…土曜日= 7です。しかし、DateFormat。parseは、January = 1の従来の方法で動作します。


4

java.util.Month

Javaは、1ベースのインデックスを数か月間使用する別の方法を提供します。java.time.Month列挙型を使用します。12か月ごとに1つのオブジェクトが事前定義されています。1〜12月の各1〜12に番号が割り当てられています。getValue番号を呼び出します。

(Gives you 6)のMonth.JULY代わりに(Gives you 7)を使用しCalendar.JULYます。

(import java.time.*;)

3

tl; dr

Month.FEBRUARY.getValue()  // February → 2.

2

細部

ジョン・スキート答えは正しいです。

これで、これらの厄介な古いレガシー日時クラスの代わりにjava.timeクラスが使用できるようになりました。

java.time.Month

これらのクラスの中には列挙型があります。列挙型は、1つ以上の事前定義されたオブジェクト、つまり、クラスのロード時に自動的にインスタンス化されるオブジェクトを保持します。上:我々はダースそのようなオブジェクトを持って、それぞれが名前を与えられた、、、などを。これらはそれぞれクラス定数です。これらのオブジェクトは、コード内の任意の場所で使用して渡すことができます。例:Month MonthJANUARYFEBRUARYMARCHstatic final publicsomeMethod( Month.AUGUST )

幸いなことに、番号は1〜12で、1は1月、12は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 
    );

フェブリエ

また、この列挙型のオブジェクトは、単なる整数ではなく、コードベースの周りに渡す必要があります。これにより、タイプセーフが提供され、有効な値の範囲が確保され、コードがより自己文書化されます。Javaの驚くほど強力な列挙型機能に慣れていない場合は、Oracleチュートリアルを参照してください。

またYearYearMonthクラスとクラスも便利です。


java.timeについて

java.timeのフレームワークは、Java 8に組み込まれており、後にされています。これらのクラスは、次のような厄介な古いレガシー日時クラスに取って代わります。java.util.Date.Calendar、& java.text.SimpleDateFormat

ジョダタイムプロジェクトは、今でメンテナンスモード、java.timeへの移行をアドバイスします。

詳細については、 Oracleチュートリアルを。また、スタックオーバーフローで多くの例と説明を検索してください。仕様はJSR 310です。

java.timeクラスはどこで入手できますか?

  • Java SE 8およびSE 9以降
    • 内蔵。
    • 実装がバンドルされた標準Java APIの一部。
    • Java 9には、いくつかのマイナーな機能と修正が追加されています。
  • Java SE 6および SE 7
    • java.time機能の多くは、ThreeTen-BackportでJava 6&7にバックポートされています。
  • アンドロイド
    • ThreeTenABPのプロジェクトは、適応ThreeTen、バックポートを、特にAndroidのための(上記の)。
    • 使い方…をご覧ください。

ThreeTen-エクストラプロジェクトでは、追加のクラスでjava.timeを拡張します。このプロジェクトは、java.timeに将来追加される可能性があることを証明する場です。あなたはここにいくつかの有用なクラスのような見つけることがIntervalYearWeekYearQuarter、および多くを


0

それ自体がゼロとして正確に定義されているわけではなく、Calendar.Januaryとして定義されています。列挙型の代わりに定数として整数を使用することの問題です。Calendar.January == 0。


1
値は同じです。APIは0を返す場合もあり、定数と同じです。Calendar.JANUARYは1として定義されている可能性があります—それが全体のポイントです。列挙型は良い解決策ですが、真の列挙型はJava 5まで言語に追加されず、Dateは最初から存在していました。残念なことですが、サードパーティのコードで使用すると、このような基本的なAPIを「修正」することはできません。できる最善の方法は、新しいAPIを提供し、古いAPIを非推奨にして、人々に前進を促すことです。ありがとう、Java 7 ...
Quinn Taylor

0

言語を書くことは見た目よりも難しく、特に処理時間はほとんどの人が考えるよりもはるかに困難です。問題のごく一部(実際にはJavaではありません)については、https://www.youtube.com/watch?v = -5wpm-gesOYのYouTubeビデオ「The Problem with Time&Timezones-Computerphile」を参照してください。頭が混乱して笑い飛ばされても驚かないでください。


-1

DannySmurfの怠惰の答えに加えて、のような定数を使用することをお勧めしますCalendar.JANUARY


5
特定の月のコードを明示的に記述している場合はそれで十分ですが、別のソースから「通常の」形式の月を取得するのは面倒です。
Jon Skeet、

1
また、月の値を特定の方法で印刷しようとするときも苦痛です。常に1を加算します。
ブライアンワーショー

-2

すべてが0で始まるからです。これがJavaでのプログラミングの基本的な事実です。それから逸脱することが1つあるとすれば、それは混乱の種をまき散らすことになります。それらの形成について議論したり、コード化したりしないでください。


2
いいえ、現実世界ではほとんどのものが1から始まります。オフセットは0から始まり、年の月はオフセットではありません。月の日が31、30、29、または28.月をオフセットとして扱うことは、特に同時に、月の日を同じ方法で扱わない場合、気まぐれです。この違いの理由は何ですか?
SantiBailors

現実の世界では1から始まります。Javaの世界では0から始まります。しかし...理由は次のとおりだと思います。それ...-さらに、必要に応じてその月の完全な日数を表示します(混乱や2月をチェックする必要はありません)。さらに、1年の月数は定期的で、1か月の日数は通常ではないので、配列を宣言し、配列に適合するようにオフセットを使用する必要がある場合は理にかなっています。
Syrrus
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.