fortel-ziweidoushu
Version:
紫微斗數排盤 Zi Wei Dou Shu
524 lines (473 loc) • 19.7 kB
text/typescript
import { Calendar, CalendarType } from '../calendar/calender'
import { defaultCalendar } from '../calendar/defaultCalendar'
import { DayTimeGround } from '../model/dayTimeGround'
import { ConfigType, Gender } from '../model/destinyConfig'
import { str2Uni } from '../utils'
const CHINESE_NUMBER_CHARACTERS = '零一二三四五六七八九十廿'
const CHINESE_NUMBER_CHARACTERS_UNICODE = str2Uni(CHINESE_NUMBER_CHARACTERS)
const ZERO_TO_NINE_CHINESE_NUMBER_CHARACTERS = '零一二三四五六七八九'
const ZERO_TO_NINE_CHINESE_NUMBER_CHARACTERS_UNICODE = str2Uni(ZERO_TO_NINE_CHINESE_NUMBER_CHARACTERS)
// const CHINESE_ONE_TO_TWELVE = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一', '十二']
const TEN_UNICODE = str2Uni('十')
const GROUND_CHAR = '子丑寅卯辰巳午未申酉戌亥'
const GROUND_CHAR_UNICODE = str2Uni(GROUND_CHAR)
function chineseNumToDecimal(input: string): number | null {
if (input.match(/[0-9]+/)) {
return parseInt(input)
}
const matches = input.match(new RegExp(`[${ZERO_TO_NINE_CHINESE_NUMBER_CHARACTERS_UNICODE}]+`, 'u'))
if (matches) {
let value = 0
for (let i = 0; i < input.length; i++) {
value *= 10
if (ZERO_TO_NINE_CHINESE_NUMBER_CHARACTERS.includes(input.charAt(i))) {
value += ZERO_TO_NINE_CHINESE_NUMBER_CHARACTERS.indexOf(input.charAt(i))
}
if (input.charAt(i) === '兩') {
value += 2
}
}
return value
} else {
return null
}
}
function text2DestinyConfig(input: string, calendar: Calendar = defaultCalendar) {
try {
let calendarType: CalendarType | null = null
let year: number | null = null
let month: number | null = null
let day: number | null = null
let isLeapMonth = false
let bornTimeGround: DayTimeGround | null = null
let hour: number | null = null
let minute: number | null = null
let gender: Gender | null = null
let configType: ConfigType | null = null
if (['農曆', '農歷', '舊曆', '舊歷'].find((keyword) => input.includes(keyword))) {
calendarType = CalendarType.LUNAR
} else if (['西曆', '西歷', '公曆', '公歷', '新曆', '新歷'].find((keyword) => input.includes(keyword))) {
calendarType = CalendarType.SOLAR
}
{
const matches = input.match(/([0-9]{2,4})-([0-9]{1,2})-([0-9]{1,2})/)
if (matches) {
calendarType = CalendarType.SOLAR
year = parseInt(matches[1])
month = parseInt(matches[2])
day = parseInt(matches[3])
}
}
if (input.includes('年')) {
const regex = new RegExp(`([0-9]{2,4})?([${ZERO_TO_NINE_CHINESE_NUMBER_CHARACTERS_UNICODE}]{2,4})?${str2Uni('年')}`, 'u')
const matches = input.match(regex)
if (matches) {
if (matches[1]) {
year = parseInt(matches[1])
} else if (matches[2]) {
year = chineseNumToDecimal(matches[2])
}
}
}
if (year !== null && year < 100) {
const thisYear = new Date().getFullYear()
const thisYearFirstTwoDigit = thisYear - (thisYear % 100)
const thisYearLastTwoDigit = thisYear % 100
if (year < thisYearLastTwoDigit + 1) {
year = thisYearFirstTwoDigit + year
} else {
year = thisYearFirstTwoDigit + year - 100
}
}
if (input.match(new RegExp(`[${str2Uni('閏閠潤')}][0-9${CHINESE_NUMBER_CHARACTERS_UNICODE}]+${str2Uni('月')}`, 'u'))) {
isLeapMonth = true
calendarType = CalendarType.LUNAR
}
if (input.includes('月')) {
let monthDetermined = false
if (input.includes('正月')) {
month = 1
monthDetermined = true
} else if (input.includes('十月')) {
month = 10
monthDetermined = true
}
if (!monthDetermined) {
//十?月
const matches = input.match(new RegExp(`[${TEN_UNICODE}]([${CHINESE_NUMBER_CHARACTERS_UNICODE}]?)${str2Uni('月')}`, 'u'))
if (matches && matches[1]) {
const digit = chineseNumToDecimal(matches[1])
if (digit) {
month = 10 + digit
monthDetermined = true
}
}
}
if (!monthDetermined) {
//?月
const matches = input.match(new RegExp(`([${CHINESE_NUMBER_CHARACTERS_UNICODE}]?)${str2Uni('月')}`, 'u'))
if (matches && matches[1]) {
month = chineseNumToDecimal(matches[1])
monthDetermined = true
}
}
if (!monthDetermined) {
const matches = input.match(new RegExp(`([0-9]{1,2})${str2Uni('月')}`, 'u'))
if (matches && matches[1]) {
month = parseInt(matches[1])
monthDetermined = true
}
}
}
let dayDetermined = false
if (input.includes('初')) {
{
const matches = input.match(new RegExp(`${str2Uni('初')}([0-9]{1,2})`, 'u'))
if (matches && matches[1]) {
calendarType = CalendarType.LUNAR
day = parseInt(matches[1])
dayDetermined = true
}
}
if (!dayDetermined) {
const matches = input.match(
new RegExp(`${str2Uni('初')}(${str2Uni('十')}?)([${ZERO_TO_NINE_CHINESE_NUMBER_CHARACTERS_UNICODE}]?)`, 'u'),
)
if (matches) {
calendarType = CalendarType.LUNAR
if (matches[1] && !matches[2]) {
day = 10
dayDetermined = true
} else if (matches[1] && matches[2]) {
const digit = chineseNumToDecimal(matches[2])
if (digit) {
day = 10 + digit
dayDetermined = true
}
} else if (!matches[1] && matches[2]) {
const digit = chineseNumToDecimal(matches[2])
if (digit) {
day = digit
dayDetermined = true
}
}
}
}
} else if (input.includes('日') || input.includes('號')) {
const dayCharPattern = `[${str2Uni('日號')}]`
if (!dayDetermined) {
const matches = input.match(new RegExp(`([0-9]{1,2})?${dayCharPattern}`, 'u'))
if (matches && matches[1]) {
day = parseInt(matches[1])
dayDetermined = true
}
}
if (!dayDetermined) {
if (input.includes('三十日') || input.includes('三十號')) {
day = 30
dayDetermined = true
} else if (input.includes('三十一日') || input.includes('三十一號')) {
day = 31
dayDetermined = true
}
}
if (!dayDetermined) {
const matches = input.match(
new RegExp(`(${str2Uni('廿')}|${str2Uni('二十')})([${ZERO_TO_NINE_CHINESE_NUMBER_CHARACTERS_UNICODE}]?)${dayCharPattern}`, 'u'),
)
if (matches && matches[1]) {
const digit = chineseNumToDecimal(matches[2])
if (digit) {
day = matches[2] ? 20 + digit : 20
}
dayDetermined = true
}
}
if (!dayDetermined) {
const matches = input.match(
new RegExp(`${str2Uni('十')}([${ZERO_TO_NINE_CHINESE_NUMBER_CHARACTERS_UNICODE}]?)${dayCharPattern}`, 'u'),
)
if (matches && matches[1]) {
const digit = chineseNumToDecimal(matches[1])
if (digit) {
day = matches[1] ? 10 + digit : 10
dayDetermined = true
}
}
}
if (!dayDetermined) {
const matches = input.match(new RegExp(`([${ZERO_TO_NINE_CHINESE_NUMBER_CHARACTERS_UNICODE}]?)${dayCharPattern}`, 'u'))
if (matches && matches[1]) {
day = chineseNumToDecimal(matches[1])
dayDetermined = true
}
}
}
let hourDetermined = false
let minuteDetermined = false
//hour:minute
{
const matches = input.match(new RegExp(`([0-9]{1,2}):([0-9]{2})`))
if (matches && matches[1] && matches[2]) {
hour = parseInt(matches[1])
minute = parseInt(matches[2])
hourDetermined = true
minuteDetermined = true
}
}
//hour
const hourCharPattern = `[${str2Uni('時點')}]`
if (!hourDetermined) {
if (input.includes('兩點')) {
hour = 2
hourDetermined = true
}
}
if (!hourDetermined) {
const matches = input.match(
new RegExp(`(${str2Uni('廿')}|(?!${str2Uni('二十')}))([${ZERO_TO_NINE_CHINESE_NUMBER_CHARACTERS_UNICODE}]?)${hourCharPattern}`, 'u'),
)
if (matches && matches[1] && matches[2]) {
const digit = chineseNumToDecimal(matches[2])
if (digit) {
hour = matches[2] ? 20 + digit : 20
hourDetermined = true
}
}
}
if (!hourDetermined) {
const matches = input.match(
new RegExp(
`([${ZERO_TO_NINE_CHINESE_NUMBER_CHARACTERS_UNICODE}]?)${str2Uni(
'十',
)}([${ZERO_TO_NINE_CHINESE_NUMBER_CHARACTERS_UNICODE}]?)${hourCharPattern}`,
'u',
),
)
if (matches) {
const digit1 = chineseNumToDecimal(matches[1])
const digit2 = chineseNumToDecimal(matches[2])
hour = 10
if (digit1) {
hour = digit1 * 10
}
if (digit2) {
hour += digit2
}
hourDetermined = true
}
}
if (!hourDetermined) {
const matches = input.match(new RegExp(`([${ZERO_TO_NINE_CHINESE_NUMBER_CHARACTERS_UNICODE}]?)${hourCharPattern}`, 'u'))
if (matches && matches[1]) {
hour = chineseNumToDecimal(matches[1])
hourDetermined = true
}
}
if (!hourDetermined) {
const matches = input.match(new RegExp(`([0-9]{1,2})${hourCharPattern}`, 'u'))
if (matches && matches[1]) {
hour = parseInt(matches[1])
hourDetermined = true
}
}
if (!hourDetermined) {
const matches = input.match(new RegExp(`([${GROUND_CHAR_UNICODE}])${hourCharPattern}`, 'u'))
if (matches && matches[1] && GROUND_CHAR.indexOf(matches[1]) >= 0) {
if (matches[1] === '子') {
hour = 0
hourDetermined = true
} else {
hour = GROUND_CHAR.indexOf(matches[1]) * 2 - 1
hourDetermined = true
}
}
}
if (
input.includes('下午') ||
input.includes('下晝') ||
input.includes('黃昏') ||
input.includes('夜') ||
input.includes('晚') ||
input.includes('PM') ||
input.includes('pm') ||
input.includes('P.M.') ||
input.includes('p.m.')
) {
if (hour !== null && hour < 12) {
hour += 12
}
}
if (hour != null && (input.includes('晚上') || input.includes('半夜') || input.includes('凌晨'))) {
if (hour >= 6 && hour < 12) {
hour += 12
} else if (hour > 12 && hour <= 17) {
hour -= 12
} else if (hour === 12) {
hour = 0
}
}
//minute
const minuteCharPattern = `[${str2Uni('分')}]`
if (!minuteDetermined) {
if (input.includes('時半') || input.includes('點半')) {
minute = 30
minuteDetermined = true
}
}
if (!minuteDetermined) {
const matches = input.match(
new RegExp(
`([${ZERO_TO_NINE_CHINESE_NUMBER_CHARACTERS_UNICODE}])[${TEN_UNICODE}]([${ZERO_TO_NINE_CHINESE_NUMBER_CHARACTERS_UNICODE}]?)${minuteCharPattern}`,
'u',
),
)
if (matches && matches[1]) {
const digit1 = chineseNumToDecimal(matches[1])
const digit2 = chineseNumToDecimal(matches[2])
if (digit1) {
minute = digit1 * 10 + (digit2 ?? 0)
minuteDetermined = true
}
}
}
if (!minuteDetermined) {
const matches = input.match(
new RegExp(
`(${str2Uni('廿')}|(?!${str2Uni('二十')}))([${ZERO_TO_NINE_CHINESE_NUMBER_CHARACTERS_UNICODE}]?)${minuteCharPattern}`,
'u',
),
)
if (matches && matches[1]) {
const digit = chineseNumToDecimal(matches[2])
if (digit) {
minute = matches[2] ? 20 + digit : 20
minuteDetermined = true
}
}
}
if (!minuteDetermined) {
const matches = input.match(
new RegExp(
`([${ZERO_TO_NINE_CHINESE_NUMBER_CHARACTERS_UNICODE}]?)${str2Uni(
'十',
)}([${ZERO_TO_NINE_CHINESE_NUMBER_CHARACTERS_UNICODE}]?)${minuteCharPattern}`,
'u',
),
)
if (matches) {
const digit1 = chineseNumToDecimal(matches[1])
const digit2 = chineseNumToDecimal(matches[2])
minute = 10
if (digit1) {
minute = digit1 * 10
}
if (digit2) {
minute += digit2
}
minuteDetermined = true
}
}
if (!minuteDetermined) {
const matches = input.match(new RegExp(`([${ZERO_TO_NINE_CHINESE_NUMBER_CHARACTERS_UNICODE}]?)${minuteCharPattern}`, 'u'))
if (matches && matches[1]) {
minute = chineseNumToDecimal(matches[1])
minuteDetermined = true
}
}
if (!minuteDetermined) {
const matches = input.match(new RegExp(`([0-9]{1,2})${minuteCharPattern}`, 'u'))
if (matches && matches[1]) {
minute = parseInt(matches[1])
minuteDetermined = true
}
}
if (typeof hour === 'number') {
bornTimeGround = DayTimeGround.getByHour(hour)
}
//盤
let configTypeDetermined = false
{
const matches = input.match(new RegExp(`([${str2Uni('地天人')}])${str2Uni('盤')}`, 'u'))
if (matches && matches[1]) {
if (matches[1] === '地') {
configType = ConfigType.GROUND
} else if (matches[1] === '人') {
configType = ConfigType.HUMAN
} else {
configType = ConfigType.SKY
}
configTypeDetermined = true
}
}
if (!configTypeDetermined) {
if (typeof minute === 'number') {
if (bornTimeGround) {
if (hour && hour >= 1 && hour < 23) {
if (hour === bornTimeGround.hourStart && minute <= 15) {
configType = ConfigType.GROUND
} else if (hour === bornTimeGround.hourStart + 1 && minute >= 45) {
configType = ConfigType.HUMAN
} else {
configType = ConfigType.SKY
}
} else if (hour) {
if (hour === 23 && minute <= 15) {
configType = ConfigType.GROUND
} else if (hour === 0 && minute >= 45) {
configType = ConfigType.HUMAN
} else {
configType = ConfigType.SKY
}
}
}
}
}
if (input.includes('男') || input.includes('先生')) {
gender = Gender.M
} else if (input.includes('女') || input.includes('小姐') || input.includes('太太')) {
gender = Gender.F
}
if (calendarType === null) {
if (year && year >= 1900 && year <= 2100 && month && day) {
let lunarError: unknown = null
try {
calendar.lunar2solar(year, month, day, isLeapMonth)
} catch (e) {
lunarError = e
}
let solarError: unknown = null
try {
calendar.solar2lunar(year, month, day)
} catch (e) {
solarError = e
}
if (solarError && !lunarError) {
calendarType = CalendarType.LUNAR
} else if (!solarError && lunarError) {
calendarType = CalendarType.SOLAR
}
}
}
return {
calendarType,
year,
month,
day,
isLeapMonth,
bornTimeGround,
hour,
minute,
gender,
configType,
}
} catch (e) {
console.error(e)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
throw new Error('Invalid input', e as any)
}
}
const destinyConfigTextParser = {
parse: text2DestinyConfig,
}
export { destinyConfigTextParser }