2017-09-01 15 views
3

これはasked(ひどく)前です - その投稿の回答が実際に問題に対処したとは思わないし、それが古くなったと思います。問題の明確なデモンストレーションをしてもう一度尋ねようとします。確実に '次の/前の月'を実装する方法

Javascript Date.setMonth()の実装は、最小の驚きの原則に従わないようです。ブラウザのコンソールでこれを試してみてください。同様に

d = new Date('2017-08-31') // Set to last day of August 
d.getMonth() // 7 - months are zero-based 
d.setMonth(8) // Try to set the month to 8 (September) 
d.getMonth() // 9 - October. WTF Javascript? 

d = new Date('2017-10-31') 
d.getMonth() // 9 
d.setMonth(8) 
d.getMonth() // 9 (still?) 

Linux上のFirefoxはさらに悪化表示されます - 時々は、その月一致しないgetMonth()から10月の日付、および結果を返します!

私の質問(と私はそのリンクされた質問からOPのだと思います)は、次の/前の月機能を一貫してどのようにして一貫して実装するかです。デートピッカー?たとえば、9月に8月31日に開始して「次へ」をクリックすると、9月をスキップするなどしてユーザーを驚かせることはありません。 1月31日からの予定は、今のところ予測不可能です。うるう年かどうかによって、3月2日か3月3日のいずれかになります!

私の個人的な見解は、次の/前月のの最後の日に移動することです。しかし、それはsetMonth()実装では、固定期間を加算/減算するだけでなく、問題の月の日数を気にする必要があります。 thisスレッドによれば、moment.jsアプローチは、30日でミリ秒数を加算/減算することであり、これはライブラリが同じ不一致を起こしやすいことを示唆しています。

答えて

0

月を加算および減算するときにsetMonth が使用された場合、開始月の日付が終了月に存在しない場合、余分な曜日によって次の月に「ロールオーバー」する3月31日から1ヶ月を引いたものは3月2〜3日です。

単純なアルゴリズムは、開始日と終了日をテストすることであり、それらが異なる場合、0に終了日を設定するので、前の月の最後の日に行きます。これで

1つの問題は、1ヶ月を減算すると、二回一回2ヶ月を減算と同じ結果が得られない可能性があることです。 2017年3月31日マイナス1ヶ月は2月28日を与え、マイナス別の月は3月31日から1月28日減算2ヶ月を与え、あなたは1月31日

セラヴィのVIEを取得します。

function addMonths(date, num) { 
 
    var d = date.getDate(); 
 
    date.setMonth(date.getMonth() + num); 
 
    if (date.getDate() != d) date.setDate(0); 
 
    return date; 
 
} 
 

 
// Subtract one month from 31 March 
 
var a = new Date(2017,2,31); 
 
console.log(addMonths(a, -1).toString()); // 28 Feb 
 

 
// Add one month to 31 January 
 
var b = new Date(2017,0,31); 
 
console.log(addMonths(b, 1).toString()); // 28 Feb 
 

 
// 29 Feb plus 12 months 
 
var c = new Date(2016,1,29) 
 
console.log(addMonths(c, 12).toString()); // 28 Feb 
 

 
// 29 Feb minus 12 months 
 
var c = new Date(2016,1,29) 
 
console.log(addMonths(c, -12).toString()); // 28 Feb 
 

 
// 31 Jul minus 1 month 
 
var d = new Date(2016,6,31) 
 
console.log(addMonths(d, -1).toString()); // 30 Jun

+0

ええ、私は思いついた解決策にかなり似ていると思います。あなたが目指していた月と同じではない月をチェックしましたが、変更された日をチェックすることはかなり同等です。 – dsl101

0

getMonth()は整数を返しますので、月オブジェクトの代わりに月オブジェクトオブジェクトを実装するだけで、月オブジェクトオブジェクトの代わりに月オブジェクトオブジェクトを作成することができます。

function nextMonth(dateObj) { 
    var month = dateObj.getMonth(); 
    if(month != 11) dateObj.setMonth(month + 1); 
    return dateObj; 
} 

