|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>Snowflake ID Decoder</title> |
|
|
<style> |
|
|
body { |
|
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; |
|
|
background-color: #f4f4f9; |
|
|
color: #333; |
|
|
display: flex; |
|
|
justify-content: center; |
|
|
align-items: center; |
|
|
min-height: 100vh; |
|
|
margin: 0; |
|
|
} |
|
|
.container { |
|
|
background: #fff; |
|
|
padding: 2rem; |
|
|
border-radius: 8px; |
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); |
|
|
width: 100%; |
|
|
max-width: 500px; |
|
|
} |
|
|
h1 { |
|
|
color: #1a1a1a; |
|
|
margin-bottom: 1.5rem; |
|
|
font-size: 1.75rem; |
|
|
text-align: center; |
|
|
} |
|
|
.input-group { |
|
|
margin-bottom: 1.5rem; |
|
|
} |
|
|
label { |
|
|
display: block; |
|
|
margin-bottom: 0.5rem; |
|
|
font-weight: 600; |
|
|
color: #555; |
|
|
} |
|
|
input[type="text"], input[type="number"], input[type="datetime-local"] { |
|
|
width: 100%; |
|
|
padding: 0.75rem; |
|
|
border: 1px solid #ccc; |
|
|
border-radius: 4px; |
|
|
font-size: 1rem; |
|
|
box-sizing: border-box; |
|
|
transition: border-color 0.3s, box-shadow 0.3s; |
|
|
} |
|
|
input:focus { |
|
|
border-color: #007bff; |
|
|
box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.25); |
|
|
outline: none; |
|
|
} |
|
|
.output-group { |
|
|
margin-top: 1.5rem; |
|
|
border-top: 1px solid #eee; |
|
|
padding-top: 1.5rem; |
|
|
} |
|
|
.output-item { |
|
|
display: flex; |
|
|
justify-content: space-between; |
|
|
margin-bottom: 0.75rem; |
|
|
} |
|
|
.output-label { |
|
|
font-weight: 600; |
|
|
} |
|
|
.output-value { |
|
|
word-break: break-all; |
|
|
} |
|
|
.error { |
|
|
color: #d9534f; |
|
|
font-size: 0.875rem; |
|
|
margin-top: 0.5rem; |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body> |
|
|
|
|
|
<div class="container"> |
|
|
<h1>Snowflake ID Converter</h1> |
|
|
|
|
|
<div class="input-group"> |
|
|
<label for="snowflake-id">Snowflake ID</label> |
|
|
<input type="text" id="snowflake-id" placeholder="Enter a Snowflake ID..."> |
|
|
<div id="snowflake-error" class="error"></div> |
|
|
</div> |
|
|
|
|
|
<div class="output-group"> |
|
|
<h3>Decoded Components</h3> |
|
|
<div class="output-item"> |
|
|
<span class="output-label">Timestamp (ISO):</span> |
|
|
<span id="readable-time" class="output-value">-</span> |
|
|
</div> |
|
|
<div class="output-item"> |
|
|
<span class="output-label">Timestamp (ms):</span> |
|
|
<span id="timestamp" class="output-value">-</span> |
|
|
</div> |
|
|
<div class="output-item"> |
|
|
<span class="output-label">Machine ID:</span> |
|
|
<span id="machine-id" class="output-value">-</span> |
|
|
</div> |
|
|
<div class="output-item"> |
|
|
<span class="output-label">Sequence ID:</span> |
|
|
<span id="sequence-id" class="output-value">-</span> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<details style="margin-top: 2rem;"> |
|
|
<summary style="cursor: pointer; font-weight: 600; margin-bottom: 1rem;">Manually Construct Snowflake ID</summary> |
|
|
|
|
|
<div class="input-group"> |
|
|
<label for="construct-timestamp">Timestamp (ms)</label> |
|
|
<input type="number" id="construct-timestamp" placeholder="Unix timestamp in milliseconds"> |
|
|
</div> |
|
|
<div class="input-group"> |
|
|
<label for="construct-machine">Machine ID (0-1023)</label> |
|
|
<input type="number" id="construct-machine" min="0" max="1023" placeholder="e.g., 1"> |
|
|
</div> |
|
|
<div class="input-group"> |
|
|
<label for="construct-sequence">Sequence ID (0-4095)</label> |
|
|
<input type="number" id="construct-sequence" min="0" max="4095" placeholder="e.g., 0"> |
|
|
</div> |
|
|
</details> |
|
|
|
|
|
</div> |
|
|
|
|
|
<script> |
|
|
|
|
|
|
|
|
|
|
|
const SNOWFLAKE_EPOCH = 1288834974657n; |
|
|
|
|
|
const snowflakeInput = document.getElementById('snowflake-id'); |
|
|
const snowflakeError = document.getElementById('snowflake-error'); |
|
|
|
|
|
const readableTimeOutput = document.getElementById('readable-time'); |
|
|
const timestampOutput = document.getElementById('timestamp'); |
|
|
const machineIdOutput = document.getElementById('machine-id'); |
|
|
const sequenceIdOutput = document.getElementById('sequence-id'); |
|
|
|
|
|
const constructTimestampInput = document.getElementById('construct-timestamp'); |
|
|
const constructMachineInput = document.getElementById('construct-machine'); |
|
|
const constructSequenceInput = document.getElementById('construct-sequence'); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function decodeSnowflake(snowflakeId) { |
|
|
if (snowflakeId < 0n) return false; |
|
|
|
|
|
|
|
|
const timestampComponent = snowflakeId >> 22n; |
|
|
const timestampMs = timestampComponent + SNOWFLAKE_EPOCH; |
|
|
|
|
|
|
|
|
if (timestampMs < SNOWFLAKE_EPOCH || timestampMs > BigInt(new Date().getTime()) + 86400000n) { |
|
|
|
|
|
} |
|
|
|
|
|
const machineId = (snowflakeId >> 12n) & 0x3FFn; |
|
|
const sequenceId = snowflakeId & 0xFFFn; |
|
|
|
|
|
const date = new Date(Math.floor(Number(timestampMs))); |
|
|
|
|
|
const isoString = isNaN(date.getTime()) ? "Invalid Date" : date.toISOString(); |
|
|
|
|
|
return { |
|
|
readable_time: isoString, |
|
|
timestamp: timestampMs.toString(), |
|
|
machine_id: machineId.toString(), |
|
|
sequence_id: sequenceId.toString() |
|
|
}; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function encodeSnowflake(timestampMs, machineId, sequenceId) { |
|
|
if (timestampMs <= 0n || machineId < 0n || machineId > 1023n || sequenceId < 0n || sequenceId > 4095n) { |
|
|
return false; |
|
|
} |
|
|
const timeComponent = timestampMs - SNOWFLAKE_EPOCH; |
|
|
if (timeComponent < 0n) return false; |
|
|
|
|
|
return (timeComponent << 22n) | (machineId << 12n) | sequenceId; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function handleSnowflakeDecode() { |
|
|
const value = snowflakeInput.value; |
|
|
if (!value) { |
|
|
clearDecodedOutputs(); |
|
|
snowflakeError.textContent = ''; |
|
|
return; |
|
|
} |
|
|
|
|
|
if (!/^\d+$/.test(value)) { |
|
|
snowflakeError.textContent = 'Invalid input. Snowflake IDs must be numbers.'; |
|
|
clearDecodedOutputs(); |
|
|
return; |
|
|
} |
|
|
|
|
|
snowflakeError.textContent = ''; |
|
|
try { |
|
|
const snowflakeId = BigInt(value); |
|
|
const components = decodeSnowflake(snowflakeId); |
|
|
|
|
|
if (!components) { |
|
|
snowflakeError.textContent = 'Invalid Snowflake ID.'; |
|
|
clearDecodedOutputs(); |
|
|
return; |
|
|
} |
|
|
|
|
|
readableTimeOutput.textContent = components.readable_time; |
|
|
timestampOutput.textContent = components.timestamp; |
|
|
machineIdOutput.textContent = components.machine_id; |
|
|
sequenceIdOutput.textContent = components.sequence_id; |
|
|
|
|
|
|
|
|
constructTimestampInput.value = components.timestamp; |
|
|
constructMachineInput.value = components.machine_id; |
|
|
constructSequenceInput.value = components.sequence_id; |
|
|
|
|
|
} catch (e) { |
|
|
console.error(e) |
|
|
snowflakeError.textContent = 'An unexpected error occurred.'; |
|
|
clearDecodedOutputs(); |
|
|
} |
|
|
} |
|
|
|
|
|
function handleConstructSnowflake() { |
|
|
const timestamp = constructTimestampInput.value; |
|
|
const machine = constructMachineInput.value; |
|
|
const sequence = constructSequenceInput.value; |
|
|
|
|
|
if (timestamp && machine && sequence) { |
|
|
try { |
|
|
const timestampBig = BigInt(timestamp); |
|
|
const machineBig = BigInt(machine); |
|
|
const sequenceBig = BigInt(sequence); |
|
|
|
|
|
const newSnowflake = encodeSnowflake(timestampBig, machineBig, sequenceBig); |
|
|
|
|
|
if (newSnowflake === false) { |
|
|
snowflakeError.textContent = 'Component values are out of valid range.'; |
|
|
} else { |
|
|
snowflakeInput.value = newSnowflake.toString(); |
|
|
handleSnowflakeDecode(); |
|
|
} |
|
|
|
|
|
} catch (e) { |
|
|
snowflakeError.textContent = 'An unexpected error during construction.'; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
function clearDecodedOutputs() { |
|
|
readableTimeOutput.textContent = '-'; |
|
|
timestampOutput.textContent = '-'; |
|
|
machineIdOutput.textContent = '-'; |
|
|
sequenceIdOutput.textContent = '-'; |
|
|
} |
|
|
|
|
|
|
|
|
snowflakeInput.addEventListener('input', handleSnowflakeDecode); |
|
|
constructTimestampInput.addEventListener('input', handleConstructSnowflake); |
|
|
constructMachineInput.addEventListener('input', handleConstructSnowflake); |
|
|
constructSequenceInput.addEventListener('input', handleConstructSnowflake); |
|
|
|
|
|
</script> |
|
|
|
|
|
</body> |
|
|
</html> |
|
|
|