-1

Calendar với Reactjs Typescript sử dụng Moment

Hướng dẫn viết logic: trả ra kết quả như hình:

  • Danh sách ngày tháng trước , trong tháng và tháng tới : item trả ra kèm theo loại ngày, ngày hiện tại
  • Chi tiết ngày hiện tại

image.png

  1. Tạo Context API - để chia sẽ dữ liệu
'use client';

import { PropsWithChildren, createContext, useContext } from 'react';
import useLogic from './hook';

type Extra = {};

type ValueCtx = ReturnType<typeof useLogic> & Extra;

export const CalendarCtx = createContext({} as ValueCtx);

export const CalendarProvider = ({ ...props }: PropsWithChildren<Extra>) => {
    const valueCtx = useLogic();
    return (
        <CalendarCtx.Provider value={{ ...valueCtx, ...props }}>
            <>{props.children}</>
        </CalendarCtx.Provider>
    );
};

export const useCalendarCtx = () => useContext(CalendarCtx);

  1. Tạo Hook - để xử lý logic
'use client';

import moment from 'moment';
import { useState } from 'react';
import { calcDays, dataDate } from './data';
import { onSolar2Lunar } from './lunar';

const useLogic = () => {
    const [date, setDate] = useState(moment());

    const handleReload = () => {
        setDate(moment());
    };

    const handlePrev = () => {
        setDate((element: any) => moment(element).subtract(1, 'month'));
    };

    const handleNext = () => {
        setDate((element: any) => moment(element).add(1, 'month'));
    };

    const currentLunar = () => {
        const curdate = dataDate({ date: moment() });
        const solar = onSolar2Lunar(
            curdate.day,
            curdate.curMonth,
            curdate.year
        );

        return solar;
    };

    return {
        date,
        setDate,
        listDay: calcDays({ ...dataDate({ date }) }),
        current: dataDate({ date }),
        handlePrev,
        handleNext,
        handleReload,
        currentLunar,
    };
};

export default useLogic;

  1. Method: Xử lý ngày tháng

import { range } from 'lodash';
import moment, { Moment } from 'moment';
import 'moment/locale/vi';
import { onSolar2Lunar } from './lunar';
import { CalcDays, ItemDay } from './types';

enum DefineDays {
    DaysOut = 'DaysOut',
    DaysIn = 'DaysIn',
}

export type DataDate = { date: Moment };

export const dataDate = ({ date }: DataDate) => {
    const day = date.date();
    const year = date.year(); // lấy năm hiện tại
    const month = date.month(); // lấy tháng hiện tại

    const daysInMonth = date.daysInMonth(); // lấy số ngày trong tháng , ví dụ như 30 ngày

    const dayOfMonth = moment(date).subtract(1, 'months'); // lấy tháng vừa rồi
    const dayOf = moment(`${year}-${month + 1}-1`); // tuần đầu tiền của tháng

    const weekDayOf = dayOf.day(); // số ngày củ cua tháng rồi

    const dayNew = moment(`${year}-${month + 1}-${daysInMonth}`); // tuần cuối cùng của tháng

    const weekDayNew = dayNew.day(); // sô ngày mới của tháng tới

    return {
        day,
        year,
        month,
        curMonth: month + 1,
        daysInMonth,
        dayOfMonth,
        weekDayOf,
        weekDayNew,
        dayNew,
    };
};

