| <!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> |
|
|