Which bits of the timestamp does the v1 branch on this page actually read
The timestamp segment of the v1 branch on this page looks like a 60-bit field, but once it is run millisecond by millisecond what is really read turns out to be a single 32-bit window. generateV1 sits on lines 126 to 162 of the template; line 131 computes ts = (now - the 1582 epoch) * 10000, and lines 134, 135 and 136 then take ts & 0xFFFFFFFF, (ts >>> 32) & 0xFFFF and (ts >>> 48) & 0x0FFF. Every statement here is about this page and this branch only, and none of it is extended to other UUID libraries.
Shift counts wrap modulo 32: 60 bits collapse into 28 positions, 24 that change
The shift count of >>> is taken modulo 32, so >>> 32 behaves as >>> 0 and >>> 48 behaves as >>> 16. Over 20000 consecutive milliseconds both timeMid === (timeLow & 0xFFFF) and timeHi === ((timeLow >>> 16) & 0x0FFF) held, 20000/20000 times: time_mid is nothing but a re-read of the low 16 bits of time_low, and the low 12 bits of time_hi_and_version are a re-read of bits 16 to 27 of that same window. Take the union of every bit the three fields touch and it is bits 0 through 27, 28 positions in all; nothing at or above bit 32 ever reaches the output.
Even fewer bits move than the 28 that are read. Scanning 2000000 millisecond steps gives a bitwise OR of 0xfffffff0 and a bitwise AND of 0, which pins bits 0 to 3 at zero all the time; intersect that with the 28 positions the fields actually read and only 24 bits really change. Line 138, timeHi | 0x1000, then nails the version nibble to a constant one on top of that.
Figure 1: all three reads re-read the same window
What that costs in practice is easy to state without any formula. A real version-one timestamp is supposed to count hundred-nanosecond ticks since the epoch that branch defines for itself, while here every bit above the low word is thrown away before the string is assembled, so two moments that differ only high in the word collapse onto the same segment.
The stuck-at-zero low nibble lands on character 8
The timestamp is always a multiple of 16: over 20000 milliseconds ts % 16 === 0 held 20000/20000 times, while ts % 32 splits evenly between 0 and 16 at 10000 each - being "a multiple of 32" is only true on half of the milliseconds, which is why a spot check is not a proof. Carried onto the string, the tail of the time_low segment, that is character 8, came out as a constant zero in all 20000 samples and in an aligned 100-line batch. The very last character of the whole string, the low nibble of the node segment, is the one that really spreads over 16 values, and it printed a zero only 3/100 times.
timeLow goes through ToInt32: a positive and a negative leg that flip by the millisecond
timeLow = ts & 0xFFFFFFFF is a signed Int32, so neighbouring milliseconds hand out positives and negatives alike. The full cycle is 2^32 / 10000 = 429496.7296 ms, that is 7.1583 minutes, and the negative half of it is 2^31 / 10000 = 214748.3648 ms, that is 3.5791 minutes. On a real, unstubbed clock the first flip needed only 109614 ms; a scan of 2400000 milliseconds puts negatives at 1242928 steps = 51.79% of them. The impression that a negative shows up once a year is only an artefact of sampling one moment per year.
Figure 2: the period and the shape of the negative leg
The negative leg does not change the length of a line, only what is inside it. For the deep case -68b10840 the minus signs sit at indexes [0,8,13,18,23]. For the shallow case -1b80 the leading minus is pushed to index 3, because line 153 runs padStart(8,'0') and the padding zeros are inserted ahead of the sign rather than after it. Line 98, substr(20,12), consumes only the first 32 characters and silently drops the last nibble of the node segment, so 31 hex digits survive; a batch of 1000 negative lines distributes as {36:1000} over lengths and {5:1000} over minus signs per line.
The rounding boundary sits at 2^57, not 2^53
Right now ts already exceeds 2^53 by about 15.56 times and falls between 2^56 and 2^57, where the spacing between representable doubles, the ULP, is exactly 16. Since diff × 10000 is always a multiple of 16, because 10000 = 16 × 625, the product already lands on that grid, so a run of 1000 milliseconds today rounds 0 lines. Real rounding waits until ts crosses 2^57 and the ULP widens to 32: stubbed to 2039-06-20T23:40:07.585Z, over the next 1000 milliseconds 499 lines are rounded, and the run stubbed at 2045-01-01 gives 500. The 2^53 mark corresponds to the year 1611, a moment this code path can never arrive at.
Input truth table: the count box, the switches and the two masks
| Input item | How it was measured | Result |
|---|---|---|
| 17 inputs typed into the count box | Three-stage fallback: convert to an integer first, then || 1, then clamp between 1 and 100 | 0, an empty string and "abc" each produce 1 line and write 1 back into the box; 101, 1000 and 1000000 each produce 100 lines and write 100 back; "2.7" truncates to 2 lines |
| The three switches in 8 combinations | Length is decided by hyphens and braces alone; case never changes it | 32 / 34 (braces added) / 36 (hyphens added) / 38 (both added) code units, four values only |
| The two masks of the v4 branch | Lines 115, 116 | Character 15 is a constant 4, 100/100; character 20 falls in 8/9/a/b with distribution {8:26, 9:23, a:29, b:22} |
nodeBytes[0] | 0x01 | Line 149 sets the low bit of the first byte | It lands on character 2 of the node segment, odd 1000/1000 times, while character 1 comes out odd only 517/1000 times |
Batches and clearing the board
Stubbed into a single millisecond with count=100, the 100 v1 lines come out identical over their first 14 characters, distinct=1, and every difference comes from the 4 characters of clock_seq and the 12 characters of the node segment. On a real clock, 1000 calls to generateV1() span 14 ms, with distinct=13 for the time_low segment, distinct=966 for the clock_seq segment and 1000 for the whole string. Clearing the board leaves residue: once lastUuids is emptied, renderList() only writes the result card display to none and resets the statistics cell to zero, while the innerHTML of the result container still holds 1285 code units.
Against the listing page copy
Compared entry by entry with the intro text in tools.php there are 25 judgements: 11 agree, 4 conflict and 10 partially agree. The conflict that matters most is the sentence "8-4-4-4-12, 32 hexadecimal characters in all": an aligned batch does line up with 32, but on this page the negative leg leaves 31. The other one is that the keywords list spells out GUID while the template never contains that word anywhere. The claim that v1 is generated from the timestamp plus node information is a partial match, because the node segment here is re-rolled random bytes on every call and no network adapter is read at all. The version bit and the variant bit are recorded in the older UUID generator article, and its scenario and probability narrative is not repeated here; the random segment on this page belongs to the same interface family as random password generation, and the quality of that entropy source was not measured; for the live details see the UUID generator tool page.
FAQ
Q: Is the v1 timestamp segment on this page the standard 60 bits?
No. All three fields draw their values from one 32-bit window, so the bits read are bits 0 through 27, 28 positions, and once the stuck-at-zero low nibble is removed only 24 bits actually change from one millisecond to the next.
Q: Why does a minus sign sometimes appear inside the uuid, with one hex digit missing?
Because timeLow goes through ToInt32 and lands on the negative leg in close to half of all milliseconds, and substr(20,12) then cuts off the final nibble of the node segment, so 31 hex digits are left.