node-calendar-js
Version:
Simple JavaScript calendar library.
335 lines (281 loc) • 9.49 kB
text/typescript
import { table } from "table";
export const CONSTANT_DAYS_ORDER = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
export interface CalendarOptions {
year?: number;
month?: number;
}
export interface CalendarDaysData {
row: number;
day?: number;
name?: string;
holiday: boolean;
}
export interface CalendarData {
year: number;
month: number;
totalDays: number;
monthName: string;
days: CalendarDaysData[];
leapYear: boolean;
leapMonth: boolean;
}
export interface createHTMLOptions {
includeStyles?: boolean;
}
class Calendar {
year: number;
month: number;
days: string[];
months: string[];
holidays: number[];
/**
* Creates new Calendar
* @example ```js
* const calendar = new Calendar({ year: 2021, month: 6 });
* console.log(calendar.create());
* ```
*/
constructor(options?: CalendarOptions) {
/**
* Current year
*/
this.year = this._validate(options && typeof options.year === "number" ? options.year : new Date().getFullYear(), "number", "year");
/**
* Current month
*/
this.month = this._validate(options && typeof options.month === "number" ? options.month : new Date().getMonth(), "number", "month");
/**
* Days format
*/
this.days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
/**
* Months format
*/
this.months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
/**
* Holiday(s). By default, it is set to sunday
*/
this.holidays = [0];
}
/**
* Sets custom days notation
* @param days Days name
*/
public setDays(days: string[]): Calendar {
const isValid = days.length === 7 && days.some(x => typeof x === "string");
if (!isValid) throw new Error("Invalid days data");
this.days = days;
return this;
}
/**
* Sets custom months notaiton
* @param months Months name
*/
public setMonths(months: string[]): Calendar {
const isValid = months.length === 12 && months.some(x => typeof x === "string");
if (!isValid) throw new Error("Invalid months data");
this.months = months;
return this;
}
/**
* Sets holidays
* @param data Holidays day array. Example: `[0]` refers to `sunday` whereas `[0, 6]` refers to `saturday` and `sunday`.
*/
public setHolidays(data: number[]) {
const isValid = data.some(x => typeof x === "number" && (x >= 0 && x < 7));
if (!isValid) throw new Error("Invalid holidays data");
this.holidays = data;
return this;
}
/**
* Returns total number of days in this month
*/
public get length(): number {
if (this.month === 1 && ((this.year % 4 == 0 && this.year % 100 !== 0) || this.year % 400 === 0)) return 29;
return CONSTANT_DAYS_ORDER[this.month]
}
/**
* If it is a leap year
*/
public get leap(): boolean {
return (this.year % 4 == 0 && this.year % 100 !== 0) || this.year % 400 === 0;
}
/**
* Returns current month name
*/
public get monthName(): string {
return this.months[this.month];
}
/**
* Creates calendar
* @example ```js
* const data = new Calendar({ year: 2020, month: 0 });
* console.log(data.create());
* ```
*/
public create(): CalendarData {
const firstDay = new Date(this.year, this.month, 1);
const intitalDay = firstDay.getDay();
const length = this.length;
const month = this.monthName;
let days = [];
let day = 1;
for (let i = 0; i < 9; i++) {
for (let j = 0; j < 7; j++) {
if (day <= length && (i > 0 || j >= intitalDay)) {
days.push({
row: i,
day: day,
name: this.days[j],
holiday: this.holidays.some(x => x === j)
});
day++;
} else {
days.push({
row: i,
day: null,
name: null,
holiday: false
})
}
}
if (day > length) break;
}
return {
year: this.year,
month: this.month,
totalDays: length,
monthName: month,
days: days,
leapMonth: this.length === 29,
leapYear: this.leap
} as CalendarData;
}
/**
* Creates HTML output
* @param options Calendar html output options
*/
public toHTML(options?: createHTMLOptions): string {
if (!options) options = {};
const data = this.create();
const cap = `<caption>${data.monthName} ${data.year}</caption>`;
let heading = "";
let days = "";
const ROWS = [...new Set(data.days.map(m => m.row))];
for (let i = 0; i < 7; i++) {
heading += `<th class="${this.days[i].toLowerCase()}">${this.days[i]}</th>`;
}
ROWS.forEach(index => {
let tempr = "";
const row = data.days.filter(x => x.row === index);
for (let i = 0; i < row.length; i++) {
tempr += `<td class="day${row[i].holiday ? " holiday" : ""}">${row[i].day || ""}</td>`;
}
days += `<tr>${tempr}</tr>`;
});
const style = `
<style>
table,
th,
td {
border: 1px solid black;
border-collapse: collapse;
}
th,
td {
padding: 5px;
text-align: left;
}
.holiday {
color: #db0000;
}
</style>
`;
return `${!!options.includeStyles ? style : ""}<table width="100%">${cap}<thead><tr>${heading}</tr></thead><tbody>${days}</tbody></table>`;
}
/**
* String representation of the calendar
*/
public toString(): string {
const data = this.create();
const ROWS = [...new Set(data.days.map(m => m.row))];
const heading: string[] = [];
const days: any[] = [];
for (let i = 0; i < 7; i++) {
heading.push(this.days[i]);
}
ROWS.forEach(index => {
const row = data.days.filter(x => x.row === index).map(m => `${m.day || ""}`);
days.push(row);
});
const header = table([[data.monthName, data.year]]);
const body = [
heading,
...days
];
const tableData = table(body, {
border: {
topBody: "─",
topJoin: "┬",
topLeft: "┌",
topRight: "┐",
bottomBody: "─",
bottomJoin: "┴",
bottomLeft: "└",
bottomRight: "┘",
bodyLeft: "│",
bodyRight: "│",
bodyJoin: "│",
joinBody: "─",
joinLeft: "├",
joinRight: "┤",
joinJoin: "┼"
}
});
return `${header}${tableData}`;
}
toJSON(): CalendarData {
return this.create();
}
/**
* Static calendar creator
* @param year Creates calendar of full year
* @example ```js
* const calendar = Calendar.createCalendar(2020);
* console.log(calendar.map(m => m.monthName));
* ```
*/
public static createCalendar(year = new Date().getFullYear()): CalendarData[] {
const valid = typeof year === "number" && year >= 1000 && year <= 9999;
if (!valid) throw new TypeError(`Invalid year: ${year}!`);
const cal = [];
for (let i = 0; i < 12; i++) {
const st = new Calendar({
year: year,
month: i
});
cal.push(st.create());
}
return cal;
}
/**
* Internal number validation method
* @param data data to validate
* @param vtype value data type
* @param dataType validation data type
*/
private _validate(data: number, vtype = "number", dataType: "year" | "month"): number {
switch(dataType) {
case "month":
if (typeof data === vtype && !(data < 0 && data > 11)) return data;
throw new TypeError(`Invalid month: ${data}!`);
case "year":
if (typeof data === vtype && !(data < 1000 && data > 9999)) return data;
throw new TypeError(`Invalid year: ${data}!`);
default:
throw new Error("Invalid type");
}
}
}
export { Calendar };
export default Calendar;