export const calcDays = ({
    weekDayOf,
    dayOfMonth,
    weekDayNew,
    daysInMonth,
    dayNew,
    year,
    month,
}: CalcDays) => {
    // lịch việt thì weekDayOf - 1 ,  weekDayOf + 1 ,
    // lịch Quốc tế thì weekDayOf , weekDayOf + 1
    const daysOld: ItemDay[] = range(weekDayOf - 1).map((item) => {
        const iday = dayOfMonth.daysInMonth() - weekDayOf + 1 + item + 1;
        return {
            daysLunar: onSolar2Lunar(iday, month, year),
            days: iday,
            type: DefineDays.DaysOut,
            daysSolar: {
                day: iday,
                month,
                year,
                ddmm: `${iday}/${month}`,
                ddmmyyyy: `${iday}/${month}/${year}`,
            },
        };
    });

    const days: ItemDay[] = range(daysInMonth).map((item) => {
        const isToday = moment(`${item + 1}/${month + 1}/${year}`).isSame(
            moment(),
            'day'
        );

        return {
            daysLunar: onSolar2Lunar(item + 1, month + 1, year),
            days: item + 1,
            type: DefineDays.DaysIn,
            isToday,
            daysSolar: {
                day: item + 1,
                month: month + 1,
                year,
                ddmm: `${item + 1}/${month + 1}`,
                ddmmyyyy: `${item + 1}/${month + 1}/${year}`,
            } as const,
        };
    });

    const daysNew: ItemDay[] = range(6 + 1 - weekDayNew).map(() => {
        const iday = dayNew.add(1, 'day').date();
        return {
            daysLunar: onSolar2Lunar(iday, month + 2, year),
            days: iday,
            type: DefineDays.DaysOut,
            daysSolar: {
                day: iday,
                month: month + 2,
                year,
                ddmm: `${iday}/${month + 2}`,
                ddmmyyyy: `${iday}/${month + 2}/${year}`,
            },
        };
    });

    return daysOld.concat(days, daysNew);
};

  1. Định nghĩa Dữ liệu
import { Moment } from 'moment';

export type CalcDays = {
    year: number;
    month: number;
    daysInMonth: any;
    dayOfMonth: Moment;
    weekDayOf: number;
    weekDayNew: number;
    dayNew: Moment;
};

export type ItemDay = {
    daysLunar: LunarType;
    days: number;
    type: string;
    isToday?: boolean;
    daysSolar?: DaySolarType;
};

export interface LunarType {
    dd: number;
    mm: any;
    yy: number;
    ix: any;
    LLLL: string;
    DM: string;
}

export type DaySolarType = {
    day: number;
    month: number;
    year: number;
    ddmm?: string;
    ddmmyyyy?: string;
};

  1. Tính lịch âm : Lunar
/* eslint-disable id-length */
/* eslint-disable prefer-const */
import { chunk, range } from 'lodash';

export const LOCAL_TIMEZONE = 7.0;

export const INT = (d = 0) => {
    return Math.floor(d);
};

export const MOD = (x = 0, y = 0) => {
    let z = x - y * Math.floor(x / y);
    if (z === 0) {
        z = y;
    }
    return z;
};

export const UniversalFromJD = (JD = 0) => {
    let Z;
    let A;
    let B;
    let C;
    let D;
    let E;
    let F;
    let alpha;
    let dd;
    let mm;
    let yyyy;

    Z = INT(JD + 0.5);
    F = JD + 0.5 - Z;

    if (Z < 2299161) {
        A = Z;
    } else {
        alpha = INT((Z - 1867216.25) / 36524.25);
        A = Z + 1 + alpha - INT(alpha / 4);
    }
    B = A + 1524;
    C = INT((B - 122.1) / 365.25);
    D = INT(365.25 * C);
    E = INT((B - D) / 30.6001);
    dd = INT(B - D - INT(30.6001 * E) + F);
    if (E < 14) {
        mm = E - 1;
    } else {
        mm = E - 13;
    }
    if (mm < 3) {
        yyyy = C - 4715;
    } else {
        yyyy = C - 4716;
    }
    return { dd, mm, yyyy };
};

export const UniversalToJD = (D = 0, M = 0, Y = 0) => {
    let JD;
    if (
        Y > 1582 ||
        (Y === 1582 && M > 10) ||
        (Y === 1582 && M === 10 && D > 14)
    ) {
        JD =
            367 * Y -
            INT((7 * (Y + INT((M + 9) / 12))) / 4) -
            INT((3 * (INT((Y + (M - 9) / 7) / 100) + 1)) / 4) +
            INT((275 * M) / 9) +
            D +
            1721028.5;
    } else {
        JD =
            367 * Y -
            INT((7 * (Y + 5001 + INT((M - 9) / 7))) / 4) +
            INT((275 * M) / 9) +
            D +
            1729776.5;
    }
    return JD;
};

