UNPKG

prs-utils

Version:

Prs Utilities

1,721 lines (1,560 loc) 50.5 kB
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; }