如何寫一個好看的實用的日曆

本文發佈在個人博客 如何寫一個日曆組件
許可協議: 署名-非商業性使用-禁止演繹 4.0 國際 轉載請保留原文連接及做者。

衆所周知,雖然 javascript中關於時間的API有很多,咱們能夠經過方法單獨的獲取年、月、日、時、分、秒、毫秒...貌似不少,最近寫了一個日曆(之前寫的,但寫得很爛,最近優化一下),因此下面簡單的記錄一下如何寫一個日曆,列出了一些我在寫日曆過程當中本身封裝的一些方法

效果圖

先來一張效果圖,因爲沒有UI設計,因此就本身簡單的設計了一個樣式(好歹我也是設計專業的,雖然已不作設計不少年),雖然略醜,但重要的是功能!!!javascript

datepicker

思路

一個日曆究竟是怎樣用代碼生成的?其實觀察一下現有的日曆展示形式,能夠很快的造成思路,就是:根據計算把日期號數對應到正確的星期幾上,並按照順序逐一輸出。
如下是個人思路:vue

  • 取得月份的天數
  • 取得月份第一天是星期幾
  • 循環對應號數和星期幾返回一個數組對象java

    • 返回數組對象的每個子項至少包含:號數,星期幾,而後根據狀況添加:是否高亮,是否當前月,是否節日...等屬性

方法封裝

注意,爲了保持方便調用javascript的方法,以及保持輸出結果符合實際,全部的方法都有以下約定:git

  • 在計算過程當中github

    • 全部的關於月份都是0~11的數字
    • 全部的關於星期都是0~6的數字
  • 在輸出的結果中數組

    • 全部關於月份的輸出默認都是1-12的數字
    • 全部關於星期的輸出默認都是1-7的數字

因此在向調用方法傳遞參數過程當中,月份以及星期幾通通都須要按照實際月份減一優化

獲取月份天數

javascript中沒有直接獲取月份天數的方法,可是它提供了一個getDate方法能夠獲取日期的某一天。那咱們只須要獲取月份的最後一天(下一個月的第0天)就能夠得知這個月的天數:ui

// year是要獲取的年份,閏年不同
// month是要獲取的月份
// 返回當前月天數
function getMonthDays(year, month){
    return new Date(year, month, 0).getDate();
}

getMonthDays(2016,2) //29
getMonthDays(2017,2) //28

獲取星期幾

// year是要獲取的年份
// month是要獲取的月份
// 返回數字幾則是星期幾
function getWeekday(year, month, day){
    return new Date(year, month-1, day).getDay();
}

getWeekday(2016,10,9) //輸出4,表示2016年11月9是星期4
getWeekday(2017,10,9) //輸出5,表示2017年11月9是星期5

獲取月份有幾個星期

要計算月份包含幾個星期,須要兩個數據:月份天數和月份第一天是星期幾,就能獲得想要的結果this

// year是要獲取的年份
// month是要獲取的月份
// 返回當前月包含幾個星期
function getweeksInMonth(year, month){

    var days = getMonthDays(year, month);
    var FirstDayWeekday = getWeekday(year, month, 1);
    return Math.ceil((days + FirstDayWeekday) / 7);
}

循環生成月份對象

有了以上方法以後,就能夠經過循環生成一個簡單的月份對象了。
在這裏須要注意,日曆的排序有兩種:spa

  • 每一行以星期日開頭
  • 每一行以星期開頭
// year是要獲取的年份
// month是要獲取的月份
// day天,用來判斷是不是當前天
// type代表要星期幾開頭,0爲星期一開頭,1爲星期日開頭,默認爲0
// 返回當前月包含幾個星期

const WEEKTABLE = [{
    cn: ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'],
    cns: ['日', '一', '二', '三', '四', '五', '六'],
    en: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
},{
    cn: ['星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日'],
    cns: ['一', '二', '三', '四', '五', '六', '日'],
    en: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
}]

