Should Unix Time Stamp change when Formatted as a number?

I’m creating some Order Numbers that I’d like to be a bit “Smarter” than just random strings. To do this, I’m concatenating the contents of various Custom States and inputs. However, to add an element of uniqueness, I’ve extracted the final 6 digits of the the Unix Time Stamp of the Current time. To append this into my Order Number however, I have to use the following:

Screenshot 2023-03-16 152313

In which “CurrentTime6” is a Custom State that contains the Unix Time Stamp of the Current Time.
This step then takes that CurrentTime6’s value, formats it as text and extracts the final
6 characters onto which I append other, more meaningful data, in future steps.

The weird thing is that the number that’s generated by this seems to bear no resemblance to the actual Current Time’s Unix Time Stamp.

So. for instance, at the time of writing this, the current time’s Unix time stamp is

1678980510 according to time.is

But the number generated by this workflow (which I had expected to be the last 6 digits of this) is

495029

And given that the Time Stamp only changes each second, I can’t imagine why the number is quite so different.

Can anyone shed any light on this? I guess, ultimately, it doesn’t really matter because it still seems to change enough to be useful.

Thank you for reading this far as I know that was horribly dull but I hope it at least makes some sense!
All the best
Joe

Hey @joefarrowsmith - so, first, I’m apparently required by law (because I cannot resist responding to posts like this) to tell you that, of course, your Order already has a unique ID (Order's unique id). So of course from a Bubble perspective you would, in general, never use your own substitute.

Now, of course, perhaps you don’t like the look of Bubble unique IDs and you want a more human-readable “unique-ish” ID, because some customer might want to call about order “blah de blah” and you don’t want them to have to verbally repeat 31 numbers with a small x in between them.

And since an Order is attached to some Customer and further orders happen on a specific date (like 3/16/2023), we don’t things to be wildly random to be able to confidently narrow down a customer’s order by doing a search for Orders, constrained to created by (or otherwise associated with) some Customer. (Should there be a collision and we find multiple orders with the same “Order ID”, well we can differentiate the two by date, most like.)

But again, unless our Order ID is truly like a universally unique ID and at least rivals Bubble’s in terms of collision resistance, we would not use our Order ID as a substitute for Bubble’s Unique ID. The Unique ID is the Thing and assures that object’s uniqueness in our app. Your Order ID is not.

(And, never fear, I will answer your specific question about what’s happening to your date strings after some fun facts.)

Bubble unique IDs are very entropic . And they actually encode the current time when they are generated. The format of a Bubble unique ID is a string that is basically:

current UNIX time x an 18-digit string of random numbers

So, Bubble unique IDs already encode the creation time of the object (or, more precisely the moment that the unique ID generator function is called. The actual function used to generate unique IDs is as follows (Date.now() is the same thing as “current date/time :extract UNIX (ms)” as you know it in Bubble and function d() there is basically a random number generator that gives us the second part):

function u() {
    const e = Math.round(Math.random() * c);
    return `${Date.now()}x${d(e, 18)}`
}

A couple of observations here:

First, understand that dates are moments in time. The resolution of the JavaScript date object (and Bubble date objects, which are the same thing) is in milliseconds and this value represents the number of milliseconds that have passed since the UNIX epoch (00:00:00 UTC on January 1, 1970). This number is asymptotically increasing to the maximum JavaScript date (which is September 13, 275760). Currently the number of digits in the UNIX time value is 13 digits.

Note, however, that the last ‘n’ digits of this number periodically repeat. So, consider the last 6 digits. Those 1 million values represented by the last 6 digits represent 1 million milliseconds. One million milliseconds transpires every 1000 seconds. So these numbers actually repeat every 16.66 minutes. Which means that more than 86 points of time during the day where every single one of these numbers will appear.

Consider further that a bunch of Orders created at the same time will share this same 6 digit value. As an example, suppose multiple orders are created at the start of the day (the UNIX time of the start of today UTC was 1678950000000), see how not just SIX, but SEVEN of the digits are all zero?

So this is not the same as using a 6-digit random number. And there’s much too much collision danger in the approach of lopping off the last 6 digits of some date value.

Now, let me digress for a moment and answer your original question:

You’re setting the value of a custom state to “Current Date Time” and then doing some manipulations on that to get a not-very-random value. What that does is get that moment in time as UNIX ms. It does that at the moment the expression is evaluated. It doesn’t keep increasing. But if you evaluate Current Date Time some time later, the values will have advanced (well the full time value will have advanced, the last 6 digits will be… different… except when they are not).

You’re just having a minor user headspace error. 495029 were the last 6 digits of Current Date Time when you evaluated the expression and stored it in the state. Of course the time value is different now when we again evaluate Current Date Time. (However, those last 6 digits could have been the same – coincidentally – as that happens about 86 times per day, as I mentioned previously.)

So, the last 6 digits of Current Date Time not being a really good choice here, if a truly random 6 digit string is acceptable, you can generate that using Generate Random String:

Or you could use my Polyhedra plugin to generate these as an actual numeric value.

I know that you intend to add additional stuff to your “Order ID” but consider whether that actually makes the Order IDs more unique or not.

If you just left the Order ID as a 6 digit number string like this, if you generated 1 Order per hour, it would take about 6 days before there’s a 1% chance of collision across your entire customer base – that’s pretty poor as unique identifiers go. (Obviously, this isn’t anything like a Universally Unique ID, but, within a given Customer’s Orders, well, they’d have to buy a decent amount of stuff from you before the collision risk gets significant, but it could still happen. If you only have 2 orders, the change of collision is literally “one in a million” which is 0.001%, but you see that this rises quite quickly!)

It’s actually slightly hard for me to calculate exactly what the collision probability is with Bubble unique ID’s (I believe it’s rather higher than a real UUID – meaning that it’s more collision prone – but I could be wrong on that [the combination of encoding time with increases the entropy in a global sense, but consider Bubble unique IDs generated at the same UNIX time have “only” an 18 digit string different between them, which I think means that if about 150 million unique IDs were generated with the same timestamp part, there’s roughly a 1% chance of a collision.

Anyway, what do we do if wanted to make something that, at least at our system level, is sufficiently unique?

Well, we could expand our character set. Making the character set bigger greatly increases the entropy of our ID. For example, if we were using NanoID (which is a very tiny crypto-random library that’s included with List Shifter and I’m going to add it to my Polyhedra plugin in a much easier and customizable form), we would find that a 6 character string generated by that library using the 26 glyphs capital A thru capital Z, at one order per hour, it would take 104 days to reach a 1% probability of collision.

Well, that’s better than 6 days, eh?

But check it out: Increasing the length of the string has a similar effect on entropy. If we expanded our string length to 16 characters A-Z (which, consider, we could format with hyphens every four letters for a quite easy to read and speak “unique ID”), we find that at one order per hour, it would take 3 million years before we had even 1% chance of a collision. At 1000 orders per hour, it would take 3000 years before there was a 1% collision chance.

Anyway, even without using Nano ID and just using Bubble’s generate random string with a length of 16 characters and cap letters A-Z the odds would be roughly the same:

Or we could store hyphens in between each 4 letters for a very nicely readable ID:

The contents of each Calculate RandomString expression is:

And then your order IDs would look like:
image

You can explore the effect of different character sets and ID length at the very interesting and fun NanoID Collision Calculator page.

Like I was saying, I’ll soon be adding NanoID to Polyhedra just for fun, but here’s an example using List Shifter’s PROCESS List function to generate NanoIDs (note that, in List Shifter, you can control the length of the string, but the alphabet is always the full set of URL-safe characters… Polyhedra will let you also pick your alphabet):

Here’s the example page:

Runtime: https://list-shifter-demo.bubbleapps.io/version-test/list-shifter-nanoid?debug_mode=true

Editor: List-shifter-demo | Bubble Editor

tl;dr An order ID of 4 random letters dash 4 random letters dash 4 random letters dash 4 random letters like:

Order ID: YFGY-QKBL-RMRN-UTNR

… has suitable entropy to be plausibly unique for thousands of years. So just do that and be done with it. (But, for statistics reasons, I actually wouldn’t go any lower than that).

4 Likes

Also, I’d like to thank the OP for not asking about guaranteeing sequential serial numbers, which is both unnecessary and computationally expensive to do in Bubble.

I feel like I just attended a TED Talk! That was great, Keith!

I’ve discovered one downside to using stock standard Bubble unique IDs with an external API:

I was trying to use them as unique order IDs with a payment processor, but they use a Luhn algorithm that would sometimes incorrectly identify the Bubble unique ID as containing credit card numbers! Obviously it’s a randomly generated string so that shouldn’t happen much but it failed often enough when used for thousands of transactions that it caused a real headache until I found out why my (supposedly thoroughly tested) API connection would sometimes mysteriously fail.

1 Like

That’s pretty interesting, @BrianHenderson. If I were building a system like Bubble I’d use NanoId and the full set of URL-safe characters (which at the default 21 characters exceeds the entropy of UUID and is faster to generate). In fact, in Calendar Grid Pro (which has unique CSS classes for each one you might put on a page, I use default NanoID strings to make them unique). There’s literally 0 chance that a CG Pro will share a CSS string with another CG Pro on the same page until the heat death of the universe.

@keith Wow! Thank you! I’m properly in awe of your answer which is so rich in so many ways. I’m also delighted that my “Sponge Bob” brain managed to keep up but I think that’s down to your writing skills in relaying this information.

You read my mind about not wanting a User to contact me and have to relay Bubble’s UniqueID and my thoughts were that my self-generated, UniqueID is “good enough” for my app’s relatively humble purposes. The full ID that I’m creating is a single string that consists of, in no particular order,

  • a PIN that identifies the seller the order is made at which is known to the seller (so that if a dastardly, Plankton-like user tries to invent an order number, the seller can more likely spot whether it’s bull or not)
  • a chunk of the UnixTimeStamp, (duly contorted by my methods)…
  • A few randomly generated characters
  • Something else (which I can’t actually remember right now due to lack-of-coffee-induced-User-brainfart)

I felt that welding all this together in one string is “good enough” for my purposes given that I don’t expect my app to have so many users making so many orders from so many sellers (defeatist, I know!) so that if there is a collision, it’ll be a rare enough occurrence that it’s an acceptable risk. (What’s more, I might even be able to put in a mechanism that catches any OrderRef duplication and alters the Unique ID although I’m thinking out loud here). Additionally, the Bubble UniqueID will still exist on each record and I do rely on that for the machinations of my app. So, I guess, in short, I’m hoping that my own generated Unique ID is good enough for a User/admin communication purposes but time will tell.

If, on the other hand, my app turns out to be a work of genius and starts growing out of control and the chance of collisions does grow, then I will employ the relevant people who have a better understanding of these things than I do to take care of it for me while I sip Martinis in my sub-oceanic lair.

Anyway, really I wanted to say thank you. I’m blown away! This really is a truly wonderful community.
All the best and have a lovely weekend.
Joe

1 Like