// Part 1: Converter functions // Sin function with degree instead of radian function sind(degree) { return Math.sin(degree * Math.PI / 180) } // Convert Gregorian date to Julian day function getJulianDay(date) { const year = date.getFullYear() const month = date.getMonth() + 1 const day = date.getDate() let a = parseInt((14 - month) / 12) let y = year + 4800 - a y = y * 365 + parseInt(y / 4) - parseInt(y / 100) + parseInt(y / 400) let m = month + 12 * a - 3 return parseInt(day + (153 * m + 2) / 5) + y - 32045 } // Convert Julian day to Gregorian date function getDateFromJulianDay(jd) { const a = jd + 32044 const b = parseInt((4 * a + 3) / 146097) const c = a - parseInt((b * 146097) / 4) const d = parseInt((4 * c + 3) / 1461) const e = c - parseInt((1461 * d) / 4) const m = parseInt((5 * e + 2) / 153) const dd = e - parseInt((153 * m + 2) / 5) + 1 const mm = m + 2 - 12 * parseInt(m / 10) const yy = b * 100 + d - 4800 + parseInt(m / 10) return new Date(yy, mm, dd) } // Get kth new moon day since 1900-01-01 in Julian day function getNewMoonDay(k, tzOffset) { const t = k / 1236.85 const t2 = t * t const t3 = t2 * t let jd1 = 2415020.75933 + 29.53058868 * k + 0.0001178 * t2 - 0.000000155 * t3 jd1 += 0.00033 * sind(166.56 + 132.87 * t - 0.009173 * t2) const m = 359.2242 + 29.10535608 * k - 0.0000333 * t2 - 0.00000347 * t3 const mpr = 306.0253 + 385.81691806 * k + 0.0107306 * t2 + 0.00001236 * t3 const f = 21.2964 + 390.67050646 * k - 0.0016528 * t2 - 0.00000239 * t3 let c1 = (0.1734 - 0.000393 * t) * sind(m) + 0.0021 * sind(2 * m) c1 -= 0.4068 * sind(mpr) + 0.0161 * sind(2 * mpr) c1 -= 0.0004 * sind(3 * mpr) c1 += 0.0104 * sind(2 * f) - 0.0051 * sind(m + mpr) c1 -= 0.0074 * sind(m - mpr) + 0.0004 * sind(2 * f + m) c1 -= 0.0004 * sind(2 * f - m) - 0.0006 * sind(2 * f + mpr) c1 += 0.0010 * sind(2 * f - mpr) + 0.0005 * sind(2 * mpr + m) let dt if (t < -11) { dt = 0.001 + 0.000839 * t + 0.0002261 * t2 - 0.00000845 * t3 - 0.000000081 * t * t3 } else { dt = -0.000278 + 0.000265 * t + 0.000262 * t2 } let jdNew = jd1 + c1 - dt return parseInt(jdNew + 0.5 + tzOffset / 24) } // Get solar term from Julian day, from 0 to 23, corresponding to Chunfen to Jingzhe. function getSolarTerm(jd, tzOffset) { t = (jd - 2451545.5 - tzOffset / 24) / 36525 t2 = t * t m = 357.52910 + 35999.05030 * t - 0.0001559 * t2 - 0.00000048 * t * t2 l0 = 280.46645 + 36000.76983 * t + 0.0003032 * t2 dl = (1.914600 - 0.004817 * t - 0.000014 * t2) * sind(m) dl = dl + (0.019993 - 0.000101 * t) * sind(2 * m) + 0.000290 * sind(3 * m) l = l0 + dl l %= 360 // normalize to (0, 360) return parseInt(l * 24 / 360) } // Get the solar longitude of the day, from 0 to 11. // 0 is corresponding to 0 degree to 30 degree and solar term Chunfen (spring equinox) - Guyu // 1 is corresponding to 30 degree to 60 degree and solar term Guyu - Xiaoman // 2 is corresponding to 60 degree to 90 degree and solar term Xiaoman - Xiazhi (summer solstince) // etc function getSunLongitude(jd, tzOffset) { t = (jd - 2451545.5 - tzOffset / 24) / 36525 t2 = t * t m = 357.52910 + 35999.05030 * t - 0.0001559 * t2 - 0.00000048 * t * t2 l0 = 280.46645 + 36000.76983 * t + 0.0003032 * t2 dl = (1.914600 - 0.004817 * t - 0.000014 * t2) * sind(m) dl = dl + (0.019993 - 0.000101 * t) * sind(2 * m) + 0.000290 * sind(3 * m) l = l0 + dl l %= 360 // normalize to (0, 360 deg) return parseInt(l * 12 / 360) } // Get Julian day for the first day of 11th lunar month of the year. function getMonth11(year, tzOffset) { off = getJulianDay(new Date(year, 11, 31)) - 2415021 k = parseInt(off / 29.530588853) nm = getNewMoonDay(k, tzOffset) sun_long = getSunLongitude(nm, tzOffset) if (sun_long >= 9) { nm = getNewMoonDay(k-1, tzOffset) } return nm } // Get the leap month offset from the 11th month before it. // // a11: Julian day of the first day of the 11th month function getLeapMonthOffset(a11, tzOffset) { k = parseInt((a11 - 2415021.076998695) / 29.530588853 + 0.5) i = 1 arc = getSunLongitude(getNewMoonDay(k + i, tzOffset), tzOffset) last = 0 while (arc != last && i < 14) { last = arc i++ arc = getSunLongitude(getNewMoonDay(k + i, tzOffset), tzOffset) } return i - 1 } function convertSolarToLunar(date, tzOffset) { const jd = getJulianDay(date) let k = parseInt((jd - 2415021.076998695) / 29.530588853) let monthStart = getNewMoonDay(k + 1, tzOffset) if (monthStart > jd) { monthStart = getNewMoonDay(k, tzOffset) } let a11 = getMonth11(date.getFullYear(), tzOffset) let b11 = a11 let lunarYear if (a11 >= monthStart) { lunarYear = date.getFullYear() a11 = getMonth11(lunarYear - 1, tzOffset) } else { lunarYear = date.getFullYear() + 1 b11 = getMonth11(lunarYear, tzOffset) } let lunarDay = jd - monthStart + 1 let diff = parseInt((monthStart - a11) / 29) let isLeapMonth = false let lunarMonth = diff + 11 if (b11 - a11 > 365) { leapMonthDiff = getLeapMonthOffset(a11, tzOffset) if (diff >= leapMonthDiff) { lunarMonth = diff + 10 if (diff == leapMonthDiff) { isLeapMonth = true } } } if (lunarMonth > 12) { lunarMonth -= 12 } if (lunarMonth >= 11 && diff < 4) { lunarYear -= 1 } return { year: lunarYear, month: lunarMonth, day: lunarDay, isLeap: isLeapMonth, jd: jd } } CAN_VI = ['Giáp', 'Ất', 'Bính', 'Đinh', 'Mậu', 'Kỷ', 'Canh', 'Tân', 'Nhâm', 'Quý'] CAN_ZH = [] CHI_VI = ['Tý', 'Sửu', 'Dần', 'Mão', 'Thìn', 'Tỵ', 'Ngọ', 'Mùi', 'Thân', 'Dậu', 'Tuất', 'Hợi'] CHI_ZH = [] // Get can/chi of a lunar date function getZodiac({year, month, day, jd}) { let yearZodiac = { can: (year + 6) % 10, chi: (year + 8) % 12 } let monthZodiac = { can: (year * 12 + month + 3) % 10, chi: (month + 1) % 12 } let dayZodiac = { can: (jd + 9) % 10, chi: (jd + 1) % 12 } return {yearZodiac, monthZodiac, dayZodiac} } function getZodiacText(zodiac, lang) { switch (lang) { case "vi": return CAN_VI[zodiac.can] + " " + CHI_VI[zodiac.chi] case "zh": return CAN_ZH[zodiac.can] + CHI_ZH[zodiac.chi] default: throw("Unsupported") } } // Part 2: Update DOM // DOM const dateInput = document.querySelector('#solar-date') const solarYearOutput = document.querySelector('#solar-year') const solarMonthOutputEN = document.querySelector('#solar-month-en') const solarMonthOutputVI = document.querySelector('#solar-month-vi') const solarDayOutput = document.querySelector('#solar-day') const solarWeekdayOutputEN = document.querySelector('#solar-weekday-en') const solarWeekdayOutputVI = document.querySelector('#solar-weekday-vi') const solarWeekdayOutputZH = document.querySelector('#solar-weekday-zh') const lunarYearOutputVI = document.querySelector('#lunar-year-vi') const lunarMonthOutputVI = document.querySelector('#lunar-month-vi') const lunarDayOutputVI = document.querySelector('#lunar-day-vi') const lunarYearOutputZH = document.querySelector('#lunar-year-zh') const lunarMonthOutputZH = document.querySelector('#lunar-month-zh') const lunarDayOutputZH = document.querySelector('#lunar-day-zh') const monthFormatEN = new Intl.DateTimeFormat("en-US", {month: "long"}) const monthFormatVI = new Intl.DateTimeFormat("vi-VN", {month: "long"}) const weekdayFormatEN = new Intl.DateTimeFormat("en-US", {weekday: "long"}) const weekdayFormatVI = new Intl.DateTimeFormat("vi-VN", {weekday: "long"}) const weekdayFormatZH = new Intl.DateTimeFormat("zh-CN", {weekday: "long"}) const LUNAR_MONTH_VI = ["một", "hai", "ba", "tư", "năm", "sáu", "bảy", "tám", "chín", "mười", "mười một", "chạp"] function updateOutputs() { if (dateInput.valueAsDate === null) { dateInput.valueAsDate = new Date() } const date = dateInput.valueAsDate const tzOffset = 7 solarYearOutput.innerText = date.getFullYear() solarMonthOutputEN.innerText = monthFormatEN.format(date) solarMonthOutputVI.innerText = monthFormatVI.format(date) solarDayOutput.innerText = date.getDate() solarWeekdayOutputEN.innerText = weekdayFormatEN.format(date) solarWeekdayOutputVI.innerText = weekdayFormatVI.format(date) solarWeekdayOutputZH.innerText = weekdayFormatZH.format(date) const lunarDate = convertSolarToLunar(date, tzOffset) const zodiac = getZodiac(lunarDate) lunarYearOutputVI.innerText = "Năm " + getZodiacText(zodiac.yearZodiac, "vi") lunarMonthOutputVI.innerText = "Tháng " + LUNAR_MONTH_VI[lunarDate.month - 1] if (lunarDate.isLeap) { lunarMonthOutputVI.innerText += " (nhuận)" } lunarDayOutputVI.innerText = "Ngày " + lunarDate.day } document.addEventListener("DOMContentLoaded", updateOutputs) dateInput.onchange = updateOutputs