getMonthDaysArray(year, month, day, type) {
    if (typeof day === 'undefined' && year === YEAR && month === MONTH) day = DAY;

    var dayArrays = [];
    var days = this.getMonthDays(year, month), preDays = this.getMonthDays(year, month - 1);
    var thisMonthFirstDayInWeek = this.getWeekday(year, month, 1), thisMonthLastDayInWeek = this.getWeekday(year, month, days);

    type = !type || type !== 1 ? 0 : 1;

    //上月在當月日曆面板中的排列
    for (var i = 0; i < thisMonthFirstDayInWeek; i++) {
        dayArrays.push({
            dayNum: (preDays - thisMonthFirstDayInWeek + i + 1),
            weekDay: WEEKTABLE[type].cn[i]
        })
    }
    //當月日曆面板中的排列
    for (var i = 1; i <= days; i++) {
        var weekDayFlag = (thisMonthFirstDayInWeek + i - 1) % 7
        dayArrays.push({
            dayNum: i,
            weekDay: WEEKTABLE[type].cn[weekDayFlag],
            selected: i === +day,
            isThisMonth: true
        })
    };
    //下月在當月日曆面板中的排列
    for (var i = 1; i <= (6 - thisMonthLastDayInWeek); i++) {
        var weekDayFlag = (thisMonthFirstDayInWeek + days + i - 1) % 7
        dayArrays.push({
            dayNum: i,
            weekDay: WEEKTABLE[type].cn[weekDayFlag]
        })
    };
    return dayArrays;
}

格式化時間

涉及到時間時,經常須要把時間格式進行轉換,爲了應對多中需求,因此本身封裝了一個

// 參數fmt必須
// date參數沒必要須,容許字符串和時間對象,不傳或者傳沒法轉換成合法時間對象的字符串則默認當前時間,
// 年(YYYY/yyyy)固定四個佔位符
// 月(M)、日(d)、小時(h)、分(m)、秒(s)能夠用 1-2個佔位符,嚴格區分大小寫,
// 毫秒(ms/mss)最多三個佔位符,分別對應56,056這種類型
// 例子:
// (Format("yyyy-MM-dd hh:mm:ss:ms") ==> 2006-07-02 08:09:04:23
// (Format("yyyy-MM-dd hh:mm:ss:mss") ==> 2006-07-02 08:09:04:023
// (Format("yyyy-M-d h:m:s:ms")      ==> 2006-7-2 8:9:4.180
function formate(fmt, date){
    date = new Date(date).toString() === 'Invalid Date' ? new Date() : new Date(date);
    var _rules = [{
        rule: '[yY]{4}',
        value: _date.getFullYear()
    }, {
        rule: 'M+',
        value: _date.getMonth() + 1
    }, {
        rule: '[dD]+',
        value: _date.getDate()
    }, {
        rule: 'h+',
        value: _date.getHours()
    }, {
        rule: 'm+',
        value: _date.getMinutes()
    }, {
        rule: 's+',
        value: _date.getSeconds()
    }, {
        rule: 'ms{1,2}',
        value: _date.getMilliseconds()
    }];

    _rules.forEach(function (_r){
        const rule = _r.rule, val = _r.value;
        fmt = fmt.replace(new RegExp(rule), function ($1) {
            const rLen = val.toString().length, fLen = $1.length;
            return (fLen !== 2 || rLen >= fLen) ? val : ['00', val].join().substr(rLen);
        });
    });
    return fmt;
}
//調用:
var time1 = formate("YYYY/MM/DD hh:mm:ss", new Date()); //2017/11/2 11:09:20
var time2 = formate("YYYY-MM-DD", time1); //2017-11-2
var time3 = formate("MM-DD-YYYY", time2); //11-2-2017

最後

附上這些方法的源碼datepicker
基於vue實現的一個日曆:

固然這只是最簡單的日曆輸出,思路也是超級簡單(感受有點Low),若是有大神願意分享它的經驗歡迎,來郵~

相關文章
相關標籤/搜索