Skip to content

Instantly share code, notes, and snippets.

@lroman242
Created May 5, 2024 13:13
Show Gist options
  • Save lroman242/c870cead4d1ee7536f91a4c1055a3e6c to your computer and use it in GitHub Desktop.
Save lroman242/c870cead4d1ee7536f91a4c1055a3e6c to your computer and use it in GitHub Desktop.
GCLID decode / get timestamp from GCLID
<?php
/**
* Function decodes GCLID (Google Click ID) and extract timestamp from it.
*
* @param string $gclid
*
* @return int|null
*/
function getTimestampFromGclid(string $gclid): ?int
{
// Decode the base64/web encoded gclid
$decodedBytes = base64_decode(str_replace(['-', '_'], ['+', '/'], $gclid));
// Check if the gclid is valid and determine its type
$type = ord($decodedBytes[0]);
switch ($type) {
case 0x08: // Old gclid Type with Microsecond-Timestamp
if (strlen($decodedBytes) >= 9) {
$bytes = substr($decodedBytes, 1, 8); // Extract the timestamp at offset 1
$microseconds = decodeVLQ($bytes); // Decode timestamp bytes into integer
return (int)ceil($microseconds / 1000000); // Convert to seconds
}
break;
case 0x10: // Version with Old gclid Message in a Sub-Sub-Message
$oldMessageOffset = strpos($decodedBytes, "\x08", 8); // Find timestamp offset
if ($oldMessageOffset !== false && strlen($decodedBytes) >= $oldMessageOffset + 9) {
$bytes = substr($decodedBytes, $oldMessageOffset + 1, 8); // Extract the timestamp at offset $oldMessageOffset + 1
$microseconds = decodeVLQ($bytes); // Decode timestamp bytes into integer
return (int)ceil($microseconds / 1000000); // Convert to seconds
}
break;
case 0x0A: // gclid version with Seconds-Since-Epoch
if (strlen($decodedBytes) >= 9) {
$bytes = substr($decodedBytes, 5, 8); // Extract the timestamp at offset 5
return decodeVLQ($bytes); // Decode timestamp bytes into integer (already in seconds)
}
break;
}
return null; // Invalid gclid
}
/**
* Decode binary data including variable-length quantities.
*
* @param $bytes
*
* @return int
*/
function decodeVLQ($bytes): int
{
// $val = 0;
// foreach (str_split($bytes) as $i => $byte) {
// $val = PHP_INT_SIZE < 8 && function_exists('bcadd') ?
// bcadd($val, bcmul(ord($byte) & 0x7f, bcpow(2, $i * 7))) :
// $val + ((ord($byte) & 0x7f) * pow(2, $i * 7));
// if (!(ord($byte) & 0x80)) break;
// }
// return $val;
// Unpack the bytes using the 'C*' format to treat each byte as an unsigned char
$unpacked = unpack('C*', $bytes);
$val = 0;
$shift = 0;
// Iterate over each unpacked byte
foreach ($unpacked as $byte) {
// Add the lower 7 bits of the byte to the decoded value
$val |= ($byte & 0x7F) << $shift;
// Shift by 7 bits for the next byte
$shift += 7;
// If the MSB is 0, stop decoding (end of the VLQ)
if (!($byte & 0x80)) {
break;
}
}
return $val;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment