1 2function convertLDTElements(config={}){const{selector='.ldt',sourceTZ='America/New_York',highlightClass='ldt-converted',injectStyle=true}=config;const LOCAL_TZ=window.location.hostname.includes('duck.')?'America/Los_Angeles':Intl.DateTimeFormat().resolvedOptions().timeZone;const SAME_TZ_TOOLTIP='In US Eastern Timezone';const cssEscape=(window.CSS&&CSS.escape)?CSS.escape:(s)=>String(s).replace(/[^a-zA-Z0-9_-]/g,'\\$&');const MONTHS={jan:1,january:1,feb:2,february:2,mar:3,march:3,apr:4,april:4,may:5,jun:6,june:6,jul:7,july:7,aug:8,august:8,sep:9,sept:9,september:9,oct:10,october:10,nov:11,november:11,dec:12,december:12};const DATE_RE=new RegExp(String.raw`\b(?:`+ 3String.raw`(?<yA>\d{4})(?<sepA>[\/-])(?<mA>\d{1,2})\k<sepA>(?<dA>\d{1,2})(?:[ T]+(?<hA>\d{1,2}):(?<minA>\d{2})(?::(?<secA>\d{2}))?(?:\s?(?<ampmA>[AP]M))?)?`+ 4String.raw`|`+ 5String.raw`(?<mB>\d{1,2})(?<sepA>[\/-])(?<dB>\d{1,2})\k<sepA>(?<yB>\d{4})(?:[ T]+(?<hB>\d{1,2}):(?<minB>\d{2})(?:\s?(?<ampmB>[AP]M))?)?`+ 6String.raw`|`+ 7String.raw`(?<monC>Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|Jun(?:e)?|Jul(?:y)?|Aug(?:ust)?|Sep(?:t(?:ember)?)?|Oct(?:ober)?|Nov(?:ember)?)\s+(?<dC>\d{1,2}),\s*(?<yC>\d{4})(?:[ T]+(?<hC>\d{1,2}):(?<minC>\d{2})(?:\s?(?<ampmC>[AP]M))?)?`+ 8String.raw`|`+ 9String.raw`(?<monC2>Dec(?:ember)?)\s+(?<dC2>\d{1,2}),\s*(?<yC2>\d{4})(?:[ T]+(?<hC2>\d{1,2}):(?<minC2>\d{2})(?:\s?(?<ampmC2>[AP]M))?)?`+ 10String.raw`)\b`,'gi');if(injectStyle)ensureHighlightStyle(highlightClass);const candidates=Array.from(document.querySelectorAll(`${selector}:not([data-ldt-processed])`));const roots=candidates.filter(el=>!(el.parentElement&&el.parentElement.closest(selector)));roots.forEach(root=>{const walker=document.createTreeWalker(root,NodeFilter.SHOW_TEXT,{acceptNode(node){const txt=node.nodeValue||'';if(!/\S/.test(txt))return NodeFilter.FILTER_REJECT;const p=node.parentElement;if(p&&p.closest(`.${cssEscape(highlightClass)}`)){return NodeFilter.FILTER_REJECT;} 11return NodeFilter.FILTER_ACCEPT;}});const toProcess=[];for(let n=walker.nextNode();n;n=walker.nextNode())toProcess.push(n);if(LOCAL_TZ===sourceTZ){toProcess.forEach(n=>annotateSameTZ(n,root));}else{toProcess.forEach(n=>processTextNode(n,root));} 12root.setAttribute('data-ldt-processed','1');root.querySelectorAll(selector).forEach(el=>el.setAttribute('data-ldt-processed','1'));});function annotateSameTZ(textNode,root){const text=textNode.nodeValue;const re=DATE_RE;re.lastIndex=0;let match,last=0;let changed=false;const frag=document.createDocumentFragment();const parentEl=textNode.parentElement;const zoAncestor=parentEl?parentEl.closest('.zo'):null;const xoAncestor=parentEl?parentEl.closest('.xo'):null;const inZo=!!(zoAncestor&&root.contains(zoAncestor));const inXo=!!(xoAncestor&&root.contains(xoAncestor));while((match=re.exec(text))!==null){const groups=match.groups||{};const start=match.index;const end=re.lastIndex;if(start>last){frag.appendChild(document.createTextNode(text.slice(last,start)));} 13const parsed=parseGroups(groups,inZo);let tzAbbr='';if(parsed){const{y,m,d,hh,mm,ss,hasTime}=parsed;if(!hasTime&&inXo){frag.appendChild(document.createTextNode(match[0]));last=end;continue;} 14const instant=wallTimeInZoneToUTC(y,m,d,hh,mm,ss,sourceTZ);tzAbbr=getTZAbbr(instant,LOCAL_TZ);}else{tzAbbr=LOCAL_TZ;} 15const rawText=match[0];const span=document.createElement('span');span.className=highlightClass;span.style.cursor='auto';span.textContent=`${rawText} ${tzAbbr}`;frag.appendChild(span);changed=true;last=end;} 16if(!changed)return;if(last<text.length){frag.appendChild(document.createTextNode(text.slice(last)));} 17if(textNode.parentNode){textNode.parentNode.replaceChild(frag,textNode);}} 18function processTextNode(textNode,root){const text=textNode.nodeValue;const re=DATE_RE;re.lastIndex=0;let match,last=0;let changed=false;const frag=document.createDocumentFragment();const parentEl=textNode.parentElement;const zoAncestor=parentEl?parentEl.closest('.zo'):null;const xoAncestor=parentEl?parentEl.closest('.xo'):null;const inZo=!!(zoAncestor&&root.contains(zoAncestor));const inXo=!!(xoAncestor&&root.contains(xoAncestor));while((match=re.exec(text))!==null){const groups=match.groups||{};const start=match.index;const end=re.lastIndex;if(start>last){frag.appendChild(document.createTextNode(text.slice(last,start)));} 19const parsed=parseGroups(groups,inZo);if(!parsed){frag.appendChild(document.createTextNode(match[0]));last=end;continue;} 20const{y,m,d,hh,mm,ss,hasTime}=parsed;if(!hasTime&&inXo){frag.appendChild(document.createTextNode(match[0]));last=end;continue;} 21const sourceInstant=wallTimeInZoneToUTC(y,m,d,hh,mm,ss,sourceTZ);const localText=formatOutInZone(sourceInstant,LOCAL_TZ);const rawText=match[0];const tooltip=`Time in your local timezone ${LOCAL_TZ}.\n${rawText} ${SAME_TZ_TOOLTIP}`;const span=document.createElement('span');span.className=highlightClass;span.textContent=localText;span.setAttribute('title',tooltip);span.setAttribute('aria-label',tooltip);span.setAttribute('data-source-tz',sourceTZ);span.setAttribute('data-raw',rawText);frag.appendChild(span);changed=true;last=end;} 22if(!changed)return;if(last<text.length){frag.appendChild(document.createTextNode(text.slice(last)));} 23if(textNode.parentNode){textNode.parentNode.replaceChild(frag,textNode);}} 24function parseGroups(g,inZo){let y,m,d,hh=0,mm=0,ss=0,hasTime=false;if(g.yA){y=+g.yA;m=+g.mA;d=+g.dA;if(g.hA&&g.minA){if(g.ampmA){const raw=+g.hA;if(raw<1||raw>12)return null;[hh,mm]=to24h(raw,+g.minA,g.ampmA);}else{hh=+g.hA;mm=+g.minA;} 25ss=g.secA?+g.secA:0;hasTime=true;}}else if(g.yB){y=+g.yB;m=+g.mB;d=+g.dB;if(g.hB&&g.minB){if(g.ampmB){const raw=+g.hB;if(raw<1||raw>12)return null;[hh,mm]=to24h(raw,+g.minB,g.ampmB);}else{hh=+g.hB;mm=+g.minB;} 26hasTime=true;}}else if(g.yC||g.yC2){const mon=(g.monC||g.monC2).toLowerCase();y=+(g.yC||g.yC2);m=MONTHS[mon];d=+(g.dC||g.dC2);const hStr=g.hC||g.hC2;const mStr=g.minC||g.minC2;const ap=g.ampmC||g.ampmC2;if(hStr&&mStr){if(ap){const raw=+hStr;if(raw<1||raw>12)return null;[hh,mm]=to24h(raw,+mStr,ap);}else{hh=+hStr;mm=+mStr;} 27hasTime=true;}}else{return null;} 28if(!hasTime){if(inZo){hh=0;mm=0;ss=0;}else{hh=23;mm=59;ss=0;}} 29if(m<1||m>12||d<1||d>31||hh<0||hh>23||mm<0||mm>59||ss<0||ss>59){return null;} 30return{y,m,d,hh,mm,ss,hasTime};} 31function to24h(h,m,ampm){const up=String(ampm).toUpperCase();let hour=h%12;if(up==='PM')hour+=12;return[hour,m];} 32function getOffsetMinutes(epochMs,timeZone){const dt=new Date(epochMs);try{const parts=new Intl.DateTimeFormat('en-US',{timeZone,timeZoneName:'shortOffset'}).formatToParts(dt);const tzn=parts.find(p=>p.type==='timeZoneName')?.value||'';const m=tzn.match(/GMT([+-]\d{1,2})(?::?(\d{2}))?/i);if(m){const sign=m[1].startsWith('-')?-1:1;const hours=Math.abs(parseInt(m[1],10));const minutes=m[2]?parseInt(m[2],10):0;return sign*(hours*60+minutes);}}catch{} 33try{const parts2=new Intl.DateTimeFormat('en-US',{timeZone,timeZoneName:'short'}).formatToParts(dt);const abbr=parts2.find(p=>p.type==='timeZoneName')?.value||'';if(/^EST$/i.test(abbr))return-300;if(/^EDT$/i.test(abbr))return-240;}catch{} 34return 0;} 35function wallTimeInZoneToUTC(y,m,d,hh,mm,ss,timeZone){let guess=Date.UTC(y,(m-1),d,hh,mm,ss);for(let i=0;i<3;i++){const offsetMin=getOffsetMinutes(guess,timeZone);const next=Date.UTC(y,(m-1),d,hh,mm,ss)-offsetMin*60000;if(Math.abs(next-guess)<1000){guess=next;break;} 36guess=next;} 37return new Date(guess);}
38function getTZAbbr(date,tz){try{const parts=new Intl.DateTimeFormat('en-US',{timeZone:tz,hour:'2-digit',minute:'2-digit',hour12:true,timeZoneName:'short'}).formatToParts(date);const tzn=parts.find(p=>p.type==='timeZoneName')?.value;if(tzn&&!tzn.match(/^GMT/))return tzn.replace(/\s+/g,'');}catch{} 39try{const parts=new Intl.DateTimeFormat('en-US',{timeZone:tz,hour:'2-digit',minute:'2-digit',hour12:true,timeZoneName:'shortGeneric'}).formatToParts(date);const tzn=parts.find(p=>p.type==='timeZoneName')?.value;if(tzn)return tzn.replace(/\s+/g,'');}catch{} 40return tz;} 41function formatOutInZone(date,tz){const dparts=new Intl.DateTimeFormat('en-CA',{timeZone:tz,year:'numeric',month:'2-digit',day:'2-digit'}).formatToParts(date);const Y=dparts.find(p=>p.type==='year')?.value||'0000';const M=dparts.find(p=>p.type==='month')?.value||'01';const D=dparts.find(p=>p.type==='day')?.value||'01';const tparts=new Intl.DateTimeFormat('en-US',{timeZone:tz,hour:'2-digit',minute:'2-digit',hour12:true}).formatToParts(date);const hh=(tparts.find(p=>p.type==='hour')?.value||'12').padStart(2,'0');const mm=(tparts.find(p=>p.type==='minute')?.value||'00');const ampm=(tparts.find(p=>p.type==='dayPeriod')?.value||'AM').toUpperCase();const tzAbbr=getTZAbbr(date,tz);return`${Y}/${M}/${D} ${hh}:${mm} ${ampm} ${tzAbbr}`;} 42function ensureHighlightStyle(cls){if(document.getElementById('ldt-converted-style'))return;const style=document.createElement('style');style.id='ldt-converted-style';style.textContent=` 43 .${cls} { 44 color: purple; /* lighter green #006600;*/ 45 cursor: help; /* indicates tooltip */ 46 text-decoration: none; 47 } 48 `;document.head.appendChild(style);}}
49convertLDTElements({selector:'.ldt',sourceTZ:'America/New_York',highlightClass:'ldt-converted',injectStyle:true});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.