export const LocalFromJD = (JD = 0) => {
    return UniversalFromJD(JD + LOCAL_TIMEZONE / 24.0);
};
export const LocalToJD = (D = 0, M = 0, Y = 0) => {
    return UniversalToJD(D, M, Y) - LOCAL_TIMEZONE / 24.0;
};

export const { PI } = Math;

export const NewMoon = (k = 0) => {
    const T = k / 1236.85;
    const T2 = T * T;
    const T3 = T2 * T;
    const dr = PI / 180;
    let Jd1 =
        2415020.75933 + 29.53058868 * k + 0.0001178 * T2 - 0.000000155 * T3;
    Jd1 = Jd1 + 0.00033 * Math.sin((166.56 + 132.87 * T - 0.009173 * T2) * dr); // Mean new moon
    const M = 359.2242 + 29.10535608 * k - 0.0000333 * T2 - 0.00000347 * T3; // Sun's mean anomaly
    const Mpr = 306.0253 + 385.81691806 * k + 0.0107306 * T2 + 0.00001236 * T3; // Moon's mean anomaly
    const F = 21.2964 + 390.67050646 * k - 0.0016528 * T2 - 0.00000239 * T3; // Moon's argument of latitude
    let C1 =
        (0.1734 - 0.000393 * T) * Math.sin(M * dr) +
        0.0021 * Math.sin(2 * dr * M);
    C1 = C1 - 0.4068 * Math.sin(Mpr * dr) + 0.0161 * Math.sin(dr * 2 * Mpr);
    C1 = C1 - 0.0004 * Math.sin(dr * 3 * Mpr);
    C1 = C1 + 0.0104 * Math.sin(dr * 2 * F) - 0.0051 * Math.sin(dr * (M + Mpr));
    C1 =
        C1 -
        0.0074 * Math.sin(dr * (M - Mpr)) +
        0.0004 * Math.sin(dr * (2 * F + M));
    C1 =
        C1 -
        0.0004 * Math.sin(dr * (2 * F - M)) -
        0.0006 * Math.sin(dr * (2 * F + Mpr));
    C1 =
        C1 +
        0.001 * Math.sin(dr * (2 * F - Mpr)) +
        0.0005 * Math.sin(dr * (2 * Mpr + M));
    let deltat;
    if (T < -11) {
        deltat =
            0.001 +
            0.000839 * T +
            0.0002261 * T2 -
            0.00000845 * T3 -
            0.000000081 * T * T3;
    } else {
        deltat = -0.000278 + 0.000265 * T + 0.000262 * T2;
    }
    const JdNew = Jd1 + C1 - deltat;
    return JdNew;
};

export const SunLongitude = (jdn = 0) => {
    const T = (jdn - 2451545.0) / 36525; // Time in Julian centuries from 2000-01-01 12:00:00 GMT
    const T2 = T * T;
    const dr = PI / 180; // degree to radian
    const M = 357.5291 + 35999.0503 * T - 0.0001559 * T2 - 0.00000048 * T * T2; // mean anomaly, degree
    const L0 = 280.46645 + 36000.76983 * T + 0.0003032 * T2; // mean longitude, degree
    let DL = (1.9146 - 0.004817 * T - 0.000014 * T2) * Math.sin(dr * M);
    DL =
        DL +
        (0.019993 - 0.000101 * T) * Math.sin(dr * 2 * M) +
        0.00029 * Math.sin(dr * 3 * M);
    let L = L0 + DL; // true longitude, degree
    L = L * dr;
    L = L - PI * 2 * INT(L / (PI * 2)); // Normalize to (0, 2*PI)
    return L;
};

export const LunarMonth11 = (Y = 0) => {
    const off = LocalToJD(31, 12, Y) - 2415021.076998695;
    const k = INT(off / 29.530588853);
    let jd = NewMoon(k);
    const ret = LocalFromJD(jd);
    const sunLong = SunLongitude(LocalToJD(ret.dd, ret.mm, ret.yyyy)); // sun longitude at local midnight
    if (sunLong > (3 * PI) / 2) {
        jd = NewMoon(k - 1);
    }
    return LocalFromJD(jd);
};