function prevMonth(dateObj) { 
    var month = dateObj.getMonth(); 
    if(month != 0) dateObj.setMonth(month - 1); 
    return dateObj; 
} 

あなたは前の月に日を一致させたい場合は、オブジェクトのルックアップテーブルを使用することができます。

さて、月の問題のあなたの最後の日のために:

function getLastDayofMonth(month) { 
    var lookUp = { 
    0:31, 
    1:28, 
    2:30, 
    3:31 
    }; 
    return lookUp[month]; 
} 

//and then a revised version 

function nextMonth(dateObj) { 
    var month = dateObj.getMonth(); 
    var day = dateObj.getDate(); 
    if(month != 12) dateObj.setMonth(month + 1); 
    if(getLastDayofMonth(month)<day)dateObj.setDate(getLastDayofMonth(month)); 
    return dateObj; 
} 
+0

これは正しくエッジケースを考慮しています、 –

+0

はうるう年または月の残りの部分を処理しません日間 –

+0

の異なる量を考慮して、それを修正し、質問に答えていません'setMonth'は間違った月に設定されます。 –

2

それはすべての単純およびロジックです。あなたの例を挙げると、idが何をしているのか見てみましょう。

だから、最初の行

d = new Date('2017-08-31') // Set to last day of August 
 
console.log(d);    // "2017-08-31T00:00:00.000Z" 
 
console.log(d.getMonth()); // 7 - months are zero-based

だから、すべての良い今のところ。次のステップ:あなたのコメントはそれを示しています:// Try to set the month to 8 (September)それは試してやっているわけではありません。あなたはそれを9月に設定するか、設定しません。あなたの例では、10月に設定します。説明がさらに下に。

d = new Date('2017-08-31') // Set to last day of August 
 
console.log(d);    // "2017-08-31T00:00:00.000Z" 
 
console.log(d.getMonth()); // 7 - months are zero-based 
 
d.setMonth(8)    // Try to set the month to 8 (September) 
 
console.log(d);    // but now I see I was wrong it is (October)

だから、良い質問はなぜですか?From MDN

注:数値は彼らの論理的範囲よりも大きい場合は日付が、複数の 引数を持つコンストラクタと呼ばれている(例えば13分値の月の値または70として提供さ である)、隣接する の値が調整されます。例えば。新しい日付(2013、13、1)は 新しい日付(2014,1,1)に相当し、両方とも2014-02-01の日付を作成します( の月は0に基づいています)。同様に他の値:新しい日付(2013,2,1,0、 70)は、新しい日付(2013,2,1,1,10)に相当し、両方とも2013年3月1日10時10分の日付を作成します。 00。

したがって、sayd 9月はわずか30日ですが、Dateオブジェクトは31です。このため、9月ではなく10月になります。

最も簡単なのは、お持ちの日付を月の初日に設定することです。そのような何か:

var d = new Date('2017-08-31') // Set to last day of August 
 
// simplest fix take the date you have and set it to first day of month 
 
d = new Date(d.getFullYear(), d.getMonth(), 1); 
 
console.log(d);    // "2017-08-31T00:00:00.000Z" 
 
console.log(d.getMonth()); // 7 - months are zero-based 
 
d.setMonth(8)    // Set the month to 8 (September) 
 
console.log(d.getMonth()); // get 8 it is (September)

+0

あなたの解決策は、エッジの場合を避けるために、日付を1に設定することになります。繰り返しますが、私は月の終わりから始めに日付を変更する必要がある月のバック/フォワードボタンを考えるべきではありません - より多くのユーザー驚き...私は説明を理解することができますが、 'setMonth()'は本当にもっとやりなさい。これに対応する。 – dsl101

+0

