web-dev-qa-db-ja.com

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

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

私は多くの人々がそれについて混乱しているのを見ました...

283

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

代わりに Joda Time を使用するか、可能であれば JSR-31 を使用してください。

編集:理由について-他の回答で述べたように、古いC APIか、もちろん、0からすべてを開始するという一般的な感覚が原因である可能性があります。元の実装チーム以外の誰かが本当に理由を述べることができるかどうかは疑います-しかし、繰り返しますが、悪い決定についてwhyについて心配しないように読者に促しますJava.util.Calendarにあるすべての意地悪を見て、もっと良いものを見つけるために。

isの1つのポイントは、0ベースのインデックスを使用することで、「名前の配列」のようなものが簡単になることです。

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

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

これはgood理由ではありませんが、a理由です。 。

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

  • 驚くべきベース(Dateの年ベースとして1900、推奨されないコンストラクターの場合は明らかに、両方の月ベースとして0)
  • 可変性-不変の型を使用すると、much実際に効果的なvalues
  • 不十分なタイプのセット:DateCalendarを異なるものとして持つのは良いことですが、「ローカル」値と「ゾーン」値の分離が欠落しています。
  • 明確に名前が付けられたメソッドの代わりに、マジック定数を使用したugいコードにつながるAPI
  • 推論するのが非常に難しいAPI-物事がいつ再計算されるかなどに関するすべてのビジネス
  • パラメータなしのコンストラクタを使用してデフォルトを「今」に設定すると、テストが困難なコードになります
  • 常にシステムのローカルタイムゾーンを使用するDate.toString()実装(これまで多くのStack Overflowユーザーを混乱させていました)
311
Jon Skeet

数ヶ月で数学をするのがずっと簡単だからです。

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

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

39
arucker

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

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

35
stesch

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

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

そうは言っても、特にJavascript(この「機能」を継承している)では、この矛盾は混乱を招きます。これは、言語から遠く離れて抽象化されるスクリプト言語です。

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

22
piksel bitworks

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

12
Alex Miller

私は怠sayと言います。配列は0から始まります(誰もが知っています)。その年の月は配列であるため、SunのエンジニアがJavaコードにこのちょっとした工夫を加えようとは思わなかったと思います。

9
TheSmurf

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

9
Paul Tomblin

プログラマは0ベースのインデックスに取り付かれているためです。わかりました、それよりも少し複雑です。0ベースのインデックス付けを使用するために低レベルのロジックで作業している場合には、より意味があります。しかし、概して、私はまだ最初の文に固執します。

6
Dinah

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

次のうち正しいものはどれですか?

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

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

これは、Joda Timeについて少し気がかりなことの1つを示しています。それは、プログラマーがハードコードされた定数の観点から考えることを奨励するかもしれません。 (しかし、ほんの少し。Jodaがforcingプログラマーのひどいプログラミングをするようなものではありません。)

4
Paul Brinkley

Java.util.Month

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

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

(import Java.time.*;)
4
Digital_Reality

tl; dr

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

2

詳細

Jon Skeetによる回答 は正しいです。

現在、これらの面倒な古いレガシー日時クラスの最新の代替品、 Java.time クラスがあります。

Java.time.Month

これらのクラスの中には Monthenum があります。列挙は、クラスがロードされると自動的にインスタンス化される1つ以上の定義済みオブジェクトを保持します。 Monthには、それぞれ JANUARYFEBRUARYMARCHなどの名前が付けられたこのようなオブジェクトが多数あります。これらはそれぞれ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.

各月の日数 を知るなど、このクラスの他の多くの便利なメソッド。クラスは、月の ローカライズされた名前を生成 でさえできます。

さまざまな長さまたは略語でローカライズされた月の名前を取得できます。

String output = 
    Month.FEBRUARY.getDisplayName( 
        TextStyle.FULL , 
        Locale.CANADA_FRENCH 
    );

フェブリエ

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

Year および YearMonth クラスも便利です。


Java.timeについて

Java.time フレームワークはJava 8以降に組み込まれています。これらのクラスは、厄介な古い legacyJava.util.Date.Calendar 、& Java.text.SimpleDateFormat などの日時クラスに取って代わります。

Joda-Time プロジェクトは、現在 メンテナンスモード であり、Java.timeへの移行を推奨しています。

詳細については、 Oracle Tutorial を参照してください。また、Stack Overflowで多くの例と説明を検索してください。指定は JSR 31 です。

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

  • Java SE 8 および SE 9 以降
    • ビルトイン。
    • 実装がバンドルされた標準Java AP​​Iの一部。
    • Java 9は、いくつかのマイナーな機能と修正を追加します。
  • Java SE 6 および SE 7
    • Java.time機能の多くは、 ThreeTen-Backport のJava 6および7にバックポートされています。 。
  • Android
    • ThreeTenABP プロジェクトが適応ThreeTen-Backport(上記)Android具体的に。
    • 使用方法… を参照してください。

ThreeTen-Extra プロジェクトは、追加のクラスでJava.timeを拡張します。このプロジェクトは、Java.timeに将来追加される可能性のある証明の場です。 IntervalYearWeekYearQuartermore などの便利なクラスがあります。

2
Basil Bourque

私にとっては、誰も mindpro.com

Gotchas

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));を使用します

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

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

DateFormatおよびGregorianCalendarは適切にメッシュしません。間接的に日付として1回、カレンダーを2回指定する必要があります。

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

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

1
Edwin Dalorzo

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

0
Tihamer

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

0
Pål GD