diff --git a/App.tsx b/App.tsx index c947883..1983127 100644 --- a/App.tsx +++ b/App.tsx @@ -1,31 +1,12 @@ import React from 'react'; import {SafeAreaView, ScrollView} from 'react-native'; - -import {Box, ButtonBox, ProgressBar, ProgressCircle} from './src/components'; +import {CalendarBox} from './src/atomic/organisms/CalendarBox'; function App(): React.JSX.Element { const [value, setValue] = React.useState(0); return ( - - - - setValue(100)} /> - - - setValue(25)} /> - - - - - - - - + + ); } diff --git a/src/atomic/organisms/CalendarBox.tsx b/src/atomic/organisms/CalendarBox.tsx new file mode 100644 index 0000000..39f885e --- /dev/null +++ b/src/atomic/organisms/CalendarBox.tsx @@ -0,0 +1,161 @@ +import React, {useEffect, useRef, useState} from 'react'; +import {Box, ButtonBox, TextBox} from '../atoms'; +import {FlatList} from 'react-native'; +import {getDaysOfMonth, getDaysOfMonths, MONTHS} from '../../utils/date.util'; + +interface Props { + width?: number; + height?: number; +} +interface Result { + year: number; + month: number; + days: number[]; + daysBefore: number[]; + daysAfter: []; +} + +export const CalendarBox = ({width = 0, height}: Props) => { + const refMonth = useRef(null); + const [currentIndex, setCurrentIndex] = useState(0); + const [offset, setOffset] = useState({ + width, + height, + }); + const [currentYear, setCurrentYear] = useState(2024); + const [months, setMonths] = useState([]); + const timerUpdate = useRef(); + + useEffect(() => { + const initMonths = () => { + const now = new Date(); + const month = now.getMonth() + 1; + const year = now.getFullYear(); + const currentMonth = getDaysOfMonth(month, year); + const nextDayMonth = + month + 1 > 12 + ? getDaysOfMonth(1, year + 1) + : getDaysOfMonth(month + 1, year); + const preMonth = + month - 1 < 0 + ? getDaysOfMonth(12, year - 1) + : getDaysOfMonth(month - 1, year); + setMonths([preMonth, currentMonth, nextDayMonth]); + }; + initMonths(); + setTimeout(() => { + scrollToIndex(1); + }); + }, []); + console.log(months.map(item => item.month)); + + const onNextMonth = () => { + let nextIndex = currentIndex + 1; + if (nextIndex > 11) { + setCurrentYear(currentYear + 1); + nextIndex = 1; + } + scrollToIndex(nextIndex); + }; + + const scrollToIndex = (index: number) => { + if (refMonth.current) { + refMonth.current.scrollToIndex({ + index, + }); + } + }; + + const renderWeek = () => { + return ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'].map(item => ( + + {item} + + )); + }; + + const renderDayOther = (data: number[]) => { + return data.map((_, index) => ( + + )); + }; + + const renderDay = (days: number[]) => { + return days.map((_, index) => ( + + )); + }; + + return ( + { + if (!offset.width) { + setOffset({ + width: Math.round(nativeEvent.layout.width + 0.5), + height: nativeEvent.layout.height, + }); + } + }}> + {months.length > 0 && ( + <> + + + {`Month ${months[currentIndex].month} ${months[currentIndex].year}`} + + + {renderWeek()} + { + timerUpdate.current && clearTimeout(timerUpdate.current); + const pageIndex = Math.round( + nativeEvent.contentOffset.x / offset.width, + ); + if (pageIndex !== currentIndex) { + timerUpdate.current = setTimeout(() => { + setCurrentIndex(pageIndex); + const newMonth = getDaysOfMonths(months, pageIndex); + if (newMonth.length > 0) { + setMonths(newMonth); + } + }, 125); + } + }} + renderItem={({item}) => ( + + + {renderDayOther(item.daysBefore)} + {renderDay(item.days)} + {renderDayOther(item.daysAfter)} + + + )} + getItemLayout={(_, index) => ({ + length: MONTHS.length, + offset: offset.width * index, + index, + })} + keyExtractor={(item, index) => `${item.month}-${item.year}`} + /> + + )} + + ); +}; diff --git a/src/library/moomTime.ts b/src/library/moomTime.ts new file mode 100644 index 0000000..1617150 --- /dev/null +++ b/src/library/moomTime.ts @@ -0,0 +1,290 @@ +// auth liujinshuai +const MAX_YEAR = 2100; +const MIN_YEAR = 2000; +const MAX_MONTH = 12; +const MIN_MONTH = 1; +const MAX_DAY = 31; +const MIN_DAY = 1; + +const time = [ + [2, 25, 30, '1-2-10-11', '3467'], + [1, 7, 29, '12', '67', '4'], + [2, 18, 29, '12-1-2', '78'], + [2, 29, 30, '1245', '8-9-10-11'], + [1, 10, 30, '34', '', '2'], + [2, 21, 29, '67', '', ''], + [1, 2, 29, '89', '', '7'], + [2, 13, 30, '12-8-9-10', '1245', ''], + [2, 23, 29, '12-1-9-10', '2356', ''], + [1, 6, 30, '12', '3-4-5.-6', '5'], + [2, 17, 30, '12-1', '67', ''], + [2, 27, 29, '12-1-3-4', '78', ''], + [1, 8, 29, '34', '', '4'], + [2, 20, 30, '56', '', ''], + [1, 0, 29, '89', '', '9'], + [2, 11, 29, '789', '34', ''], + [2, 22, 30, '89', '45', ''], + [1, 4, 30, '', '56', '6'], + [2, 15, 30, '12-0', '56', ''], + [2, 26, 30, '12-1', '6-7-9-10', ''], + [1, 7, 30, '234', '67', '4'], + [2, 18, 29, '23', '', ''], + [2, 29, 30, '56', '', ''], + [1, 10, 30, '4578', '2.-3', '2'], + [2, 20, 29, '7-8-10-11', '34', ''], + [1, 2, 29, '9-10', '45', '6'], + [1, 13, 30, '9-10', '4578', ''], + [2, 24, 30, '1-2-10-11', '5689', ''], + [1, 5, 29, '123', '5.-6-8-9', '5'], + [2, 17, 30, '12', '9-10', ''], + [2, 28, 30, '12-4-5', '', ''], + [1, 8, 29, '2367', '1', '3'], + [2, 19, 30, '6-7-9-10', '23', ''], + [1, 1, 30, '9-10-11', '34', '11'], + [2, 11, 29, '9-10', '34', ''], + [2, 22, 29, '12-1-9-10', '4578', ''], + [1, 4, 30, '12', '5678', '6'], + [2, 16, 30, '12-1-2', '5689', ''], + [2, 26, 29, '12-1-2', '9-10', ''], + [1, 7, 29, '1245', '', '5'], + [2, 17, 29, '3489', '12-0', ''], + [2, 29, 30, '6-7-9-10', '12-1', ''], + [1, 10, 30, '89', '2.-3', '2'], + [2, 21, 30, '12-8-9', '3467', ''], + [1, 2, 30, '1', '4-5-7.-7', '7'], + [2, 14, 30, '12-1', '4578', ''], + [2, 25, 30, '12-1', '89', ''], + [1, 6, 30, '134', '89', '5'], + [2, 16, 29, '2356', '9-10', ''], + [2, 28, 30, '5689', '', ''], + [1, 8, 29, '78', '', '3'], + [2, 19, 30, '78', '2356', ''], + [2, 30, 30, '12-9-10', '3467', '8'], + [2, 12, 30, '12-0', '3467', ''], + [2, 23, 30, '12-2-3', '4578', ''], + [1, 4, 30, '23', '78', '6'], + [2, 15, 30, '123', '89', ''], + [2, 26, 29, '23', '', ''], + [1, 7, 29, '6-7-9-10', '', '4'], + [2, 17, 29, '9-10', '12-0', ''], + [2, 28, 30, '9-10-11', '2356', ''], + [1, 10, 29, '1-2-9-10', '3.-3-5-6', '3'], + [2, 21, 30, '1-2-10-11', '3467', ''], + [1, 2, 29, '12', '7.-7-', '7'], + [2, 13, 29, '12-1-2', '78', ''], + [2, 25, 30, '1245', '89', ''], + [1, 6, 30, '45', '', '5'], + [2, 16, 29, '67', '', ''], + [2, 27, 30, '6-7-9-10', '45', ''], + [1, 9, 30, '8-9-10', '4.-5', '4'], + [2, 19, 29, '9-10', '56', ''], + [1, 1, 30, '12-1', '67', '8'], + [2, 12, 30, '12-1', '67', ''], + [2, 23, 29, '12-1-3-4', '78', ''], + [1, 4, 29, '34', '', '6'], + [2, 15, 30, '56', '', ''], + [2, 25, 29, '89', '', ''], + [1, 7, 29, '789', '4.-4', '4'], + [2, 18, 30, '89', '45', ''], + [2, 29, 30, '12-0', '56', ''], + [1, 10, 30, '1', '5689', '3'], + [2, 22, 30, '12-2-3', '6-7-9-10', ''], + [1, 3, 30, '234', '5-6-9-10', '7'], + [2, 14, 30, '12-2-3', '', ''], + [2, 24, 30, '56', '', ''], + [1, 6, 30, '5.-5-7-8', '34', '5'], + [2, 16, 29, '78', '34', ''], + [2, 27, 30, '10-11', '45', ''], + [1, 8, 30, '9-10', '4.-5-7-8', '4'], + [2, 20, 30, '12', '567', ''], + [1, 1, 29, '123', '6-7-8.-9', '8'], + [2, 12, 30, '12', '9-10', ''], + [2, 23, 30, '1245', '', ''], + [1, 4, 29, '2-3-6.-7', '1', '6'], + [2, 15, 30, '6-7-9-10', '12-1', ''], + [2, 25, 29, '9-10-11', '34', ''], + [1, 6, 29, '8-9-10', '4.-4-6-7', '4'], + [2, 18, 29, '12-1-9-10', '456', ''], + [2, 29, 29, '12-1-2', '567', ''], + [1, 11, 30, '1234', '5689', '2'], + [2, 21, 29, '12-1-2', '9-10', ''], +]; +var zodiac = [ + 'Monkey', + 'Chicken', + 'Dog', + 'Pig', + 'Rat', + 'Cow', + 'Tiger', + 'Rabbit', + 'Dragon', + 'Snake', + 'Horse', + 'Sheep', +]; +const gan = ['庚', '辛', '壬', '癸', '甲', '乙', '丙', '丁', '戊', '己']; +const zhi = [ + '申', + '酉', + '戌', + '亥', + '子', + '丑', + '寅', + '卯', + '辰', + '巳', + '午', + '未', +]; +const zodiacYear = (year: number): string => zodiac[year % 12]; + +const lunarYearMeth = (year: number): string => gan[year % 10] + zhi[year % 12]; + +const getConsecutiveMonths = (msg: string) => { + if (!msg) return []; + + return msg.includes('-') ? msg.split('-') : msg.split(''); +}; + +const calculate = (year: number, month: number, day: number) => { + if ( + year > MAX_YEAR || + year < MIN_YEAR || + month > MAX_MONTH || + month < MIN_MONTH || + day > MAX_DAY || + day < MIN_DAY + ) { + throw 'Only receive data from 2000-2100, 1-12 months, 1-31 days'; + } + const obj: { + year: number; + month: number; + day: number; + leapMonth: number | null; + } = { + year, + month, + day, + leapMonth: null, + }; + let theYear = year % MIN_YEAR; + let ruleArr = time[theYear]; + const ruleFirst = ruleArr[0] as number; + const ruleDaysBefore = ruleArr[1] as number; + const ruleDays = ruleArr[2] as number; + const ruleMonth = ruleArr[3] as string; + const ruleDayOfEndYear = ruleArr[4] as string; + const ruleLeap = ruleArr[5] as string; + const currentTime = new Date(`${year}/${month}/${day}`).getTime(); + const startTime = new Date(`${year}/1/1`).getTime(); + let nowDay: number = Math.floor( + (currentTime - startTime) / (24 * 60 * 60 * 1000), + ); + let nowYear = ruleFirst > 0 ? year-- : year; + let monthOld = 12 + 1 - ruleFirst; + let oldDay = ruleDays - ruleDaysBefore + 1; + let thirtyMonth = getConsecutiveMonths(ruleMonth); + let twentyNineMonth = getConsecutiveMonths(ruleDayOfEndYear); + let nowMonth = 0; + let isThirty = ruleDays == 30 ? true : false; + let leapMonth = ruleLeap ? ruleLeap : null; + let isLeapMonth = false; + let getMonth = function () { + if (monthOld + 1 > 12) { + year += 1; + monthOld = 1; + isLeapMonth = false; + } else if (leapMonth) { + if (monthOld === Number(leapMonth)) { + obj.leapMonth = month; + leapMonth = null; + isLeapMonth = true; + } else { + leapMonth += 1; + isLeapMonth = false; + } + } else { + month += 1; + isLeapMonth = false; + } + }; + + let getIsThirty = function () { + if (thirtyMonth.find(e => Number(e) == monthOld)) { + if (isLeapMonth) { + if (thirtyMonth.find(e => String(e) == String(monthOld + '.'))) { + isThirty = true; + } else { + isThirty = !isThirty; + } + } else if (thirtyMonth.find(e => String(e) == String(monthOld))) { + isThirty = true; + } else { + isThirty = !isThirty; + } + } else if (ruleArr[4] && thirtyMonth.find(e => Number(e) == monthOld)) { + if (isLeapMonth) { + if (thirtyMonth.find(e => String(e) == String(month + '.'))) { + isThirty = false; + } else { + isThirty = !isThirty; + } + } else if (twentyNineMonth.find(e => String(e) == String(month))) { + isThirty = false; + } else { + isThirty = !isThirty; + } + } else { + isThirty = !isThirty; + } + }; + if (nowDay > oldDay) { + nowDay -= oldDay; + + if (monthOld + 1 > 12) { + obj.year += 1; + + monthOld = 1; + } else { + monthOld += 1; + } + + getIsThirty(); + + while (true) { + //30/29 + if (nowDay > (isThirty ? 30 : 29)) { + nowDay -= isThirty ? 30 : 29; + + getMonth(); + + getIsThirty(); + } else { + break; + } + } + + return Object.assign(obj, { + month: monthOld <= (obj?.leapMonth ? obj.leapMonth : monthOld), + + day: nowDay, + + zodiac: zodiacYear(obj.year), + + lunarYear: lunarYearMeth(obj.year), + }); + } else { + return Object.assign(obj, { + month, + + day: ruleDaysBefore + nowDay - 1, + + zodiac: zodiacYear(obj.year), + + lunarYear: lunarYearMeth(obj.year), + }); + } +}; diff --git a/src/styles/common.styles.ts b/src/styles/common.styles.ts index 94f88ea..b53cf39 100644 --- a/src/styles/common.styles.ts +++ b/src/styles/common.styles.ts @@ -100,6 +100,7 @@ export const commonStyles = StyleSheet.create({ 'w-2/5': {width: '40%'}, 'w-3/5': {width: '60%'}, 'w-4/5': {width: '80%'}, + 'w-1/7': {width: `${(1 / 7) * 100}%`}, 'w-full': {width: '100%'}, 'w-screen': {width: device.width}, 'min-w-auto': {minWidth: 'auto'}, diff --git a/src/utils/date.util.ts b/src/utils/date.util.ts new file mode 100644 index 0000000..29a0dd6 --- /dev/null +++ b/src/utils/date.util.ts @@ -0,0 +1,50 @@ +export const MONTHS = Array.from({length: 12}); +interface Result { + year: number; + month: number; + days: number[]; + daysBefore: number[]; + daysAfter: []; +} + +export const getDaysOfMonths = (data: Result[], currentIndex: number) => { + const currentData = data[currentIndex]; + let currentMonth = currentData.month; + let currentYear = currentData.year; + if (currentIndex === data.length - 1) { + if (currentData.month + 1 > 12) { + currentMonth = 1; + currentYear += 1; + } else { + currentMonth += 1; + } + return [...data, getDaysOfMonth(currentMonth, currentYear)]; + } else if (currentIndex === 0) { + if (currentData.month - 1 < 0) { + currentMonth = 12; + currentYear -= 1; + } else { + currentMonth -= 1; + } + return [getDaysOfMonth(currentMonth, currentYear), ...data]; + } + return []; +}; + +export const getDaysOfMonth = (month: number, year: number): Result => { + const startDayOfWeek = new Date(`${year}-${month}-01`).getDay(); + const dayNo = startDayOfWeek === 0 ? 6 : startDayOfWeek - 1; + const monthDay = new Date(year, month, 0); + return { + year, + month, + days: Array.from({length: monthDay.getDate()}), + daysBefore: + dayNo > 0 + ? Array.from({ + length: dayNo, + }) + : [], + daysAfter: [], + }; +};