{"id":2232,"date":"2026-04-06T11:30:09","date_gmt":"2026-04-06T11:30:09","guid":{"rendered":"https:\/\/primetoolhub.com\/?page_id=2232"},"modified":"2026-08-08T13:39:09","modified_gmt":"2026-08-08T13:39:09","slug":"unix-epoch-time-converter","status":"publish","type":"page","link":"https:\/\/schoolict.net\/tools\/unix-epoch-time-converter\/","title":{"rendered":"Free Unix Epoch Time Converter &amp; Timestamp Studio &#8211; Offline Tool"},"content":{"rendered":"<div class=\"pth-hero-section\">\n<div class=\"pth-hero-content\">\n<h2><b data-path-to-node=\"1,0,0\" data-index-in-node=\"0\">Instant Unix Timestamp Converter &amp; Studio<\/b> \u2014 Fast, Accurate Epoch Conversions (100% Offline)<\/h2>\n<p>Convert Unix Epoch timestamps to human-readable dates and vice versa with our 100% offline Unix Epoch Time Converter. Features a live epoch clock and UTC support.<\/p>\n<div id=\"pth-toc-placeholder\">\u00a0<\/div>\n<\/div>\n<div class=\"pth-hero-image\"><img decoding=\"async\" src=\"https:\/\/schoolict.net\/tools\/wp-content\/uploads\/2026\/04\/Unix-Epoch-Time-Converter-1024x572.jpeg\" alt=\"Unix Epoch Time Converter\" width=\"450\" height=\"253\" data-no-lazy=\"1\" \/><\/div>\n<\/div>\n\n\n<div class=\"wp-block-rank-math-toc-block\" id=\"rank-math-toc\"><h2>Table of Contents<\/h2><nav><ul><li><a href=\"#\ud83d\udfe2-more-than-epoch-in-date-out\">\ud83d\udfe2 More Than Epoch In, Date Out<\/a><\/li><li><a href=\"#\ud83d\udfe1-the-formats-and-where-you-meet-them\">\ud83d\udfe1 The Formats, And Where You Meet Them<\/a><\/li><li><a href=\"#\ud83d\udd34-the-warning-that-saves-a-migration\">\ud83d\udd34 The Warning That Saves A Migration<\/a><\/li><li><a href=\"#\ud83d\udfe2-using-the-meeting-planner\">\ud83d\udfe2 Using The Meeting Planner<\/a><\/li><li><a href=\"#\ud83d\udfe1-two-jobs-worth-knowing\">\ud83d\udfe1 Two Jobs Worth Knowing<\/a><ul><li><a href=\"#making-sense-of-a-log-file\">Making sense of a log file<\/a><\/li><li><a href=\"#working-out-when-something-actually-expires\">Working out when something actually expires<\/a><\/li><\/ul><\/li><li><a href=\"#\ud83d\udd34-common-mistakes-and-real-limits\">\ud83d\udd34 Common Mistakes and Real Limits<\/a><\/li><\/ul><\/nav><\/div>\n\n\n<style>\r\n\/* STRICTLY SCOPED CSS - PTH EPOCH & TIME STUDIO *\/\r\n#pth-epoch-wrapper {\r\n  --blu: #2563eb; --grn: #047857; --red: #dc2626; --amb: #b45309; --pur: #6d28d9;\r\n  --bg1: #ffffff; --bg2: #f8fafc; --bg3: #f1f5f9;\r\n  --bd1: #cbd5e1; --bd2: #e2e8f0;\r\n  --tx1: #0f172a; --tx2: #475569;\r\n  font-family: 'Inter', system-ui, sans-serif;\r\n  max-width: 1400px; margin: 0 auto; box-sizing: border-box;\r\n  background: var(--bg3); padding: 20px; border-radius: 16px; color: var(--tx1); opacity: 1;\r\n}\r\n#pth-epoch-wrapper * { box-sizing: border-box; min-width: 0; }\r\n\r\n#pth-epoch-wrapper .pth-intro { background: var(--bg1); border: 1px solid var(--bd2); border-radius: 12px; padding: 22px; margin-bottom: 18px; }\r\n#pth-epoch-wrapper .pth-tool-title { font-size: 27px; font-weight: 800; color: var(--blu); margin: 0 0 10px 0; line-height: 1.25; }\r\n#pth-epoch-wrapper .pth-intro p { font-size: 14px; color: var(--tx2); margin: 0; line-height: 1.65; }\r\n#pth-epoch-wrapper .pth-badges { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 12px; }\r\n#pth-epoch-wrapper .pth-badge-top { font-size: 12px; font-weight: 600; padding: 4px 12px; border-radius: 20px; }\r\n#pth-epoch-wrapper .pth-version-badge { background: #eff6ff; color: #1d4ed8; border: 1px solid #bfdbfe; }\r\n#pth-epoch-wrapper .pth-status-offline { background: #ecfdf5; color: #065f46; border: 1px solid #a7f3d0; }\r\n#pth-epoch-wrapper .pth-badge-ora { background: #fff7ed; color: #9a3412; border: 1px solid #fed7aa; }\r\n\r\n#pth-epoch-wrapper .pth-actions-bar { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 16px; align-items: center; }\r\n#pth-epoch-wrapper .layout { display: grid; grid-template-columns: 1.8fr 1fr; gap: 16px; align-items: start; }\r\n#pth-epoch-wrapper .layout.fw { grid-template-columns: 1fr; }\r\n\r\n#pth-epoch-wrapper .card { background: var(--bg1); border: 1px solid var(--bd2); border-radius: 12px; overflow: hidden; }\r\n#pth-epoch-wrapper .card-pad { padding: 16px; }\r\n#pth-epoch-wrapper .hdr { display: flex; align-items: center; justify-content: space-between; padding: 10px 14px; border-bottom: 1px solid var(--bd2); background: var(--bg2); flex-wrap: wrap; gap: 8px; }\r\n#pth-epoch-wrapper .hdr-title { font-size: 15px; font-weight: 700; color: var(--tx1); margin: 0; }\r\n#pth-epoch-wrapper label { font-size: 12px; color: var(--tx2); font-weight: 600; }\r\n\r\n#pth-epoch-wrapper .btn { padding: 8px 14px; border-radius: 8px; font-size: 13px; font-weight: 600; cursor: pointer; border: 1px solid var(--bd1); transition: background .15s; display: inline-flex; align-items: center; gap: 6px; font-family: inherit; background: var(--bg1); color: var(--tx1); }\r\n#pth-epoch-wrapper .btn:hover { background: var(--bg2); }\r\n#pth-epoch-wrapper .btn-primary { background: var(--blu); color: #fff; border-color: var(--blu); }\r\n#pth-epoch-wrapper .btn-primary:hover { background: #1d4ed8; }\r\n#pth-epoch-wrapper .btn-grn { background: var(--grn); color: #fff; border-color: var(--grn); }\r\n#pth-epoch-wrapper .btn-red { background: var(--red); color: #fff; border-color: var(--red); }\r\n#pth-epoch-wrapper .btn-sm { padding: 5px 10px; font-size: 12px; }\r\n\r\n#pth-epoch-wrapper .tabs { display: flex; gap: 4px; padding: 8px 8px 0; background: var(--bg2); border-bottom: 1px solid var(--bd2); flex-wrap: wrap; }\r\n#pth-epoch-wrapper .tab { padding: 9px 15px; border-radius: 8px 8px 0 0; border: 1px solid transparent; background: transparent; color: var(--tx2); font-size: 13px; font-weight: 700; cursor: pointer; font-family: inherit; }\r\n#pth-epoch-wrapper .tab:hover { background: var(--bg3); }\r\n#pth-epoch-wrapper .tab.on { background: var(--bg1); color: var(--blu); border-color: var(--bd2); border-bottom-color: var(--bg1); margin-bottom: -1px; }\r\n#pth-epoch-wrapper .panel { display: none; }\r\n#pth-epoch-wrapper .panel.on { display: block; }\r\n\r\n#pth-epoch-wrapper .sect { border-top: 1px solid var(--bd2); padding-top: 14px; margin-top: 14px; }\r\n#pth-epoch-wrapper .sect:first-child { border-top: none; padding-top: 0; margin-top: 0; }\r\n#pth-epoch-wrapper .sect h4 { font-size: 12px; font-weight: 800; color: var(--tx2); margin: 0 0 10px; text-transform: uppercase; letter-spacing: .5px; }\r\n#pth-epoch-wrapper .grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }\r\n#pth-epoch-wrapper .grid3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }\r\n#pth-epoch-wrapper .fld { margin-bottom: 11px; }\r\n#pth-epoch-wrapper .fld label { display: block; margin-bottom: 4px; }\r\n#pth-epoch-wrapper .row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; margin-bottom: 11px; }\r\n\r\n#pth-epoch-wrapper input[type=\"text\"], #pth-epoch-wrapper input[type=\"number\"], #pth-epoch-wrapper input[type=\"date\"],\r\n#pth-epoch-wrapper input[type=\"time\"], #pth-epoch-wrapper select, #pth-epoch-wrapper textarea {\r\n  width: 100%; padding: 8px 10px; border: 1px solid var(--bd1); border-radius: 7px; font-size: 13px; font-family: inherit; color: var(--tx1); background: var(--bg1);\r\n}\r\n#pth-epoch-wrapper textarea { resize: vertical; font-family: 'Courier New', monospace; min-height: 120px; }\r\n#pth-epoch-wrapper input.big { font-family: 'Courier New', monospace; font-size: 17px; font-weight: 700; padding: 11px 12px; }\r\n#pth-epoch-wrapper input[type=\"checkbox\"] { width: auto; }\r\n#pth-epoch-wrapper .chk { display: flex; align-items: center; gap: 7px; font-size: 13px; color: var(--tx1); font-weight: 600; cursor: pointer; }\r\n\r\n#pth-epoch-wrapper .fmt { display: flex; align-items: center; gap: 9px; padding: 9px 11px; border-bottom: 1px solid var(--bd2); }\r\n#pth-epoch-wrapper .fmt:last-child { border-bottom: none; }\r\n#pth-epoch-wrapper .fmt .n { font-size: 11px; font-weight: 800; color: var(--tx2); width: 128px; flex-shrink: 0; text-transform: uppercase; letter-spacing: .3px; }\r\n#pth-epoch-wrapper .fmt .v { flex: 1; font-family: 'Courier New', monospace; font-size: 13px; color: var(--tx1); font-weight: 600; word-break: break-all; }\r\n#pth-epoch-wrapper .fmt button { border: 1px solid var(--bd1); background: var(--bg1); border-radius: 6px; padding: 3px 9px; font-size: 11px; cursor: pointer; font-family: inherit; color: var(--tx2); font-weight: 700; flex-shrink: 0; }\r\n#pth-epoch-wrapper .fmt button:hover { background: var(--bg2); }\r\n#pth-epoch-wrapper .fmtbox { border: 1px solid var(--bd2); border-radius: 9px; overflow: hidden; }\r\n\r\n#pth-epoch-wrapper .rel { background: #eff6ff; border: 1px solid #bfdbfe; border-radius: 9px; padding: 13px; text-align: center; margin-bottom: 12px; }\r\n#pth-epoch-wrapper .rel b { display: block; font-size: 19px; color: #1d4ed8; font-weight: 800; }\r\n#pth-epoch-wrapper .rel span { font-size: 12px; color: var(--tx2); font-weight: 600; }\r\n\r\n#pth-epoch-wrapper .zone { display: flex; align-items: center; gap: 9px; padding: 10px 11px; border-bottom: 1px solid var(--bd2); }\r\n#pth-epoch-wrapper .zone:last-child { border-bottom: none; }\r\n#pth-epoch-wrapper .zone .zn { flex: 1; min-width: 0; }\r\n#pth-epoch-wrapper .zone .zn b { display: block; font-size: 13px; color: var(--tx1); font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\r\n#pth-epoch-wrapper .zone .zn span { font-size: 11px; color: var(--tx2); font-weight: 600; }\r\n#pth-epoch-wrapper .zone .zt { font-family: 'Courier New', monospace; font-size: 15px; font-weight: 700; color: var(--blu); flex-shrink: 0; }\r\n#pth-epoch-wrapper .zone .zd { font-size: 10px; font-weight: 800; border-radius: 5px; padding: 2px 6px; flex-shrink: 0; }\r\n#pth-epoch-wrapper .dst-on { background: #fef3c7; color: #92400e; }\r\n#pth-epoch-wrapper .dst-off { background: var(--bg3); color: var(--tx2); }\r\n#pth-epoch-wrapper .zone button { border: none; background: transparent; color: var(--red); cursor: pointer; font-size: 15px; font-weight: 800; padding: 0 4px; }\r\n\r\n#pth-epoch-wrapper .plan { overflow-x: auto; }\r\n#pth-epoch-wrapper .ptbl { border-collapse: collapse; font-size: 11px; width: 100%; }\r\n#pth-epoch-wrapper .ptbl th, #pth-epoch-wrapper .ptbl td { border: 1px solid var(--bd2); padding: 4px 3px; text-align: center; color: var(--tx1); }\r\n#pth-epoch-wrapper .ptbl th { background: var(--bg2); font-weight: 700; }\r\n#pth-epoch-wrapper .ptbl th.zh { text-align: left; padding-left: 8px; min-width: 130px; }\r\n#pth-epoch-wrapper .ptbl td.zl { text-align: left; padding-left: 8px; font-weight: 700; font-size: 12px; }\r\n#pth-epoch-wrapper .ptbl td.work { background: #d1fae5; color: #065f46; font-weight: 700; }\r\n#pth-epoch-wrapper .ptbl td.edge { background: #fef3c7; color: #92400e; }\r\n#pth-epoch-wrapper .ptbl td.off { background: var(--bg3); color: #94a3b8; }\r\n#pth-epoch-wrapper .ptbl td.all { background: #047857; color: #fff; font-weight: 800; }\r\n\r\n#pth-epoch-wrapper .res { background: var(--bg2); border: 1px solid var(--bd2); border-radius: 9px; padding: 14px; margin-top: 12px; }\r\n#pth-epoch-wrapper .res .big2 { font-size: 21px; font-weight: 800; color: var(--blu); margin-bottom: 6px; }\r\n#pth-epoch-wrapper .res .kv { display: flex; justify-content: space-between; gap: 10px; padding: 5px 0; border-bottom: 1px solid var(--bd2); font-size: 12px; }\r\n#pth-epoch-wrapper .res .kv:last-child { border-bottom: none; }\r\n#pth-epoch-wrapper .res .kv span:first-child { color: var(--tx2); font-weight: 600; }\r\n#pth-epoch-wrapper .res .kv span:last-child { color: var(--tx1); font-weight: 700; font-family: 'Courier New', monospace; }\r\n\r\n#pth-epoch-wrapper .out { background: #0f172a; color: #e2e8f0; border-radius: 9px; padding: 14px; font-family: 'Courier New', monospace; font-size: 12px; line-height: 1.6; overflow: auto; max-height: 340px; white-space: pre-wrap; word-break: break-word; min-height: 110px; }\r\n\r\n#pth-epoch-wrapper .warn { background: #fef2f2; border: 1px solid #fecaca; border-radius: 8px; padding: 10px 12px; font-size: 12px; color: #991b1b; line-height: 1.6; margin-bottom: 12px; font-weight: 600; display: none; }\r\n#pth-epoch-wrapper .warn.on { display: block; }\r\n#pth-epoch-wrapper .note { background: #fffbeb; border: 1px solid #fde68a; border-radius: 8px; padding: 10px 12px; font-size: 12px; color: #78350f; line-height: 1.6; margin-bottom: 12px; }\r\n#pth-epoch-wrapper .note a { color: #1d4ed8; font-weight: 700; }\r\n#pth-epoch-wrapper .info { background: #eff6ff; border: 1px solid #bfdbfe; border-radius: 8px; padding: 10px 12px; font-size: 12px; color: #1e3a8a; line-height: 1.6; margin-bottom: 12px; }\r\n#pth-epoch-wrapper .empty { font-size: 12px; color: var(--tx2); text-align: center; padding: 20px 10px; }\r\n\r\n#pth-epoch-wrapper #pth-ep-toast { position: fixed; bottom: 20px; right: 20px; background: #0f172a; color: #fff; padding: 12px 18px; border-radius: 10px; font-size: 13px; font-weight: 600; z-index: 999999; display: none; max-width: 330px; line-height: 1.5; }\r\n#pth-epoch-wrapper #pth-ep-toast.show { display: block; }\r\n#pth-epoch-wrapper #pth-ep-toast.err { background: #991b1b; }\r\n#pth-epoch-wrapper #pth-ep-toast.ok { background: #065f46; }\r\n\r\n@media (max-width: 900px) {\r\n  #pth-epoch-wrapper { padding: 14px; }\r\n  #pth-epoch-wrapper .layout { grid-template-columns: 1fr; }\r\n  #pth-epoch-wrapper .grid3 { grid-template-columns: 1fr; }\r\n  #pth-epoch-wrapper .fmt .n { width: 96px; }\r\n}\r\n<\/style>\r\n\r\n<div id=\"pth-epoch-wrapper\">\r\n\r\n  <div class=\"pth-intro\">\r\n    <h2 class=\"pth-tool-title\">Epoch &amp; Time Studio - Convert, Compare and Calculate<\/h2>\r\n    <div class=\"pth-badges\">\r\n      <span class=\"pth-badge-top pth-version-badge\">v5.1<\/span>\r\n      <span class=\"pth-badge-top pth-status-offline\">100% Offline<\/span>\r\n      <span class=\"pth-badge-top pth-badge-ora\">No Libraries Loaded<\/span>\r\n    <\/div>\r\n    <p>Turn a Unix timestamp into every format a developer actually meets - ISO 8601, SQL, Excel serial numbers, .NET ticks and Windows FILETIME - then compare that instant across time zones, work out durations and business days, or convert a whole column at once. Runs on your browser's own date engine with nothing downloaded.<\/p>\r\n  <\/div>\r\n\r\n  <div class=\"pth-actions-bar\">\r\n    <button type=\"button\" class=\"btn\" data-act=\"sample\">Load Sample<\/button>\r\n    <button type=\"button\" class=\"btn\" data-act=\"fw\">Full Width<\/button>\r\n    <button type=\"button\" class=\"btn\" data-act=\"clear\">Clear<\/button>\r\n    <button type=\"button\" class=\"btn btn-primary\" data-act=\"now\">Now<\/button>\r\n  <\/div>\r\n\r\n  <div class=\"card\" style=\"margin-bottom:16px\">\r\n    <div class=\"tabs\" role=\"tablist\">\r\n      <button type=\"button\" class=\"tab on\" data-tab=\"conv\" role=\"tab\">Convert<\/button>\r\n      <button type=\"button\" class=\"tab\" data-tab=\"world\" role=\"tab\">World Clock<\/button>\r\n      <button type=\"button\" class=\"tab\" data-tab=\"calc\" role=\"tab\">Date Calculator<\/button>\r\n      <button type=\"button\" class=\"tab\" data-tab=\"batch\" role=\"tab\">Batch &amp; Code<\/button>\r\n    <\/div>\r\n  <\/div>\r\n\r\n  <!-- ============ CONVERT ============ -->\r\n  <div class=\"panel on\" data-panel=\"conv\">\r\n    <div class=\"layout\" id=\"pth-ep-layout\">\r\n\r\n      <div class=\"card\">\r\n        <div class=\"hdr\"><h3 class=\"hdr-title\">Input<\/h3><\/div>\r\n        <div class=\"card-pad\">\r\n\r\n          <div class=\"sect\">\r\n            <h4>From a Timestamp<\/h4>\r\n            <div class=\"grid3\">\r\n              <div class=\"fld\" style=\"grid-column: span 2\">\r\n                <label for=\"ep-ts\">Timestamp<\/label>\r\n                <input type=\"text\" id=\"ep-ts\" class=\"big\" value=\"1753100000\" spellcheck=\"false\">\r\n              <\/div>\r\n              <div class=\"fld\">\r\n                <label for=\"ep-unit\">Unit<\/label>\r\n                <select id=\"ep-unit\">\r\n                  <option value=\"s\" selected>Seconds<\/option>\r\n                  <option value=\"ms\">Milliseconds<\/option>\r\n                  <option value=\"us\">Microseconds<\/option>\r\n                  <option value=\"ns\">Nanoseconds<\/option>\r\n                <\/select>\r\n              <\/div>\r\n            <\/div>\r\n            <div class=\"warn\" id=\"ep-warn\"><\/div>\r\n          <\/div>\r\n\r\n          <div class=\"sect\">\r\n            <h4>Or From a Date<\/h4>\r\n            <div class=\"grid3\">\r\n              <div class=\"fld\"><label for=\"ep-date\">Date<\/label><input type=\"date\" id=\"ep-date\"><\/div>\r\n              <div class=\"fld\"><label for=\"ep-time\">Time<\/label><input type=\"time\" id=\"ep-time\" step=\"1\"><\/div>\r\n              <div class=\"fld\"><label for=\"ep-tz\">Time zone<\/label><select id=\"ep-tz\" aria-label=\"Input time zone\"><\/select><\/div>\r\n            <\/div>\r\n            <div class=\"row\">\r\n              <button type=\"button\" class=\"btn btn-sm btn-primary\" data-act=\"fromdate\">Convert This Date<\/button>\r\n              <button type=\"button\" class=\"btn btn-sm\" data-act=\"today\">Start of Today<\/button>\r\n              <button type=\"button\" class=\"btn btn-sm\" data-act=\"startmonth\">Start of Month<\/button>\r\n            <\/div>\r\n          <\/div>\r\n\r\n          <div class=\"sect\">\r\n            <h4>Or Paste Any Format<\/h4>\r\n            <div class=\"fld\">\r\n              <label for=\"ep-paste\">ISO, RFC, SQL, Excel serial, .NET ticks or FILETIME - the format is detected<\/label>\r\n              <input type=\"text\" id=\"ep-paste\" placeholder=\"2026-07-21T14:30:00Z\" spellcheck=\"false\">\r\n            <\/div>\r\n            <div class=\"row\"><button type=\"button\" class=\"btn btn-sm\" data-act=\"parse\">Detect and Convert<\/button><\/div>\r\n          <\/div>\r\n\r\n        <\/div>\r\n      <\/div>\r\n\r\n      <div class=\"card\">\r\n        <div class=\"hdr\"><h3 class=\"hdr-title\">Every Format<\/h3><\/div>\r\n        <div class=\"card-pad\">\r\n          <div class=\"rel\" id=\"ep-rel\"><b>-<\/b><span>relative to now<\/span><\/div>\r\n          <div class=\"fmtbox\" id=\"ep-formats\"><\/div>\r\n          <div class=\"note\" style=\"margin-top:12px\">Scheduling a recurring job rather than converting one instant? The <a href=\"https:\/\/schoolict.net\/tools\/cron-job-generator\/\">Cron Studio Pro<\/a> builds and explains cron expressions and previews their next runs.<\/div>\r\n        <\/div>\r\n      <\/div>\r\n\r\n    <\/div>\r\n  <\/div>\r\n\r\n  <!-- ============ WORLD CLOCK ============ -->\r\n  <div class=\"panel\" data-panel=\"world\">\r\n    <div class=\"layout\">\r\n\r\n      <div class=\"card\">\r\n        <div class=\"hdr\">\r\n          <h3 class=\"hdr-title\">Meeting Planner<\/h3>\r\n          <span class=\"chk\"><label for=\"ep-work\">Working hours<\/label>\r\n            <select id=\"ep-work\" style=\"width:auto\" aria-label=\"Working hours range\">\r\n              <option value=\"9-17\" selected>9am to 5pm<\/option>\r\n              <option value=\"8-16\">8am to 4pm<\/option>\r\n              <option value=\"10-18\">10am to 6pm<\/option>\r\n            <\/select><\/span>\r\n        <\/div>\r\n        <div class=\"card-pad\">\r\n          <div class=\"info\">Each row shows one full day in that zone for the instant selected on the Convert tab. Green means inside working hours, and a dark cell means every zone in the list is available at that moment.<\/div>\r\n          <div class=\"plan\" id=\"ep-plan\"><div class=\"empty\">Add zones to build the planner.<\/div><\/div>\r\n        <\/div>\r\n      <\/div>\r\n\r\n      <div class=\"card\">\r\n        <div class=\"hdr\"><h3 class=\"hdr-title\">Zones<\/h3><\/div>\r\n        <div class=\"card-pad\">\r\n          <div class=\"fld\">\r\n            <label for=\"ep-addzone\">Add a time zone<\/label>\r\n            <select id=\"ep-addzone\" aria-label=\"Add a time zone\"><\/select>\r\n          <\/div>\r\n          <div class=\"row\"><button type=\"button\" class=\"btn btn-sm btn-primary\" data-act=\"addzone\">Add Zone<\/button><\/div>\r\n          <div class=\"fmtbox\" id=\"ep-zones\"><\/div>\r\n          <div class=\"note\" style=\"margin-top:12px\">A highlighted DST label means daylight saving is active in that zone on this date, so its usual offset does not apply.<\/div>\r\n        <\/div>\r\n      <\/div>\r\n\r\n    <\/div>\r\n  <\/div>\r\n\r\n  <!-- ============ DATE CALCULATOR ============ -->\r\n  <div class=\"panel\" data-panel=\"calc\">\r\n    <div class=\"layout\">\r\n\r\n      <div class=\"card\">\r\n        <div class=\"hdr\"><h3 class=\"hdr-title\">Add or Subtract<\/h3><\/div>\r\n        <div class=\"card-pad\">\r\n          <div class=\"grid2\">\r\n            <div class=\"fld\"><label for=\"ca-date\">Starting date<\/label><input type=\"date\" id=\"ca-date\"><\/div>\r\n            <div class=\"fld\"><label for=\"ca-time\">Starting time<\/label><input type=\"time\" id=\"ca-time\" step=\"1\"><\/div>\r\n          <\/div>\r\n          <div class=\"grid3\">\r\n            <div class=\"fld\"><label for=\"ca-y\">Years<\/label><input type=\"number\" id=\"ca-y\" value=\"0\"><\/div>\r\n            <div class=\"fld\"><label for=\"ca-mo\">Months<\/label><input type=\"number\" id=\"ca-mo\" value=\"0\"><\/div>\r\n            <div class=\"fld\"><label for=\"ca-d\">Days<\/label><input type=\"number\" id=\"ca-d\" value=\"30\"><\/div>\r\n          <\/div>\r\n          <div class=\"grid3\">\r\n            <div class=\"fld\"><label for=\"ca-h\">Hours<\/label><input type=\"number\" id=\"ca-h\" value=\"0\"><\/div>\r\n            <div class=\"fld\"><label for=\"ca-mi\">Minutes<\/label><input type=\"number\" id=\"ca-mi\" value=\"0\"><\/div>\r\n            <div class=\"fld\"><label for=\"ca-s\">Seconds<\/label><input type=\"number\" id=\"ca-s\" value=\"0\"><\/div>\r\n          <\/div>\r\n          <div class=\"row\">\r\n            <button type=\"button\" class=\"btn btn-primary\" data-act=\"add\">Add<\/button>\r\n            <button type=\"button\" class=\"btn\" data-act=\"sub\">Subtract<\/button>\r\n          <\/div>\r\n          <div id=\"ca-out\"><\/div>\r\n\r\n          <div class=\"sect\">\r\n            <h4>Difference Between Two Dates<\/h4>\r\n            <div class=\"grid2\">\r\n              <div class=\"fld\"><label for=\"df-a\">From<\/label><input type=\"date\" id=\"df-a\"><\/div>\r\n              <div class=\"fld\"><label for=\"df-b\">To<\/label><input type=\"date\" id=\"df-b\"><\/div>\r\n            <\/div>\r\n            <div class=\"row\"><button type=\"button\" class=\"btn btn-primary\" data-act=\"diff\">Calculate Difference<\/button><\/div>\r\n            <div id=\"df-out\"><\/div>\r\n          <\/div>\r\n        <\/div>\r\n      <\/div>\r\n\r\n      <div class=\"card\">\r\n        <div class=\"hdr\"><h3 class=\"hdr-title\">Notes on This Date<\/h3><\/div>\r\n        <div class=\"card-pad\">\r\n          <div class=\"info\">Local times are not always continuous. When clocks jump forward an hour never exists, and when they go back one hour happens twice. Both are checked below for the zone chosen on the Convert tab.<\/div>\r\n          <div id=\"ep-dst\"><div class=\"empty\">Convert a date to see its notes.<\/div><\/div>\r\n          <div class=\"note\" style=\"margin-top:12px\">Business day counts exclude Saturday and Sunday only. Public holidays vary by country and are not applied.<\/div>\r\n        <\/div>\r\n      <\/div>\r\n\r\n    <\/div>\r\n  <\/div>\r\n\r\n  <!-- ============ BATCH & CODE ============ -->\r\n  <div class=\"panel\" data-panel=\"batch\">\r\n    <div class=\"layout\">\r\n\r\n      <div class=\"card\">\r\n        <div class=\"hdr\"><h3 class=\"hdr-title\">Batch Convert<\/h3><\/div>\r\n        <div class=\"card-pad\">\r\n          <div class=\"fld\">\r\n            <label for=\"ep-batch\">Paste one timestamp per line<\/label>\r\n            <textarea id=\"ep-batch\" spellcheck=\"false\" placeholder=\"1753100000&#10;1753186400&#10;1753272800\"><\/textarea>\r\n          <\/div>\r\n          <div class=\"grid2\">\r\n            <div class=\"fld\"><label for=\"ep-bunit\">Input unit<\/label>\r\n              <select id=\"ep-bunit\"><option value=\"auto\" selected>Detect automatically<\/option><option value=\"s\">Seconds<\/option><option value=\"ms\">Milliseconds<\/option><\/select><\/div>\r\n            <div class=\"fld\"><label for=\"ep-bfmt\">Output format<\/label>\r\n              <select id=\"ep-bfmt\">\r\n                <option value=\"iso\">ISO 8601<\/option>\r\n                <option value=\"sql\">SQL DATETIME<\/option>\r\n                <option value=\"local\">Local readable<\/option>\r\n                <option value=\"utc\">UTC readable<\/option>\r\n              <\/select><\/div>\r\n          <\/div>\r\n          <div class=\"row\">\r\n            <button type=\"button\" class=\"btn btn-primary\" data-act=\"batch\">Convert All<\/button>\r\n            <button type=\"button\" class=\"btn btn-grn\" data-act=\"batchcsv\">Download CSV<\/button>\r\n          <\/div>\r\n          <div class=\"out\" id=\"ep-bout\">Converted rows appear here.<\/div>\r\n        <\/div>\r\n      <\/div>\r\n\r\n      <div class=\"card\">\r\n        <div class=\"hdr\"><h3 class=\"hdr-title\">Code Snippets<\/h3><\/div>\r\n        <div class=\"card-pad\">\r\n          <div class=\"fld\">\r\n            <label for=\"ep-lang\">Language<\/label>\r\n            <select id=\"ep-lang\">\r\n              <option value=\"js\">JavaScript<\/option>\r\n              <option value=\"py\">Python<\/option>\r\n              <option value=\"php\">PHP<\/option>\r\n              <option value=\"sql\">SQL<\/option>\r\n              <option value=\"java\">Java<\/option>\r\n              <option value=\"go\">Go<\/option>\r\n              <option value=\"bash\">Bash<\/option>\r\n            <\/select>\r\n          <\/div>\r\n          <div class=\"row\">\r\n            <button type=\"button\" class=\"btn btn-primary\" data-act=\"snip\">Generate<\/button>\r\n            <button type=\"button\" class=\"btn btn-grn\" data-act=\"copysnip\">Copy<\/button>\r\n          <\/div>\r\n          <div class=\"out\" id=\"ep-snip\">Pick a language and press Generate.<\/div>\r\n          <div class=\"note\" style=\"margin-top:12px\">Working with the raw integer in another base? The <a href=\"https:\/\/schoolict.net\/tools\/number-base-converter-calculator\/\">Number Base Converter<\/a> turns a timestamp into binary or hexadecimal.<\/div>\r\n        <\/div>\r\n      <\/div>\r\n\r\n    <\/div>\r\n  <\/div>\r\n\r\n  <div id=\"pth-ep-toast\" role=\"status\" aria-live=\"polite\"><\/div>\r\n<\/div>\r\n\r\n<script data-no-optimize=\"1\" data-no-minify=\"1\" data-cfasync=\"false\">\r\nwindow.pthEpochStudioApp = (function () {\r\n  'use strict';\r\n\r\n  \/* Angle-bracket + closing-tag helpers, kept split so no raw tag substring sits in\r\n     a JS string. WordPress\/LiteSpeed mangles such substrings and kills the whole\r\n     script (every button dies), so we keep this file free of raw tag literals. *\/\r\n  var LT = String.fromCharCode(60);\r\n  var GT = String.fromCharCode(62);\r\n  var CT = LT + '\/';\r\n\r\n  var W = null, booted = false;\r\n  var S = { ms: Date.now(), zones: [], batch: [] };\r\n\r\n  var ZONES = [\r\n    'UTC', 'Asia\/Colombo', 'Asia\/Kolkata', 'Asia\/Dubai', 'Asia\/Karachi', 'Asia\/Dhaka',\r\n    'Asia\/Bangkok', 'Asia\/Singapore', 'Asia\/Hong_Kong', 'Asia\/Shanghai', 'Asia\/Tokyo',\r\n    'Asia\/Seoul', 'Australia\/Sydney', 'Australia\/Perth', 'Pacific\/Auckland',\r\n    'Europe\/London', 'Europe\/Dublin', 'Europe\/Paris', 'Europe\/Berlin', 'Europe\/Madrid',\r\n    'Europe\/Rome', 'Europe\/Amsterdam', 'Europe\/Stockholm', 'Europe\/Moscow', 'Europe\/Istanbul',\r\n    'Africa\/Cairo', 'Africa\/Lagos', 'Africa\/Nairobi', 'Africa\/Johannesburg',\r\n    'America\/New_York', 'America\/Chicago', 'America\/Denver', 'America\/Los_Angeles',\r\n    'America\/Toronto', 'America\/Vancouver', 'America\/Mexico_City', 'America\/Bogota',\r\n    'America\/Sao_Paulo', 'America\/Argentina\/Buenos_Aires'\r\n  ];\r\n\r\n  var MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];\r\n  var DAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];\r\n\r\n  function $(id) { return document.getElementById(id); }\r\n  function qa(sel) { return W ? W.querySelectorAll(sel) : []; }\r\n  function val(id) { var e = $(id); return e ? e.value : ''; }\r\n\r\n  var tTimer = null;\r\n  function toast(msg, kind) {\r\n    var t = $('pth-ep-toast');\r\n    if (!t) { return; }\r\n    t.className = 'show' + (kind ? ' ' + kind : '');\r\n    t.textContent = msg;\r\n    if (tTimer) { clearTimeout(tTimer); }\r\n    tTimer = setTimeout(function () { t.className = ''; }, 3000);\r\n  }\r\n\r\n  function esc(s) {\r\n    return String(s).replace(\/&\/g, '&amp;').replace(new RegExp(LT, 'g'), '&lt;')\r\n      .replace(new RegExp(GT, 'g'), '&gt;').replace(\/\"\/g, '&quot;');\r\n  }\r\n\r\n  function p2(n) { return (n < 10 ? '0' : '') + n; }\r\n  function p3(n) { return (n < 10 ? '00' : (n < 100 ? '0' : '')) + n; }\r\n\r\n  \/* ---------- Time zone maths ---------- *\/\r\n  function tzOffsetMs(ms, tz) {\r\n    try {\r\n      var dtf = new Intl.DateTimeFormat('en-US', {\r\n        timeZone: tz, hour12: false,\r\n        year: 'numeric', month: '2-digit', day: '2-digit',\r\n        hour: '2-digit', minute: '2-digit', second: '2-digit'\r\n      });\r\n      var parts = dtf.formatToParts(new Date(ms));\r\n      var m = {}, i;\r\n      for (i = 0; i < parts.length; i++) { m[parts[i].type] = parts[i].value; }\r\n      var h = parseInt(m.hour, 10); if (h === 24) { h = 0; }\r\n      var asUTC = Date.UTC(parseInt(m.year, 10), parseInt(m.month, 10) - 1, parseInt(m.day, 10), h, parseInt(m.minute, 10), parseInt(m.second, 10));\r\n      return asUTC - ms;\r\n    } catch (e) { return 0; }\r\n  }\r\n\r\n  function partsIn(ms, tz) {\r\n    var off = tzOffsetMs(ms, tz);\r\n    var d = new Date(ms + off);\r\n    return {\r\n      y: d.getUTCFullYear(), mo: d.getUTCMonth() + 1, d: d.getUTCDate(),\r\n      h: d.getUTCHours(), mi: d.getUTCMinutes(), s: d.getUTCSeconds(),\r\n      dow: d.getUTCDay(), off: off\r\n    };\r\n  }\r\n\r\n  function fromWall(y, mo, d, h, mi, s, tz) {\r\n    var guess = Date.UTC(y, mo - 1, d, h, mi, s);\r\n    var off = tzOffsetMs(guess, tz);\r\n    var ts = guess - off;\r\n    off = tzOffsetMs(ts, tz);\r\n    return guess - off;\r\n  }\r\n\r\n  function offsetLabel(off) {\r\n    var mins = Math.round(off \/ 60000);\r\n    var sign = mins < 0 ? '-' : '+';\r\n    mins = Math.abs(mins);\r\n    return 'UTC' + sign + p2(Math.floor(mins \/ 60)) + ':' + p2(mins % 60);\r\n  }\r\n\r\n  function isDST(ms, tz) {\r\n    var y = new Date(ms).getUTCFullYear();\r\n    var jan = tzOffsetMs(Date.UTC(y, 0, 1), tz);\r\n    var jul = tzOffsetMs(Date.UTC(y, 6, 1), tz);\r\n    var std = Math.min(jan, jul);\r\n    return tzOffsetMs(ms, tz) > std;\r\n  }\r\n\r\n  \/* ---------- Formats ---------- *\/\r\n  function fmtISO(ms) {\r\n    var d = new Date(ms);\r\n    return d.getUTCFullYear() + '-' + p2(d.getUTCMonth() + 1) + '-' + p2(d.getUTCDate())\r\n      + 'T' + p2(d.getUTCHours()) + ':' + p2(d.getUTCMinutes()) + ':' + p2(d.getUTCSeconds())\r\n      + '.' + p3(d.getUTCMilliseconds()) + 'Z';\r\n  }\r\n\r\n  function fmtSQL(ms, tz) {\r\n    var p = partsIn(ms, tz);\r\n    return p.y + '-' + p2(p.mo) + '-' + p2(p.d) + ' ' + p2(p.h) + ':' + p2(p.mi) + ':' + p2(p.s);\r\n  }\r\n\r\n  function fmtRFC3339(ms, tz) {\r\n    var p = partsIn(ms, tz);\r\n    var off = offsetLabel(p.off).replace('UTC', '');\r\n    if (off === '+00:00') { off = 'Z'; }\r\n    return p.y + '-' + p2(p.mo) + '-' + p2(p.d) + 'T' + p2(p.h) + ':' + p2(p.mi) + ':' + p2(p.s) + off;\r\n  }\r\n\r\n  function fmtRFC2822(ms) {\r\n    var d = new Date(ms);\r\n    return DAYS[d.getUTCDay()] + ', ' + p2(d.getUTCDate()) + ' ' + MONTHS[d.getUTCMonth()] + ' '\r\n      + d.getUTCFullYear() + ' ' + p2(d.getUTCHours()) + ':' + p2(d.getUTCMinutes()) + ':' + p2(d.getUTCSeconds()) + ' +0000';\r\n  }\r\n\r\n  function fmtReadable(ms, tz) {\r\n    var p = partsIn(ms, tz);\r\n    return DAYS[p.dow] + ', ' + p.d + ' ' + MONTHS[p.mo - 1] + ' ' + p.y + ' at ' + p2(p.h) + ':' + p2(p.mi) + ':' + p2(p.s) + ' (' + offsetLabel(p.off) + ')';\r\n  }\r\n\r\n  function excelSerial(ms) { return (ms \/ 86400000 + 25569).toFixed(8); }\r\n  function dotNetTicks(ms) { return String(Math.round(ms \/ 1000 + 62135596800) * 10000000); }\r\n  function fileTime(ms) { return String(Math.round(ms \/ 1000 + 11644473600) * 10000000); }\r\n  function macAbsolute(ms) { return String(Math.round(ms \/ 1000 - 978307200)); }\r\n\r\n  function relative(ms) {\r\n    var diff = ms - Date.now(), fut = diff > 0, a = Math.abs(diff);\r\n    var s = Math.round(a \/ 1000), out;\r\n    if (s < 45) { out = s + ' seconds'; }\r\n    else if (s < 5400) { out = Math.round(s \/ 60) + ' minutes'; }\r\n    else if (s < 129600) { out = Math.round(s \/ 3600) + ' hours'; }\r\n    else if (s < 2592000) { out = Math.round(s \/ 86400) + ' days'; }\r\n    else if (s < 31536000) { out = Math.round(s \/ 2592000) + ' months'; }\r\n    else { out = (a \/ 31536000000).toFixed(1) + ' years'; }\r\n    if (s < 3) { return 'right now'; }\r\n    return fut ? ('in ' + out) : (out + ' ago');\r\n  }\r\n\r\n  \/* ---------- Render the convert tab ---------- *\/\r\n  function readTs() {\r\n    var raw = String(val('ep-ts')).replace(\/[\\s,_]\/g, '');\r\n    if (!raw || !\/^-?\\d+(\\.\\d+)?$\/.test(raw)) { return null; }\r\n    var n = parseFloat(raw), u = val('ep-unit');\r\n    if (u === 's') { return n * 1000; }\r\n    if (u === 'ms') { return n; }\r\n    if (u === 'us') { return n \/ 1000; }\r\n    return n \/ 1000000;\r\n  }\r\n\r\n  function checkWarn(ms) {\r\n    var w = $('ep-warn'), y = new Date(ms).getUTCFullYear();\r\n    var secs = ms \/ 1000;\r\n    if (secs > 2147483647) {\r\n      w.className = 'warn on';\r\n      w.textContent = 'This instant is past 19 January 2038. Systems storing time in a signed 32-bit integer cannot represent it and will overflow to a negative date.';\r\n      return;\r\n    }\r\n    if (secs > 2100000000) {\r\n      w.className = 'warn on';\r\n      w.textContent = 'This is close to the 32-bit limit of 19 January 2038. Anything still using a signed 32-bit time field will fail shortly after this point.';\r\n      return;\r\n    }\r\n    if (y < 1970) {\r\n      w.className = 'warn on';\r\n      w.textContent = 'This is before 1 January 1970, so the timestamp is negative. Some languages and databases reject negative epoch values.';\r\n      return;\r\n    }\r\n    w.className = 'warn';\r\n  }\r\n\r\n  function renderFormats() {\r\n    var ms = S.ms, tz = val('ep-tz') || 'UTC';\r\n    var rows = [\r\n      ['Seconds', String(Math.floor(ms \/ 1000))],\r\n      ['Milliseconds', String(Math.round(ms))],\r\n      ['Microseconds', String(Math.round(ms * 1000))],\r\n      ['ISO 8601 (UTC)', fmtISO(ms)],\r\n      ['RFC 3339', fmtRFC3339(ms, tz)],\r\n      ['RFC 2822', fmtRFC2822(ms)],\r\n      ['HTTP date', new Date(ms).toUTCString()],\r\n      ['SQL DATETIME', fmtSQL(ms, tz)],\r\n      ['Readable (zone)', fmtReadable(ms, tz)],\r\n      ['Readable (UTC)', fmtReadable(ms, 'UTC')],\r\n      ['Excel serial', excelSerial(ms)],\r\n      ['.NET ticks', dotNetTicks(ms)],\r\n      ['Windows FILETIME', fileTime(ms)],\r\n      ['Mac absolute', macAbsolute(ms)],\r\n      ['Day of year', String(dayOfYear(ms, tz))],\r\n      ['ISO week', isoWeek(ms)]\r\n    ];\r\n    var html = '', i;\r\n    for (i = 0; i < rows.length; i++) {\r\n      html += '<div class=\"fmt\"><span class=\"n\">' + esc(rows[i][0]) + '' + CT + 'span>'\r\n        + '<span class=\"v\">' + esc(rows[i][1]) + '' + CT + 'span>'\r\n        + '<button type=\"button\" data-copy=\"' + esc(rows[i][1]) + '\">Copy' + CT + 'button>' + CT + 'div>';\r\n    }\r\n    $('ep-formats').innerHTML = html;\r\n    $('ep-rel').innerHTML = '<b>' + esc(relative(ms)) + '' + CT + 'b><span>relative to now' + CT + 'span>';\r\n    checkWarn(ms);\r\n  }\r\n\r\n  function dayOfYear(ms, tz) {\r\n    var p = partsIn(ms, tz);\r\n    var start = Date.UTC(p.y, 0, 1);\r\n    var cur = Date.UTC(p.y, p.mo - 1, p.d);\r\n    return Math.round((cur - start) \/ 86400000) + 1;\r\n  }\r\n\r\n  function isoWeek(ms) {\r\n    var d = new Date(ms);\r\n    var t = Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate());\r\n    var dn = new Date(t).getUTCDay() || 7;\r\n    t += (4 - dn) * 86400000;\r\n    var y = new Date(t).getUTCFullYear();\r\n    var jan4 = Date.UTC(y, 0, 4);\r\n    var wk = Math.round((t - jan4) \/ 604800000) + 1;\r\n    return y + '-W' + p2(wk);\r\n  }\r\n\r\n  function setMs(ms, quiet) {\r\n    if (!isFinite(ms)) { toast('That is not a valid time.', 'err'); return; }\r\n    S.ms = ms;\r\n    var u = val('ep-unit');\r\n    $('ep-ts').value = (u === 's') ? String(Math.floor(ms \/ 1000))\r\n      : (u === 'ms') ? String(Math.round(ms))\r\n        : (u === 'us') ? String(Math.round(ms * 1000)) : String(Math.round(ms * 1000000));\r\n    var tz = val('ep-tz') || 'UTC';\r\n    var p = partsIn(ms, tz);\r\n    $('ep-date').value = p.y + '-' + p2(p.mo) + '-' + p2(p.d);\r\n    $('ep-time').value = p2(p.h) + ':' + p2(p.mi) + ':' + p2(p.s);\r\n    renderFormats();\r\n    renderZones();\r\n    renderPlanner();\r\n    renderDstNotes();\r\n    if (!quiet) { toast('Converted.', 'ok'); }\r\n  }\r\n\r\n  \/* ---------- Parse any format ---------- *\/\r\n  function detectParse(txt) {\r\n    var t = String(txt).replace(\/^\\s+|\\s+$\/g, '');\r\n    if (!t) { return null; }\r\n    var clean = t.replace(\/[\\s,_]\/g, '');\r\n\r\n    if (\/^-?\\d+(\\.\\d+)?$\/.test(clean)) {\r\n      var n = parseFloat(clean), abs = Math.abs(n);\r\n      if (abs > 1e17) { return { ms: n \/ 10000 - 11644473600000, how: 'Windows FILETIME' }; }\r\n      if (abs > 1e16) { return { ms: n \/ 10000 - 62135596800000, how: '.NET ticks' }; }\r\n      if (abs > 1e14) { return { ms: n \/ 1000000, how: 'nanoseconds' }; }\r\n      if (abs > 1e11) { return { ms: n, how: 'milliseconds' }; }\r\n      if (abs > 1e9) { return { ms: n * 1000, how: 'seconds' }; }\r\n      if (abs > 20000 && abs < 80000) { return { ms: (n - 25569) * 86400000, how: 'Excel serial' }; }\r\n      if (abs > 1e8) { return { ms: n * 1000, how: 'seconds' }; }\r\n      return { ms: n * 1000, how: 'seconds' };\r\n    }\r\n\r\n    var d = Date.parse(t);\r\n    if (!isNaN(d)) { return { ms: d, how: 'date string' }; }\r\n\r\n    var m = t.match(\/^(\\d{4})-(\\d{2})-(\\d{2})[ T](\\d{2}):(\\d{2})(:(\\d{2}))?$\/);\r\n    if (m) {\r\n      return {\r\n        ms: fromWall(+m[1], +m[2], +m[3], +m[4], +m[5], +(m[7] || 0), val('ep-tz') || 'UTC'),\r\n        how: 'SQL DATETIME in the selected zone'\r\n      };\r\n    }\r\n    return null;\r\n  }\r\n\r\n  \/* ---------- World clock ---------- *\/\r\n  function renderZones() {\r\n    var box = $('ep-zones');\r\n    if (!S.zones.length) { box.innerHTML = '<div class=\"empty\">No zones added yet.' + CT + 'div>'; return; }\r\n    var html = '', i, p, dst;\r\n    for (i = 0; i < S.zones.length; i++) {\r\n      p = partsIn(S.ms, S.zones[i]);\r\n      dst = isDST(S.ms, S.zones[i]);\r\n      html += '<div class=\"zone\">'\r\n        + '<div class=\"zn\"><b>' + esc(S.zones[i].split('\/').pop().replace(\/_\/g, ' ')) + '' + CT + 'b>'\r\n        + '<span>' + esc(S.zones[i]) + ' - ' + offsetLabel(p.off) + '' + CT + 'span>' + CT + 'div>'\r\n        + '<span class=\"zd ' + (dst ? 'dst-on' : 'dst-off') + '\">' + (dst ? 'DST' : 'STD') + '' + CT + 'span>'\r\n        + '<span class=\"zt\">' + p2(p.h) + ':' + p2(p.mi) + '' + CT + 'span>'\r\n        + '<button type=\"button\" data-delzone=\"' + esc(S.zones[i]) + '\" aria-label=\"Remove zone\">&times;' + CT + 'button>'\r\n        + '' + CT + 'div>';\r\n    }\r\n    box.innerHTML = html;\r\n  }\r\n\r\n  function renderPlanner() {\r\n    var box = $('ep-plan');\r\n    if (!S.zones.length) { box.innerHTML = '<div class=\"empty\">Add zones to build the planner.' + CT + 'div>'; return; }\r\n    var wr = String(val('ep-work') || '9-17').split('-');\r\n    var w0 = parseInt(wr[0], 10), w1 = parseInt(wr[1], 10);\r\n    var base = S.ms - (S.ms % 3600000);\r\n    var cols = 24, i, h, p, cls, cellMs;\r\n\r\n    var counts = [];\r\n    for (h = 0; h < cols; h++) {\r\n      cellMs = base + (h - 12) * 3600000;\r\n      var ok = 0;\r\n      for (i = 0; i < S.zones.length; i++) {\r\n        p = partsIn(cellMs, S.zones[i]);\r\n        if (p.h >= w0 && p.h < w1 && p.dow !== 0 && p.dow !== 6) { ok++; }\r\n      }\r\n      counts.push(ok);\r\n    }\r\n\r\n    var html = '<table class=\"ptbl\"><thead><tr><th class=\"zh\">Zone' + CT + 'th>';\r\n    for (h = 0; h < cols; h++) {\r\n      cellMs = base + (h - 12) * 3600000;\r\n      html += '<th>' + p2(partsIn(cellMs, 'UTC').h) + '' + CT + 'th>';\r\n    }\r\n    html += '' + CT + 'tr>' + CT + 'thead><tbody>';\r\n\r\n    for (i = 0; i < S.zones.length; i++) {\r\n      html += '<tr><td class=\"zl\">' + esc(S.zones[i].split('\/').pop().replace(\/_\/g, ' ')) + '' + CT + 'td>';\r\n      for (h = 0; h < cols; h++) {\r\n        cellMs = base + (h - 12) * 3600000;\r\n        p = partsIn(cellMs, S.zones[i]);\r\n        var work = (p.h >= w0 && p.h < w1 && p.dow !== 0 && p.dow !== 6);\r\n        var edge = (!work && p.h >= w0 - 1 && p.h < w1 + 1 && p.dow !== 0 && p.dow !== 6);\r\n        cls = (work && counts[h] === S.zones.length) ? 'all' : (work ? 'work' : (edge ? 'edge' : 'off'));\r\n        html += '<td class=\"' + cls + '\">' + p2(p.h) + '' + CT + 'td>';\r\n      }\r\n      html += '' + CT + 'tr>';\r\n    }\r\n    html += '' + CT + 'tbody>' + CT + 'table>';\r\n    box.innerHTML = html;\r\n  }\r\n\r\n  \/* ---------- DST notes ---------- *\/\r\n  function renderDstNotes() {\r\n    var tz = val('ep-tz') || 'UTC', box = $('ep-dst');\r\n    var p = partsIn(S.ms, tz);\r\n    var back = fromWall(p.y, p.mo, p.d, p.h, p.mi, p.s, tz);\r\n    var round = partsIn(back, tz);\r\n    var lines = [];\r\n\r\n    lines.push(['Zone', tz]);\r\n    lines.push(['Offset', offsetLabel(p.off)]);\r\n    lines.push(['Daylight saving', isDST(S.ms, tz) ? 'Active on this date' : 'Not active on this date']);\r\n    lines.push(['Day of week', DAYS[p.dow]]);\r\n    lines.push(['Leap year', (p.y % 4 === 0 && (p.y % 100 !== 0 || p.y % 400 === 0)) ? 'Yes' : 'No']);\r\n\r\n    var gap = (round.h !== p.h || round.d !== p.d);\r\n    var ambiguous = false;\r\n    var earlier = fromWall(p.y, p.mo, p.d, p.h, p.mi, p.s, tz) - 3600000;\r\n    if (!gap && tzOffsetMs(earlier, tz) !== p.off && partsIn(earlier, tz).h === p.h) { ambiguous = true; }\r\n\r\n    var html = '<div class=\"res\">';\r\n    var i;\r\n    for (i = 0; i < lines.length; i++) {\r\n      html += '<div class=\"kv\"><span>' + esc(lines[i][0]) + '' + CT + 'span><span>' + esc(lines[i][1]) + '' + CT + 'span>' + CT + 'div>';\r\n    }\r\n    html += '' + CT + 'div>';\r\n    if (gap) {\r\n      html += '<div class=\"warn on\" style=\"margin-top:12px\">This local time does not exist in ' + esc(tz) + '. The clocks jump forward across it, so the nearest real instant has been used instead.' + CT + 'div>';\r\n    } else if (ambiguous) {\r\n      html += '<div class=\"warn on\" style=\"margin-top:12px\">This local time happens twice in ' + esc(tz) + ' because the clocks go back. The earlier of the two instants has been used.' + CT + 'div>';\r\n    }\r\n    box.innerHTML = html;\r\n  }\r\n\r\n  \/* ---------- Date calculator ---------- *\/\r\n  function calcShift(sign) {\r\n    var d = val('ca-date'), t = val('ca-time') || '00:00:00';\r\n    if (!d) { toast('Pick a starting date first.', 'err'); return; }\r\n    var dp = d.split('-'), tp = t.split(':');\r\n    var base = new Date(Date.UTC(+dp[0], +dp[1] - 1, +dp[2], +tp[0], +(tp[1] || 0), +(tp[2] || 0)));\r\n\r\n    base.setUTCFullYear(base.getUTCFullYear() + sign * (parseInt(val('ca-y'), 10) || 0));\r\n    base.setUTCMonth(base.getUTCMonth() + sign * (parseInt(val('ca-mo'), 10) || 0));\r\n    base.setUTCDate(base.getUTCDate() + sign * (parseInt(val('ca-d'), 10) || 0));\r\n    base.setUTCHours(base.getUTCHours() + sign * (parseInt(val('ca-h'), 10) || 0));\r\n    base.setUTCMinutes(base.getUTCMinutes() + sign * (parseInt(val('ca-mi'), 10) || 0));\r\n    base.setUTCSeconds(base.getUTCSeconds() + sign * (parseInt(val('ca-s'), 10) || 0));\r\n\r\n    var ms = base.getTime();\r\n    var p = partsIn(ms, 'UTC');\r\n    $('ca-out').innerHTML = '<div class=\"res\"><div class=\"big2\">'\r\n      + esc(DAYS[p.dow] + ', ' + p.d + ' ' + MONTHS[p.mo - 1] + ' ' + p.y) + '' + CT + 'div>'\r\n      + '<div class=\"kv\"><span>Time' + CT + 'span><span>' + p2(p.h) + ':' + p2(p.mi) + ':' + p2(p.s) + '' + CT + 'span>' + CT + 'div>'\r\n      + '<div class=\"kv\"><span>ISO 8601' + CT + 'span><span>' + esc(fmtISO(ms)) + '' + CT + 'span>' + CT + 'div>'\r\n      + '<div class=\"kv\"><span>Epoch seconds' + CT + 'span><span>' + Math.floor(ms \/ 1000) + '' + CT + 'span>' + CT + 'div>'\r\n      + '<div class=\"kv\"><span>Relative' + CT + 'span><span>' + esc(relative(ms)) + '' + CT + 'span>' + CT + 'div>' + CT + 'div>';\r\n    toast(sign > 0 ? 'Added.' : 'Subtracted.', 'ok');\r\n  }\r\n\r\n  function calcDiff() {\r\n    var a = val('df-a'), b = val('df-b');\r\n    if (!a || !b) { toast('Pick both dates first.', 'err'); return; }\r\n    var ap = a.split('-'), bp = b.split('-');\r\n    var A = Date.UTC(+ap[0], +ap[1] - 1, +ap[2]);\r\n    var B = Date.UTC(+bp[0], +bp[1] - 1, +bp[2]);\r\n    var sign = B < A ? -1 : 1;\r\n    var lo = Math.min(A, B), hi = Math.max(A, B);\r\n    var totalDays = Math.round((hi - lo) \/ 86400000);\r\n\r\n    var y = 0, mo = 0, cur = new Date(lo);\r\n    while (true) {\r\n      var nxt = new Date(cur.getTime());\r\n      nxt.setUTCFullYear(nxt.getUTCFullYear() + 1);\r\n      if (nxt.getTime() <= hi) { cur = nxt; y++; } else { break; }\r\n    }\r\n    while (true) {\r\n      var nxm = new Date(cur.getTime());\r\n      nxm.setUTCMonth(nxm.getUTCMonth() + 1);\r\n      if (nxm.getTime() <= hi) { cur = nxm; mo++; } else { break; }\r\n    }\r\n    var days = Math.round((hi - cur.getTime()) \/ 86400000);\r\n\r\n    var biz = 0, walk = lo;\r\n    while (walk < hi) {\r\n      var dow = new Date(walk).getUTCDay();\r\n      if (dow !== 0 && dow !== 6) { biz++; }\r\n      walk += 86400000;\r\n    }\r\n\r\n    $('df-out').innerHTML = '<div class=\"res\"><div class=\"big2\">'\r\n      + (sign < 0 ? '-' : '') + y + 'y ' + mo + 'm ' + days + 'd' + CT + 'div>'\r\n      + '<div class=\"kv\"><span>Total days' + CT + 'span><span>' + (sign * totalDays) + '' + CT + 'span>' + CT + 'div>'\r\n      + '<div class=\"kv\"><span>Business days' + CT + 'span><span>' + (sign * biz) + '' + CT + 'span>' + CT + 'div>'\r\n      + '<div class=\"kv\"><span>Total weeks' + CT + 'span><span>' + (sign * totalDays \/ 7).toFixed(2) + '' + CT + 'span>' + CT + 'div>'\r\n      + '<div class=\"kv\"><span>Total hours' + CT + 'span><span>' + (sign * totalDays * 24) + '' + CT + 'span>' + CT + 'div>'\r\n      + '<div class=\"kv\"><span>Total seconds' + CT + 'span><span>' + (sign * totalDays * 86400) + '' + CT + 'span>' + CT + 'div>' + CT + 'div>';\r\n    toast('Difference calculated.', 'ok');\r\n  }\r\n\r\n  \/* ---------- Batch ---------- *\/\r\n  function runBatch() {\r\n    var raw = val('ep-batch'), lines = raw.split('\\n');\r\n    var unit = val('ep-bunit'), fmt = val('ep-bfmt'), tz = val('ep-tz') || 'UTC';\r\n    var out = [], i, t, ms, n;\r\n    S.batch = [];\r\n    for (i = 0; i < lines.length; i++) {\r\n      t = lines[i].replace(\/^\\s+|\\s+$\/g, '');\r\n      if (!t) { continue; }\r\n      n = parseFloat(t.replace(\/[\\s,_]\/g, ''));\r\n      if (isNaN(n)) { out.push(t + '  ->  not a number'); S.batch.push([t, 'not a number']); continue; }\r\n      if (unit === 's') { ms = n * 1000; }\r\n      else if (unit === 'ms') { ms = n; }\r\n      else { ms = Math.abs(n) > 1e11 ? n : n * 1000; }\r\n      var res = (fmt === 'iso') ? fmtISO(ms)\r\n        : (fmt === 'sql') ? fmtSQL(ms, tz)\r\n          : (fmt === 'local') ? fmtReadable(ms, tz) : fmtReadable(ms, 'UTC');\r\n      out.push(t + '  ->  ' + res);\r\n      S.batch.push([t, res]);\r\n    }\r\n    $('ep-bout').textContent = out.length ? out.join('\\n') : 'Nothing to convert.';\r\n    toast(S.batch.length + ' row(s) converted.', 'ok');\r\n  }\r\n\r\n  function batchCsv() {\r\n    if (!S.batch.length) { toast('Convert some rows first.', 'err'); return; }\r\n    var rows = ['input,converted'], i;\r\n    for (i = 0; i < S.batch.length; i++) {\r\n      rows.push('\"' + String(S.batch[i][0]).replace(\/\"\/g, '\"\"') + '\",\"' + String(S.batch[i][1]).replace(\/\"\/g, '\"\"') + '\"');\r\n    }\r\n    var b = new Blob([rows.join('\\n')], { type: 'text\/csv' });\r\n    var u = URL.createObjectURL(b);\r\n    var a = document.createElement('a');\r\n    a.href = u; a.download = 'timestamps.csv';\r\n    document.body.appendChild(a); a.click(); document.body.removeChild(a);\r\n    setTimeout(function () { URL.revokeObjectURL(u); }, 1200);\r\n    toast('CSV downloaded.', 'ok');\r\n  }\r\n\r\n  \/* ---------- Snippets ---------- *\/\r\n  function snippet() {\r\n    var lang = val('ep-lang'), secs = Math.floor(S.ms \/ 1000), tz = val('ep-tz') || 'UTC', lines = [];\r\n    if (lang === 'js') {\r\n      lines = ['\/\/ Epoch to date', 'const d = new Date(' + secs + ' * 1000);',\r\n        'console.log(d.toISOString());',\r\n        'console.log(d.toLocaleString(\"en-GB\", { timeZone: \"' + tz + '\" }));',\r\n        '', '\/\/ Date to epoch', 'const secs = Math.floor(Date.now() \/ 1000);'];\r\n    } else if (lang === 'py') {\r\n      lines = ['from datetime import datetime, timezone',\r\n        'from zoneinfo import ZoneInfo', '',\r\n        '# Epoch to date',\r\n        'd = datetime.fromtimestamp(' + secs + ', tz=timezone.utc)',\r\n        'print(d.isoformat())',\r\n        'print(d.astimezone(ZoneInfo(\"' + tz + '\")))', '',\r\n        '# Date to epoch',\r\n        'secs = int(datetime.now(timezone.utc).timestamp())'];\r\n    } else if (lang === 'php') {\r\n      lines = ['\/\/ Epoch to date',\r\n        '$d = new DateTime(\"@' + secs + '\");',\r\n        '$d->setTimezone(new DateTimeZone(\"' + tz + '\"));',\r\n        'echo $d->format(\"Y-m-d H:i:s P\");', '',\r\n        '\/\/ Date to epoch', '$secs = time();'];\r\n    } else if (lang === 'sql') {\r\n      lines = ['-- MySQL', 'SELECT FROM_UNIXTIME(' + secs + ');',\r\n        'SELECT UNIX_TIMESTAMP(NOW());', '',\r\n        '-- PostgreSQL', \"SELECT to_timestamp(\" + secs + \");\",\r\n        \"SELECT EXTRACT(EPOCH FROM NOW())::bigint;\", '',\r\n        '-- SQLite', \"SELECT datetime(\" + secs + \", 'unixepoch');\"];\r\n    } else if (lang === 'java') {\r\n      lines = ['import java.time.*;', '',\r\n        '\/\/ Epoch to date',\r\n        'Instant i = Instant.ofEpochSecond(' + secs + 'L);',\r\n        'System.out.println(i);',\r\n        'System.out.println(i.atZone(ZoneId.of(\"' + tz + '\")));', '',\r\n        '\/\/ Date to epoch',\r\n        'long secs = Instant.now().getEpochSecond();'];\r\n    } else if (lang === 'go') {\r\n      lines = ['import (\"fmt\"; \"time\")', '',\r\n        '\/\/ Epoch to date',\r\n        't := time.Unix(' + secs + ', 0).UTC()',\r\n        'fmt.Println(t.Format(time.RFC3339))', '',\r\n        'loc, _ := time.LoadLocation(\"' + tz + '\")',\r\n        'fmt.Println(t.In(loc))', '',\r\n        '\/\/ Date to epoch', 'secs := time.Now().Unix()'];\r\n    } else {\r\n      lines = ['# Epoch to date',\r\n        'date -u -d @' + secs + ' \"+%Y-%m-%dT%H:%M:%SZ\"',\r\n        'TZ=\"' + tz + '\" date -d @' + secs, '',\r\n        '# Date to epoch', 'date +%s'];\r\n    }\r\n    $('ep-snip').textContent = lines.join('\\n');\r\n    toast('Snippet generated.', 'ok');\r\n  }\r\n\r\n  \/* ---------- Delegated events ---------- *\/\r\n  function onClick(e) {\r\n    var t = e.target;\r\n    if (!t || !W || !W.contains(t)) { return; }\r\n\r\n    var copyBtn = t.getAttribute && t.getAttribute('data-copy');\r\n    if (copyBtn !== null && copyBtn !== undefined && t.tagName === 'BUTTON') {\r\n      if (navigator.clipboard && navigator.clipboard.writeText) {\r\n        navigator.clipboard.writeText(copyBtn).then(function () { toast('Copied.', 'ok'); })\r\n          ['catch'](function () { toast('Copy was blocked by the browser.', 'err'); });\r\n      } else { toast('Copy is not available here.', 'err'); }\r\n      return;\r\n    }\r\n\r\n    var del = t.getAttribute && t.getAttribute('data-delzone');\r\n    if (del) {\r\n      var k, kept = [];\r\n      for (k = 0; k < S.zones.length; k++) { if (S.zones[k] !== del) { kept.push(S.zones[k]); } }\r\n      S.zones = kept;\r\n      renderZones(); renderPlanner();\r\n      toast('Zone removed.');\r\n      return;\r\n    }\r\n\r\n    var tab = t.getAttribute && t.getAttribute('data-tab');\r\n    if (tab) {\r\n      var all = qa('.tab'), pans = qa('.panel'), i;\r\n      for (i = 0; i < all.length; i++) { all[i].classList.remove('on'); }\r\n      for (i = 0; i < pans.length; i++) { pans[i].classList.remove('on'); }\r\n      t.classList.add('on');\r\n      var p = W.querySelector('.panel[data-panel=\"' + tab + '\"]');\r\n      if (p) { p.classList.add('on'); }\r\n      return;\r\n    }\r\n\r\n    var act = t.getAttribute && t.getAttribute('data-act');\r\n    if (!act) { return; }\r\n\r\n    if (act === 'now') { setMs(Date.now()); }\r\n    else if (act === 'fw') { var ls = qa('.layout'), j; for (j = 0; j < ls.length; j++) { ls[j].classList.toggle('fw'); } }\r\n    else if (act === 'sample') { loadSample(); }\r\n    else if (act === 'clear') { clearAll(); }\r\n    else if (act === 'fromdate') {\r\n      var d = val('ep-date'), tt = val('ep-time') || '00:00:00';\r\n      if (!d) { toast('Pick a date first.', 'err'); return; }\r\n      var dp = d.split('-'), tp = tt.split(':');\r\n      setMs(fromWall(+dp[0], +dp[1], +dp[2], +tp[0], +(tp[1] || 0), +(tp[2] || 0), val('ep-tz') || 'UTC'));\r\n    }\r\n    else if (act === 'today') {\r\n      var pt = partsIn(Date.now(), val('ep-tz') || 'UTC');\r\n      setMs(fromWall(pt.y, pt.mo, pt.d, 0, 0, 0, val('ep-tz') || 'UTC'));\r\n    }\r\n    else if (act === 'startmonth') {\r\n      var pm = partsIn(Date.now(), val('ep-tz') || 'UTC');\r\n      setMs(fromWall(pm.y, pm.mo, 1, 0, 0, 0, val('ep-tz') || 'UTC'));\r\n    }\r\n    else if (act === 'parse') {\r\n      var r = detectParse(val('ep-paste'));\r\n      if (!r) { toast('That format was not recognised.', 'err'); return; }\r\n      setMs(r.ms, true);\r\n      toast('Read as ' + r.how + '.', 'ok');\r\n    }\r\n    else if (act === 'addzone') {\r\n      var z = val('ep-addzone');\r\n      if (!z) { return; }\r\n      if (S.zones.indexOf(z) >= 0) { toast('That zone is already listed.', 'err'); return; }\r\n      if (S.zones.length >= 10) { toast('Ten zones is the practical limit for the planner.', 'err'); return; }\r\n      S.zones.push(z);\r\n      renderZones(); renderPlanner();\r\n      toast('Zone added.', 'ok');\r\n    }\r\n    else if (act === 'add') { calcShift(1); }\r\n    else if (act === 'sub') { calcShift(-1); }\r\n    else if (act === 'diff') { calcDiff(); }\r\n    else if (act === 'batch') { runBatch(); }\r\n    else if (act === 'batchcsv') { batchCsv(); }\r\n    else if (act === 'snip') { snippet(); }\r\n    else if (act === 'copysnip') {\r\n      var s = $('ep-snip').textContent;\r\n      if (!s || s.indexOf('Pick a language') === 0) { toast('Generate a snippet first.', 'err'); return; }\r\n      if (navigator.clipboard && navigator.clipboard.writeText) {\r\n        navigator.clipboard.writeText(s).then(function () { toast('Copied.', 'ok'); })\r\n          ['catch'](function () { toast('Copy was blocked.', 'err'); });\r\n      }\r\n    }\r\n  }\r\n\r\n  function onChange(e) {\r\n    var t = e.target;\r\n    if (!t || !W || !W.contains(t)) { return; }\r\n    if (t.id === 'ep-ts' || t.id === 'ep-unit') {\r\n      var ms = readTs();\r\n      if (ms !== null) { S.ms = ms; renderFormats(); renderZones(); renderPlanner(); renderDstNotes(); }\r\n      return;\r\n    }\r\n    if (t.id === 'ep-tz') { setMs(S.ms, true); return; }\r\n    if (t.id === 'ep-work') { renderPlanner(); return; }\r\n  }\r\n\r\n  \/* ---------- Sample and clear ---------- *\/\r\n  function loadSample() {\r\n    S.zones = ['Asia\/Colombo', 'Europe\/London', 'America\/New_York'];\r\n    $('ep-unit').value = 's';\r\n    $('ep-tz').value = 'Asia\/Colombo';\r\n    $('ep-paste').value = '2026-07-21T14:30:00Z';\r\n    $('ep-batch').value = '1753100000\\n1753186400\\n1753272800\\n1900000000';\r\n    var d = new Date();\r\n    $('ca-date').value = d.getUTCFullYear() + '-' + p2(d.getUTCMonth() + 1) + '-' + p2(d.getUTCDate());\r\n    $('ca-time').value = '09:00:00';\r\n    $('df-a').value = d.getUTCFullYear() + '-01-01';\r\n    $('df-b').value = d.getUTCFullYear() + '-12-31';\r\n    setMs(Date.now(), true);\r\n    runBatch();\r\n    snippet();\r\n    toast('Sample loaded with three zones.', 'ok');\r\n  }\r\n\r\n  function clearAll() {\r\n    S.zones = []; S.batch = [];\r\n    $('ep-paste').value = '';\r\n    $('ep-batch').value = '';\r\n    $('ep-bout').textContent = 'Converted rows appear here.';\r\n    $('ep-snip').textContent = 'Pick a language and press Generate.';\r\n    $('ca-out').innerHTML = '';\r\n    $('df-out').innerHTML = '';\r\n    renderZones(); renderPlanner();\r\n    toast('Cleared.');\r\n  }\r\n\r\n  \/* ---------- Init ---------- *\/\r\n  function fillZoneSelects() {\r\n    var list = ZONES.slice(0), i;\r\n    try {\r\n      if (Intl && Intl.supportedValuesOf) {\r\n        var all = Intl.supportedValuesOf('timeZone');\r\n        if (all && all.length > 20) { list = all; }\r\n      }\r\n    } catch (e) { }\r\n    var local = 'UTC';\r\n    try { local = Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC'; } catch (e2) { }\r\n    if (list.indexOf(local) < 0) { list.unshift(local); }\r\n\r\n    var html = '', sel;\r\n    for (i = 0; i < list.length; i++) {\r\n      html += '<option value=\"' + esc(list[i]) + '\">' + esc(list[i].replace(\/_\/g, ' ')) + '' + CT + 'option>';\r\n    }\r\n    sel = $('ep-tz'); sel.innerHTML = html; sel.value = local;\r\n    sel = $('ep-addzone'); sel.innerHTML = html; sel.value = 'UTC';\r\n  }\r\n\r\n  function boot() {\r\n    if (booted) { return; }\r\n    W = document.getElementById('pth-epoch-wrapper');\r\n    if (!W) { return; }\r\n    booted = true;\r\n    try {\r\n      fillZoneSelects();\r\n      document.addEventListener('click', onClick);\r\n      document.addEventListener('change', onChange);\r\n      document.addEventListener('input', function (e) {\r\n        if (e.target && e.target.id === 'ep-ts') { onChange(e); }\r\n      });\r\n      S.zones = ['UTC'];\r\n      setMs(Date.now(), true);\r\n    } catch (err) {\r\n      booted = false;\r\n      if (window.console && console.error) { console.error('Epoch Studio failed to start:', err); }\r\n    }\r\n  }\r\n\r\n  boot();\r\n  if (document.readyState === 'loading') {\r\n    document.addEventListener('DOMContentLoaded', boot);\r\n  }\r\n  window.addEventListener('load', function () { setTimeout(boot, 50); });\r\n  if (!booted && window.MutationObserver) {\r\n    var mo = new MutationObserver(function () { boot(); if (booted) { mo.disconnect(); } });\r\n    try { mo.observe(document.documentElement, { childList: true, subtree: true }); } catch (e) { }\r\n    setTimeout(function () { try { mo.disconnect(); } catch (e2) { } }, 10000);\r\n  }\r\n\r\n  return { boot: boot };\r\n})();\r\n<\/script>\n\n\n\n \n<style>\n.pth-c37 { max-width: 900px; margin: 38px auto 0; font-family: 'Inter', system-ui, sans-serif; color: #1e293b; line-height: 1.75; }\n.pth-c37 h2 { font-size: 25px; font-weight: 800; color: #0f172a; margin: 32px 0 12px; }\n.pth-c37 h3 { font-size: 18px; font-weight: 700; color: #1e293b; margin: 22px 0 8px; }\n.pth-c37 h4 { font-size: 15px; font-weight: 700; color: #334155; margin: 16px 0 6px; }\n.pth-c37 ul, .pth-c37 ol { margin: 0 0 15px; padding-left: 22px; }\n.pth-c37 li { margin-bottom: 7px; color: #1e293b; font-weight: 500; }\n.pth-c37 a { color: #1d4ed8; font-weight: 600; text-decoration: none; }\n.pth-c37 a:hover { text-decoration: underline; }\n.pth-c37 code { background: #f1f5f9; padding: 2px 6px; border-radius: 4px; font-family: 'Courier New', monospace; font-size: 14px; color: #be123c; }\n.pth-c37 pre { background: #0f172a; color: #e2e8f0; border-radius: 9px; padding: 14px 16px; overflow-x: auto; font-family: 'Courier New', monospace; font-size: 13px; line-height: 1.6; margin: 0 0 16px; }\n.pth-c37-feat { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 12px; margin: 18px 0; }\n.pth-c37-card { border: 1px solid #e2e8f0; border-radius: 10px; padding: 15px 17px; background: #f8fafc; }\n.pth-c37-card b { display: block; color: #0f172a; font-size: 15px; margin-bottom: 4px; }\n.pth-c37-card span { font-size: 14px; color: #475569; font-weight: 500; }\n.pth-c37-steps { counter-reset: ptht; list-style: none; padding-left: 0; }\n.pth-c37-steps li { counter-increment: ptht; position: relative; padding: 5px 0 5px 42px; }\n.pth-c37-steps li::before { content: counter(ptht); position: absolute; left: 0; top: 5px; width: 27px; height: 27px; background: #2563eb; color: #fff; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: 800; font-size: 13px; }\n.pth-c37-tbl { width: 100%; border-collapse: collapse; margin: 0 0 18px; font-size: 14px; }\n.pth-c37-tbl th, .pth-c37-tbl td { border: 1px solid #e2e8f0; padding: 9px 12px; text-align: left; color: #1e293b; }\n.pth-c37-tbl th { background: #f8fafc; font-weight: 700; color: #0f172a; }\n.pth-c37-warn { border-left: 4px solid #b45309; background: #fffbeb; border-radius: 0 8px 8px 0; padding: 13px 17px; margin: 0 0 18px; color: #78350f; font-weight: 500; }\n.pth-c37-links { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 11px; margin: 16px 0; }\n.pth-c37-links a { display: block; border: 1px solid #e2e8f0; border-radius: 10px; padding: 13px 15px; background: #f8fafc; font-weight: 700; }\n.pth-c37-links a:hover { border-color: #2563eb; background: #eff6ff; text-decoration: none; }\n.pth-c37-links a em { display: block; font-style: normal; font-weight: 500; font-size: 13px; color: #475569; margin-top: 3px; }\n.pth-c37-more { max-height: 100%; overflow: visible; }\n@media (max-width: 640px) {\n  .pth-c37-more { max-height: 420px; overflow: hidden; position: relative; }\n  .pth-c37-more.pth-open { max-height: 100%; }\n  .pth-c37-more:not(.pth-open)::after { content: \"\"; position: absolute; left: 0; right: 0; bottom: 0; height: 70px; background: linear-gradient(rgba(255,255,255,0), #ffffff); }\n  .pth-c37-btn { display: block; width: 100%; margin: 10px 0 0; padding: 11px; border: 1px solid #cbd5e1; border-radius: 9px; background: #f1f5f9; color: #0f172a; font-weight: 700; font-size: 14px; cursor: pointer; font-family: inherit; }\n}\n@media (min-width: 641px) { .pth-c37-btn { display: none; } }\n<\/style>\n \n<div class=\"pth-c37\">\n \n<!-- HERO IMAGE PLACEHOLDER - paste the tag below once the image is uploaded to Media Library\n<img decoding=\"async\" src=\"MEDIA_URL_HERE\" alt=\"Unix timestamp being converted into multiple date formats alongside a world clock grid\" width=\"1200\" height=\"630\" loading=\"eager\" fetchpriority=\"high\" data-no-lazy=\"1\">\n-->\n \n<div class=\"pth-c37-feat\">\n  <div class=\"pth-c37-card\"><b>Sixteen formats at once<\/b><span>ISO 8601, RFC 3339, SQL, HTTP date, Excel serial, .NET ticks and Windows FILETIME.<\/span><\/div>\n  <div class=\"pth-c37-card\"><b>Paste anything<\/b><span>Drop in a number or a date string and the format is worked out for you.<\/span><\/div>\n  <div class=\"pth-c37-card\"><b>2038 warning built in<\/b><span>A timestamp past the 32-bit limit is flagged before it reaches your database.<\/span><\/div>\n  <div class=\"pth-c37-card\"><b>Meeting planner<\/b><span>One grid showing when every zone in your list is inside working hours.<\/span><\/div>\n  <div class=\"pth-c37-card\"><b>Duration maths<\/b><span>Add or subtract intervals, and count business days between two dates.<\/span><\/div>\n  <div class=\"pth-c37-card\"><b>No libraries loaded<\/b><span>Built on the date engine already in your browser, so nothing downloads.<\/span><\/div>\n<\/div>\n \n\n\n\n<p class=\"wp-block-paragraph\">Last Updated: August 2026<\/p>\n\n\n\n<h2 id=\"\ud83d\udfe2-more-than-epoch-in-date-out\" class=\"wp-block-heading\">\ud83d\udfe2 More Than Epoch In, Date Out<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Converting&nbsp;<code>1753100000<\/code>&nbsp;into a readable date takes one line of code. The reason a tool is still useful is everything around that: the log file gives you milliseconds, the spreadsheet wants an Excel serial number, the Windows event log hands you an eighteen-digit FILETIME, and the person waiting for your answer is in a different time zone.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This page does the whole set in one place, and it runs on the date engine already built into your browser &#8211; there is no library to download.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Tab<\/th><th>What it handles<\/th><\/tr><\/thead><tbody><tr><td>Convert<\/td><td>Seconds through nanoseconds, sixteen output formats, relative time, format detection on paste<\/td><\/tr><tr><td>World Clock<\/td><td>One instant across many zones with daylight saving marked, plus a working-hours planner<\/td><\/tr><tr><td>Date Calculator<\/td><td>Add or subtract intervals, differences with business days, and notes on awkward local times<\/td><\/tr><tr><td>Batch &amp; Code<\/td><td>A whole column converted to CSV, and conversion snippets in seven languages<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 id=\"\ud83d\udfe1-the-formats-and-where-you-meet-them\" class=\"wp-block-heading\">\ud83d\udfe1 The Formats, And Where You Meet Them<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Most people arrive knowing epoch seconds and nothing else. These are the others worth recognising, because each one comes from somewhere specific.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Format<\/th><th>Looks like<\/th><th>Where it comes from<\/th><\/tr><\/thead><tbody><tr><td>Epoch seconds<\/td><td><code>1753100000<\/code><\/td><td>Unix systems, APIs, most databases<\/td><\/tr><tr><td>Milliseconds<\/td><td><code>1753100000000<\/code><\/td><td>JavaScript and Java<\/td><\/tr><tr><td>ISO 8601<\/td><td><code>2026-07-21T14:30:00Z<\/code><\/td><td>APIs and data interchange<\/td><\/tr><tr><td>SQL DATETIME<\/td><td><code>2026-07-21 14:30:00<\/code><\/td><td>Database columns and query output<\/td><\/tr><tr><td>Excel serial<\/td><td><code>46224.60417<\/code><\/td><td>Spreadsheet cells &#8211; a day count, not seconds<\/td><\/tr><tr><td>.NET ticks<\/td><td><code>639201888000000000<\/code><\/td><td>C# and .NET, counted from year 1<\/td><\/tr><tr><td>Windows FILETIME<\/td><td><code>134290656000000000<\/code><\/td><td>Windows APIs and event logs, counted from 1601<\/td><\/tr><tr><td>Mac absolute<\/td><td><code>806284800<\/code><\/td><td>Apple frameworks, counted from 2001<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The Paste box handles all of these. It reads the magnitude of the number and decides what it must be &#8211; an eighteen-digit value is FILETIME, a five-digit value in the forty-thousands is an Excel serial &#8211; so you rarely have to say which one you have.<\/p>\n\n\n\n<h2 id=\"\ud83d\udd34-the-warning-that-saves-a-migration\" class=\"wp-block-heading\">\ud83d\udd34 The Warning That Saves A Migration<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Enter&nbsp;<code>2147483648<\/code>&nbsp;and a red panel appears.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That number is one past the largest value a signed 32-bit integer can hold. Any system still storing time in a 32-bit field cannot represent it, and instead of a date in 2038 it produces a date in 1901. The tool flags anything approaching that limit, which is worth knowing before a column of future expiry dates goes into a legacy table.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A negative timestamp gets flagged too. Dates before 1970 are valid Unix time and are rejected outright by some languages and database drivers, which is a common surprise when importing historical records.<\/p>\n\n\n\n<h2 id=\"\ud83d\udfe2-using-the-meeting-planner\" class=\"wp-block-heading\">\ud83d\udfe2 Using The Meeting Planner<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Set the instant on the Convert tab, then open World Clock and add the zones you care about. The grid shows one full day across every zone at once.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\ud83d\udd35 Green means that hour falls inside working hours on a weekday in that zone.<\/li>\n\n\n\n<li>\ud83d\udfe0 Amber marks the hour either side, which is usually workable if someone is willing.<\/li>\n\n\n\n<li>\ud83d\udfe3 A dark cell means every zone in your list is inside working hours at the same moment &#8211; that is your meeting slot.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">The DST badge next to each zone matters more than it looks. Daylight saving shifts a zone by an hour twice a year, and the shift dates differ between countries. A slot that works in June may not work in November, and the badge tells you which state you are currently looking at.<\/p>\n\n\n\n<h2 id=\"\ud83d\udfe1-two-jobs-worth-knowing\" class=\"wp-block-heading\">\ud83d\udfe1 Two Jobs Worth Knowing<\/h2>\n\n\n\n<h3 id=\"making-sense-of-a-log-file\" class=\"wp-block-heading\">Making sense of a log file<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Copy the timestamp column out of the log, paste it into the Batch tab, choose your output format and press Convert All. Detection handles the seconds-or-milliseconds question automatically. Download the CSV and paste the readable column beside the original.<\/p>\n\n\n\n<h3 id=\"working-out-when-something-actually-expires\" class=\"wp-block-heading\">Working out when something actually expires<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Paste the expiry timestamp, read the relative line &#8211; it says something like&nbsp;<code>in 4 months<\/code>&nbsp;&#8211; and check the 2038 warning has not fired. Then use the Date Calculator to work out the renewal date thirty days before it.<\/p>\n\n\n\n<h2 id=\"\ud83d\udd34-common-mistakes-and-real-limits\" class=\"wp-block-heading\">\ud83d\udd34 Common Mistakes and Real Limits<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\ud83d\udd35\u00a0<strong>Confusing seconds with milliseconds.<\/strong>\u00a0A ten-digit number is seconds; thirteen digits is milliseconds. Feed milliseconds into a seconds field and you land somewhere around the year 57000.<\/li>\n\n\n\n<li>\ud83d\udfe0\u00a0<strong>Assuming an offset is a time zone.<\/strong>\u00a0<code>+05:30<\/code>\u00a0is an offset at one moment.\u00a0<code>Asia\/Colombo<\/code>\u00a0is a zone with a full history of rule changes. Storing the offset alone loses the information needed for future dates.<\/li>\n\n\n\n<li>\ud83d\udfe3\u00a0<strong>Excel serial numbers are days, not seconds.<\/strong>\u00a0They also count from 1899, and the spreadsheet&#8217;s own leap-year quirk is built into the number. Never treat one as an epoch value.<\/li>\n\n\n\n<li>\ud83d\udd35\u00a0<strong>Business days here exclude weekends only.<\/strong>\u00a0Public holidays differ by country and are not applied, so treat the count as a starting figure rather than a payroll answer.<\/li>\n\n\n\n<li>\ud83d\udfe0\u00a0<strong>Leap seconds are not counted.<\/strong>\u00a0Unix time deliberately ignores them, so a duration across a leap second is off by a second. That matches every standard system and is almost never a problem outside scientific work.<\/li>\n\n\n\n<li>\ud83d\udfe3\u00a0<strong>Some local times do not exist.<\/strong>\u00a0On the morning clocks jump forward, an hour is skipped entirely. The Date Calculator tab tells you when the time you entered is one of those, instead of quietly moving it.<\/li>\n<\/ul>\n\n\n\n\n<style>\n.pth-faq-wrap { max-width: 900px; margin: 30px auto 0; font-family: 'Inter', system-ui, sans-serif; }\n.pth-faq-title { font-size: 25px; font-weight: 800; color: #0f172a; margin: 0 0 16px; }\n.pth-faq-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 13px; }\n.pth-faq-card { border: 1px solid #e2e8f0; border-radius: 11px; padding: 16px 18px; background: #f8fafc; }\n.pth-faq-q { font-size: 15px; font-weight: 800; color: #0f172a; margin: 0 0 7px; line-height: 1.45; }\n.pth-faq-a { font-size: 14px; font-weight: 500; color: #334155; margin: 0; line-height: 1.65; }\n@media (max-width: 900px) { .pth-faq-grid { grid-template-columns: repeat(2, 1fr); } }\n@media (max-width: 600px) { .pth-faq-grid { grid-template-columns: 1fr; } }\n<\/style>\n \n<div class=\"pth-faq-wrap\">\n  <p class=\"pth-faq-title\">Frequently Asked Questions<\/p>\n  <div class=\"pth-faq-grid\">\n \n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">Is my data sent anywhere?<\/p>\n      <p class=\"pth-faq-a\">No. Every calculation uses the date engine already in your browser. No library is downloaded and nothing you paste leaves your device.<\/p>\n    <\/div>\n \n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">How do I tell seconds from milliseconds?<\/p>\n      <p class=\"pth-faq-a\">Count the digits. Ten digits is seconds and thirteen is milliseconds for any date near today. The Paste box works this out for you automatically.<\/p>\n    <\/div>\n \n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">What is the 2038 warning about?<\/p>\n      <p class=\"pth-faq-a\">A signed 32-bit integer runs out on 19 January 2038. Systems still using one cannot store a later time and wrap around to 1901 instead, so future dates are flagged before you save them.<\/p>\n    <\/div>\n \n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">Why is an Excel serial number so small?<\/p>\n      <p class=\"pth-faq-a\">Because it counts days rather than seconds, starting from 1899. A date in 2026 is roughly forty-six thousand days later, which is why the number looks nothing like an epoch value.<\/p>\n    <\/div>\n \n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">Can I convert a whole column at once?<\/p>\n      <p class=\"pth-faq-a\">Yes. Paste one timestamp per line into the Batch tab, pick an output format, and download the result as a CSV with the original and converted values side by side.<\/p>\n    <\/div>\n \n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">Does the meeting planner handle daylight saving?<\/p>\n      <p class=\"pth-faq-a\">Yes. Each zone shows whether daylight saving is active on the selected date, and the grid uses the real offset for that date rather than a fixed one.<\/p>\n    <\/div>\n \n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">Are public holidays counted in business days?<\/p>\n      <p class=\"pth-faq-a\">No. Only Saturday and Sunday are excluded, because holidays vary by country and region. Treat the figure as a starting point rather than a final answer.<\/p>\n    <\/div>\n \n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">What happens with a date before 1970?<\/p>\n      <p class=\"pth-faq-a\">It converts normally and produces a negative timestamp, which is valid Unix time. A warning appears because some languages and database drivers refuse negative values.<\/p>\n    <\/div>\n \n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">Can I schedule a recurring job here?<\/p>\n      <p class=\"pth-faq-a\">No, this page converts single instants. Recurring schedules are cron expressions, which the Cron Studio Pro builds, explains in plain English and previews.<\/p>\n    <\/div>\n \n  <\/div>\n<\/div>\n \n","protected":false},"excerpt":{"rendered":"<p>Instant Unix Timestamp Converter &amp; Studio \u2014 Fast, Accurate Epoch Conversions (100% Offline) Convert Unix Epoch timestamps to human-readable dates and vice versa with our 100% offline Unix Epoch Time Converter. Features a live epoch clock and UTC support. \u00a0 Sixteen formats at onceISO 8601, RFC 3339, SQL, HTTP date, Excel serial, .NET ticks and &#8230; <a title=\"Free Unix Epoch Time Converter &amp; Timestamp Studio &#8211; Offline Tool\" class=\"read-more\" href=\"https:\/\/schoolict.net\/tools\/unix-epoch-time-converter\/\" aria-label=\"Read more about Free Unix Epoch Time Converter &amp; Timestamp Studio &#8211; Offline Tool\">Read more<\/a><\/p>\n","protected":false},"author":1,"featured_media":2235,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-2232","page","type-page","status-publish","has-post-thumbnail"],"_links":{"self":[{"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/pages\/2232","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/comments?post=2232"}],"version-history":[{"count":2,"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/pages\/2232\/revisions"}],"predecessor-version":[{"id":6990,"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/pages\/2232\/revisions\/6990"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/media\/2235"}],"wp:attachment":[{"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/media?parent=2232"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}