InfluxDB 2.0: Group Periods and Sum Up Duration - javascript

I have an InfluxDB (version 2), where an entry is written every second into my bucket, together with an identifier (uuid of the source) and a side value (some domain-specific measurement from 1 to 6). Instead of having a long list of such by-second logs:
2020-05-18T15:57:18 side=1
2020-05-18T15:57:19 side=1
2020-05-18T15:57:20 side=3
2020-05-18T15:57:21 side=3
2020-05-18T15:57:22 side=3
2020-05-18T15:57:23 side=2
2020-05-18T15:57:24 side=2
I'd like to condense those entries, so that I can calculate the duration, for how long side=x held true:
from 2020-05-18T15:57:18 to 2020-05-18T15:57:19 side=1 duration=2s
from 2020-05-18T15:57:20 to 2020-05-18T15:57:22 side=3 duration=3s
from 2020-05-18T15:57:23 to 2020-05-18T15:57:24 side=2 duration=2s
I also restrict the time period by a from/to range. This is my current query:
from(bucket: "mybucket")
|>range(start:2020-01-01T00:00:00.000Z, stop:2020-12-31T00:00:00.000Z
|>filter(fn:(r) => r.identifier == "3c583530-0152-4ed1-b15f-5bb0747e771e")
My approach is to read the raw data, and then iterate over it, detect changes of the side (something like current.side != last.side), and then report that as a new entry to my logbook. This approach is very inefficient, of course (JavaScript):
const data = fetchFromInfluxDB(from, to):
const terminator = { 'time': new Date(), 'identifier': '', 'side': -1 };
data.push(terminator); // make sure last item is also reported
const logbook = new Array();
let lastSide = -1;
let from = undefined;
for (let e of data) {
if (e.side != lastSide) {
if (from != undefined) {
let to = e.time;
let diff = Math.floor((new Date(to) - new Date(from)) / 1000);
if (diff > 0) {
logbook.push({'from': from, 'to': to, 'side': lastSide, 'seconds': diff});
}
}
lastSide = e.side;
from = e.time;
}
}
Is there a way to group and sum up that data using the InfluxDB query language?

Related

Combine array of dates sorted in ascending order into array of date ranges

I have one array of dates, I want to create object containing start and end by checking continue dates.
EX.
dateArray = [
"2020-01-22T00:00:00.000Z",
"2020-01-23T00:00:00.000Z",
"2020-01-28T00:00:00.000Z",
"2020-01-29T00:00:00.000Z",
"2020-01-30T00:00:00.000Z",
"2020-01-31T00:00:00.000Z",
"2020-02-01T00:00:00.000Z",
"2020-02-02T00:00:00.000Z",
"2020-02-03T00:00:00.000Z",
"2020-02-04T00:00:00.000Z",
"2020-02-05T00:00:00.000Z",
"2020-02-06T00:00:00.000Z",
"2020-02-07T00:00:00.000Z",
"2020-02-16T00:00:00.000Z",
"2020-02-17T00:00:00.000Z",
"2020-02-18T00:00:00.000Z",
"2020-02-19T00:00:00.000Z",
"2020-02-20T00:00:00.000Z"
]
myRequirement = [{
start: "2020-01-22T00:00:00.000Z",
end: "2020-01-23T00:00:00.000Z"
},
{
start: "2020-01-28T00:00:00.000Z",
end: "2020-02-07T00:00:00.000Z"
},
{
start: "2020-02-16T00:00:00.000Z",
end: "2020-02-20T00:00:00.000Z"
}
]
I want to do this using in node.js.
I tried this using some nested for loops.
First i am running loop on main dateArray, Then checking is it first date or not, If it is first date then storing it as first objects start date, Then in next date case checking is it next most date of previous date or not.
let gapArray = [];
let startEndObj = {};
let tempStartDate;
let tempEndDate;
let tempNextDate;
await asyncForEach(finalAvailablityDatesArrayOFi.availeblityDatesArray, async (availeblityDatesArrayOFi) => {
console.log("availeblityDatesArrayOFi", availeblityDatesArrayOFi);
if (!tempStartDate) {
console.log("In if");
startEndObj.startDate = availeblityDatesArrayOFi;
tempStartDate = availeblityDatesArrayOFi;
let oneDatePlus = new Date(availeblityDatesArrayOFi).setDate(new Date(availeblityDatesArrayOFi).getDate() + 1);
tempNextDate = new Date(oneDatePlus);
console.log("startEndObj", startEndObj);
}
else if (tempStartDate) {
console.log("in else");
if (new Date(availeblityDatesArrayOFi).getTime() == new Date(tempNextDate).getTime()) {
console.log("Do nothing!");
tempStartDate = availeblityDatesArrayOFi;
tempEndDate = availeblityDatesArrayOFi;
let oneDatePlus = new Date(availeblityDatesArrayOFi).setDate(new Date(availeblityDatesArrayOFi).getDate() + 1);
tempNextDate = new Date(oneDatePlus);
}
else {
startEndObj.endDate = new Date(tempEndDate);
gapArray.push(startEndObj);
tempStartDate = '';
tempEndDate = '';
startEndObj = {};
}
}
});
Thank you!
Looks like a job for Array.prototype.reduce().
Note: hereafter assumption is made that few prerequisites are met:
source array items are valid ISO-formatted date strings or others, parseable by new Date() constructor, otherwise should be brought to one of supported format
source array items are sorted in ascending order, otherwise Array.prototype.sort() method must be applied in advance
array items do not include time of the day part (or this part is exactly the same for all items), otherwise consecutive date records may happen to have difference greater than 864e5 milliseconds (1 day) and more complex comparison is required
You may walk through your array and compare current items with previous/following, once you have a gap greater than 1 day you push new range into resulting array or modify end date for the last one:
const src = ["2020-01-22T00:00:00.000Z","2020-01-23T00:00:00.000Z","2020-01-28T00:00:00.000Z","2020-01-29T00:00:00.000Z","2020-01-30T00:00:00.000Z","2020-01-31T00:00:00.000Z","2020-02-01T00:00:00.000Z","2020-02-02T00:00:00.000Z","2020-02-03T00:00:00.000Z","2020-02-04T00:00:00.000Z","2020-02-05T00:00:00.000Z","2020-02-06T00:00:00.000Z","2020-02-07T00:00:00.000Z","2020-02-16T00:00:00.000Z","2020-02-17T00:00:00.000Z","2020-02-18T00:00:00.000Z","2020-02-19T00:00:00.000Z","2020-02-20T00:00:00.000Z"],
ranges = src.reduce((res,date,idx,self) => {
const rangeStart = !idx || new Date(date) - new Date(self[idx-1]) > 864e5,
rangeEnd = idx == self.length-1 || new Date(self[idx+1]) - new Date(date) > 864e5
if(rangeStart) res.push({startdate:date,enddate:date})
else if(rangeEnd) res[res.length-1]['enddate'] = date
return res
}, [])
console.log(ranges)
.as-console-wrapper {min-height:100%}
You need to be careful with this type of processing to determine all the business rules exactly. If the time component is not to be considered, then it should be removed, otherwise when comparing say 2020-01-01T00:00:00 to 2020-01-02T012:00:00 you will get a difference greater than 1 day but might not want it to be treated as the start of a new range.
For that reason, the "days difference" logic should be in a separate function, which also makes it easier to change date libraries if you're using one. The days difference is also signed, so make sure they are passed in the right order.
Otherwise, the following is pretty much the same as Yevgen's answer but a little more efficient I think as it only creates two Dates on each iteration instead of four.
let dateArray = [
"2020-01-22T00:00:00.000Z",
"2020-01-23T00:00:00.000Z",
"2020-01-28T00:00:00.000Z",
"2020-01-29T00:00:00.000Z",
"2020-01-30T00:00:00.000Z",
"2020-01-31T00:00:00.000Z",
"2020-02-01T00:00:00.000Z",
"2020-02-02T00:00:00.000Z",
"2020-02-03T00:00:00.000Z",
"2020-02-04T00:00:00.000Z",
"2020-02-05T00:00:00.000Z",
"2020-02-06T00:00:00.000Z",
"2020-02-07T00:00:00.000Z",
"2020-02-16T00:00:00.000Z",
"2020-02-17T00:00:00.000Z",
"2020-02-18T00:00:00.000Z",
"2020-02-19T00:00:00.000Z",
"2020-02-20T00:00:00.000Z"
];
// Simple difference in days function
function daysDiff(d0, d1) {
return Math.round((d1 - d0) / 8.64e7);
}
let ranges = dateArray.reduce((acc, curr, i, arr) => {
// If first date, initialise first object
if (!acc.length) {
acc.push({start: curr, end: curr});
} else {
let d0 = new Date(curr);
let d1 = new Date(arr[i-1]);
// If difference greater than 1 day, end previous range
// and start a new range
if (daysDiff(d1, d0) > 1) {
acc[acc.length - 1].end = arr[i-1];
acc.push({start: curr, end: curr});
}
}
return acc;
}, []);
console.log(ranges);

comparing two arrays for intersecting values

I'm trying to use JavaScript to compare 2 different arrays and test for intersecting values.
This array contains ranges of times of availability for a 7 day week. In the availability array below each key represents a day of the week starting with Sunday. So key 0 = Sunday, key 1 = Monday..... maximum number of keys would be 6 which = Saturday
var availability = [["8:30AM-12PM","2PM-6PM"],
["6:15AM-9:30AM","1PM-4PM","8PM-11:15PM"],[],["9AM-9PM"]];
The below array need1 contains ranges of times of need for specific days throughout the week (anywhere from Sunday{key0} to Saturday{key6}). It uses the same key format as the above availability array. need1 array should match within the times and days listed in the availability array. So when comparing need1 to availability a match should be found.
var need1 = [["9AM-11:30AM","3PM-5PM"],[],[],["2PM-6:30PM"]]; //matches
The below array need2 is an example of an non-match, since the times of need ranges are outside the ranges of times and days listed in the availability array. So when comparing need2 to availability NO match should be found.
var need2 = [["7:30AM-11:30AM"],[],[],["2PM-6:30PM", "8PM-10:30PM"]]; //does not match
I'm trying to figure out how to compare these 2 arrays to match up the need ranges with the availability ranges. Meaning the need ranges have to completely fit inside the availability ranges for each day. All I'm looking for is a simple true or false returned value when comparing. true = match and false = NOT a match. However, I have no idea how to to efficiently do this. Any help would be appreciated!
Also, If need be I can lay out the array values in a different format if it would make comparing them easier. i.e. using date values instead of strings, or 24 hour format instead of 12 hour format
I'm going to take a risk here and take a slightly different approach than you've started with, but which provides a solution to your basic question: are there any matches between the available times and the needed times?
Before I present code, here are some recommendations:
1. Use an Object at the top level, not an array. Using array positions as meaningful is dangerous. Instead utilize the power of JavaScript object literals.
2. Separate your start and stop times, do not put them in the same string.
3. Utilize the power of native Array methods.
I make these recommendations and provide the following code snippet as it seems that you have some wiggle room in how you approach the problem.
UPDATED
Changed assumption per request. Only returns match for a given day, if every needed start/time slot finds a matched time slot in the availability object.
Something like the following should get you where you want to go. This will return an object of the matched needs.
/* structure your schedule as objects with arrays of objects of start/stop times */
const availability1 = {
sunday: [
{
start: '08:30',
stop: '12:00'
},
{
start: '14:00',
stop: '18:00'
}
],
monday: [{
start: '06:25',
stop: '11:45'
}],
wednesday: []
};
const need1 = {
// will be matched, every needed time slot is within an availability slot
// on the same day
sunday: [
{
start: '09:45', // has a match
stop: '12:00'
},
{
start: '14:00', // has a match
stop: '18:00'
}
],
monday: [ // will not be matched because...
{
start: '14:00', // has NO match
stop: '16:00'
},
{
start: '07:00', // has a match
stop: '10:00'
}
],
tuesday: []
};
const getMinutes = (timeString) => {
const timeParts = timeString.split(':');
const hours = Number(timeParts[0]);
const minutes = Number(timeParts[1]);
const timeInMinutes = (hours * 60) + minutes;
// console.log(`timeInMinutes: ${timeInMinutes}`);
return timeInMinutes;
}
const isTimeMatch = (availabilityTime, needTime) => {
const availStart = getMinutes(availabilityTime.start);
const availStop = getMinutes(availabilityTime.stop);
const needStart = getMinutes(needTime.start);
const needStop = getMinutes(needTime.stop);
console.log(`Availibility ${availabilityTime.start} (${availStart}) - ${availabilityTime.stop} (${availStop})`)
console.log(`Need ${needTime.start} (${needStart}) - ${needTime.stop} (${needStop})`)
const startTimeMatch = availStart <= needStart;
const stopTimeMatch = availStop >= needStop;
const isMatch = startTimeMatch && stopTimeMatch;
console.log(`is match: ${isMatch}`);
return isMatch;
};
const compareDays = (availTimes, needTimes) => {
return needTimes.map((needTime, i) => {
const matches = availTimes.filter((availTime) => {
return (isTimeMatch(availTime, needTime));
});
needTime.match = matches.length > 0;
return needTime;
}).filter((needTime, i, allNeedTimes) => {
return (allNeedTimes.every((i) => i.match === true));
});
}
function findMatches(availability, need) {
const matches = Object.keys(availability).reduce((accumulator, day) => {
if (availability[day] && need[day]) {
console.log(`Possible matches found on ${day}`)
const matches = compareDays(availability[day], need[day]);
if (matches.length > 0) {
accumulator[day] = matches;
}
}
return accumulator;
}, {});
return (Object.keys(matches).length === 0) ?
false : matches;
}
const matchedTimes = findMatches(availability1, need1);
if (matchedTimes) {
console.log('The following needs match availability:');
console.log(matchedTimes);
} else {
console.log('No matches found');
}

How do I splice ranges in JavaScript?

I'm working on a schedule management system which loads a person's work schedule into an array, and then the user is allowed to "inject" a new shift within that day. There are a couple of rules the code must follow, of course, and they are:
There must be no gaps between existing shifts and the added item. This means the added item's end time must be equal to or greater than the first item's start time, and the added item's start time must be equal to or less than the last item's end time.
The added shift must overwrite any existing shifts which occur during it's time frame. Essentially, it needs to splice itself into the array, but I'm unsure of how to do so using the .splice() method.
Any affected time blocks must be truncated/compressed to accommodate the new item. For example, if there is an existing time block which is an hour in duration, and we inject a 10 minute block into that beginning 20 minutes in, it would result in the first 20 minute portion of the original block, followed by the 10 minute added item, followed by the remainder of the original block. Added items must also be able to overlap other time blocks as well. So if we add a three hour block that covers 4 other time blocks (or starts/stops within them!) it must overwrite each of those.
This seems like a simple enough task, because basically it's just splicing ranges, but I don't know how to splice ranges in JavaScript.
Here's the code I've written to implement these rules so far:
var obj = {
start: '',
end: '',
};
var objArray = [];
var tmpArray = [];
var finalArray = [];
objArray.push({start: "12:00", end: "12:45"});
objArray.push({start: "12:45", end: "1:00"});
objArray.push({start: "1:00", end: "1:30"});
objArray.push({start: "1:30", end: "2:30"});
// added object
obj.start = "12:00";
obj.end = "12:10";
for (var i = 0; i < objArray.length; i++) { tmpArray.push(objArray[i]); }
//tmpArray = objArray; // preserve the original array
objArray.push(obj);
console.clear();
console.log("%o", objArray);
objArray.sort(function(a, b) { var x = a.start; var y = b.start; return ((x < y) ? -1 : ((x > y) ? 1 : 0)); });
// sanity check
if (obj.start >= obj.end){
console.log('Time logic is invalid.');
return false;
}
if (obj.end < tmpArray[0].start) {
console.log('There is a gap before the first item.');
return false;
}
if (obj.start > tmpArray[tmpArray.length - 1].end){
console.log('There is a gap after the last item.');
return false;
}
// Now for the fun stuff...
for (var i = 0; i < objArray.length; i++){
var tmpobj = objArray[i];
if (tmpobj.start == obj.start && tmpobj.end == obj.end){ // find our inserted object
index = i;
console.log('<<< obj injected: %s - %s [%d] >>>', obj.start, obj.end, index);
if (index == 0){ // is first item, start time was less than first item's start time
finalArray.push(obj);
if (obj.end == tmpArray[0].start){
finalArray.push(tmpArray[0]); // item insertion was a complete prepend...
} else if (obj.end >= tmpArray[tmpArray.length - 1].end){
console.log('entire array is consumed'); // item covers entire shift...
} else {
// This code is reached when obj start time is before or equal to first item
console.log('obj <= tmpArray[0].start, end > tmpArray[0].start');
}
} else {
if (obj.start == tmpArray[tmpArray.length - 1].end){
console.log('Item added at end of shift');
finalArray.push(tmpArray[i - 1]);
finalArray.push(obj);
}
}
} else {
console.log('obj tested: %s - %s [%d]', tmpobj.start, tmpobj.end, i);
if (obj.start > tmpobj.end) {
finalArray.push(tmpobj);
}
if (obj.end < tmpobj.start) {
finalArray.push(tmpobj);
}
}
// now iterate through array and combine like terms (activity type)
}
for (var i = 0; i < finalArray.length; i++){
console.log('%d) s: %s, e: %s', i, finalArray[i].start, finalArray[i].end);
}
How do I splice ranges in JavaScript?
You could consider your time blocks in term of start and end separately. I conceptually think of it as a linked list, which you can implement as an Array. With a special requirement that each node in the list must follow the order of START_TIME -> END_TIME and never have 2 START_TIME or 2 END_TIME nodes adjacent to each other. And any in between (free time blocks) is also defined by a START_TIME and END_TIME node.
Sis START_TIME
E is END_TIME
The array of valid time blocks may look like this [S1, E1, S2, E2, S3 ,E3]
So then when coding the logic to insert new blocks of time, you find your desired block by going to the appropriate START_TIME node for your new timeblock, then "splice" by creating an END_TIME E* node and a new START_TIME S-NEW node marking the beginning of your new block.
[S1, E1 ,S2 ,E* ,S-NEW, E2, S3, E3]
Then you may define the ending time by creating END_TIME node if necessary (remove old END_TIME E2 and creating a "free" block),
[S1, E1, S2, E*, S-NEW, E-NEW, S-FREE, E-FREE, S3, E3]
or reuse the old END_TIME E2 node that you "spliced" earlier if they end at the same time
[S1, E1, S2, E*, S-NEW, E2, S3, E3]
The reason you're hurting your brain at the end is because your code is doing too much all at once. I suggest breaking it up into functions, here are some suggestions:
canStart(time)
canEnd(time)
insertBlock(startBlock, endBlock)
removeBlock(startBlock, endBlock)

Find last document of the day for the last 7 days

I am adding entries to a schema every hour in order to track growth over the course of days while maintaining a current score for the current day. Now I would like to be able to pull the most recent record for each day for the past week. The results would be 6 records at or around midnight for 6 days previous and the 7th being the latest for the current day.
Here is my schema:
var schema = new Schema({
aid: { type: Number }
, name: { type: String }
, score: { type: Number }
, createdAt: { type: Date, default: Date.now() }
})
Edit
I've tried using this static, but it pulls the exact same record 7 times
schema.statics.getLastWeek = function(name, fn) {
var oneday = 60 * 60 * 24
, now = Date.now()
, docs = []
for (var i = 1; i <= 7; i++) {
this.where('name', new RegExp(name, 'i'))
.where('createdAt')
.gte(now - (i * oneday))
.desc('createdAt')
.findOne(function(err,doc){
docs.push(doc)
})
}
}
If I were using SQL I would do a subquery selecting MAXDATE and join it to my main query in order to retrieve the results I want. Anyway to do this here?
Kristina Chodorow gives a detailed recipe for this exact task in her book MongoDB: The Definitive Guide:
Suppose we have a site that keeps track of stock prices. Every few
minutes from 10 a.m. to 4 p.m., it gets the latest price for a stock,
which it stores in MongoDB. Now, as part of a reporting application,
we want to find the closing price for the past 30 days. This can be
easily accomplished using group.
I'm not familiar with Mongoose, however I've tried to adapt her example to your case below. Note I changed the createdAt default property from a value to a function and added an extra field datestamp to your schema:
var oneday = 24 * 60 * 60;
var schema = new Schema({
aid: { type: Number }
, name: { type: String }
, score: { type: Number }
// default: is a function and called every time; not a one-time value!
, createdAt: { type: Date, default: Date.now }
// For grouping by day; documents created on same day should have same value
, datestamp: { type: Number
, default: function () { return Math.floor(Date.now() / oneday); }
}
});
schema.statics.getLastWeek = function(name, fn) {
var oneweekago = Date.now() - (7 * oneday);
ret = this.collection.group({
// Group by this key. One document per unique datestamp is returned.
key: "datestamp"
// Seed document for each group in result array.
, initial: { "createdAt": 0 }
// Update seed document if more recent document found.
, reduce: function(doc, prev) {
if (doc.createdAt > prev.createdAt) {
prev.createdAt = doc.createdAt;
prev.score = doc.score;
// Add other fields, if desired:
prev.name = doc.name;
}
// Process only documents created within past seven days
, condition: { "createdAt" : {"$gt": oneweekago} }
}});
return ret.retval;
// Note ret, the result of group() has other useful fields like:
// total "count" of documents,
// number of unique "keys",
// and "ok" is false if a problem occurred during group()
);
A solution is to use group() to groups records by day. It's fancy, slow and can be blocking (meaning nothing else can run at the same time), but if your record set isn't too huge it's pretty powerful.
Group: http://www.mongodb.org/display/DOCS/Aggregation#Aggregation-Group
As for mongoose, I'm not sure if it supports group() directly, but you can use the node-mongodb-native implementation, by doing something like this (pseudo-code, mostly):
schema.statics.getLastWeek = function(name, cb) {
var keys = {} // can't remember what this is for
var condition = {} // maybe restrict to last 7 days
var initial = {day1:[],day2:[],day3:[],day4:[],day5:[],day6:[],day7:[]}
var reduce = function(obj, prev) {
// prev is basically the same as initial (except with whatever is added)
var day = obj.date.slice(0,-10) // figure out day, however it works
prev["day" + day].push(obj) // create grouped arrays
// you could also do something here to sort by _id
// which is probably also going to get you the latest for that day
// and use it to replace the last item in the prev["day" + 1] array if
// it's > that the previous _id, which could simplify things later
}
this.collection.group(keys, condition, initial, reduce, function(err, results) {
// console.log(results)
var days = results // it may be a property, can't remember
var lastDays = {}
days.forEach(function(day) {
// sort each day array and grab last element
lastDays[day] = days[day].sort(function(a, b) {
return a.date - b.date // check sort syntax, you may need a diff sort function if it's a string
}).slice(-1) // i think that will give you the last one
})
cb(lastDays) // your stuff
})
}
Some more comparisons between groups and map reduce from my blog:
http://j-query.blogspot.com/2011/06/mongodb-performance-group-vs-find-vs.html
There are no docs about the group command in the native driver, so you'll have to peer through the source code here:
https://github.com/christkv/node-mongodb-native/blob/master/lib/mongodb/collection.js
Also for sort, check check https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/sort for exact syntax
Edit: Better Idea!!!
Just have a special collection called "lastRequestOfDay" and make the _id the day.
Overwrite the value with each new request. It will be super easy to query and fast to write and will always have the last value written each day!
Add another property to the schema named dateAdded or something.
schema.statics.getLastWeek = function(name, fn) {
var oneday = 60 * 60 * 24
, now = Date.now()
, docs = []
for (var i = 0; i < 7; i++) {
this.where('name', new RegExp(name, 'i'))
.where('createdAt')
.lt(now - (i * oneday))
.gte(now - ((i + 1) * oneday))
.desc('createdAt')
.findOne(function(err,doc){
// might not always find one
docs.push(doc)
})
}
return fn(null, docs)
}
Try something like this:
schema.statics.getLastWeek = function(name, fn) {
var oneday = 60 * 60 * 24
, now = Date.now()
, docs = []
, that = this
function getDay(day){
that.where('name', new RegExp(name, 'i'))
.where('createdAt')
.gte(now - (day * oneday))
.desc('createdAt')
.findOne(function(err,doc){
docs.push(doc)
})
}
for (var i = 1; i <= 7; i++) {
getDay(i);
}
}
Nobody seems to be trying for "close to midnight". :) The issue I saw with the original code was that it checked for a time greater than or equal to x days ago... which will always return the most recent time. I'm confused as to why DeaDEnD's solution returns the same record 7 times, though. Also, you never called fn, but that's not really the biggest of your concerns, is it?
Try adding on .lt(now - (now % oneday) - (i - 1) * oneday) (assuming 0-indexed; if it's 1-indexed, change that to say i - 2)

Best way to generate unique ids client-side (with Javascript)

I need to generate unique ids in the browser. Currently, I'm using this:
Math.floor(Math.random() * 10000000000000001)
I'd like to use the current UNIX time ((new Date).getTime()), but I'm worried that if two clients generate ids at the exact same time, they wouldn't be unique.
Can I use the current UNIX time (I'd like to because that way ids would store more information)? If not, what's the best way to do this (maybe UNIX time + 2 random digits?)
you can create a GUID using the following links:
http://softwareas.com/guid0-a-javascript-guid-generator
Create GUID / UUID in JavaScript?
This will maximise your chance of "uniqueness."
Alternatively, if it is a secure page, you can concatenate the date/time with the username to prevent multiple simultaneous generated values.
https://github.com/uuidjs/uuid provides RFC compliant UUIDs based on either timestamp or random #'s. Single-file with no dependencies, supports timestamp or random #-based UUIDs, uses native APIs for crypto-quality random numbers if available, plus other goodies.
In modern browser you can use crypto:
var array = new Uint32Array(1);
window.crypto.getRandomValues(array);
console.log(array);
var c = 1;
function cuniq() {
var d = new Date(),
m = d.getMilliseconds() + "",
u = ++d + m + (++c === 10000 ? (c = 1) : c);
return u;
}
Here is my javascript code to generate guid. It does quick hex mapping and very efficient:
AuthenticationContext.prototype._guid = function () {
// RFC4122: The version 4 UUID is meant for generating UUIDs from truly-random or
// pseudo-random numbers.
// The algorithm is as follows:
// Set the two most significant bits (bits 6 and 7) of the
// clock_seq_hi_and_reserved to zero and one, respectively.
// Set the four most significant bits (bits 12 through 15) of the
// time_hi_and_version field to the 4-bit version number from
// Section 4.1.3. Version4
// Set all the other bits to randomly (or pseudo-randomly) chosen
// values.
// UUID = time-low "-" time-mid "-"time-high-and-version "-"clock-seq-reserved and low(2hexOctet)"-" node
// time-low = 4hexOctet
// time-mid = 2hexOctet
// time-high-and-version = 2hexOctet
// clock-seq-and-reserved = hexOctet:
// clock-seq-low = hexOctet
// node = 6hexOctet
// Format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
// y could be 1000, 1001, 1010, 1011 since most significant two bits needs to be 10
// y values are 8, 9, A, B
var guidHolder = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx';
var hex = '0123456789abcdef';
var r = 0;
var guidResponse = "";
for (var i = 0; i < 36; i++) {
if (guidHolder[i] !== '-' && guidHolder[i] !== '4') {
// each x and y needs to be random
r = Math.random() * 16 | 0;
}
if (guidHolder[i] === 'x') {
guidResponse += hex[r];
} else if (guidHolder[i] === 'y') {
// clock-seq-and-reserved first hex is filtered and remaining hex values are random
r &= 0x3; // bit and with 0011 to set pos 2 to zero ?0??
r |= 0x8; // set pos 3 to 1 as 1???
guidResponse += hex[r];
} else {
guidResponse += guidHolder[i];
}
}
return guidResponse;
};
You can always run a test against existing IDs in the set to accept or reject the generated random number recursively.
for example:
const randomID = function(){
let id = Math.floor(Math.random() * 10000000000000001) + new Date();
if (idObjectArray.contains(id)) {
randomID;
} else {
idObjectArray.push(id);
}
};
This example assumes you would just be pushing the id into a 1D array, but you get the idea. There shouldn't be many collisions given the uniqueness of the random number with the date, so it should be efficient.
There are two ways to achieve this
js const id = Date.now().toString()
While this does not guarantee uniqueness (When you are creating multiple objects within 1ms), this will work on a practical level, since it is usually not long before the objects on the client are sent to a real server.
If you wanted to create multiple records withing 1ms, I suggest using the code below
const { randomBytes } = require("crypto");
// 32 Characters
const id = randomBytes(16).toString("hex");
It works similar to a uuid4 without needing to add an external library (Assuming you have access to NodeJs at some point)

Categories