const COBIE_EPOCH = 0; const COBIE_UNITS = { second: 1, xenocycle: 0x10, quantic: 0x100, chronon: 0x1000, eonstrip: 0x10000, megasequence: 0x100000, cosmocycle: 0x1000000, galactic_year: 0x10000000, universal_eon: 0x100000000, celestial_era: 0x1000000000, epoch_of_cosmos: 0x10000000000, cosmic_aeon: 0x100000000000, metaepoch: 0x1000000000000, eternum: 0x10000000000000, infinitum: 0x100000000000000, astralmillennia: 0x1000000000000000 }; const EONSTRIP_NAMES = [ 'Solprime', 'Lunex', 'Terros', 'Aquarion', 'Ventaso', 'Ignisar', 'Crystalos', 'Floraen', 'Faunor', 'Nebulus', 'Astraeus', 'Umbranox', 'Electros', 'Chronar', 'Radiantae', 'Etherion' ]; const MEGASEQUENCE_NAMES = [ 'Azurean Tide', 'Sable Gleam', 'Verdanth Starfall', 'Crimson Dusk', 'Cobalt Frost', 'Amber Blaze', 'Viridian Bloom', 'Argent Veil', 'Helian Rise', 'Nocturne Shade', 'Celestine Aura', 'Pyralis Light', 'Zephyrine Whisper', 'Lustran Bounty', 'Umbral Echo', 'Mythran Epoch' ]; function floorDiv(a, b) { return Math.trunc(a / b); } function parseCobiets(str) { const m = /^([+-]?)([0-9A-Fa-f]+)\.([0-9A-Fa-f]{1,})$/.exec(str.trim()); if (!m) return null; const sign = m[1] === '-' ? -1 : 1; const allDateKeys = [ 'astralmillennia','infinitum','eternum','metaepoch','cosmic_aeon', 'epoch_of_cosmos','celestial_era','universal_eon','galactic_year', 'cosmocycle','megasequence','eonstrip' ]; let rawDateHex = m[2]; if (rawDateHex.length < allDateKeys.length) { rawDateHex = rawDateHex.padStart(allDateKeys.length, '0'); } let dateKeys = [...allDateKeys]; if (rawDateHex.length > allDateKeys.length) { const extraCount = rawDateHex.length - allDateKeys.length; for (let i = 0; i < extraCount; i++) { dateKeys.unshift(null); } } const timeHexRaw = m[3]; const timeHex = timeHexRaw.padStart(4, '0'); const timeKeys = ['chronon', 'quantic', 'xenocycle', 'second']; let total = 0; for (let i = 0; i < rawDateHex.length; i++) { const digit = parseInt(rawDateHex[i], 16); const key = dateKeys[i]; if (key === null) { const power = rawDateHex.length - 1 - i; total += digit * Math.pow(16, power) * COBIE_UNITS['eonstrip']; } else { total += digit * COBIE_UNITS[key]; } } timeHex.split('').forEach((h, i) => { total += parseInt(h, 16) * COBIE_UNITS[timeKeys[i]]; }); return sign * total; } function getTAIOffsetAt(date) { const taiEpoch = new Date('1958-01-01T00:00:00Z'); if (date < taiEpoch) { return 0; } const leapSeconds = [ { date: '1972-01-01T00:00:00Z', offset: 10 }, { date: '1972-07-01T00:00:00Z', offset: 11 }, { date: '1973-01-01T00:00:00Z', offset: 12 }, { date: '1974-01-01T00:00:00Z', offset: 13 }, { date: '1975-01-01T00:00:00Z', offset: 14 }, { date: '1976-01-01T00:00:00Z', offset: 15 }, { date: '1977-01-01T00:00:00Z', offset: 16 }, { date: '1978-01-01T00:00:00Z', offset: 17 }, { date: '1979-01-01T00:00:00Z', offset: 18 }, { date: '1980-01-01T00:00:00Z', offset: 19 }, { date: '1981-07-01T00:00:00Z', offset: 20 }, { date: '1982-07-01T00:00:00Z', offset: 21 }, { date: '1983-07-01T00:00:00Z', offset: 22 }, { date: '1985-07-01T00:00:00Z', offset: 23 }, { date: '1988-01-01T00:00:00Z', offset: 24 }, { date: '1990-01-01T00:00:00Z', offset: 25 }, { date: '1991-01-01T00:00:00Z', offset: 26 }, { date: '1992-07-01T00:00:00Z', offset: 27 }, { date: '1993-07-01T00:00:00Z', offset: 28 }, { date: '1994-07-01T00:00:00Z', offset: 29 }, { date: '1996-01-01T00:00:00Z', offset: 30 }, { date: '1997-07-01T00:00:00Z', offset: 31 }, { date: '1999-01-01T00:00:00Z', offset: 32 }, { date: '2006-01-01T00:00:00Z', offset: 33 }, { date: '2009-01-01T00:00:00Z', offset: 34 }, { date: '2012-07-01T00:00:00Z', offset: 35 }, { date: '2015-07-01T00:00:00Z', offset: 36 }, { date: '2017-01-01T00:00:00Z', offset: 37 } ]; for (let i = 0; i < leapSeconds.length; i++) { const leapDate = new Date(leapSeconds[i].date); if (date < leapDate) { return i === 0 ? 10 : leapSeconds[i - 1].offset; } } return 37; } function toCobiets(date) { const utcSec = floorDiv(date.getTime(), 1000); const taiSec = utcSec + getTAIOffsetAt(date); return taiSec - COBIE_EPOCH; } function fromCobiets(cobiets) { const taiSeconds = cobiets + COBIE_EPOCH; const taiMs = taiSeconds * 1000; let utcMs = taiMs; for (let i = 0; i < 3; i++) { const off = getTAIOffsetAt(new Date(utcMs)); utcMs = taiMs - off * 1000; } return new Date(utcMs); } const UNIT_KEYS = [ 'astralmillennia','infinitum','eternum','metaepoch','cosmic_aeon','epoch_of_cosmos','celestial_era','universal_eon','galactic_year','cosmocycle','megasequence','eonstrip','chronon','quantic','xenocycle','second' ]; function breakdownNonNeg(cob) { let rem = cob, bd = {}; for (let key of UNIT_KEYS) { bd[key] = floorDiv(rem, COBIE_UNITS[key]); rem %= COBIE_UNITS[key]; } return bd; } function formatCobieTimestamp(cobiets) { const sign = cobiets < 0 ? '-' : '+'; const absCob = Math.abs(cobiets); const bd = breakdownNonNeg(absCob); const dateUnits = [ 'astralmillennia','infinitum','eternum','metaepoch','cosmic_aeon','epoch_of_cosmos','celestial_era','universal_eon','galactic_year','cosmocycle','megasequence','eonstrip' ]; let rawDateHex = dateUnits.map(key => bd[key].toString(16)).join(''); rawDateHex = rawDateHex.replace(/^0+/, ''); if (rawDateHex === '') rawDateHex = '0'; const timeHex = [bd.chronon, bd.quantic, bd.xenocycle, bd.second] .map(n => n.toString(16)).join(''); const paddedTimeHex = timeHex.padStart(4, '0'); return sign + rawDateHex + '.' + paddedTimeHex; } const Cobie = { COBIE_EPOCH, COBIE_UNITS, floorDiv, parseCobiets, getTAIOffsetAt, toCobiets, fromCobiets, formatCobieTimestamp, breakdownNonNeg, EONSTRIP_NAMES, MEGASEQUENCE_NAMES }; if (typeof module !== 'undefined' && module.exports) { module.exports = Cobie; } // Expose globally when loaded in a browser environment if (typeof window !== 'undefined') { window.Cobie = Cobie; }