export const initLeapYear = (ret: any) => {
    const sunLongitudes = [ret.length];
    for (let index = 0; index < ret.length; index++) {
        const a = ret[index];
        const jdAtMonthBegin = LocalToJD(a[0], a[1], a[2]);
        sunLongitudes[index] = SunLongitude(jdAtMonthBegin);
    }
    let found = false;
    for (let index = 0; index < ret.length; index++) {
        if (found) {
            ret[index][3] = MOD(index + 10, 12);
            continue;
        }
        const sl1 = sunLongitudes[index];
        const sl2 = sunLongitudes[index + 1];
        const hasMajorTerm =
            Math.floor((sl1 / PI) * 6) !== Math.floor((sl2 / PI) * 6);
        if (!hasMajorTerm) {
            found = true;
            ret[index][4] = 1;
            ret[index][3] = MOD(index + 10, 12);
        }
    }
};

export const LunarYear = (Y = 0) => {
    let ret: any = chunk(range(13 * 5), 5);
    const month11A = LunarMonth11(Y - 1);
    const jdMonth11A = LocalToJD(month11A.dd, month11A.mm, month11A.yyyy);
    const k = Math.floor(0.5 + (jdMonth11A - 2415021.076998695) / 29.530588853);
    const month11B = LunarMonth11(Y);
    const off = LocalToJD(month11B.dd, month11B.mm, month11B.yyyy) - jdMonth11A;

    const leap = off > 365.0;

    if (!leap) {
        ret = chunk(range(13 * 5), 5);
    }

    ret[0] = [month11A.dd, month11A.mm, month11A.yyyy, 0, 0];
    ret[ret.length - 1] = [month11B.dd, month11B.mm, month11B.yyyy, 0, 0];
    for (let index = 1; index < ret.length - 1; index++) {
        const nm = NewMoon(k + index);
        const a = LocalFromJD(nm);
        ret[index] = [a.dd, a.mm, a.yyyy, 0, 0];
    }
    for (let index = 0; index < ret.length; index++) {
        ret[index][3] = MOD(index + 11, 12);
    }
    if (leap) {
        initLeapYear(ret);
    }
    return ret;
};

// dương sang âm
export const onSolar2Lunar = (D = 0, M = 0, Y = 0) => {
    let yy = Y;
    let ly = LunarYear(Y);

    const month11 = ly[ly.length - 1];
    const jdToday = LocalToJD(D, M, Y);
    const jdMonth11 = LocalToJD(month11[0], month11[1], month11[2]);
    if (jdToday >= jdMonth11) {
        ly = LunarYear(Y + 1);
        yy = Y + 1;
    }
    let index = Number(ly.length - 1);
    while (jdToday < LocalToJD(ly[index][0], ly[index][1], ly[index][2])) {
        index--;
    }
    const dd =
        jdToday - LocalToJD(ly[index][0], ly[index][1], ly[index][2]) + 1;
    const mm = ly[index][3];
    if (mm >= 11) {
        yy--;
    }
    const ix = ly[index][4];
    return {
        dd,
        mm,
        yy,
        ix,
        LLLL: `${dd}/${mm}/${yy}`,
        DM: `${dd}/${mm}`,
    };
};

// âm sang dương
export const Lunar2Solar = (D = 0, M = 0, Y = 0, leap = 0) => {
    let yy = Y;
    if (M >= 11) {
        yy = Y + 1;
    }
    const lunarYear = LunarYear(yy);
    let lunarMonth = null;

    for (const index of lunarYear) {
        const lm = lunarYear[index];
        if (lm[3] === M && lm[4] === leap) {
            lunarMonth = lm;
            break;
        }
    }
    if (lunarMonth != null) {
        const jd = LocalToJD(lunarMonth[0], lunarMonth[1], lunarMonth[2]);
        return LocalFromJD(jd + D - 1);
    } else {
        return null;
    }
};

Rất đơn giản đúng hông, tính sẳn hết rùi.

Phần tiếp theo là Countdown


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.