@ dsl101 ok、この質問を閉じるために投票してください。あなたはむしろjavascriptの開発者に連絡し、 'setMonth()'の実装に満足していないと伝えますが、正直言って私は彼らがそれを聞きたいとは思わないのです。あなたがそれを世話するべきである 'setMonth()'ではないので、それはあなたです。あなたは、辺の場合に何が起こるべきかを知っている人なので、あなたが決定します。 javascriptのドキュメントは、何が起こったか、起こったか、起こったのか、なぜ起こるのかを正確に伝えます。あなたはそれをすべて扱うことができます。それは自由のようです。 'setMonth()'には何も問題はありませんが、それは私の意見です... – caramba

0

これは月のインクリメントのために働く必要があり、あなたはデクリメントする同様の戦略を使用することができます。

// isLeapYear :: Number -> Boolean 
const isLeapYear = ((err) => { 
    return yr => { 
    // check for the special years, see https://www.wwu.edu/skywise/leapyear.html 
    if (yr === 0) { 
     throw err; 
    } 
    // after 8 AD, follows 'normal' leap year rules 
    let passed = true; 
    // not technically true as there were 13 LY BCE, but hey. 
    if (yr === 4 || yr < 0 || (yr % 4)) { 
     passed = false; 
    } else { 
     if (yr % 400) { 
     if (!(yr % 100)) { 
      passed = false; 
     } 
     } 
    } 
    return passed; 
    }; 
})(new Error('Year zero does not exist, refers to 1 BCE')); 

const daysInMonth = [ 
    31, 
    28, 
    31, 
    30, 
    31, 
    30, 
    31, 
    31, 
    30, 
    31, 
    30, 
    31 
]; 

// isLastDay :: Number, Number -> Boolean 
const isLastDay = (d, m, y) => { 
    let dm = isLeapYear(y) && m === 1 ? 29 : daysInMonth(m); 
    return dm === d; 
}; 

// getLastDay :: Number, Number -> Number 
const getLastDay = (m, y) => isLeapYear(y) && m === 1 ? 29 : daysInMonth[m]; 

// incMonth :: Date -> Date 
const incMonth = d => { 
    let dd = new Date(d.getTime()); 
    let day = dd.getDate(); 
    let month = dd.getMonth() + 1; 
    dd.setDate(5); // should avoid edge-case shenanigans 
    dd.setMonth(month); 
    let year = dd.getFullYear(); 
    if (isLastDay(day, month, year)) day = getLastDay(month, year); 
    dd.setDate(day); 
    return dd; 
}; 
+0

@carambaの答えと同様に、私は本当にやりたくないエッジケースを避けるために日を変えることに沸きます。 8月27日を選択して「次へ」(月)をクリックした場合、9月5日の表示は期待できません。 – dsl101

0

これは私が思いついた解決策でしたが、これは私が知る限り小さくて信頼できるものです。余分なデータ構造は必要ありません.を使用して、エッジケースの月の最終日を選択します。それ以外の場合は、日付だけが残っています。これは、私が望む動作です。また、(いずれかの方向に)次の1年からラッピングラウンドを処理します。私はそれを使用していますので、targetMonthは、現在の月より1高いまたはより低いのいずれかであることでこれをのみテストしてみた

function reallySetMonth(dateObj, targetMonth) { 
    const newDate = new Date(dateObj.setMonth(targetMonth)) 
    if (newDate.getMonth() !== ((targetMonth % 12) + 12) % 12) { // Get the target month modulo 12 (see https://stackoverflow.com/a/4467559/1454454 for details about modulo in Javascript) 
    newDate.setDate(0) 
    } 
    return newDate 
} 

注意'次へ'/'戻る'ボタンが付いています。それは、任意の月を使ってさらなるユーザーをテストする必要があります。

+0

'targetMonth%12'は冗長です。 – RobG

+0

これはおそらくこの例ではベルトとブレースですが、ここから来ています:https://stackoverflow.com/a/4467559/1454454 targetMonthが-12より小さい場合は、それがないとうまくいかないと思います。 – dsl101

+0

うーん、興味深い問題。おそらくあなたは答えに参照を含めるべきです。 – RobG

関連する問題