anitimejs
Version:
Thư viện xử lý chuỗi số và thời gian trong JavaScript/Typescript
1,335 lines • 58 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.RegexHelper = exports.isValidSlug = exports.getSlugPart = exports.createUniqueSlug = exports.createSlug = exports.easingFunctions = exports.effects = exports.createTransform = exports.sequence = exports.physics = exports.spring = exports.stagger = exports.timeline = exports.sortArray = exports.random = exports.numbers = exports.animate = exports.useTimer = exports.anitimejsGlobalConfig = exports.anitimejs = exports.numberOfTime = void 0;
const helper_1 = require("./helper");
const useTimer_1 = require("./useTimer");
exports.useTimer = useTimer_1.default;
const animate_1 = require("./animate");
Object.defineProperty(exports, "animate", { enumerable: true, get: function () { return animate_1.animate; } });
Object.defineProperty(exports, "timeline", { enumerable: true, get: function () { return animate_1.timeline; } });
Object.defineProperty(exports, "stagger", { enumerable: true, get: function () { return animate_1.stagger; } });
Object.defineProperty(exports, "spring", { enumerable: true, get: function () { return animate_1.spring; } });
Object.defineProperty(exports, "physics", { enumerable: true, get: function () { return animate_1.physics; } });
Object.defineProperty(exports, "sequence", { enumerable: true, get: function () { return animate_1.sequence; } });
Object.defineProperty(exports, "createTransform", { enumerable: true, get: function () { return animate_1.createTransform; } });
Object.defineProperty(exports, "effects", { enumerable: true, get: function () { return animate_1.effects; } });
Object.defineProperty(exports, "easingFunctions", { enumerable: true, get: function () { return animate_1.easingFunctions; } });
const number_1 = require("./number");
Object.defineProperty(exports, "numbers", { enumerable: true, get: function () { return number_1.numbers; } });
const random_1 = require("./random");
Object.defineProperty(exports, "random", { enumerable: true, get: function () { return random_1.random; } });
const sort_1 = require("./sort");
Object.defineProperty(exports, "sortArray", { enumerable: true, get: function () { return sort_1.sortArray; } });
const slug_1 = require("./slug");
Object.defineProperty(exports, "createSlug", { enumerable: true, get: function () { return slug_1.createSlug; } });
Object.defineProperty(exports, "createUniqueSlug", { enumerable: true, get: function () { return slug_1.createUniqueSlug; } });
Object.defineProperty(exports, "getSlugPart", { enumerable: true, get: function () { return slug_1.getSlugPart; } });
Object.defineProperty(exports, "isValidSlug", { enumerable: true, get: function () { return slug_1.isValidSlug; } });
const regex_1 = require("./regex");
Object.defineProperty(exports, "RegexHelper", { enumerable: true, get: function () { return regex_1.RegexHelper; } });
class Time {
constructor(date, endDate) {
this.date = date ? this.parseDate(date) : new Date();
if (isNaN(this.date.getTime())) {
throw new Error("The date is not valid. Please provide a valid date string or Date object.");
}
if (endDate) {
this.endDate = this.parseDate(endDate);
if (isNaN(this.endDate.getTime())) {
throw new Error("The endDate is not valid. Please provide a valid date string or Date object.");
}
}
else {
this.endDate = null;
}
this.setTimeZone(Time.timezone);
}
parseDate(date) {
if (date instanceof Date) {
return new Date(date);
}
if (typeof date === "string") {
// Try parsing standard date format first
const standardDate = new Date(date);
if (!isNaN(standardDate.getTime())) {
return standardDate;
}
// Try custom DD/MM/YYYY format
const dateRegex = /^(\d{1,2})[/.-](\d{1,2})[/.-](\d{4})$/;
const dateTimeRegex = /^(\d{1,2})[/.-](\d{1,2})[/.-](\d{4}) (\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/;
const dateMatch = date.match(dateRegex);
const dateTimeMatch = date.match(dateTimeRegex);
if (dateMatch) {
const day = parseInt(dateMatch[1], 10);
const month = parseInt(dateMatch[2], 10) - 1; // Month is 0-based
const year = parseInt(dateMatch[3], 10);
// Validate ranges
if (month < 0 || month > 11)
throw new Error(`Invalid month: ${month + 1}`);
if (day < 1 || day > 31)
throw new Error(`Invalid day: ${day}`);
return new Date(year, month, day);
}
if (dateTimeMatch) {
const day = parseInt(dateTimeMatch[1], 10);
const month = parseInt(dateTimeMatch[2], 10) - 1; // Month is 0-based
const year = parseInt(dateTimeMatch[3], 10);
const hours = parseInt(dateTimeMatch[4], 10);
const minutes = parseInt(dateTimeMatch[5], 10);
const seconds = dateTimeMatch[6] ? parseInt(dateTimeMatch[6], 10) : 0;
// Validate ranges
if (month < 0 || month > 11)
throw new Error(`Invalid month: ${month + 1}`);
if (day < 1 || day > 31)
throw new Error(`Invalid day: ${day}`);
if (hours < 0 || hours > 23)
throw new Error(`Invalid hours: ${hours}`);
if (minutes < 0 || minutes > 59)
throw new Error(`Invalid minutes: ${minutes}`);
if (seconds < 0 || seconds > 59)
throw new Error(`Invalid seconds: ${seconds}`);
return new Date(year, month, day, hours, minutes, seconds);
}
// Additional format: DD-MM-YYYY
const dashDateRegex = /^(\d{1,2})-(\d{1,2})-(\d{4})$/;
const dashMatch = date.match(dashDateRegex);
if (dashMatch) {
const day = parseInt(dashMatch[1], 10);
const month = parseInt(dashMatch[2], 10) - 1;
const year = parseInt(dashMatch[3], 10);
if (month < 0 || month > 11)
throw new Error(`Invalid month: ${month + 1}`);
if (day < 1 || day > 31)
throw new Error(`Invalid day: ${day}`);
return new Date(year, month, day);
}
// If we've gotten here, try one more standard parsing approach
const fallbackDate = new Date(date.replace(/-/g, "/"));
if (!isNaN(fallbackDate.getTime())) {
return fallbackDate;
}
throw new Error(`Unable to parse date from string: ${date}`);
}
// If we get here, the input is neither a Date nor a string
throw new Error(`Invalid date input: ${String(date)}`);
}
formatDate(format, formatLanguage) {
let newFormat = format;
if (format === "L") {
newFormat = "L, DD eM tY YYYY";
}
if (format === "LL") {
newFormat = "L, DD eM tY YYYY tK HH:mm";
}
if (format === "LT") {
newFormat = "L, DD eM tY YYYY tK hh:mm";
}
if (format === "F") {
const now = new Date().getTime();
const timeUnil = this.date.getTime();
const diff = now - timeUnil;
// Thay đổi đơn vị thời gian theo nhu cầu
const seconds = 1000;
const minutes = seconds * 60;
const hours = minutes * 60;
const days = hours * 24;
const month = days * 30;
const year = days * 365.6;
newFormat = `MM/YYYY`;
if (diff < minutes) {
newFormat = "vx";
}
if (diff < 30 * seconds) {
newFormat = "vp";
}
if (diff < 0) {
newFormat = "vs";
}
if (diff >= minutes && diff < hours) {
newFormat = `${Math.floor(diff / minutes)} tI vv`;
}
if (diff >= hours && diff < days) {
newFormat = `${Math.floor(diff / hours)} tH vv`;
}
if (diff >= days && diff < month) {
newFormat = `${Math.floor(diff / days)} tD vv`;
}
if (diff >= month && diff < year) {
newFormat = `DD/MM`;
}
}
const pad = (n) => (n < 10 ? `0${n}` : n.toString());
const year = this.date.getFullYear();
const month = pad(this.date.getMonth() + 1);
const day = pad(this.date.getDate());
// Lấy giờ, phút, giây và mili giây
const hours24 = this.date.getHours();
const minutes = pad(this.date.getMinutes());
const seconds = pad(this.date.getSeconds());
const milliseconds = this.date.getMilliseconds(); // Mili giây không cần pad vì có thể lớn hơn 100
// Lấy thứ trong tuần (0: Chủ nhật, 1: Thứ 2, ... 6: Thứ 7)
const yearShort = year.toString().slice(-2);
const hours12 = hours24 % 12 || 12;
const ampm = hours24 >= 12 ? "PM" : "AM";
return newFormat
.split(" ")
.map((key) => (key === null || key === void 0 ? void 0 : key.trim())
? key
.replace("YYYY", year.toString())
.replace("YY", yearShort)
.replace("MM", month)
.replace("DD", day)
.replace("HH", pad(hours24)) // giờ theo định dạng 24 giờ
.replace("hh", pad(hours12)) // giờ theo định dạng 12 giờ
.replace("mm", minutes)
.replace("ss", seconds)
.replace("SSS", milliseconds)
.replace("A", ampm) // thêm AM/PM
.replace("L", formatLanguage.L[this.date.getDay()])
.replace("l", formatLanguage.l[this.date.getDay()])
.replace("tK", formatLanguage.tK)
.replace("TK", formatLanguage.TK)
.replace("eM", formatLanguage.eM[this.date.getMonth()])
.replace("EM", formatLanguage.EM[this.date.getMonth()])
.replace("tY", formatLanguage.tY)
.replace("TY", formatLanguage.TY)
.replace("vx", formatLanguage.vx)
.replace("vp", formatLanguage.vp)
.replace("vv", formatLanguage.vv)
.replace("vs", formatLanguage.vs)
.replace("xx", formatLanguage.xx)
.replace("tD", formatLanguage.tD)
.replace("TD", formatLanguage.TD)
.replace("tM", formatLanguage.tM)
.replace("TM", formatLanguage.TM)
.replace("tH", formatLanguage.tH)
.replace("TH", formatLanguage.TH)
.replace("tI", formatLanguage.tI)
.replace("TI", formatLanguage.TI)
.replace("tS", formatLanguage.tS)
.replace("TS", formatLanguage.TS)
: "")
.join(" ");
}
// Kiểm tra startDate hay endDate lớn hơn
isStartDateGreater() {
if (this.endDate) {
return this.date > this.endDate;
}
return false;
}
// Tính khoảng cách giữa hai ngày (trả về mili giây)
getDateDifference() {
if (this.endDate) {
return Math.abs(this.endDate.getTime() - this.date.getTime());
}
return 0;
}
dateCal() {
const diffMilliseconds = this.getDateDifference();
const seconds = Math.floor(diffMilliseconds / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);
const years = Math.floor(days / 365.25); // Trung bình có 365.25 ngày trong một năm
const months = Math.floor(days / 30.44); // Trung bình có 30.44 ngày trong một tháng
return {
milliseconds: diffMilliseconds,
seconds: seconds,
minutes: minutes,
hours: hours,
days: days,
months: months,
years: years,
};
}
format(format = "DD/MM/YYYY", configLanguage) {
const formatLanguage = helper_1.multilingual[Time.locale];
return this.formatDate(format, Object.assign(Object.assign({}, formatLanguage), (configLanguage ? configLanguage : {})));
}
formats(formats, configLanguage) {
const formatLanguage = helper_1.multilingual[Time.locale];
const currentTime = Date.now();
// Lặp qua từng định dạng trong mảng
for (const { logic, time } of formats) {
const timeInMilliseconds = time * 1000;
// Nếu thời gian hiện tại trong khoảng thời gian `time`, trả về định dạng này
if (currentTime - this.date.getTime() <= timeInMilliseconds) {
return this.formatDate(logic, Object.assign(Object.assign({}, formatLanguage), (configLanguage ? configLanguage : {})));
}
}
// Trường hợp không thuộc khoảng nào, lấy định dạng cuối cùng
const lastFormat = formats[formats.length - 1].logic;
return this.formatDate(lastFormat, Object.assign(Object.assign({}, formatLanguage), (configLanguage ? configLanguage : {})));
}
countdown() {
const target = this.date; // Sử dụng this.date làm mốc thời gian
const now = new Date();
const diff = target.getTime() - now.getTime();
const milliseconds = Math.abs(diff);
const seconds = Math.floor((milliseconds / 1000) % 60);
const minutes = Math.floor((milliseconds / (1000 * 60)) % 60);
const hours = Math.floor((milliseconds / (1000 * 60 * 60)) % 24);
const days = Math.floor(milliseconds / (1000 * 60 * 60 * 24));
return {
days,
hours,
minutes,
seconds,
milliseconds: milliseconds % 1000, // Lấy mili giây còn lại
isPast: diff < 0, // Kiểm tra xem thời gian đã qua hay chưa
};
}
lang(lang) {
if (!helper_1.multilingual[lang]) {
throw new Error(`Language '${lang}' not supported.`);
}
Time.locale = lang;
return this;
}
toString() {
return this.format("DD/MM/YYYY HH:mm:ss");
}
toLocaleDateString() {
return this.format("DD/MM/YYYY");
}
toLocaleTimeString() {
return this.format("DD/MM/YYYY HH:mm:ss.SSS");
}
getDay() {
return this.date.getDate();
}
getMonth() {
return this.date.getMonth() + 1; // Tháng trong JavaScript bắt đầu từ 0, nên cần +1
}
getYear() {
return this.date.getFullYear();
}
getHours() {
return this.date.getHours();
}
getMinutes() {
return this.date.getMinutes();
}
getSeconds() {
return this.date.getSeconds();
}
getTime() {
return this.date.getTime();
}
getYears(start = 0, end = 0) {
// Lấy năm hiện tại từ `this.date`
const currentYear = this.date.getFullYear();
// Xác định năm bắt đầu và năm kết thúc dựa trên start và end
const startYear = currentYear - start;
const endYear = currentYear + end;
// Tạo mảng chứa các năm trong khoảng thời gian từ startYear đến endYear
const years = [];
for (let year = startYear; year <= endYear; year++) {
years.push(year);
}
return years;
}
getWeek() {
const currentDayIndex = this.date.getDay(); // Lấy chỉ số của ngày hiện tại trong tuần
const startOfWeek = new Date(this.date); // Tạo một bản sao của `this.date`
// Điều chỉnh `startOfWeek` để trỏ đến Chủ Nhật của tuần hiện tại
startOfWeek.setDate(this.date.getDate() - currentDayIndex);
// Tạo mảng chứa các ngày trong tuần
const weekDays = [];
for (let i = 0; i < 7; i++) {
// Đẩy từng ngày vào mảng, từ Chủ Nhật đến Thứ Bảy
const day = new Date(startOfWeek);
day.setDate(startOfWeek.getDate() + i);
weekDays.push(day);
}
return weekDays;
}
getDayInWeek() {
return this.date.getDay();
}
getTimeZone() {
return Time.timezone;
}
getLocale() {
return Time.locale;
}
getPreviousDay() {
const previousDay = new Date(this.date); // Tạo một bản sao của `this.date`
previousDay.setDate(this.date.getDate() - 1); // Lùi ngày xuống 1
return previousDay;
}
getNextDay() {
const nextDay = new Date(this.date); // Tạo một bản sao của `this.date`
nextDay.setDate(this.date.getDate() + 1); // Tăng ngày lên 1
return nextDay;
}
getPreviousYear() {
const previousYear = new Date(this.date);
previousYear.setFullYear(this.date.getFullYear() - 1); // Lùi năm xuống 1
return previousYear;
}
getNextYear() {
const nextYear = new Date(this.date);
nextYear.setFullYear(this.date.getFullYear() + 1); // Tăng năm lên 1
return nextYear;
}
getPreviousMonth() {
const previousMonth = new Date(this.date);
previousMonth.setMonth(this.date.getMonth() - 1); // Lùi tháng xuống 1
return previousMonth;
}
getNextMonth() {
const nextMonth = new Date(this.date);
nextMonth.setMonth(this.date.getMonth() + 1); // Tăng tháng lên 1
return nextMonth;
}
getWeekOfYear() {
// Tạo bản sao của `this.date` để đảm bảo không thay đổi giá trị gốc
const currentDate = new Date(this.date);
// Đặt ngày về thứ Hai của tuần hiện tại
currentDate.setHours(0, 0, 0, 0);
currentDate.setDate(currentDate.getDate() + 3 - ((currentDate.getDay() + 6) % 7));
// Tính số ngày từ ngày 1 tháng 1
const firstOfYear = new Date(currentDate.getFullYear(), 0, 1);
const daysSinceFirstOfYear = Math.floor((currentDate.getTime() - firstOfYear.getTime()) / (24 * 60 * 60 * 1000));
// Tính tuần trong năm
const weekOfYear = Math.ceil((daysSinceFirstOfYear + 1) / 7);
return weekOfYear;
}
getDaysOfWeekInYear(dayOfWeek) {
const year = this.date.getFullYear(); // Lấy năm từ this.date
const dates = [];
// Khởi tạo ngày đầu tiên của năm
let currentDate = new Date(year, 0, 1);
// Tìm ngày đầu tiên trong năm khớp với `dayOfWeek`
while (currentDate.getDay() !== dayOfWeek) {
currentDate.setDate(currentDate.getDate() + 1);
}
// Thêm tất cả các ngày trong năm trùng với `dayOfWeek`
while (currentDate.getFullYear() === year) {
dates.push(new Date(currentDate));
currentDate.setDate(currentDate.getDate() + 7); // Tăng lên 7 ngày để đến tuần tiếp theo
}
return dates;
}
setTimeZone(timeZone) {
try {
// Lấy thời gian hiện tại theo múi giờ mới
const localDate = new Date(this.date.toLocaleString("en-US", { timeZone }));
// Cập nhật `this.date` với thời gian mới
this.date.setTime(localDate.getTime());
// Cập nhật biến static timezone
Time.timezone = timeZone;
}
catch (error) {
throw new Error(`Invalid time zone: '${timeZone}'`);
}
return this;
}
// Hàm thêm thời gian
add(value, unit) {
switch (unit) {
case "days":
this.date.setDate(this.date.getDate() + value);
break;
case "hours":
this.date.setHours(this.date.getHours() + value);
break;
case "minutes":
this.date.setMinutes(this.date.getMinutes() + value);
break;
case "seconds":
this.date.setSeconds(this.date.getSeconds() + value);
break;
case "months":
this.date.setMonth(this.date.getMonth() + value);
break;
case "years":
this.date.setFullYear(this.date.getFullYear() + value);
break;
default:
throw new Error("Unsupported time unit.");
}
return this;
}
// Hàm trừ thời gian
subtract(value, unit) {
return this.add(-value, unit);
}
arrangeTime(arrayDate, order = "asc") {
// Chuyển đổi tất cả các giá trị trong mảng thành đối tượng Date
const dates = arrayDate.map((date) => new Date(date));
// Kiểm tra xem có giá trị không hợp lệ trong mảng không
if (dates.some((date) => isNaN(date.getTime()))) {
throw new Error("Một trong các ngày không hợp lệ.");
}
// Sắp xếp mảng theo thứ tự tăng dần hoặc giảm dần
dates.sort((a, b) => order === "asc" ? a.getTime() - b.getTime() : b.getTime() - a.getTime());
return this;
}
arrangeTimeObject(arrayDate, order = "asc", keyPath = "date") {
// Hàm phụ để lấy giá trị ngày từ đối tượng theo `keyPath`
const getDateValue = (obj) => {
// Tách keyPath thành các phần (hỗ trợ các trường hợp lồng nhau như `time.date`)
const keys = keyPath.split(".");
// Duyệt qua từng phần để lấy giá trị cuối cùng
let value = obj;
for (const key of keys) {
value = value[key];
if (value === undefined) {
throw new Error(`Không tìm thấy đường dẫn '${keyPath}' trong đối tượng.`);
}
}
return new Date(value);
};
// Chuyển đổi tất cả các giá trị thành đối tượng Date
const dates = arrayDate.map((obj) => {
const date = getDateValue(obj);
if (isNaN(date.getTime())) {
throw new Error("Một trong các ngày không hợp lệ.");
}
return Object.assign(Object.assign({}, obj), { _parsedDate: date }); // Lưu ngày đã phân tích để sắp xếp
});
// Sắp xếp mảng theo thứ tự tăng dần hoặc giảm dần
dates.sort((a, b) => order === "asc"
? a._parsedDate.getTime() - b._parsedDate.getTime()
: b._parsedDate.getTime() - a._parsedDate.getTime());
// Xóa trường `_parsedDate` sau khi sắp xếp
dates.forEach((obj) => delete obj._parsedDate);
// Gán lại mảng đã sắp xếp
arrayDate.length = 0;
arrayDate.push(...dates);
return this;
}
calculateWorkingDays(week, // Mảng chứa các thứ trong tuần muốn loại bỏ (0: Chủ nhật, 1: Thứ 2, ..., 6: Thứ 7)
holidays // Mảng chứa các ngày lễ muốn loại bỏ, có thể ở dạng "DD/MM" hoặc "DD/MM/YYYY"
) {
const start = this.date;
const end = this.endDate || this.date;
const holidaysExcluded = [];
const currentYear = start.getFullYear();
// Chuyển đổi và lọc ngày lễ
const holidayDates = holidays
.map((holiday) => {
if (holiday instanceof Date) {
return holiday;
}
if (typeof holiday === "string") {
// Xử lý định dạng "DD/MM"
const dayMonthMatch = holiday.match(/^(\d{1,2})\/(\d{1,2})$/);
if (dayMonthMatch) {
const day = parseInt(dayMonthMatch[1], 10);
const month = parseInt(dayMonthMatch[2], 10) - 1; // Tháng bắt đầu từ 0
return new Date(currentYear, month, day);
}
// Xử lý các định dạng khác qua hàm parseDate hiện có
try {
return this.parseDate(holiday);
}
catch (e) {
console.warn(`Không thể chuyển đổi ngày lễ: ${holiday}`);
return new Date(NaN); // Trả về ngày không hợp lệ
}
}
return new Date(NaN); // Trả về ngày không hợp lệ
})
.filter((date) => !isNaN(date.getTime()));
const workingDays = [];
// Clone start date to avoid modifying the original
const currentDate = new Date(start);
while (currentDate <= end) {
const dayOfWeek = currentDate.getDay();
// Kiểm tra xem ngày hiện tại có phải là ngày nghỉ cuối tuần hoặc ngày lễ không
const isWeekend = week.includes(dayOfWeek);
const isHoliday = holidayDates.some((holiday) => holiday.getDate() === currentDate.getDate() &&
holiday.getMonth() === currentDate.getMonth());
if (!isWeekend && !isHoliday) {
workingDays.push(new Date(currentDate)); // Thêm vào ngày làm việc
}
else if (isHoliday) {
holidaysExcluded.push(new Date(currentDate)); // Thêm vào ngày lễ
}
// Tăng ngày lên 1
currentDate.setDate(currentDate.getDate() + 1);
}
return { workingDays, holidaysExcluded };
}
/**
* Lấy danh sách các ngày lễ theo quốc gia cho năm cụ thể
* @param year Năm cần lấy ngày lễ (mặc định là năm hiện tại)
* @param country Mã quốc gia (mặc định: 'vi' - Việt Nam)
* @returns Mảng các ngày lễ
*/
getHolidays(year, country = "vi") {
const targetYear = year || this.date.getFullYear();
// Lấy danh sách ngày lễ có tên
const namedHolidays = this.getNamedHolidays(targetYear, country);
// Chỉ trả về phần Date
return namedHolidays.map((holiday) => holiday.date);
}
/**
* Lấy danh sách các ngày lễ kèm tên theo quốc gia cho năm cụ thể
* @param year Năm cần lấy ngày lễ (mặc định là năm hiện tại)
* @param country Mã quốc gia (mặc định: 'vi' - Việt Nam)
* @returns Mảng các ngày lễ kèm tên
*/
getNamedHolidays(year, country = "vi") {
const targetYear = year || this.date.getFullYear();
// Danh sách ngày lễ cố định (dương lịch) theo quốc gia
const fixedHolidays = {
// Việt Nam
vi: [
{ name: "Tết Dương Lịch", day: 1, month: 1 },
{ name: "Ngày Giỗ Tổ Hùng Vương", day: 10, month: 4 },
{ name: "Ngày Giải phóng miền Nam", day: 30, month: 4 },
{ name: "Ngày Quốc tế Lao động", day: 1, month: 5 },
{ name: "Ngày Quốc khánh", day: 2, month: 9 },
],
// Hoa Kỳ/Anh (tiếng Anh)
en: [
{ name: "New Year's Day", day: 1, month: 1 },
{ name: "Independence Day", day: 4, month: 7 },
{ name: "Veterans Day", day: 11, month: 11 },
{ name: "Christmas Day", day: 25, month: 12 },
{ name: "Boxing Day", day: 26, month: 12 },
],
// Nhật Bản
ja: [
{ name: "New Year's Day (元日)", day: 1, month: 1 },
{ name: "Coming of Age Day (成人の日)", day: 8, month: 1 },
{ name: "National Foundation Day (建国記念の日)", day: 11, month: 2 },
{ name: "Showa Day (昭和の日)", day: 29, month: 4 },
{ name: "Constitution Memorial Day (憲法記念日)", day: 3, month: 5 },
{ name: "Greenery Day (みどりの日)", day: 4, month: 5 },
{ name: "Children's Day (こどもの日)", day: 5, month: 5 },
{ name: "Marine Day (海の日)", day: 18, month: 7 },
{ name: "Mountain Day (山の日)", day: 11, month: 8 },
{ name: "Respect for the Aged Day (敬老の日)", day: 15, month: 9 },
{ name: "Culture Day (文化の日)", day: 3, month: 11 },
{ name: "Labor Thanksgiving Day (勤労感謝の日)", day: 23, month: 11 },
{ name: "Emperor's Birthday (天皇誕生日)", day: 23, month: 2 },
],
// Lào
lo: [
{ name: "Pi Mai Lào (Lào New Year)", day: 14, month: 4 },
{ name: "International Labour Day", day: 1, month: 5 },
{ name: "National Day (ວັນຊາດ)", day: 2, month: 12 },
{ name: "New Year's Day", day: 1, month: 1 },
],
// Trung Quốc
zh: [
{ name: "春节 (Tết Nguyên Đán)", day: 1, month: 1 },
{ name: "清明节 (Thanh Minh)", day: 4, month: 4 },
{ name: "劳动节 (Ngày Lao động)", day: 1, month: 5 },
{ name: "国庆节 (Quốc khánh)", day: 1, month: 10 },
{ name: "中秋节 (Tết Trung Thu)", day: 15, month: 8 },
],
// Pháp
fr: [
{ name: "Jour de l'an", day: 1, month: 1 },
{ name: "Fête du Travail", day: 1, month: 5 },
{ name: "Victoire 1945", day: 8, month: 5 },
{ name: "Fête nationale", day: 14, month: 7 },
{ name: "Assomption", day: 15, month: 8 },
{ name: "Toussaint", day: 1, month: 11 },
{ name: "Noël", day: 25, month: 12 },
],
// Hàn Quốc
ko: [
{ name: "New Year's Day (신정)", day: 1, month: 1 },
{ name: "Independence Movement Day (삼일절)", day: 1, month: 3 },
{ name: "Children's Day (어린이날)", day: 5, month: 5 },
{ name: "Memorial Day (현충일)", day: 6, month: 6 },
{ name: "Liberation Day (광복절)", day: 15, month: 8 },
{ name: "National Foundation Day (개천절)", day: 3, month: 10 },
{ name: "Hangul Day (한글날)", day: 9, month: 10 },
{ name: "Christmas Day (크리스마스)", day: 25, month: 12 },
],
};
// Danh sách ngày lễ âm lịch theo quốc gia
const lunarHolidays = {
// Việt Nam
vi: [
{ name: "Tết Nguyên Đán", day: 1, month: 1, durationDays: 5 },
{ name: "Tết Hàn Thực", day: 3, month: 3 },
{ name: "Lễ Phật Đản", day: 15, month: 4 },
{ name: "Tết Đoan Ngọ", day: 5, month: 5 },
{ name: "Lễ Vu Lan", day: 15, month: 7 },
{ name: "Tết Trung Thu", day: 15, month: 8 },
{ name: "Tết Thường Tân", day: 10, month: 10 },
],
// Trung Quốc
zh: [
{ name: "春节", day: 1, month: 1, durationDays: 7 },
{ name: "元宵节", day: 15, month: 1 },
{ name: "清明节", day: 5, month: 4 },
{ name: "端午节", day: 5, month: 5 },
{ name: "中秋节", day: 15, month: 8 },
],
// Hàn Quốc
ko: [
{ name: "설날 (Seollal)", day: 1, month: 1, durationDays: 3 },
{ name: "석가탄신일 (Buddha's Birthday)", day: 8, month: 4 },
{ name: "추석 (Chuseok)", day: 15, month: 8, durationDays: 3 },
],
// Lào
lo: [
{
name: "Boun Khao Phansa (Start of Buddhist Lent)",
day: 1,
month: 11,
},
{ name: "Boun Ok Phansa (End of Buddhist Lent)", day: 15, month: 3 },
],
};
const result = [];
// Thêm các ngày lễ dương lịch cố định
const countryFixedHolidays = fixedHolidays[country] || [];
countryFixedHolidays.forEach((holiday) => {
result.push({
name: holiday.name,
date: new Date(targetYear, holiday.month - 1, holiday.day),
});
});
// Thêm các ngày lễ âm lịch (chuyển sang dương lịch)
const countryLunarHolidays = lunarHolidays[country] || [];
countryLunarHolidays.forEach((holiday) => {
// Chuyển từ ngày âm lịch sang dương lịch
const solarDate = this.fromLunarDate(holiday.day, holiday.month, targetYear);
// Thêm ngày lễ chính
result.push({
name: holiday.name,
date: solarDate,
});
// Thêm các ngày nghỉ bổ sung nếu có
if (holiday.durationDays && holiday.durationDays > 1) {
for (let i = 1; i < holiday.durationDays; i++) {
const nextDate = new Date(solarDate);
nextDate.setDate(solarDate.getDate() + i);
result.push({
name: `${holiday.name} (ngày ${i + 1})`,
date: nextDate,
});
}
}
});
return result;
}
/**
* Chuyển đổi từ ngày dương lịch sang ngày âm lịch
* @param date Ngày dương lịch (nếu không cung cấp, sử dụng this.date)
* @returns Đối tượng chứa thông tin ngày âm lịch
*/
toLunarDate(date) {
const targetDate = date || this.date;
// Bảng dữ liệu lịch âm từ 1900-2100
// Mỗi số trong mảng đại diện cho thông tin về một năm âm lịch
// Bit 0-4: Tháng nhuận trong năm (0 nếu không có tháng nhuận)
// Bit 5-16: Lưu thông tin về độ dài của các tháng (0: 29 ngày, 1: 30 ngày)
// Bit 17-20: Đại diện cho năm nhuận (thường dùng bit 17 để kiểm tra)
const lunarInfo = [
0x04bd8,
0x04ae0,
0x0a570,
0x054d5,
0x0d260,
0x0d950,
0x16554,
0x056a0,
0x09ad0,
0x055d2, // 1900-1909
0x04ae0,
0x0a5b6,
0x0a4d0,
0x0d250,
0x1d255,
0x0b540,
0x0d6a0,
0x0ada2,
0x095b0,
0x14977, // 1910-1919
0x04970,
0x0a4b0,
0x0b4b5,
0x06a50,
0x06d40,
0x1ab54,
0x02b60,
0x09570,
0x052f2,
0x04970, // 1920-1929
0x06566,
0x0d4a0,
0x0ea50,
0x06e95,
0x05ad0,
0x02b60,
0x186e3,
0x092e0,
0x1c8d7,
0x0c950, // 1930-1939
0x0d4a0,
0x1d8a6,
0x0b550,
0x056a0,
0x1a5b4,
0x025d0,
0x092d0,
0x0d2b2,
0x0a950,
0x0b557, // 1940-1949
0x06ca0,
0x0b550,
0x15355,
0x04da0,
0x0a5b0,
0x14573,
0x052b0,
0x0a9a8,
0x0e950,
0x06aa0, // 1950-1959
0x0aea6,
0x0ab50,
0x04b60,
0x0aae4,
0x0a570,
0x05260,
0x0f263,
0x0d950,
0x05b57,
0x056a0, // 1960-1969
0x096d0,
0x04dd5,
0x04ad0,
0x0a4d0,
0x0d4d4,
0x0d250,
0x0d558,
0x0b540,
0x0b6a0,
0x195a6, // 1970-1979
0x095b0,
0x049b0,
0x0a974,
0x0a4b0,
0x0b27a,
0x06a50,
0x06d40,
0x0af46,
0x0ab60,
0x09570, // 1980-1989
0x04af5,
0x04970,
0x064b0,
0x074a3,
0x0ea50,
0x06b58,
0x055c0,
0x0ab60,
0x096d5,
0x092e0, // 1990-1999
0x0c960,
0x0d954,
0x0d4a0,
0x0da50,
0x07552,
0x056a0,
0x0abb7,
0x025d0,
0x092d0,
0x0cab5, // 2000-2009
0x0a950,
0x0b4a0,
0x0baa4,
0x0ad50,
0x055d9,
0x04ba0,
0x0a5b0,
0x15176,
0x052b0,
0x0a930, // 2010-2019
0x07954,
0x06aa0,
0x0ad50,
0x05b52,
0x04b60,
0x0a6e6,
0x0a4e0,
0x0d260,
0x0ea65,
0x0d530, // 2020-2029
0x05aa0,
0x076a3,
0x096d0,
0x04afb,
0x04ad0,
0x0a4d0,
0x1d0b6,
0x0d250,
0x0d520,
0x0dd45, // 2030-2039
0x0b5a0,
0x056d0,
0x055b2,
0x049b0,
0x0a577,
0x0a4b0,
0x0aa50,
0x1b255,
0x06d20,
0x0ada0, // 2040-2049
0x14b63,
0x09370,
0x049f8,
0x04970,
0x064b0,
0x168a6,
0x0ea50,
0x06b20,
0x1a6c4,
0x0aae0, // 2050-2059
0x0a2e0,
0x0d2e3,
0x0c960,
0x0d557,
0x0d4a0,
0x0da50,
0x05d55,
0x056a0,
0x0a6d0,
0x055d4, // 2060-2069
0x052d0,
0x0a9b8,
0x0a950,
0x0b4a0,
0x0b6a6,
0x0ad50,
0x055a0,
0x0aba4,
0x0a5b0,
0x052b0, // 2070-2079
0x0b273,
0x06930,
0x07337,
0x06aa0,
0x0ad50,
0x14b55,
0x04b60,
0x0a570,
0x054e4,
0x0d160, // 2080-2089
0x0e968,
0x0d520,
0x0daa0,
0x16aa6,
0x056d0,
0x04ae0,
0x0a9d4,
0x0a2d0,
0x0d150,
0x0f252, // 2090-2099
0x0d520, // 2100
];
// Tính ngày Julius
const baseDate = new Date(1900, 0, 31); // 31/01/1900
let offset = Math.floor((targetDate.getTime() - baseDate.getTime()) / 86400000);
// Tìm năm âm lịch
let i, temp = 0, leap;
for (i = 1900; i < 2101 && offset > 0; i++) {
temp = this.getLunarYearDays(i, lunarInfo);
offset -= temp;
}
if (offset < 0) {
offset += temp;
i--;
}
const year = i;
// Tìm tháng âm lịch
leap = this.getLunarLeapMonth(year, lunarInfo);
let isLeap = false;
for (i = 1; i < 13 && offset > 0; i++) {
// Kiểm tra tháng nhuận
if (leap > 0 && i === leap + 1 && isLeap === false) {
--i;
isLeap = true;
temp = this.getLunarLeapDays(year, lunarInfo);
}
else {
temp = this.getLunarMonthDays(year, i, lunarInfo);
}
// Nếu là tháng nhuận, khi tính xong thì đánh dấu là kết thúc tháng nhuận
if (isLeap === true && i === leap + 1) {
isLeap = false;
}
offset -= temp;
}
// Nếu offset = 0 và đang ở tháng nhuận, cần điều chỉnh
if (offset === 0 && leap > 0 && i === leap + 1) {
if (isLeap) {
isLeap = false;
}
else {
isLeap = true;
--i;
}
}
// Nếu offset < 0, trừ về tháng trước
if (offset < 0) {
offset += temp;
--i;
}
const month = i;
const day = offset + 1;
return {
day,
month,
year,
leap: isLeap,
dateString: `${day}/${month}/${year}${isLeap ? " (nhuận)" : ""}`,
};
}
/**
* Chuyển đổi từ ngày âm lịch sang ngày dương lịch
* @param lunarDay Ngày âm lịch
* @param lunarMonth Tháng âm lịch
* @param lunarYear Năm âm lịch
* @param isLeapMonth Có phải tháng nhuận không
* @returns Ngày dương lịch tương ứng
*/
fromLunarDate(lunarDay, lunarMonth, lunarYear, isLeapMonth = false) {
// Kiểm tra tính hợp lệ của ngày tháng năm
if (lunarMonth < 1 || lunarMonth > 12) {
throw new Error("Tháng âm lịch phải từ 1 đến 12");
}
// Bảng dữ liệu lịch âm
const lunarInfo = [
0x04bd8,
0x04ae0,
0x0a570,
0x054d5,
0x0d260,
0x0d950,
0x16554,
0x056a0,
0x09ad0,
0x055d2, // 1900-1909
0x04ae0,
0x0a5b6,
0x0a4d0,
0x0d250,
0x1d255,
0x0b540,
0x0d6a0,
0x0ada2,
0x095b0,
0x14977, // 1910-1919
0x04970,
0x0a4b0,
0x0b4b5,
0x06a50,
0x06d40,
0x1ab54,
0x02b60,
0x09570,
0x052f2,
0x04970, // 1920-1929
0x06566,
0x0d4a0,
0x0ea50,
0x06e95,
0x05ad0,
0x02b60,
0x186e3,
0x092e0,
0x1c8d7,
0x0c950, // 1930-1939
0x0d4a0,
0x1d8a6,
0x0b550,
0x056a0,
0x1a5b4,
0x025d0,
0x092d0,
0x0d2b2,
0x0a950,
0x0b557, // 1940-1949
0x06ca0,
0x0b550,
0x15355,
0x04da0,
0x0a5b0,
0x14573,
0x052b0,
0x0a9a8,
0x0e950,
0x06aa0, // 1950-1959
0x0aea6,
0x0ab50,
0x04b60,
0x0aae4,
0x0a570,
0x05260,
0x0f263,
0x0d950,
0x05b57,
0x056a0, // 1960-1969
0x096d0,
0x04dd5,
0x04ad0,
0x0a4d0,
0x0d4d4,
0x0d250,
0x0d558,
0x0b540,
0x0b6a0,
0x195a6, // 1970-1979
0x095b0,
0x049b0,
0x0a974,
0x0a4b0,
0x0b27a,
0x06a50,
0x06d40,
0x0af46,
0x0ab60,
0x09570, // 1980-1989
0x04af5,
0x04970,
0x064b0,
0x074a3,
0x0ea50,
0x06b58,
0x055c0,
0x0ab60,
0x096d5,
0x092e0, // 1990-1999
0x0c960,
0x0d954,
0x0d4a0,
0x0da50,
0x07552,
0x056a0,
0x0abb7,
0x025d0,
0x092d0,
0x0cab5, // 2000-2009
0x0a950,
0x0b4a0,
0x0baa4,
0x0ad50,
0x055d9,
0x04ba0,
0x0a5b0,
0x15176,
0x052b0,
0x0a930, // 2010-2019
0x07954,
0x06aa0,
0x0ad50,
0x05b52,
0x04b60,
0x0a6e6,
0x0a4e0,
0x0d260,
0x0ea65,
0x0d530, // 2020-2029
0x05aa0,
0x076a3,
0x096d0,
0x04afb,
0x04ad0,
0x0a4d0,
0x1d0b6,
0x0d250,
0x0d520,
0x0dd45, // 2030-2039
0x0b5a0,
0x056d0,
0x055b2,
0x049b0,
0x0a577,
0x0a4b0,
0x0aa50,
0x1b255,
0x06d20,
0x0ada0, // 2040-2049
0x14b63,
0x09370,
0x049f8,
0x04970,
0x064b0,
0x168a6,
0x0ea50,
0x06b20,
0x1a6c4,
0x0aae0, // 2050-2059
0x0a2e0,
0x0d2e3,
0x0c960,
0x0d557,
0x0d4a0,
0x0da50,
0x05d55,
0x056a0,
0x0a6d0,
0x055d4, // 2060-2069
0x052d0,
0x0a9b8,
0x0a950,
0x0b4a0,
0x0b6a6,
0x0ad50,
0x055a0,
0x0aba4,
0x0a5b0,
0x052b0, // 2070-2079
0x0b273,
0x06930,
0x07337,
0x06aa0,
0x0ad50,
0x14b55,
0x04b60,
0x0a570,
0x054e4,
0x0d160, // 2080-2089
0x0e968,
0x0d520,
0x0daa0,
0x16aa6,
0x056d0,
0x04ae0,
0x0a9d4,
0x0a2d0,
0x0d150,
0x0f252, // 2090-2099
0x0d520, // 2100
];
// Kiểm tra giới hạn ngày
const maxDays = isLeapMonth
? this.getLunarLeapDays(lunarYear, lunarInfo)
: this.getLunarMonthDays(lunarYear, lunarMonth, lunarInfo);
if (lunarDay < 1 || lunarDay > maxDays) {
throw new Error(`Ngày âm lịch phải từ 1 đến ${maxDays} đối với ${lunarMonth}/${lunarYear}${isLeapMonth ? " (nhuận)" : ""}`);
}
if (lunarYear < 1900 || lunarYear > 2100) {
throw new Error("Năm âm lịch phải từ 1900 đến 2100");
}
// Tính tổng số ngày từ 1900/1/1 âm lịch
let offset = 0;
// Tính tổng số ngày các năm trước
for (let i = 1900; i < lunarYear; i++) {
offset += this.getLunarYearDays(i, lunarInfo);
}
// Tháng nhuận của năm hiện tại
const leapMonth = this.getLunarLeapMonth(lunarYear, lunarInfo);
// Xử lý tháng nhuận
if (isLeapMonth && leapMonth !== lunarMonth) {
throw new Error(`Năm ${lunarYear} không có tháng ${lunarMonth} nhuận`);
}
// Cộng số ngày của các tháng trước trong năm
for (let i = 1; i < lunarMonth; i++) {
// Cộng số ngày của tháng thường
offset += this.getLunarMonthDays(lunarYear, i, lunarInfo);
// Nếu có tháng nhuận trước tháng hiện tại, cộng thêm số ngày
if (leapMonth > 0 && i === leapMonth) {
offset += this.getLunarLeapDays(lunarYear, lunarInfo);
}
}
// Nếu là tháng nhuận, cộng thêm số ngày của tháng thường cùng số
if (isLeapMonth) {
offset += this.getLunarMonthDays(lunarYear, lunarMonth, lunarInfo);
}
// Cộng số ngày trong tháng hiện tại
offset += lunarDay - 1;
// Tính ngày dương lịch tương ứng (từ ngày 31/1/1900)
const baseDate = new Date(1900, 0, 31);
const result = new Date(baseDate);
result.setDate(baseDate.getDate() + offset);
return result;
}
/**
* Chuyển đổi một khoảng thời gian từ dương lịch sang âm lịch
* @returns Mảng các ngày âm lịch trong khoảng từ this.date đến this.endDate
*/
toLunarDateRange() {
if (!this.endDate) {
throw new Error("EndDate không được xác định. Vui lòng thiết lập endDate trước khi gọi toLunarDateRange.");
}
const result = [];
const currentDate = new Date(this.date);
// Tạo bản sao để không ảnh hưởng đến this.date gốc
while (currentDate <= this.endDate) {
const lunarDate = this.toLunarDate(new Date(currentDate));
result.push({
solar: new Date(currentDate), // Tạo bản sao để tránh tham chiếu
lunar: lunarDate,
});
// Tăng ngày lên 1
currentDate.setDate(currentDate.getDate() + 1);
}
return result;
}
/**
* Lấy thông tin về tháng nhuận trong năm âm lịch
* @param lunarYear Năm âm lịch
* @param lunarInfo Bảng dữ liệu lịch âm
* @returns Số thứ tự của tháng nhuận (1-12), 0 nếu không có tháng nhuận
*/
getLunarLeapMonth(lunarYear, lunarInfo) {
const yearCode = lunarInfo[lunarYear - 1900];
return yearCode & 0xf; // Lấy 4 bit cuối để xác định tháng nhuận
}
/**
* Lấy số ngày của tháng nhuận trong năm âm lịch
* @param lunarYear Năm âm lịch
* @param lunarInfo Bảng dữ liệu lịch âm
* @returns Số ngày của tháng nhuận (29 hoặc 30)
*/
getLunarLeapDays(lunarYear, lunarInfo) {
const leapMonth = this.getLunarLeapMonth(lunarYear, lunarInfo);
if (leapMonth === 0) {
return 0;
}
const yearCode = lunarInfo[lunarYear - 1900];
// Bit thứ 16 (0x10000) xác định tháng nhuận có 30 ngày (1) hay 29 ngày (0)
return yearCode & 0x10000 ? 30 : 29;
}
/**
* Lấy số ngày của một tháng âm lịch thường
* @param lunarYear Năm âm lịch
* @param lunarMonth Tháng âm lịch
* @param lunarInfo Bảng dữ liệu lịch âm
* @returns Số ngày của tháng (29 hoặc 30)
*/
getLunarMonthDays(lunarYear, lunarMonth, lunarInfo) {
const yearCode = lunarInfo[lunarYear - 1900];
// Bit tương ứng với tháng xác định số ngày là 30 (1) hay 29 (0)
// Bit 4-15 tương ứng với các tháng từ 12 đến 1
const bitPosition = 0x10000 >> lunarMonth;
return yearCode & bitPosition ? 30 : 29;
}
/**
* Lấy tổng số ngày trong năm âm lịch
* @param lunarYear Năm âm lịch
* @param lunarInfo Bảng dữ liệu lịch âm
* @returns Tổng số ngày trong năm
*/
getLunarYearDays(lunarYear, lunarInfo) {
let totalDays = 0;
const leapMonth = this.getLunarLeapMonth(lunarYear, lunarInfo);
// Tính số ngày của 12 tháng thường
for (let i = 1; i <= 12; i++) {
totalDays += this.getLunarMonthDays(lunarYear, i, lunarInfo);
}
// Cộng thêm số ngày của tháng nhuận (nếu có)
if (leapMonth > 0) {
totalDays += this.getLunarLeapDays(lunarYear, lunarInfo);
}
return totalDays;
}
getMonthStartEndDates() {
const inputDate = this.date;
if (isNaN(inputDate.getTime())) {
throw new Er