@radkototh/dan
Version:
Date As Number
304 lines (269 loc) • 9.64 kB
JavaScript
import en from './locale/en.js';
import sk from './locale/sk.js';
const locales = {en, sk}
const danData = {
firstYear: 1970,
weekdayOfZeroDay: 4,
years: Array.from({length: 101}, (_, i) => i === 0 ? 0 : Array.from({length: i}, (_, k) => 365 + (new Date(1970 + k, 1, 29).getDate() === 29 ? 1 : 0)).reduce((s, d) => s + d, 0)),
months: [
[0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365],
[0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366],
],
}
// creator
export function dan(date = null, format = null, formatLanguage) {
let result;
if (date === null) {
result = dateToDan(new Date());
} else if (date?.$isDayjsObject) {
result = formatDanNumber(date.$y, date.$M + 1, date.$D);
} else if (date instanceof Date) {
result = dateToDan(date);
} else if (typeof date === 'number') {
if (date > 20000000 && date < 30000000) {
result = date;
}
} else if (typeof date === 'string') {
const cleanedDate = date.replace(/[^0-9]/g, '');
if (cleanedDate.length === 8) result = parseInt(date, 10);
}
return format !== null ? danFormat(result, format, formatLanguage) : result;
}
// manipulators
export function danAdd(dan, amount, unit, format = null, formatLanguage) {
let result;
if (unit === 'd') {
const jdn = danToJdn(dan);
result = jdnToDan(jdn + amount);
} else if (unit === 'm') {
const {year, month, day} = parseDan(dan);
let newYear = year;
let newMonth = month + amount;
while (newMonth > 12) {
newMonth -= 12;
newYear++;
}
while (newMonth < 1) {
newMonth += 12;
newYear--;
}
const maxDay = danDaysInMonth(newYear, newMonth);
const newDay = Math.min(day, maxDay);
result = formatDanNumber(newYear, newMonth, newDay);
} else if (unit === 'y') {
const {year, month, day} = parseDan(dan);
const newYear = year + amount;
const maxDay = danDaysInMonth(newYear, month);
const newDay = Math.min(day, maxDay);
result = formatDanNumber(newYear, month, newDay);
} else if (unit === 'w') {
const jdn = danToJdn(dan);
result = jdnToDan(jdn + amount * 7);
} else {
throw new Error('Unsupported unit: ' + unit);
}
return format !== null ? danFormat(result, format, formatLanguage) : result;
}
export function danSubtract(dan, amount, unit, format = null, formatLanguage) {
return danAdd(dan, -amount, unit, format, formatLanguage);
}
export function danSet(dan, unit, value, format = null, formatLanguage) {
const {year, month, day} = parseDan(dan);
let result;
if (unit === 'y') {
const newYear = value;
const maxDay = danDaysInMonth(newYear, month);
const newDay = Math.min(day, maxDay);
result = formatDanNumber(newYear, month, newDay);
} else if (unit === 'm') {
const newMonth = value;
const maxDay = danDaysInMonth(year, newMonth);
const newDay = Math.min(day, maxDay);
result = formatDanNumber(year, newMonth, newDay);
} else if (unit === 'd') {
const maxDay = danDaysInMonth(year, month);
const newDay = value > maxDay ? maxDay : value;
result = formatDanNumber(year, month, newDay);
} else {
throw new Error('Unsupported unit: ' + unit);
}
return format !== null ? danFormat(result, format, formatLanguage) : result;
}
export function danStartOf(dan, unit, format = null, formatLanguage) {
const {year, month} = parseDan(dan);
let result;
if (unit === 'm') {
result = formatDanNumber(year, month, 1);
} else if (unit === 'y') {
result = formatDanNumber(year, 1, 1);
} else if (unit === 'w') {
const jdn = danToJdn(dan);
const startOfWeek = jdn - (danWeekday(dan) - 1);
result = jdnToDan(startOfWeek);
} else {
throw new Error('Unsupported unit: ' + unit);
}
return format !== null ? danFormat(result, format, formatLanguage) : result;
}
export function danEndOf(dan, unit, format = null, formatLanguage) {
const {year, month} = parseDan(dan);
let result;
if (unit === 'm') {
const lastDayOfMonth = danDaysInMonth(year, month);
result = formatDanNumber(year, month, lastDayOfMonth);
} else if (unit === 'y') {
result = formatDanNumber(year, 12, 31);
} else if (unit === 'w') {
const jdn = danToJdn(dan);
const endOfWeek = jdn + (7 - danWeekday(dan));
result = jdnToDan(endOfWeek);
} else {
throw new Error('Unsupported unit: ' + unit);
}
return format !== null ? danFormat(result, format, formatLanguage) : result;
}
// getters
export function danGet(dan, unit) {
if (unit === 'wd') return danWeekday(dan);
if (unit === 'w') { // iso week
const jdn = danToJdn(dan);
return Math.floor((jdn + 3) / 7);
}
const {year, month, day} = parseDan(dan);
if (unit === 'y') return year;
if (unit === 'm') return month;
if (unit === 'd') return day;
throw new Error('Unsupported unit: ' + unit);
}
export function danDiff(start, end) {
return danToJdn(end) - danToJdn(start);
}
export function danNightsCount(start, end) {
return danToJdn(end) - danToJdn(start);
}
export function danNightsArray(start, end) {
const jdnFirst = danToJdn(start);
const jdnLast = danToJdn(end);
if (jdnFirst > jdnLast) return [];
const nights = [];
for (let jdn = jdnFirst; jdn < jdnLast; jdn++) {
nights.push(jdnToDan(jdn));
}
return nights;
}
export function danToDate(dan) {
const parsed = parseDan(dan);
return new Date(parsed.year, parsed.month - 1, parsed.day);
}
export function danWeekday(dan) {
const weekday = (danToJdn(dan) + danData.weekdayOfZeroDay) % 7;
return weekday === 0 ? 7 : weekday; // convert 0 to 7
}
export function danDaysInMonth(year, month = null) {
if (month === null) {
const {year: y, month: m} = parseDan(year);
month = m;
year = y;
}
return getMonthDays(year)[month];
}
export function isDan(dan) {
return dan && typeof dan === 'number' && dan >= 20000000 && dan <= 30000000;
}
function parseDan(dan) {
if (!isDan(dan)) dan = dan(dan);
if (typeof dan === 'string') dan = parseInt(dan, 10);
return {
year: Math.floor(dan / 10000),
month: Math.floor((dan % 10000) / 100),
day: dan % 100,
};
}
function formatDanNumber(year, month, day) {
return year * 10000 + month * 100 + day;
}
function isLeapYear(year) {
return (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0);
}
function getMonthDays(year) {
return [0, 31, isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
}
// Converts a Dan (YYYYMMDD) to a day index (number of days since 1970-01-01)
function danToJdnOld(dan) {
const {year, month, day} = parseDan(dan);
let days = 0;
// Accumulate days for full years from 1970 up to (but not including) the given year
for (let y = danData.firstYear; y < year; y++) {
days += isLeapYear(y) ? 366 : 365;
}
// Add days for the months passed in the current year
const monthDays = getMonthDays(year);
for (let m = 1; m < month; m++) {
days += monthDays[m];
}
// Add the days passed in the current month (using zero-based index)
days += (day - 1);
return days;
}
function danToJdn(dan) {
const {year, month, day} = parseDan(dan);
return danData['years'][year - danData.firstYear] + danData['months'][isLeapYear(year) ? 1 : 0][month - 1] + day - 1;
}
// Converts a day index (number of days since 1970-01-01) back to a Dan (YYYYMMDD)
function jdnToDanOld(dayIndex) {
if (!dayIndex) {
console.log('ERROR jdnToDan', dayIndex, new Error().stack);
return dan();
}
let year = danData.firstYear;
// Determine the year by subtracting full years' days
while (true) {
const daysInYear = isLeapYear(year) ? 366 : 365;
if (dayIndex < daysInYear) break;
dayIndex -= daysInYear;
year++;
}
// Determine the month by subtracting each month's days
const monthDays = getMonthDays(year);
let month = 1;
for (let i = 1; i < 13; i++) {
if (dayIndex < monthDays[i]) {
month = i;
break;
}
dayIndex -= monthDays[i];
}
// The remaining dayIndex corresponds to the day (add 1 because day count is zero-based)
const day = dayIndex + 1;
return formatDanNumber(year, month, day);
}
function jdnToDan(dayIndex) {
const yearsIndex = danData.years.findIndex((days) => days > dayIndex) - 1;
const year = yearsIndex + danData.firstYear;
const isLeap = isLeapYear(year) ? 1 : 0
dayIndex -= danData.years[yearsIndex];
const month = danData.months[isLeap].findIndex((days) => days > dayIndex);
dayIndex -= danData.months[isLeap][month - 1];
return formatDanNumber(year, month, dayIndex + 1);
}
function danFormat(dan, format = 'YYYY-MM-DD', language = 'en') {
if (format === 'date') return danToDate(dan);
if (format === 'string') return dan.toString();
const {year, month, day} = parseDan(dan);
return format
.replace('MMMM', locales[language].monthNames[month - 1]) // names stored in 0-11
.replace('MMM', locales[language].monthNamesShort[month - 1]) // names stored in 0-11
.replace('MM', month < 10 ? '0' + month : month.toString())
.replace('M', month.toString())
.replace('DD', day < 10 ? '0' + day : day.toString())
.replace('D', day.toString())
.replace('dddd', locales[language].weekdayNamesLong[danWeekday(dan) - 1]) // names stored in 0-6
.replace('ddd', locales[language].weekdayNames[danWeekday(dan) - 1]) // names stored in 0-6
.replace('YYYY', year.toString())
}
function dateToDan(date) {
const year = date.getFullYear();
const month = date.getMonth() + 1; // Months are zero-based in JavaScript
const day = date.getDate();
return formatDanNumber(year, month, day);
}