prs-utils
Version:
Prs Utilities
1,721 lines (1,560 loc) • 50.5 kB
JavaScript
export default function prsTimeline(idHolder) {
let fontSize = 12;
let fontFamily = "tahoma";
let eventWidth = 10;
let eventColWidth = 20;
let horLineShow = 1;
let horLineClr = "#00000010";
let horLineGap = 100;
let horLineLabel = 1;
let marginLeft = 20;
let reuseEventLine = 1;
let objCanvas = null;
let txtTimelineDoc = "";
let prsMultiCalendar = null;
function gei(id) {
return document.getElementById(id);
}
function getVariableValueAsNumber(variableName, valueOnErr) {
let v = getVariableValue(variableName, valueOnErr);
let r = parseFloat(v);
if (isNaN(r)) return valueOnErr;
return r;
}
function updateGui(txtDoc) {
txtTimelineDoc = txtDoc;
fontSize = getVariableValueAsNumber("--font-size", 16);
eventWidth = getVariableValueAsNumber("--event-width", 10);
eventColWidth = getVariableValueAsNumber("--event-col-width", 20);
fontFamily = getVariableValue("--font-family", "Tahoma");
horLineShow = getVariableValueAsNumber("--hor-line-show", 1);
horLineClr = getVariableValue("--hor-line-clr", "#00000010");
horLineGap = getVariableValueAsNumber("--hor-line-gap", 100);
horLineLabel = getVariableValueAsNumber("--hor-line-label", 1);
marginLeft = getVariableValueAsNumber("--margin-left", 20);
reuseEventLine = getVariableValueAsNumber("--reuse-event-line", 1);
checkCanvasCoordinates(txtDoc);
let items = txtDoc
.split("\n")
.filter((x) => tr(x).startsWith("-") === false)
.filter((x) => tr(x).startsWith("//") === false)
.join("\n")
.split("@")
.filter((x) => tr(x) != "")
.map((x) => "@" + x)
.map((x) => tr(x))
.map((x) => {
if (x.indexOf("}") == -1) {
let idx = x.indexOf(")");
return tr(x.substring(0, idx + 1));
} else {
let idx = x.indexOf("}");
return tr(x.substring(0, idx + 1));
}
})
.map((x) => parsePattern(x))
.filter((x) => x != null);
// ### TEST ###
// items.forEach((x) => console.log(x));
plotItems(items, reuseEventLine);
}
function getVariableValue(variableName, valueOnErr) {
// let docText = gei("txtChron").value;
let docText = txtTimelineDoc;
let v = (
docText
.split("\n")
.filter((x) => x.trim().toLowerCase().startsWith(variableName))[0] || ""
)
.toLowerCase()
.replaceAll(variableName, "")
.trim();
if (tr(v) == "") return valueOnErr;
return v;
}
function checkCanvasCoordinates(docText) {
if (objCanvas == null) {
console.log("objCanvas is null");
return;
}
// Example
// --size 1024, 768
// get canvas coordinate
let arr = (
docText
.split("\n")
.filter((x) => x.trim().toLowerCase().startsWith("--size"))[0] || ""
)
.toLowerCase()
.replaceAll("--size", "")
.split(",")
.map((x) => parseFloat(x))
.filter((x) => !isNaN(x));
if (arr.length === 2) {
let width = arr[0];
let height = arr[1];
let max = 50000;
if (0 < width && width < max && 0 < height && height < max) {
if (!(objCanvas.width == width && objCanvas.height == height)) {
resizeCanvas(objCanvas, width, height);
}
}
}
}
function plotItems(items, reuseEventLine = 1) {
function colToIndent(col) {
return (col + 1) * eventColWidth;
}
function getNextAvailableColumn(currentItem, arrCols) {
for (let i = 0; i < arrCols.length; i++) {
let brr = arrCols[i];
let conflicts = false;
for (let j = 0; j < brr.length; j++) {
conflicts ||= hasConflict(currentItem, brr[j]);
if (conflicts) break;
}
if (!conflicts) {
arrCols[i].push(currentItem);
return i;
}
}
// if all columns are occupied, make a new column and push this current
// item into it
arrCols.push([currentItem]);
return arrCols.length - 1;
}
let min = Math.min(
...items.map((x) => parseFloat(x.from)),
...items.map((x) => parseFloat(x.to))
);
let max = Math.max(
...items.map((x) => parseFloat(x.to)),
...items.map((x) => parseFloat(x.from))
);
// Sort Items. Longer ones go first
items.sort((x, y) => Math.abs(y.to - y.from) - Math.abs(x.to - x.from));
canvasClear("white");
if (horLineShow === 1) {
plotHorLines("cnvChron", min, max, horLineClr, horLineGap, horLineLabel);
}
if (reuseEventLine === 0) {
for (let i = 0; i < items.length; i++) {
items[i].indent = colToIndent(i);
}
}
// If it is chosen not to reuse any available column for events
else {
let arrCols = [];
for (let i = 0; i < items.length; i++) {
items[i].indent = colToIndent(
getNextAvailableColumn(items[i], arrCols)
);
}
}
// Do the plot
for (let i = 0; i < items.length; i++) {
drawInterval("cnvChron", items[i], min, max, marginLeft, eventWidth);
drawItemCaption(
"cnvChron",
items[i],
min,
max,
marginLeft,
fontSize,
fontFamily
);
}
// ### TEST ###
// console.log(min, max);
}
function resizeCanvas(objCanvas, newWidth, newHeight) {
// const canvas = document.getElementById(objCanvas);
const canvas = objCanvas;
if (!canvas) {
return;
}
canvas.width = newWidth;
canvas.height = newHeight;
}
function drawLine(
canvasId,
startX,
startY,
endX,
endY,
color = "black",
lineWidth = 1
) {
// Get the canvas element by ID
// const canvas = document.getElementById(canvasId);
const canvas = objCanvas;
if (!canvas || !canvas.getContext) {
console.error("Canvas not found or unsupported.");
return;
}
// Get the canvas drawing context
const ctx = canvas.getContext("2d");
// Set line styles
ctx.strokeStyle = color; // Line color
ctx.lineWidth = lineWidth; // Line width
// Begin a new path
ctx.beginPath();
ctx.moveTo(startX, startY); // Start position
ctx.lineTo(endX, endY); // End position
ctx.stroke(); // Draw the line
}
function parsePattern(inputString) {
// Check if we have all essential items
if ("@()".split("").every((x) => inputString.includes(x)) === false)
return null;
let indxAt = inputString.indexOf("@");
let indxOpen = inputString.indexOf("(");
let indxClose = inputString.indexOf(")");
// Check the order
if (indxAt > indxOpen) return null;
if (indxAt > indxClose) return null;
if (indxOpen > indxClose) return null;
let indxOpenOverrides = inputString.indexOf("{");
let indxCloseOverrides = inputString.indexOf("}");
let overrides = {};
overrides.cal = "g"; // g: gregorian, j: jalali, h: hijri
overrides.label = "mid"; // g: gregorian, j: jalali, h: hijri
let ovStr = "";
if (indxOpenOverrides != -1 && indxCloseOverrides) {
ovStr = inputString.substring(indxOpenOverrides + 1, indxCloseOverrides);
}
// Extract override object if any values specified
ovStr.split(",").forEach((x) => {
let y = x.split(":");
let key = tr(y[0]);
let val = tr(y[1]);
if (key != "" && val != "") {
overrides[key] = val;
}
});
// console.log("Overrides Here:", overrides)
let caption = tr(inputString.substring(indxAt + 1, indxOpen));
let args = inputString.substring(indxOpen + 1, indxClose);
let parts = args
.split(",")
.map((x) => x.replaceAll(/\s/g, ""))
.filter((x) => tr(x) != "");
let fromDateStr = parts[0];
// if the second arg is not specified the first arg is used
let toDateStr = parts[1] || parts[0];
if (
toDateStr.toLowerCase() === "now" ||
toDateStr.toLowerCase() === "present" ||
toDateStr.toLowerCase() === "current"
) {
toDateStr = timestamp().substring(0, 10);
}
let from = parseDate(fromDateStr);
if (from == -1) return null;
let to = parseDate(toDateStr);
if (to == -1) return null;
let clr = overrides.clr || "#FF000055";
// Return the object with extracted values
return {
caption,
from,
to,
clr,
fromDateStr,
toDateStr,
overrides,
indent: -1,
};
}
function tr(str) {
if (str == null) return "";
if (typeof str === "string") return str.trim();
return (str + "").trim();
}
function drawTextOnCanvas(
canvasId,
text,
x,
y,
color,
fontSize = 12,
fontFamily = "tahoma"
) {
// Get the canvas element by its ID
// const canvas = document.getElementById(canvasId);
const canvas = objCanvas;
// Check if the canvas exists and supports 2D context
if (!canvas || !canvas.getContext) {
console.error("Canvas not found or not supported.");
return;
}
// Get the 2D context
const ctx = canvas.getContext("2d");
// ctx.direction = 'rtl';
// Set font and style
ctx.font = `${fontSize}px ${fontFamily || "tahoma"}`;
ctx.fillStyle = color || "black";
// Draw the text
// RTL Right to Left
ctx.textAlign = "right";
ctx.fillText(text, x + ctx.measureText(text).width, y);
// ctx.fillText(text, x , y);
// ???
// console.log(ctx.measureText(text));
}
function dateToText(dt){
let r = dt.toLowerCase().trim();
if (r.endsWith("j")) {
r = r.substring(0, r.length - 1);
r += " "
// r += "جلالی"
r += "خورشیدی"
return r.replaceAll("-","/")
}
else if (r.endsWith("h")) {
r = r.substring(0, r.length - 1);
r += " "
r += "هجری"
return r.replaceAll("-","/")
}
else{
if(r.startsWith("-")){
r = r.substring(1)
r += " "
r += "پیش از میلاد"
}else {
r += " "
r += "میلادی"
}
return r
}
}
function drawItemCaption(
canvasId,
item,
min,
max,
marginLeft,
fontSize = 12,
fontFamily = "tahoma",
color = "black"
) {
if (tr(item.caption) == "") return;
let d = Math.abs(max - min);
if (d == 0) return;
// const canvas = document.getElementById(canvasId);
const canvas = objCanvas;
let ch = canvas.height;
let y = item.from || 0;
y = ch * ((y - min) / d);
/* Vertical offset */
y += 12;
let extraSpace = parseFloat(item.overrides["sp"] || 0);
if (isNaN(extraSpace)) extraSpace = 0;
let x = marginLeft + item.indent + 30 + extraSpace;
let txt = "";
if (item.fromDateStr === item.toDateStr) {
// txt = `${item.caption}(${item.fromDateStr})`;
txt += item.caption;
txt += " ";
txt += "در";
txt += " ";
txt += dateToText(item.fromDateStr);
} else {
let diff = Math.round((item.to - item.from) * 100) / 100;
let jal = "";
if (item.overrides.cal.toLowerCase() == "j") {
jal = `(${fnGregToPersian(item.fromDateStr)},${fnGregToPersian(
item.toDateStr
)})`;
}
let hij = "";
if (item.overrides.cal.toLowerCase() == "h") {
hij = `(${fnGregToHijri(item.fromDateStr)},${fnGregToHijri(
item.toDateStr
)})`;
}
// txt = `${item.caption}(${item.fromDateStr},${item.toDateStr})${jal}${hij}:${diff}`;
txt = item.caption
txt += " "
txt += "از"
txt += " "
txt += dateToText(item.fromDateStr)
txt += " "
txt += "تا"
txt += " "
txt += dateToText(item.toDateStr)
txt += " "
txt += "به مدت"
txt += " "
txt += diff
txt += " "
txt += "سال"
}
let y1 = item.from || 0;
let y2 = item.to || 0;
y1 = ch * ((y1 - min) / d);
y2 = ch * ((y2 - min) / d);
let mid = (y1 + y2) / 2;
let elevation = mid;
let ratio = parseFloat(tr(item.overrides.label));
if (isNaN(ratio)) ratio = 50;
if (ratio > 100) ratio = 100;
if (ratio < 0) ratio = 0;
ratio = ratio / 100;
elevation = y1 + (y2 - y1) * ratio;
drawTextOnCanvas(
canvasId,
txt,
x,
elevation + fontSize / 2,
color,
fontSize,
fontFamily
);
}
function plotHorLines(
canvasId,
min,
max,
horLineClr,
horLineGap,
horLineLabel
) {
// const canvas = document.getElementById(canvasId);
const canvas = objCanvas;
if (!canvas) {
return;
}
let d = Math.abs(max - min);
if (d == 0) return;
let ch = canvas.height;
let cw = canvas.width;
let y = 0;
let dy = ch * (horLineGap / d);
let i = 0;
let loopGuard = 4000;
while (y < ch) {
if (i >= loopGuard) break;
if (dy < 1) break;
drawLine(canvasId, 0, y, cw, y, horLineClr, 1);
if (horLineLabel === 1) {
let strYear = (Math.round(min + i * horLineGap) + "").padStart(4, " ");
drawTextOnCanvas(canvasId, strYear, 10, y + 4, "#00000099");
drawTextOnCanvas(canvasId, strYear, cw - 40, y + 4, "#00000099");
}
y += dy;
i++;
}
}
function parseDate(dateStr) {
if (dateStr == null) return -1;
let parts = dateStr
.split(/-|\/|\|/)
.map((x) => tr(x))
.filter((x) => x != "")
.map((x) => parseFloat(x));
let y = parts[0];
if (isNaN(y)) return -1;
let m = parts[1] || 1;
let d = parts[2] || 1;
// if the input date string is Jalali (Persian)
if (dateStr.trim().toLowerCase().endsWith("j")) {
return parseDate(fnPersianToGregorian(dateStr.replaceAll(/j/gi, "")));
}
// if the input date string is Jalali (Hijri)
if (dateStr.trim().toLowerCase().endsWith("h")) {
return parseDate(fnHijriToGreg(dateStr.replaceAll(/h/gi, "")));
}
if (y > 5000) return -1;
if (y < -5000) return -1;
if (m > 12) m = 12;
if (m < 1) m = 1;
if (d > 31) d = 31;
if (d < 1) d = 1;
let r = 0;
r += y;
if (tr(dateStr).startsWith("-")) r = -r;
r += (m - 1) / 12;
r += d / 365;
r = Math.round(r * 100) / 100;
return r;
}
function getOverrideValue(item, key) {
if (item == null) return "";
if (item.overrides == null) return "";
return item.overrides[key] || "";
}
function getOverrideValueAsNumber(item, key, valueOnErr) {
let r = parseFloat(getOverrideValue(item, key));
if (isNaN(r)) return valueOnErr;
return r;
}
function fnGregToPersian(dateString) {
if (prsMultiCalendar == null) {
console.log("prsMultiCalendar is null");
return;
}
try {
const [year, month, day] = dateString.split(/\/|-/g).map(Number);
let dt = prsMultiCalendar.convert(year, month || 1, day || 1, "g", "j");
return dt.DateString.replaceAll("/", "-");
} catch (err) {
return null;
}
}
function fnPersianToGregorian(dateString) {
if (prsMultiCalendar == null) {
console.log("prsMultiCalendar is null");
return;
}
try {
const [year, month, day] = dateString.split(/\/|-/g).map(Number);
let dt = prsMultiCalendar.convert(year, month || 1, day || 1, "j", "g");
return dt.DateString.replaceAll("/", "-");
} catch (err) {
return null;
}
}
function fnGregToHijri(dateString) {
if (prsMultiCalendar == null) {
console.log("prsMultiCalendar is null");
return;
}
try {
const [year, month, day] = dateString.split(/\/|-/g).map(Number);
let dt = prsMultiCalendar.convert(year, month || 1, day || 1, "g", "l");
return dt.DateString.replaceAll("/", "-");
} catch (err) {
return null;
}
}
function fnHijriToGreg(dateString) {
if (prsMultiCalendar == null) {
console.log("prsMultiCalendar is null");
return;
}
try {
const [year, month, day] = dateString.split(/\/|-/g).map(Number);
let dt = prsMultiCalendar.convert(year, month || 1, day || 1, "l", "g");
return dt.DateString.replaceAll("/", "-");
} catch (err) {
return null;
}
}
function getCurrentTimeInfo() {
const now = new Date();
const year = now.getFullYear(); // Current year
const month = now.getMonth() + 1; // Current month (0-based, so add 1)
const day = now.getDate(); // Current day of the month
const hour = now.getHours(); // Current hour
const minutes = now.getMinutes(); // Current minutes
const seconds = now.getSeconds(); // Current seconds
const milliseconds = now.getMilliseconds(); // Current milliseconds
return { year, month, day, hour, minutes, seconds, milliseconds };
}
function timestamp() {
function pd(x, n = 2) {
return (x + "").padStart(n, "0");
}
let d = getCurrentTimeInfo();
let r = `${pd(d.year, 4)}-${pd(d.month)}-${pd(d.day)}--${pd(d.hour)}-${pd(
d.minutes
)}-${pd(d.seconds)}-${pd(d.milliseconds, 3)}`;
return r;
}
function timestampIso() {
return new Date().toISOString();
}
function guid() {
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(
/[xy]/g,
function (c) {
var r = (Math.random() * 16) | 0,
v = c == "x" ? r : (r & 0x3) | 0x8;
return v.toString(16);
}
);
}
function canvasClear(color = "white") {
// const canvas = document.getElementById(canvasId);
const canvas = objCanvas;
const ctx = canvas.getContext("2d");
ctx.fillStyle = color;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Clear the canvas
function clearCanvas() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
}
function hasConflict(itemA, itemB) {
let r = !(itemA.from > itemB.to || itemA.to < itemB.from);
return r;
}
function drawInterval(
canvasId,
item,
/* indentHor, */ min,
max,
marginLeft,
width = 20
) {
if (tr(item.caption) == "") return;
let d = Math.abs(max - min);
if (d == 0) return;
// const canvas = document.getElementById(canvasId);
const canvas = objCanvas;
let ch = canvas.height;
let y1 = item.from || 0;
let y2 = item.to || 0;
y1 = ch * ((y1 - min) / d);
y2 = ch * ((y2 - min) / d);
let mid = (y1 + y2) / 2;
let eventWidthOverride = getOverrideValueAsNumber(item, "w", 0);
if (eventWidthOverride != 0) width = eventWidthOverride;
let color = item.clr || "black";
// The event Line
drawLine(
canvasId,
marginLeft + item.indent,
y1,
marginLeft + item.indent,
y2,
color,
width
);
let extraSpace = getOverrideValueAsNumber(item, "sp", 0);
let left = marginLeft + item.indent + width / 2;
let len = marginLeft + item.indent + 25 + extraSpace;
// Check label position
let elevation = mid;
let ratio = parseFloat(tr(item.overrides.label));
if (isNaN(ratio)) ratio = 50;
if (ratio > 100) ratio = 100;
if (ratio < 0) ratio = 0;
ratio = ratio / 100;
elevation = y1 + (y2 - y1) * ratio;
// The hor line attached to event
drawLine(canvasId, left, elevation, len, elevation, "#00000060", 2);
// The vertical line attached to event
drawLine(canvasId, left, y1, left, y2, "#00000060", 2);
drawDisk(canvasId, left, elevation, 2, "black", "black");
}
function drawDisk(
canvasId,
centerX,
centerY,
radius,
color = "black",
stroke = "black"
) {
// const canvas = document.getElementById(canvasId);
const canvas = objCanvas;
if (!canvas) {
console.error(`Canvas with ID "${canvasId}" not found.`);
return;
}
const ctx = canvas.getContext("2d");
if (!ctx) {
console.error("Could not get 2D context from canvas.");
return;
}
// Draw the filled circle
ctx.beginPath();
ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
ctx.fillStyle = color;
ctx.fill();
// Draw the circle's border
ctx.strokeStyle = stroke;
ctx.stroke();
}
function MultiCalendar() {
/*Global Values*/
var GREG_ORG_GDP = 0; /* The origine of Gerigorian calendar (0001/01/01 Greg)*/
var GREG_MAX_GDP = 3652058; /* Days passd from 1/1/1 AD to 9999 Dec 31 AD which is the maximum supported date in gregorian calendar (9999/12/31 Greg)*/
var JAL_MAX_GDP = 3652058;
var JAL_ORG_GDP = 226894;
var LUN_MAX_GDP = 3652058;
var LUN_ORG_GDP = JAL_ORG_GDP + 119; /*BIAS*/
/*Gregorian Section*/
let Greg = {
greg_week_days: [
"Saturday",
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
],
greg_week_days_abbr: ["Sat", "Sun", "Mon", "Tue", "Wed", "Thr", "Fri"],
greg_months: [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"Septamber",
"October",
"November",
"December",
],
greg_months_abbr: [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec",
],
greg_months_fa: [
"زانويه",
"فوريه",
"مارچ",
"آوريل",
"مي",
"ژون",
"جولاي",
"آگوست",
"سپتامبر",
"اکتبر",
"نوامبر",
"دسامبر",
],
gregToGdp: function (Year, Month, DayOfMonth) {
var c = 0;
var y = 1;
var d = 0;
var M = [];
while ((c + 1) * 100 < Year) {
c++;
if (c % 4 == 0) d += 36525;
else d += 36524;
}
while (c * 100 + y < Year) {
if (Greg.isGregLeapYear(c * 100 + y)) d += 366;
else d += 365;
y++;
}
if (Greg.isGregLeapYear(Year))
M = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
else M = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
for (var m = 1; m < Month; m++) {
d += M[m - 1];
}
d += DayOfMonth;
return d - 1;
},
isValidDate(year, month, day) {
if (year < 1) return false;
if (year > 9999) return false;
if (month < 1) return false;
if (month > 12) return false;
if (day < 1) return false;
if (day > 31) return false;
var M;
if (Greg.isGregLeapYear(year))
M = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
else M = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
if (!Greg.isGregLeapYear(year)) {
if (month == 2) if (day > 28) return false;
} else {
if (month == 2) if (day > 29) return false;
}
if (month == 4 || month == 6 || month == 9 || month == 11)
if (day > 30) return false;
return true;
},
daysInYear: function (year) {
var y1 = Greg.gregToGdp(year, 1, 1);
var y2 = Greg.gregToGdp(year + 1, 1, 1);
return y2 - y1;
},
daysInMonth: function (year, month) {
var g1 = Greg.gregToGdp(year, month, 1);
var m = month + 1;
var y = year;
if (m == 13) {
m = 1;
y++;
}
var g2 = Greg.gregToGdp(y, m, 1);
return g2 - g1;
},
isGregLeapYear: function (AYear) {
if (AYear <= 0) return false;
if (AYear % 4 != 0) return false;
if (AYear % 100 == 0) {
if (AYear % 400 == 0) return true;
else return false;
}
return true;
},
getGregCentury: function (GregDaysPassed) {
var RemainingDays = 0;
var d = 0;
var c = 0;
var c_days = 0;
while (true) {
c++;
if (c % 4 == 0) c_days = 36525;
else c_days = 36524;
if (d + c_days > GregDaysPassed) break;
d += c_days;
}
RemainingDays = GregDaysPassed - d;
return {
Century: c,
RemainingDays: RemainingDays,
};
},
getGregYear: function (GregDaysPassed) {
var RemainingDays = 0;
if (GregDaysPassed < GREG_ORG_GDP) return -1;
if (GregDaysPassed > GREG_MAX_GDP) return -1;
var res = Greg.getGregCentury(GregDaysPassed);
var c = res.Century;
var rem = res.RemainingDays;
var d = 0;
var y = 0;
var y_days = 0;
while (true) {
y++;
if (Greg.isGregLeapYear(y + (c - 1) * 100)) y_days = 366;
else y_days = 365;
if (d + y_days > rem) break;
d += y_days;
}
RemainingDays = rem - d;
var g = y + (c - 1) * 100;
return {
GregYear: g,
RemainingDays: RemainingDays,
};
},
getGregMonth: function (IsLeapYear, YearDaysPassed) {
var RemainingDays = 0;
if (YearDaysPassed > 366) return -1;
if (YearDaysPassed < 0) return -1;
var M = [];
if (IsLeapYear) M = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
else M = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
var d = 0;
var m = 1;
while (d + M[m - 1] <= YearDaysPassed) {
d += M[m - 1];
m++;
}
RemainingDays = YearDaysPassed - d;
return {
GregMonth: m,
RemainingDays: RemainingDays,
};
},
}; // end of Greg
/*Jalali (Persian Solar) Section*/
let Jal = {
week_days_abbr: ["ش", "ی", "د", "س", "چ", "پ", "ج"],
week_days: [
"شنبه",
"یکشنبه",
"دوشنبه",
"سه شنبه",
"چهار شنبه",
"پنج شنبه",
"جمعه",
],
month_titles: [
"فروردین",
"اردیبهشت",
"خرداد",
"تیر",
"مرداد",
"شهریور",
"مهر",
"آبان",
"آذر",
"دی",
"بهمن",
"اسفند",
],
isJalLeapYear: function (jalYear) {
for (var i = 0; i < 7; i++)
if ((jalYear - (i * 4 - 7)) % 33 == 0) return true;
if ((jalYear - (7 * 4 - 6)) % 33 == 0) return true;
return false;
},
getJalYear: function (GregDaysPassed) {
function GetJalLeapSequence(GregDaysPassed) {
var remaining_days = 0;
var jal_days_passed = GregDaysPassed - JAL_ORG_GDP;
var n = Math.floor((jal_days_passed + 2922) / 12053);
if (n == 0) {
remaining_days = jal_days_passed;
return {
seq: n,
rem: remaining_days,
};
} else if (n == 1) {
remaining_days = jal_days_passed - 9131;
return {
seq: n,
rem: remaining_days,
};
} else {
remaining_days = jal_days_passed - (9131 + (n - 1) * 12053);
return {
seq: n,
rem: remaining_days,
};
}
}
var RemainingDays = 0;
if (GregDaysPassed < JAL_ORG_GDP)
return {
year: 0,
rem: 0,
err:
"GDP " +
GregDaysPassed +
" is less than minimum value " +
JAL_ORG_GDP,
};
if (GregDaysPassed > JAL_MAX_GDP)
return {
year: 0,
rem: 0,
err:
"GDP " +
GregDaysPassed +
" is more than maximum value " +
JAL_MAX_GDP,
};
var d = 0;
var y = 0;
var obj = GetJalLeapSequence(GregDaysPassed);
var Rem = obj.rem;
var n = obj.seq;
if (n == 0) y = 0;
else y = n * 33 - 8;
var y_days = 0;
while (true) {
y++;
if (Jal.isJalLeapYear(y)) y_days = 366;
else y_days = 365;
if (d + y_days > Rem) break;
d += y_days;
}
RemainingDays = Rem - d;
return {
year: y,
rem: RemainingDays,
err: "",
};
},
getJalMonth: function (IsLeapYear, YearDaysPassed) {
var RemainingDays = 0;
if (YearDaysPassed > 366) return -1;
if (YearDaysPassed < 0) return -1;
var M = [];
if (IsLeapYear) M = [31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 30];
else M = [31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29];
var d = 0;
var m = 1;
while (d + M[m - 1] <= YearDaysPassed) {
d += M[m - 1];
m++;
}
RemainingDays = YearDaysPassed - d;
return {
month: m,
rem: RemainingDays,
};
},
jalToGdp: function (Year, Month, DayOfMonth) {
if (Jal.isValidJalDate(Year, Month, DayOfMonth) == false) return null;
var n = Math.floor((Year + 7) / 33);
var y = 0;
var d = 0;
var m = 1;
var M = [];
if (n == 0) {
d = 0;
y = 1;
} else {
d = 9131 + (n - 1) * 12053;
y = n * 33 - 7;
}
while (y < Year) {
if (Jal.isJalLeapYear(y)) d += 366;
else d += 365;
y++;
}
if (Jal.isJalLeapYear(y))
M = [31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 30];
else M = [31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29];
while (m < Month) {
d += M[m - 1];
m++;
}
d += DayOfMonth;
d += JAL_ORG_GDP;
return d - 1;
},
isValidJalDate: function (JalYear, JalMonth, JalDayOfMonth) {
if (JalYear < 0) return false;
if (JalYear > 9999) return false;
if (JalMonth < 0) return false;
if (JalMonth > 12) return false;
if (JalDayOfMonth < 0) return false;
if (JalDayOfMonth > 31) return false;
if (JalMonth > 6 && JalDayOfMonth > 30) return false;
var is_leap = Jal.isJalLeapYear(JalYear);
if (!is_leap && JalMonth == 12 && JalDayOfMonth > 29) return false;
if (is_leap && JalMonth == 12 && JalDayOfMonth > 30) return false;
return true;
},
}; // end of Jal
/*Lunar (Arabic) Section*/
let Lun = {
lun_week_days: [
"السبت",
"الاحد",
"الاثنین",
"الثلاثاء",
"الارباء",
"الخمیس",
"الجمعه",
],
lun_months: [
"محرم",
"صفر",
"ربیع الاول",
"ربیع الثانی",
"جمادی الاول",
"جمادی الثانی",
"رجب",
"شعبان",
"رمضان",
"شوال",
"ذی القعده",
"ذی الحجه",
],
gdpToLun: function (gdp) {
var gdp_lun_org = LUN_ORG_GDP;
var oly = 354.367; /* One Average Lunar Year measured in days */
var dph =
Math.floor(gdp) -
gdp_lun_org; /* Days Passed after Lunar Gdp upto currend gdp */
var ay = dph / oly; /*Arabic Year(float)*/
var arabic_year = Math.ceil(ay);
var rem_year =
(ay - Math.floor(ay)) *
oly; /*year remainder measured in days (float)*/
var dpm = 29.530588; /* Days Per Lunar Month */
var am = rem_year / dpm; /*arabic month (float)*/
var arabic_month = Math.ceil(am);
var rem_month = (am - Math.floor(am)) * dpm;
var arabic_day_of_month = Math.ceil(rem_month);
var rem = gdp - Math.floor(gdp);
var date_str =
fillz(arabic_year, 4) +
"/" +
fillz(arabic_month) +
"/" +
fillz(arabic_day_of_month);
return {
date_str,
arabic_year,
arabic_month,
arabic_day_of_month,
rem,
};
},
lunToGdp: function (year, month, day) {
var target_date_str =
fillz(year, 4) + "/" + fillz(month) + "/" + fillz(day);
var gs = LUN_ORG_GDP,
ge = LUN_MAX_GDP;
var g;
var finder_date_str;
var n = 0;
var hit = false;
while (gs < ge) {
g = Math.floor((gs + ge) / 2);
let gtl = Lun.gdpToLun(g);
finder_date_str = gtl["date_str"];
if (finder_date_str == target_date_str) {
hit = true;
break;
} else if (finder_date_str < target_date_str) {
gs = g;
} else {
ge = g;
}
n++;
if (n > 100) break;
}
return { gdp: g, success: hit, iterations: n };
},
daysInMonth: function (gdp, currentArabicMonth, currentDayOfMonth) {
var am, lc;
var g = gdp;
var days_in_month = currentDayOfMonth;
while (true) {
var lc = Lun.gdpToLun(g);
am = lc["arabic_month"];
if (am != currentArabicMonth) break;
days_in_month++;
g++;
}
return days_in_month;
},
monthInfo: function (year, month) {
var ltg1 = Lun.lunToGdp(year, month, 1);
if (ltg1["success"] == 0)
return { GdpStart: 0, GdpEnd: 0, DaysInYear: 0, success: 0 };
var y = year;
var m = month;
m++;
if (m == 13) {
m = 1;
y++;
}
var ltg2 = Lun.lunToGdp(y, m, 1);
if (ltg2["success"] == 0)
return { GdpStart: 0, GdpEnd: 0, DaysInYear: 0, success: 0 };
return {
GdpStart: ltg1["gdp"],
GdpEnd: ltg2["gdp"] - 1,
DaysInMonth: ltg2["gdp"] - ltg1["gdp"],
success: 1,
};
},
yearInfo: function (year) {
var ltg1 = Lun.lunToGdp(year, 1, 1);
if (ltg1["success"] == 0)
return { GdpStart: 0, GdpEnd: 0, DaysInYear: 0, success: 0 };
var ltg2 = Lun.lunToGdp(year + 1, 1, 1);
if (ltg2["success"] == 0)
return { GdpStart: 0, GdpEnd: 0, DaysInYear: 0, success: 0 };
return {
GdpStart: ltg1["gdp"],
GdpEnd: ltg2["gdp"] - 1,
DaysInYear: ltg2["gdp"] - ltg1["gdp"],
success: 1,
};
},
}; // end of Lun
function fillz(n, m = 2) {
var res = "" + n;
while (res.length < m) res = "0" + res;
return res;
}
/*Calculate 24hours time from given fraction[0.0 to 1.0]*/
function getTime(frac = 0) {
var rem = frac;
var hour24 = rem * 24;
rem = hour24 - Math.floor(hour24);
var minutes = rem * 60;
rem = minutes - Math.floor(minutes);
var seconds = rem * 60;
rem = seconds - Math.floor(seconds);
hour24 = Math.floor(hour24);
minutes = Math.floor(minutes);
seconds = Math.floor(seconds);
var time_str =
fillz(hour24) + ":" + fillz(minutes) + ":" + fillz(seconds);
return { time_str, hour24, minutes, seconds, rem };
}
/*Provides GDP from given date info and calendar type*/
let getGdp = function (year, month, dayOfMonth, calendarType) {
var gdp = 0;
var success = false;
var cp = calendarType.toLowerCase();
if (cp == "g" || cp == "greg") {
gdp = Greg.gregToGdp(year, month, dayOfMonth);
success = Greg.isValidDate(year, month, dayOfMonth);
} else if (cp == "j" || cp == "jal") {
gdp = Jal.jalToGdp(year, month, dayOfMonth);
success = Jal.isValidJalDate(year, month, dayOfMonth);
} else if (cp == "l" || cp == "lun") {
var res = Lun.lunToGdp(year, month, dayOfMonth);
gdp = res["gdp"];
success = res["success"];
gdp = res["gdp"];
}
return { gdp, success };
};
/*Gets date Parts comprising year, month and day of month*/
let getDateParts = function (dateStr) {
var y = 0;
var m = 0;
var d = 0;
try {
var parts = dateStr.trim().split("/");
if (parts.length != 3) return null;
y = parseInt(parts[0]);
m = parseInt(parts[1]);
d = parseInt(parts[2]);
return {
year: y,
month: m,
day: d,
};
} catch (e) {
return null;
}
};
/*Provides GDP from given date info and calendar type*/
let getGdps = function (dateStr, calendarType) {
var year, month, dayOfMonth;
var res = getDateParts(dateStr);
if (res == null)
return { gdp: 0, success: 0, err: "bad date string: " + dateStr };
year = res["year"];
month = res["month"];
dayOfMonth = res["day"];
return getGdp(year, month, dayOfMonth, calendarType);
};
/*Checks if the given Date is valid*/
let isValid = function (year, month, dayOfMonth, calendarType = "g") {
var res = getGdp(year, month, dayOfMonth, calendarType);
return res["success"];
};
/*Checks if the given Date is valid*/
let isValids = function (dateStr, calendarType = "g") {
var res = getDateParts(dateStr);
if (res == null)
return { gdp: 0, success: 0, err: "bad date string: " + dateStr };
var year = res["year"];
var month = res["month"];
var dayOfMonth = res["day"];
return isValid(year, month, dayOfMonth, calendarType);
};
/*Gets GDP from client date and time*/
let getGdpClientSide = function () {
var year, month, dayOfMonth;
var d = new Date();
year = d.getFullYear();
month = d.getMonth() + 1;
dayOfMonth = d.getDate();
return getGdp(year, month, dayOfMonth, "g");
};
/*Provides Date Info from given GDP and calendar type*/
let getDateInfo = function (gdp, calendarType = "g") {
if (calendarType == null) return;
calendarType = calendarType.toLowerCase();
// Jalalian Calendar
if (calendarType == "j" || calendarType == "jal") {
// ---
var err = "";
var jal_year = 0;
var jal_month = 0;
var jal_day_of_month = 0;
var jal_year_result = Jal.getJalYear(gdp);
jal_year = jal_year_result["year"];
var rem = jal_year_result["rem"];
if (jal_year_result["err"] != "") err += jal_year_result["err"] + " ";
var is_leap = Jal.isJalLeapYear(jal_year);
var jal_month_result = Jal.getJalMonth(is_leap, rem);
jal_month = jal_month_result["month"];
jal_day_of_month = Math.floor(jal_month_result["rem"]) + 1;
var date_str = "";
date_str += fillz(jal_year, 4) + "/";
date_str += fillz(jal_month) + "/";
date_str += fillz(jal_day_of_month);
var day_of_week_number = (Math.floor(gdp) + 2) % 7;
var day_title = Jal.week_days[day_of_week_number];
var day_title_abbr = Jal.week_days_abbr[day_of_week_number];
var month_title = Jal.month_titles[jal_month - 1];
var full_date_str = "";
full_date_str += day_title + " ";
full_date_str += jal_day_of_month + " ";
full_date_str += month_title + " ";
full_date_str += jal_year;
var days_in_year = 365;
if (is_leap) days_in_year++;
var days_in_month = 31;
if (7 <= jal_month && jal_month <= 11) days_in_month = 30;
if (jal_month == 12) {
if (is_leap) days_in_month = 30;
else days_in_month = 29;
}
rem = jal_month_result["rem"] - Math.floor(jal_month_result["rem"]);
var tr = getTime(rem);
var hour24 = tr["hour24"],
minutes = tr["minutes"],
seconds = tr["seconds"];
rem = tr["rem"];
var time_str = tr["time_str"];
return {
DateString: date_str,
Year: jal_year,
Month: jal_month,
DayOfMonth: jal_day_of_month,
DayOfWeek: day_of_week_number,
DayTitle: day_title,
MonthTitle: month_title,
DayTitleAbbr: day_title_abbr,
DaysInYear: days_in_year,
DaysInMonth: days_in_month,
Hours: hour24,
Minutes: minutes,
Seconds: seconds,
Rem: rem,
TimeString: time_str,
Gdp: gdp,
CalendarType: "jal",
Err: err,
};
// ---
} // end of jal calendar
else if (calendarType == "g" || calendarType == "greg") {
var err = "";
var res = Greg.getGregYear(Math.floor(gdp));
var Rem = res["RemainingDays"];
var Y = res["GregYear"];
var ress = Greg.getGregMonth(Greg.isGregLeapYear(Y), Rem);
var M = ress["GregMonth"];
Rem = ress["RemainingDays"];
var D = Rem + 1;
var date_str = "";
date_str += fillz(Y, 4) + "/";
date_str += fillz(M) + "/";
date_str += fillz(D);
var day_of_week_number = (Math.floor(gdp) + 2) % 7;
var day_title = Greg.greg_week_days[day_of_week_number];
var day_title_abbr = Greg.greg_week_days_abbr[day_of_week_number];
var month_title = Greg.greg_months_abbr[M - 1];
var full_date_str = "";
full_date_str += day_title + ", ";
full_date_str += month_title + ", ";
full_date_str += D + ", ";
full_date_str += Y;
var rem = gdp - Math.floor(gdp);
var tr = getTime(rem);
var hour24 = tr["hour24"],
minutes = tr["minutes"],
seconds = tr["seconds"];
rem = tr["rem"];
var time_str = tr["time_str"];
var days_in_year = Greg.daysInYear(Y);
var days_in_month = Greg.daysInMonth(Y, M);
if (gdp < GREG_ORG_GDP)
err = "GDP " + gdp + " is less than minimum value " + GREG_ORG_GDP;
if (gdp > GREG_MAX_GDP)
err = "GDP " + gdp + " is more than maximum value " + GREG_MAX_GDP;
return {
DateString: date_str,
FullDateString: full_date_str,
Year: Y,
Month: M,
DayOfMonth: D,
DayOfWeek: day_of_week_number,
DayTitle: day_title,
MonthTitle: month_title,
DayTitleAbbr: day_title_abbr,
DaysInYear: days_in_year,
DaysInMonth: days_in_month,
Hours: hour24,
Minutes: minutes,
Seconds: seconds,
Rem: rem,
TimeString: time_str,
Gdp: gdp,
CalendarType: "greg",
Err: err,
};
} // end of greg calendar
else if (calendarType == "l" || calendarType == "lun") {
var err = "";
var fr = Lun.gdpToLun(gdp);
var arabic_year = fr["arabic_year"];
var arabic_month = fr["arabic_month"];
var arabic_day_of_month = fr["arabic_day_of_month"];
var date_str = fr["date_str"];
var day_of_week_number = (Math.floor(gdp) + 2) % 7;
var days_in_month = Lun.daysInMonth(
gdp,
arabic_month,
arabic_day_of_month
);
var rem = fr["rem"];
var tr = getTime(rem);
var hour24 = tr["hour24"],
minutes = tr["minutes"],
seconds = tr["seconds"];
rem = tr["rem"];
var time_str = tr["time_str"];
var year_info = Lun.yearInfo(arabic_year);
var days_in_year = year_info["DaysInYear"];
var month_info = Lun.monthInfo(arabic_year, arabic_month);
var day_title = Lun.lun_week_days[day_of_week_number];
var month_title = Lun.lun_months[arabic_month - 1];
if (gdp < LUN_ORG_GDP)
err = "GDP " + gdp + " is less than minimum value " + LUN_ORG_GDP;
if (gdp > LUN_MAX_GDP)
err = "GDP " + gdp + " is more than maximum value " + LUN_MAX_GDP;
return {
DateString: date_str,
Year: arabic_year,
Month: arabic_month,
DayOfMonth: arabic_day_of_month,
DayOfWeek: day_of_week_number,
DayTitle: day_title,
MonthTitle: month_title,
DayTitleAbbr: day_title,
DaysInYear: days_in_year,
DaysInMonth: days_in_month,
Hours: hour24,
Minutes: minutes,
Seconds: seconds,
Rem: rem,
TimeString: time_str,
Gdp: gdp,
CalendarType: "lun",
Err: err,
YearInfo: year_info,
MonthInfo: month_info,
};
} // end of lunar calendar
else {
return {
DateString: "",
Year: 0,
Month: 0,
DayOfMonth: 0,
DayOfWeek: 0,
DayTitle: "",
MonthTitle: "",
DayTitleAbbr: "",
DaysInYear: 0,
DaysInMonth: 0,
Hours: 0,
Minutes: 0,
Seconds: 0,
Rem: 0,
TimeString: "",
Gdp: 0,
CalendarType: "",
Err: "Unknown calendar type. Use greg, jal, or lun",
};
}
};
/*Converts a given date from source calendar type to target*/
let convert = function (
year,
month,
dayOfMonth,
sourceCalendarType = "g",
targetCalendarType = "j"
) {
var res = getGdp(year, month, dayOfMonth, sourceCalendarType);
if (res["success"] == false) return { err: "invalid date" };
return getDateInfo(res["gdp"], targetCalendarType);
};
/*Converts a given date from source calendar type to target*/
let converts = function (
dateStr,
sourceCalendarType = "g",
targetCalendarType = "j"
) {
var res = getDateParts(dateStr);
if (res == null) return { err: "bad date string" };
var year = res["year"];
var month = res["month"];
var dayOfMonth = res["day"];
return convert(
year,
month,
dayOfMonth,
sourceCalendarType,
targetCalendarType
);
};
/*Provides Date String from given GDP and calendar type*/
let getDateStr = function (gdp, calendarType = "g") {
var di = getDateInfo(gdp, calendarType);
return di["DateString"];
};
/*Calculates days between two dates*/
let getDateDiff = function (dateStrFrom, dateStrTo, calendarType = "g") {
if (!isValids(dateStrFrom, calendarType)) return 0;
if (!isValids(dateStrTo, calendarType)) return 0;
var g_from = getGdps(dateStrFrom, calendarType)["gdp"];
var g_to = getGdps(dateStrTo, calendarType)["gdp"];
return g_to - g_from;
};
/*Adds given days to supplied date*/
let addDays = function (dateStr, days, calendarType = "g") {
if (!isValids(dateStr, calendarType)) return "";
var g = getGdps(dateStr, calendarType)["gdp"];
return getDateStr(g + days, calendarType);
};
/*Gets client side date string*/
let getDateStrClientSide = function (calendarType = "g") {
var g = getGdpClientSide()["gdp"];
return getDateStr(g, calendarType);
};
return {
convert,
converts,
};
}
function present(txtTimelineDoc) {
checkCanvasCoordinates(txtTimelineDoc || "");
updateGui(txtTimelineDoc || "");
}
function help() {
return `
1- consider a container element like a div with a given id like divTimeline
<div id="divTimeline"></div>
2- define your timeline variable
const myTimeline = prsTimeline("divTimeline");
3- provide a document for the timeline
Example Document:
--size 800, 400
--margin-left 20
--font-size 12
--font-family tahoma
--event-width 12
--event-col-width 50
--hor-line-show 1
--hor-line-clr #00000010
--hor-line-gap 20
--hor-line-label 1
--reuse-event-line 1
--------------------
@(2000,current)
@T1(1380j)
@T2(1381j, 1388j)
@T3(2012, 2020){clr: red, sp:20, label:5}
4- Present the document:
myTimeline.present(doc)
Date Prefixes:
h: postfix for Lunar Arabic Calendar
j: postfix for Jalali Persian Calendar
<no postfix>: gregorian Calendar
`;
}
function prsTimelineInit(idHolder) {
if (prsMultiCalendar == null) {
prsMultiCalendar = MultiCalendar();
}
if (objCanvas == null) {
objCanvas = document.createElement("canvas");
let holder = gei(idHolder);
if (holder == null) {
console.log(`Holder with id ${idHolder} is missing!`);
return;
}