327

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

迷っている人を何人も見てきました…

4

17 に答える 17

344

これは、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)
  • 可変性 - 不変型を使用すると、実際に効果的な値を簡単に操作できます
  • 型のセットが不十分:DateCalendarを異なるものとして持つのは良いことですが、「ローカル」値と「ゾーン」値の分離が欠落しており、日付/時刻と日付と時刻も同様です。
  • 明確に名前が付けられたメソッドではなく、魔法の定数を使用した醜いコードにつながる API
  • 推論するのが非常に難しい API - 物事がいつ再計算されるかなどに関するすべてのビジネス
  • パラメーターなしのコンストラクターを使用してデフォルトを "now" にするため、コードのテストが困難になる
  • Date.toString()常にシステムのローカル タイム ゾーンを使用する実装 (これまで多くの Stack Overflow ユーザーを混乱させてきました)
于 2008-12-05T16:29:41.283 に答える
53

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

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

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

于 2013-08-01T19:34:20.807 に答える
37

C ベースの言語は、ある程度 C をコピーします。tm構造体 ( で定義) には、0 ~ 11の(コメント付き) 範囲のtime.h整数フィールドがあります。tm_mon

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

于 2008-12-05T16:35:42.207 に答える
12

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

于 2008-12-05T19:25:22.960 に答える
10

怠惰と言えます。配列は 0 から始まります (誰もが知っています)。1 年の月は配列になっているので、Sun のエンジニアの中には、Java コードにこのちょっとした機能を入れようとしなかった人がいると思います。

于 2008-12-05T16:25:13.273 に答える
9

おそらく、C の「struct tm」が同じことを行うためです。

于 2008-12-05T16:37:16.090 に答える
8

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

于 2008-12-05T16:26:02.523 に答える
4

私にとって、mindpro.com ほどよく説明している人はいません。

落とし穴

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

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

残念ながら、どちらを意図したかを判断する方法はありません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 */

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

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

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

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

DateFormatかみ合いGregorianCalendarません。Calendar を 2 回指定する必要があります。1 回は間接的に Date として指定します。

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

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

于 2012-04-29T14:26:18.783 に答える
4

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

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

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

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

これは、Joda Time について私を少しイライラさせる 1 つのことを示しています。これは、プログラマーがハードコーディングされた定数の観点から考えるように促す可能性があります。(ただし、ほんの少しです。Joda がプログラマーに下手なプログラミングを強いているわけではありません )

于 2008-12-05T17:09:57.630 に答える
4

java.time.Month

Java provides you another way to use 1 based indexes for months. Use the java.time.Month enum. One object is predefined for each of the twelve months. They have numbers assigned to each 1-12 for January-December; call getValue for the number.

Make use of Month.JULY (Gives you 7) instead of Calendar.JULY (Gives you 6).

(import java.time.*;)
于 2016-06-28T03:41:34.770 に答える
3
于 2016-08-30T05:47:28.000 に答える
0

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

于 2008-12-06T14:32:11.770 に答える
-1

ダニースマーフの怠惰の答えに加えて、Calendar.JANUARY.

于 2008-12-05T16:27:01.070 に答える