Related
I have bunch of electricity meter readings which have irregular dates. See below :
ReadingDate Meter
19/01/2021 5270
06/03/2021 5915
11/05/2021 6792
08/07/2021 7367
9/9/2021 8095
8/11/2021 8849
02/12/2021 9065
17/01/2022 9950
Now I'd like to transform this into monthly readings, using just this data, to end up with a table like this
Month Usage
2021-01 452
2021-02 393
2021-03 416
2021-04 399
2021-05 341
2021-06 297
2021-07 347
2021-08 358
2021-09 369
2021-10 389
2021-11 295
2021-12 586
2022-01 308
Now, I have a working solution, but I'm sure there's a more beautiful concise way of doing it.
What I do is to create an intermediate array that has one line for each date between first and last meter readings.
Each item in the array has 3 values :
the date
the average value for that date (calculated by counting the days between meter readings and dividing that by change in the meter.
the corresponding month
The last step then is to loop over this intermediate array and sum the values for each different month.
Here's the working code (its taken from Google Apps Script so please ignore the spreadsheet specific stuff:
var DailyAveragesArray = [['Date','Usage','Month']];
var monthlyObject = {};
var monthlyArray = [['Month','Usage']];
function calculateAverageDailyFigures() {
// give indices for the useful columns, 0 numbered
var ReadingDateColumn = 0;
var MeterReading = 1;
// Read into an array
var MeterReadingData = ss.getDataRange().getValues() // Get array of values
const sortedReadings = MeterReadingData.slice(1).sort((a, b) => a[0] - b[0]);
// from https://flaviocopes.com/how-to-sort-array-by-date-javascript/
// First calculate the number of days and average daily figure for each row
// Note we don't do this for the last row
for(i=0; i < sortedReadings.length - 1 ; i++){
var NumberOfDays = (sortedReadings[i+1][0] - sortedReadings[i][0])/(1000*3600*24);
sortedReadings[i].push(NumberOfDays);
var MeterDifference = sortedReadings[i+1][1] - sortedReadings[i][1];
var AverageDailyFigure = MeterDifference/NumberOfDays;
sortedReadings[i].push(AverageDailyFigure);
}
BuildDailyArray(sortedReadings);
}
function BuildDailyArray(sortedReadings){
// For each row in sorted , loop from the date to the next date-1 and create columns date and Usage
for(i=0; i<sortedReadings.length -1 ;i++){
for (var d = sortedReadings[i][0]; d < sortedReadings[i+1][0]; d.setDate(d.getDate() + 1)) {
var newDate = new Date(d);
var month = newDate.getFullYear() + '-' + ('0' + (newDate.getMonth() + 1)).slice(-2);
DailyAveragesArray.push([newDate,sortedReadings[i][3],month]);
// Check if the month is in the object and add value, otherwise create object an add value
if(month in monthlyObject){
monthlyObject[month] = monthlyObject[month] + sortedReadings[i][3];
} else {
Logger.log('Didnt find month so create it');
monthlyObject[month] = sortedReadings[i][3];
}
}
}
Logger.log(DailyAveragesArray.length);
Logger.log(monthlyObject);
var DailyUsageData = ss.getRange('D1:F'+DailyAveragesArray.length);
DailyUsageData.setValues(DailyAveragesArray);
BuildMonthlyArray();
}
function BuildMonthlyArray(){
const keys = Object.keys(monthlyObject);
Logger.log(keys);
keys.forEach((key, index) => {
monthlyArray.push([key,Math.round(monthlyObject[key])]);
});
var MonthlyUsageData = ss.getRange('H1:I'+monthlyArray.length);
MonthlyUsageData.setValues(monthlyArray);
}
So, my question is, how would I do this nicer, more beautifully, not so verbose ?
I'm not sure what the correct term is for what I want to do. I don't think it's resampling .
I'd appreciate any comments.
Thanks / Colm
Here is my shot on this.
The way i'm doing it:
Initializing all days and its value
Grouping by month
Calculating the average per month
Explanation a bit more precise
initDateFromString
The method initDateFromString takes a dates with the format DD/MM/YYYY and return the associated js date object
initAllDates
The method initAllDates will split the data into day and add the average value of the difference for each day
for example, for the first two readings, it will result to an array of dates looking like :
date
value
19/01/2021
14.02
20/01/2021
14.02
....
....
05/03/2021
14.02
06/03/2021
14.02
The value 14.02 comme from the following calcul :
(newReadingMeter - oldReadingMeter)/nbDaysBetweenDates
Which in this example is (5915 - 5270)/46 = 14.02
joinToMonth
The joinToMonth method will then group the days into month with all the days value summed !
const data = [{
ReadingDate: '19/01/2021',
Meter: 5270
},
{
ReadingDate: '06/03/2021',
Meter: 5915
},
{
ReadingDate: '11/05/2021',
Meter: 6792
},
{
ReadingDate: '08/07/2021',
Meter: 7367
},
{
ReadingDate: '9/9/2021',
Meter: 8095
},
{
ReadingDate: '8/11/2021',
Meter: 8849
},
{
ReadingDate: '02/12/2021',
Meter: 9065
},
{
ReadingDate: '17/01/2022',
Meter: 9950
}
]
function initDateFromString(dateString){
let dateParts = dateString.split("/");
return new Date(+dateParts[2], dateParts[1] - 1, +dateParts[0]);
}
function initAllDates(data){
let dates = []
let currentValue = data.shift()
const currentDate = initDateFromString(currentValue.ReadingDate)
data.forEach(metric => {
const date = initDateFromString(metric.ReadingDate)
const newDates = []
while(currentDate < date){
newDates.push({date: new Date(currentDate)})
currentDate.setDate(currentDate.getDate() + 1)
}
dates = dates.concat(newDates.map(x => {
return {Usage: (metric.Meter - currentValue.Meter) / newDates.length, date: x.date}}
))
currentDate.setDate(date.getDate())
currentValue = metric
})
return dates
}
function joinToMonth(dates){
return dates.reduce((months, day) => {
const month = day.date.getMonth()
const year = day.date.getFullYear()
const existingObject = months.find(x => x.month === month && x.year === year)
if (existingObject) {
existingObject.total += day.Usage
} else {
months.push({
month: day.date.getMonth(),
year: day.date.getFullYear(),
total: day.Usage,
})
}
return months;
}, []);
}
const dates = initAllDates(data)
const joinedData = joinToMonth(dates)
console.log(joinedData)
I have an array of date ranges(selectedRanges) which shows assigned dates for a member between the main date range. I want to know the date ranges where he/she is unassigned. Please refer to the below example.
mainDateRange = ['01-01-2020', '14-06-2020'];
selectedRanges = [
['03-01-2020','04-01-2020'],
['03-01-2020','05-01-2020'], //overlapping dates
['11-01-2020','13-01-2020'],
['01-02-2020','20-02-2020'],
['15-03-2020','18-03-2020'],
['06-01-2020','06-01-2020'], //date ranges will not be ordered
['03-01-2020','04-01-2020']
]; //dates that the member has work assigned
Desired output
excludedRanges = [
['01-01-2020','02-01-2020'],
['07-01-2020','10-01-2020'],
['14-01-2020','31-01-2020'],
['21-02-2020','14-03-2020'],
['19-03-2020','14-06-2020']
]; //shows all the unassigned periods(ranges)
selectedRanges date ranges will have ranges in random order and also may have duplicate and overlapping dates.
I have searched a lot and found nothing. I am only able to get the unselected dates, not as a range. Please help.
Thank you
Interesting problem, I'll propose an approach to achieve this desired behavior by doing the following:
Transform all string dates into date objects.
Sort the selectedRanges array in ascending order using the start and end dates. This sorting step is cricual to finding the date range gaps.
Adding a "moving cursor" date that moves between the mainDateRange to find and add the missing ranges to the output array.
Before we start the date calculations, we'll need a few helper functions. I've added two functions to go back and forth between the date object and the string format you have (dd-mm-yyyy). Please note that you may not need these two helper function if you use something like Moment.js, but I won't impose an extra dependency on your project.
function stringToDate(stringDate) {
const parts = stringDate.split('-').map((p) => parseInt(p));
parts[1] -= 1;
return new Date(...parts.reverse());
}
function dateToString(date) {
return `${('0' + date.getDate()).slice(-2)}-${('0' + (date.getMonth() + 1)).slice(-2)}-${date.getFullYear()}`;
}
I've also added a sorter function that makes sure the ranges are sorted in an ascending fashion (smaller ranges first).
function dateRangeSorter(a, b) {
if (a[0] < b[0]) return -1;
else if (a[0] > b[0]) return 1;
if (a[1] < b[1]) return -1;
else if (a[1] > b[1]) return 1;
return 0;
}
Now we're good to go on the calculation, here is a code snippet that will log the output at the end.
// data
const output = [];
const oneDayInMs = 24 * 60 * 60 * 1000;
const mainDateRange = ['01-01-2020', '14-06-2020'];
const selectedRanges = [
['03-01-2020','04-01-2020'],
['03-01-2020','05-01-2020'],
['11-01-2020','13-01-2020'],
['01-02-2020','20-02-2020'],
['15-03-2020','18-03-2020'],
['06-01-2020','06-01-2020'],
['03-01-2020','04-01-2020']
];
// helpers
function stringToDate(stringDate) {
const parts = stringDate.split('-').map((p) => parseInt(p));
parts[1] -= 1;
return new Date(...parts.reverse());
}
function dateToString(date) {
return `${('0' + date.getDate()).slice(-2)}-${('0' + (date.getMonth() + 1)).slice(-2)}-${date.getFullYear()}`;
}
function dateRangeSorter(a, b) {
if (a[0] < b[0]) return -1;
else if (a[0] > b[0]) return 1;
if (a[1] < b[1]) return -1;
else if (a[1] > b[1]) return 1;
return 0;
}
// transform into date and sort
const mainDateRangeAsDates = mainDateRange.map(stringToDate);
const selectedRangesAsDates = selectedRanges.map((range) => (range.map(stringToDate)))
.sort(dateRangeSorter);
// start at the beginning of the main date range
let movingDate = mainDateRangeAsDates[0];
// loop through the selected ranges
selectedRangesAsDates.forEach(([startDate, endDate]) => {
// if there's a gap, add it to the output
if (movingDate < startDate) {
output.push([
dateToString(movingDate),
dateToString(new Date(startDate.getTime() - oneDayInMs))
]);
}
// move the cursor date to one day after the end of current rage
movingDate = new Date(endDate.getTime() + oneDayInMs);
});
// if there is a gap at the end, add it as well
if (movingDate < mainDateRangeAsDates[1]) {
output.push([
dateToString(movingDate),
dateToString(mainDateRangeAsDates[1])
]);
}
console.log(output);
Used a similar approach to this: How to make sure every number of a bigger range is within some smaller ranges?
Convert all strings to Dates. Sorts by minimum of range.
Moves minimum position forward, until it finds a gap, and pushes to res array.
Pushes range from last minimum to maximum if it exists
mainDateRange = ['01-01-2020', '14-06-2020'];
selectedRanges = [
['03-01-2020', '04-01-2020'],
['03-01-2020', '05-01-2020'], //overlapping dates
['11-01-2020', '13-01-2020'],
['01-02-2020', '20-02-2020'],
['15-03-2020', '18-03-2020'],
['06-01-2020', '06-01-2020'], //date ranges will not be ordered
['03-01-2020', '04-01-2020']
]; //dates that the member has work assigned
function gapFinder(mainDateRange, selectedRanges) {
const dateToInt = a => new Date(a.split('-').reverse().join('-'))
const intToDate = a => new Date(a).toISOString().slice(0, 10).split('-').reverse().join('-')
// convert to numbers
selectedRanges = selectedRanges.map(r => r.map(dateToInt))
// presort ranges
selectedRanges.sort(([a, ], [b, ]) => a - b)
let [min, max] = mainDateRange.map(dateToInt)
const res = []
for (const [x, y] of selectedRanges) {
if (min > max) break
if (min < x)
res.push([min, x.setDate(x.getDate() - 1)])
min = Math.max(min, y.setDate(y.getDate() + 1))
}
if (min <= max) res.push([min, max])
return res.map(r => r.map(intToDate))
}
console.log(JSON.stringify(gapFinder(mainDateRange,selectedRanges)))
selectedRanges.push(['11-06-2020', '13-06-2020'])
console.log(JSON.stringify(gapFinder(mainDateRange,selectedRanges)))
I have the following values.
var list = "09:05, 10:05, 12:30, 16:30 , ... , ..."
The type of values ββin the list is a regular string, not an object.
Based on this value, I want to divide from 0 to 12 am and from 13 to 23 pm.
Therefore, the result I want is as follows.(If you check the log value)
var am = am 09:05 , am 10:05
var pm = pm 12:30 , pm 16:30
It may be a simple question, but it is a very difficult problem for me as a script novice.
Please help me.
Create a sort function first
var sort = ( a, b ) => convertToMin( a ) - convertToMin( b );
var convertToMin = ( a ) => ( items = a.split( ":" ).map( Number ), items[ 0 ] * 60 + items[ 1 ] );
Now use reduce to segregate the array
var output = list.reduce( (a,b) => //using reduce to iterate, a is the accumulator and b is item in array for current iteration
( convertToMin(b) > 12*60 ? a.pm.push( b ) : a.am.push( b ), a ) ,
{ am :[], pm : [] }) ; //accumulator is initialized to { am :[], pm : [] }
output.am.sort( sort );
output.pm.sort( sort );
Demo
var list = ["09:05", "10:05", "12:30", "16:30"];
var sort = (a, b) => convertToMin(a) - convertToMin(b);
var convertToMin = (a) => (items = a.split(":").map(Number), items[0] * 60 + items[1]);
var output = list.reduce((a, b) =>
(convertToMin(b) > 12 * 60 ? a.pm.push(b) : a.am.push(b), a), {
am: [],
pm: []
});
output.am.sort(sort);
output.pm.sort(sort);
console.log(output);
Here's what I'd do to solve this problem.
Separate the values in the string into an array. I'd Google javascript split string into array. Nothing wrong with Googling stuff; even seasoned devs have to do it all the time! At least I do. :)
Then create a for loop that goes through each element of the array. A good search for how to do that is javascript for loop array.
Then for each element, split the string again (this time by the :).
Then convert the first part into a number (javascript convert string
to integer) and see whether it is bigger or smaller than 12.
You could adjusted value with am/pm time and sort it to the wanted array.
function format(v) { return ('0' + v).slice(-2); }
function getM(t) {
var v = t.split(':');
return (v[0] < 12 ? 'am' : 'pm') + ' ' + [v[0] % 12 || 12, v[1]].map(format).join(':');
}
var list = '09:05, 10:05, 12:30, 16:30',
am = [],
pm = []
result = { am: am, pm: pm };
list
.split(', ')
.map(getM)
.forEach(function (s) {
result[s.slice(0, 2)].push(s);
});
console.log(am);
console.log(pm);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Split the items using the appropriate separator, process them with a cycle then join them with the appropriate separator
var items = list.split(", ");
var ams = [];
var pms = [];
for (var index = 0; index < list.length; index++) {
var isPM = ((list[index].substring(0, 2) >= 12);
var currentArray = window[isPM ? "pms" : "ams"];
var found = false;
var val = (isPM ? "pm" : "am") + " " + items[index];
for (var innerIndex = 0; (!found) && (innerIndex < currentArray.length); innerIndex++) {
if (currentArray[innerIndex] > val) {
found = true;
currentArray.splice(innerIndex, 0, val);
}
}
if (!found) currentArray.push(val);
}
var am = ams.join(" , ");
var pm = pms.join(" , ");
Try with .split method like this,
Updated without jQuery
var list = "09:05, 10:05, 12:30, 16:30";
var options = list.split(',').map(time => {
h = time.split(':')[0];
return parseInt(h) >= 12 ? 'pm ' + time : 'am ' + time;
})
console.log(options);
I have an array with the following values (example):
[
1491408000000,
1491494400000,
1491753600000,
1493222400000,
1493308800000,
1493568000000
]
Where the index is a date time. The date time will always be at 12:00:00 on a date.
In this example, the first 3 dates are consecutive cross weekend (weekend is holiday so count as leave), then another group of 3 dates cross weekend and month.
Now, what I am trying to do is find sequential dates (cross week and month) and put them into an array as follows:
[
1491408000000,
1491494400000,
1491753600000
],
[
1493222400000,
1493308800000,
1493568000000
]
I have tried the following code to get the sequential dates but this cannot cross week and month, how to modify the code to get above result? Any help would be much appreciated!
var timeValue = new Date(dateReview).getTime();
valueCon.push(timeValue);
var k = 0;
sortedValue[k] = [];
valueCon.sort( function ( a, b ){
return +a > +b ? 1 : +a == +b ? 0: -1;
})
.forEach( function( v , i ){
var a = v,b = valueCon[i+1]||0;
sortedValue[k].push( +a );
if ( (+b - +a) > 86400000) {
sortedValue[++k] = []
}
return 1;
});
sortedValue.sort( function ( a,b ){
return a.length > b.length ? -1: 1;
});
This requires help from a function to test if two dates are in the same week. The following goes over the set of time values provided in an array and puts the first value into an array within the array. For each subsequent value, it tests if it's in the same week as the first value in each array within the outer array.
If it's in the same week as the first value in any existing array, it's pushed into that array. Otherwise, it's put in a new array and pushed into the outer array.
There may be a neater way to implement the algorithm, but I'll leave that for others.
Due to time zone differences, they are adjusted to the host time zone based on the original time values representing noon in the source time zone.
// Given 2 dates, return true if they are in the same week (Mon to Sun).
// Otherwise, return false
function sameWeek(a, b){
var e = new Date(+a);
// Week starts at 00:00:00.000 on Monday on or before date
var s = new Date(e.setDate(e.getDate() - ((e.getDay()||7) -1)));
s.setHours(0,0,0,0);
// Week ends at 23:59:59.999 the following Sunday
e.setDate(e.getDate() + 6);
e.setHours(23,59,59,999);
// Test b and return value
return b >= s && b <= e;
}
// Given time value for UTC-0400, adjust to same date and time
// in local time zone and return a date
function adjust(n) {
var d = new Date(n);
d.setMinutes(d.getMinutes() - 240 + d.getTimezoneOffset());
return d;
}
var result = [1491408000000,1491494400000,1491753600000,1493222400000,1493308800000,1493568000000
].reduce(function(acc, n) {
var d = adjust(n);
var used;
if (acc.length != 0) {
used = acc.some(function(arr) {
if (sameWeek(adjust(arr[0]), d)) {
arr.push(n);
return true;
}
});
}
if (!used || acc.length == 0) {
acc.push([n]);
}
return acc;
},[]);
// Result array
console.log(result);
// Printed as date strings adjusted to same host local time
result.forEach(arr => {
arr.forEach(n => console.log(adjust(n).toString()))
console.log('\n');
});
Manipulation of timestamps is a pain. JavaScript has a built-in Date type, as you know, and I would suggest you use it. Date#getUTCDay returns the day of the week as an integer (for reference, 4 is Friday, or the day before a weekend), while Date#setUTCDate and Date#getUTCDate together allow you to adjust the date in day increments (and have it overflow/underflow to the next/previous month). Thus, to determine whether a timestamp b follows "sequentially" (excluding weekends) after a, you can use:
function sequential (a, b) {
a = new Date(a)
return a.setUTCDate(a.getUTCDate() + (a.getUTCDay() === 4 ? 3 : 1)) === b
}
Grouping is just an exercise after that; the code above contains all of the real logic behind this solution.
Example Snippet
var dates = [
1491408000000,
1491494400000,
1491753600000,
1493222400000,
1493308800000,
1493568000000
]
function sequential (a, b) {
a = new Date(a)
return a.setUTCDate(a.getUTCDate() + (a.getUTCDay() === 4 ? 3 : 1)) === b
}
function groupSequential(dates) {
if (dates.length < 2) return [dates.slice()]
dates.sort(function(a, b) { return a - b })
var result = [], group
for (var i = 0; i < dates.length; i++) {
sequential(dates[i - 1], dates[i]) || result.push(group = [])
group.push(dates[i])
}
return result
}
console.log(groupSequential(dates))
I need to generate unique id numbers on the fly using javascript. In the past, I've done this by creating a number using time. The number would be made up of the four digit year, two digit month, two digit day, two digit hour, two digit minute, two digit second, and three digit millisecond. So it would look something like this: 20111104103912732 ... this would give enough certainty of a unique number for my purposes.
It's been a while since I've done this and I don't have the code anymore. Anyone have the code to do this, or have a better suggestion for generating a unique ID?
A better approach would be:
new Date().valueOf();
instead of
new Date().getUTCMilliseconds();
valueOf() is "most likely" a unique number. http://www.w3schools.com/jsref/jsref_valueof_date.asp.
The shortest way to create a number that you can be pretty sure will be unique among as many separate instances as you can think of is
Date.now() + Math.random()
If there is a 1 millisecond difference in function call, it is 100% guaranteed to generate a different number. For function calls within the same millisecond you should only start to be worried if you are creating more than a few million numbers within this same millisecond, which is not very probable.
For more on the probability of getting a repeated number within the same millisecond see https://stackoverflow.com/a/28220928/4617597
If you just want a unique-ish number, then
var timestamp = new Date().getUTCMilliseconds();
would get you a simple number. But if you need the readable version, you're in for a bit of processing:
var now = new Date();
timestamp = now.getFullYear().toString(); // 2011
timestamp += (now.getMonth < 9 ? '0' : '') + now.getMonth().toString(); // JS months are 0-based, so +1 and pad with 0's
timestamp += ((now.getDate < 10) ? '0' : '') + now.getDate().toString(); // pad with a 0
... etc... with .getHours(), getMinutes(), getSeconds(), getMilliseconds()
This can be achieved simply with the following code:
var date = new Date();
var components = [
date.getYear(),
date.getMonth(),
date.getDate(),
date.getHours(),
date.getMinutes(),
date.getSeconds(),
date.getMilliseconds()
];
var id = components.join("");
Here's what I do when I want something smaller than a bunch of numbers - change base.
var uid = (new Date().getTime()).toString(36)
This performs faster than creating a Date instance, uses less code and will always produce a unique number (locally):
function uniqueNumber() {
var date = Date.now();
// If created at same millisecond as previous
if (date <= uniqueNumber.previous) {
date = ++uniqueNumber.previous;
} else {
uniqueNumber.previous = date;
}
return date;
}
uniqueNumber.previous = 0;
jsfiddle: http://jsfiddle.net/j8aLocan/
I've released this on Bower and npm: https://github.com/stevenvachon/unique-number
You could also use something more elaborate such as cuid, puid or shortid to generate a non-number.
I use
Math.floor(new Date().valueOf() * Math.random())
So if by any chance the code is fired at the same time there is also a teeny chance that the random numbers will be the same.
In 2023, you can use the in-browser Crypto API to generate cryptographically strong random values.
function getRandomNumbers() {
const typedArray = new Uint8Array(10);
const randomValues = window.crypto.getRandomValues(typedArray);
return randomValues.join('');
}
console.log(getRandomNumbers());
// 1857488137147725264738
function getRandomNumbers() {
const typedArray = new Uint8Array(10);
const randomValues = window.crypto.getRandomValues(typedArray);
return randomValues.join('');
}
console.log(getRandomNumbers());
both Uint8Array constructor and Crypto.getRandomValues are supported on all major browsers, including IE11
This should do :
var uniqueNumber = new Date().getTime(); // milliseconds since 1st Jan. 1970
if you want a unique number after few mili seconds then use Date.now(), if you want to use it inside a for loop then use Date.now() and Math.random() together
unique number inside a for loop
function getUniqueID(){
for(var i = 0; i< 5; i++)
console.log(Date.now() + ( (Math.random()*100000).toFixed()))
}
getUniqueID()
output:: all numbers are unique
15598251485988384
155982514859810330
155982514859860737
155982514859882244
155982514859883316
unique number without Math.random()
function getUniqueID(){
for(var i = 0; i< 5; i++)
console.log(Date.now())
}
getUniqueID()
output:: Numbers are repeated
1559825328327
1559825328327
1559825328327
1559825328328
1559825328328
From investigating online I came up with the following object that creates a unique id per session:
window.mwUnique ={
prevTimeId : 0,
prevUniqueId : 0,
getUniqueID : function(){
try {
var d=new Date();
var newUniqueId = d.getTime();
if (newUniqueId == mwUnique.prevTimeId)
mwUnique.prevUniqueId = mwUnique.prevUniqueId + 1;
else {
mwUnique.prevTimeId = newUniqueId;
mwUnique.prevUniqueId = 0;
}
newUniqueId = newUniqueId + '' + mwUnique.prevUniqueId;
return newUniqueId;
}
catch(e) {
mwTool.logError('mwUnique.getUniqueID error:' + e.message + '.');
}
}
}
It maybe helpful to some people.
Cheers
Andrew
This also should do:
(function() {
var uniquePrevious = 0;
uniqueId = function() {
return uniquePrevious++;
};
}());
In ES6:
const ID_LENGTH = 36
const START_LETTERS_ASCII = 97 // Use 64 for uppercase
const ALPHABET_LENGTH = 26
const uniqueID = () => [...new Array(ID_LENGTH)]
.map(() => String.fromCharCode(START_LETTERS_ASCII + Math.random() * ALPHABET_LENGTH))
.join('')
Example:
> uniqueID()
> "bxppcnanpuxzpyewttifptbklkurvvetigra"
Always get unique Id in JS
function getUniqueId(){
return (new Date().getTime()).toString(36) + new Date().getUTCMilliseconds();
}
getUniqueId() // Call the function
------------results like
//"ka2high4264"
//"ka2hj115905"
//"ka2hj1my690"
//"ka2hj23j287"
//"ka2hj2jp869"
Updated for 2021, numbers and ids are not guaranteed to be unique but should be satisfactory unique enough:
(oh, and who knew something.toString(36) is even a thing π)
// a pseudo-random floating number based on Date.now()
const generateRandomNumber = () =>
Math.log2(Date.now()) + Math.random();
console.log("a pseudo-random floating number based on Date.now():");
console.log(generateRandomNumber());
// a locally unique-ish HTML id
const generateUniqueId = () => `_${Date.now().toString(36)}${Math.floor(Number.MAX_SAFE_INTEGER * Math.random()).toString(36)}`;
console.log("a locally unique-ish HTML id:");
console.log(generateUniqueId())
// a pseudo-random BigInt
const generateRandomBigInt = () =>
BigInt(Date.now()) * BigInt(Number.MAX_SAFE_INTEGER) +
BigInt(Math.floor(Number.MAX_SAFE_INTEGER * Math.random()));
console.log("a pseudo-random BigInt:");
console.log(generateRandomBigInt().toString());
// same but base32-encoded (each char is 5 bits)
console.log("same but base32-encoded (each char is 5 bits):");
console.log(generateRandomBigInt().toString(32));
// extracting the "Date.now" timestamp of when it was generated:
console.log('extracting the "Date.now" timestamp of when it was generated:');
console.log(Number(generateRandomBigInt() / BigInt(Number.MAX_SAFE_INTEGER)))
// generate a run of random BigInt in ascending order
function generateRandomBigIntFactory() {
let count = 0, prev = 0;
return () => {
const now = Date.now();
if (now === prev) { ++count; }
else { count = 0; prev = now; }
return (BigInt(now) * BigInt(16384) + BigInt(count)) * BigInt(Number.MAX_SAFE_INTEGER) +
BigInt(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER));
}
}
// verify the order is ascending
const generate = generateRandomBigIntFactory();
let prev = 0;
for (let i = 0; i < 65536; i++) {
const num = generate();
if (num <= prev) console.log(`error: ${prev}, ${num}`);
prev = num;
}
console.log("the last random BigInt:");
console.log(prev.toString());
use this:for creating unique number in javascript
var uniqueNumber=(new Date().getTime()).toString(36);
It really works. :)
simple solution I found
var today = new Date().valueOf();
console.log( today );
This creates an almost guaranteed unique 32 character key client side, if you want just numbers change the "chars" var.
var d = new Date().valueOf();
var n = d.toString();
var result = '';
var length = 32;
var p = 0;
var chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
for (var i = length; i > 0; --i){
result += ((i & 1) && n.charAt(p) ? '<b>' + n.charAt(p) + '</b>' : chars[Math.floor(Math.random() * chars.length)]);
if(i & 1) p++;
};
https://jsfiddle.net/j0evrdf1/1/
function UniqueValue(d){
var dat_e = new Date();
var uniqu_e = ((Math.random() *1000) +"").slice(-4)
dat_e = dat_e.toISOString().replace(/[^0-9]/g, "").replace(dat_e.getFullYear(),uniqu_e);
if(d==dat_e)
dat_e = UniqueValue(dat_e);
return dat_e;
}
Call 1: UniqueValue('0')
Call 2: UniqueValue(UniqueValue('0')) // will be complex
Sample Output:
for(var i =0;i<10;i++){ console.log(UniqueValue(UniqueValue('0')));}
60950116113248802
26780116113248803
53920116113248803
35840116113248803
47430116113248803
41680116113248803
42980116113248804
34750116113248804
20950116113248804
03730116113248804
Since milliseconds are not updated every millisecond in node, following is an answer. This generates a unique human readable ticket number. I am new to programming and nodejs. Please correct me if I am wrong.
function get2Digit(value) {
if (value.length == 1) return "0" + "" + value;
else return value;
}
function get3Digit(value) {
if (value.length == 1) return "00" + "" + value;
else return value;
}
function generateID() {
var d = new Date();
var year = d.getFullYear();
var month = get2Digit(d.getMonth() + 1);
var date = get2Digit(d.getDate());
var hours = get2Digit(d.getHours());
var minutes = get2Digit(d.getMinutes());
var seconds = get2Digit(d.getSeconds());
var millSeconds = get2Digit(d.getMilliseconds());
var dateValue = year + "" + month + "" + date;
var uniqueID = hours + "" + minutes + "" + seconds + "" + millSeconds;
if (lastUniqueID == "false" || lastUniqueID < uniqueID) lastUniqueID = uniqueID;
else lastUniqueID = Number(lastUniqueID) + 1;
return dateValue + "" + lastUniqueID;
}
let uuid = ((new Date().getTime()).toString(36))+'_'+(Date.now() + Math.random().toString()).split('.').join("_")
sample result "k3jobnvt_15750033412250_18299601769317408"
I came across this question while trying to find a simple UID generation technique that was also sortable (so I can order by uid and items will appear in order of creation / uid generation). The major problem with most (all?) of the solutions here is that they either rely on millisecond accuracy (at best) == clashes(!) or a pseudo-random number == clashes(!) && non-sortable(!).
Technique below uses micro-second precision where available (i.e. not where fingerprinting-resistance techniques are in play, e.g. firefox) combined with an incrementing, stateful suffix. Not perfect, or particularly performant for large numbers of IDs (see example with 1,000,000 below), but it works and is reversible.
// return a uid, sortable by creation order
let increment;
let tuidPrev;
const uid = (uidPrev) => {
// get current time to microsecond precision (if available) and remove decimals
const tuid = ((performance.timing.navigationStart + performance.now()) * 1000)
// convert timestamp to base36 string
.toString(36);
// previous uid has been provided (stateful)
if (uidPrev) {
tuidPrev = uidPrev.slice(0, 10);
increment = uidPrev.length > 10 ? parseInt(uidPrev.slice(10), 36) : 0;
}
// if tuid is changed reset the increment
if (tuid !== tuidPrev) {
tuidPrev = tuid;
increment = 0;
}
// return timed uid + suffix (4^36 values) === very unique id!
return tuid + ('000' + (increment++).toString(36)).slice(-4);
}
// EXAMPLE (check the console!)
const iterations = 1000000;
const uids = [];
const uidMap = {};
const timeMap = {}
const microMap = {};
let time = performance.now();
for (let i = 0; i < iterations; i++) {
const id = uid();
uids.push(id);
uidMap[id] = i;
timeMap[Date.now()] = i;
microMap[performance.now()] = i;
}
console.log(`Time taken: ${performance.now() - time}ms`);
console.log('Unique IDs:', Object.keys(uidMap).length.toLocaleString());
console.log('Clashing timestamps:', (iterations - Object.keys(timeMap).length).toLocaleString());
console.log('Clashing microseconds:', (iterations - Object.keys(microMap).length).toLocaleString());
console.log('Sortable:', !uids.slice().sort().find((id, i) => uids[i] !== id))
The usual way in which I generate unique IDs is by using Date.now();
const ID = Date.now();
console.log(ID);
The other way is by using a library as idgp which can be installed using npm.
The link: https://www.npmjs.com/package/idgp
Assumed that the solution proposed by #abarber it's a good solution because uses (new Date()).getTime() so it has a windows of milliseconds and sum a tick in case of collisions in this interval, we could consider to use built-in as
we can clearly see here in action:
Fist we can see here how there can be collisions in the 1/1000 window frame using (new Date()).getTime():
console.log( (new Date()).getTime() ); console.log( (new Date()).getTime() )
VM1155:1 1469615396590
VM1155:1 1469615396591
console.log( (new Date()).getTime() ); console.log( (new Date()).getTime() )
VM1156:1 1469615398845
VM1156:1 1469615398846
console.log( (new Date()).getTime() ); console.log( (new Date()).getTime() )
VM1158:1 1469615403045
VM1158:1 1469615403045
Second we try the proposed solution that avoid collisions in the 1/1000 window:
console.log( window.mwUnique.getUniqueID() ); console.log( window.mwUnique.getUniqueID() );
VM1159:1 14696154132130
VM1159:1 14696154132131
That said we could consider to use functions like the node process.nextTick that is called in the event loop as a single tick and it's well explained here.
Of course in the browser there is no process.nextTick so we have to figure how how to do that.
This implementation will install a nextTick function in the browser using the most closer functions to the I/O in the browser that are setTimeout(fnc,0), setImmediate(fnc), window.requestAnimationFrame. As suggested here we could add the window.postMessage, but I leave this to the reader since it needs a addEventListener as well. I have modified the original module versions to keep it simpler here:
getUniqueID = (c => {
if(typeof(nextTick)=='undefined')
nextTick = (function(window, prefixes, i, p, fnc) {
while (!fnc && i < prefixes.length) {
fnc = window[prefixes[i++] + 'equestAnimationFrame'];
}
return (fnc && fnc.bind(window)) || window.setImmediate || function(fnc) {window.setTimeout(fnc, 0);};
})(window, 'r webkitR mozR msR oR'.split(' '), 0);
nextTick(() => {
return c( (new Date()).getTime() )
})
})
So we have in the 1/1000 window:
getUniqueID(function(c) { console.log(c); });getUniqueID(function(c) { console.log(c); });
undefined
VM1160:1 1469615416965
VM1160:1 1469615416966
Maybe even better would be to use getTime() or valueOf(), but this way it returns unique plus human understandable number (representing date and time):
window.getUniqNr = function() {
var now = new Date();
if (typeof window.uniqCounter === 'undefined') window.uniqCounter = 0;
window.uniqCounter++;
var m = now.getMonth(); var d = now.getDay();
var h = now.getHours(); var i = now.getMinutes();
var s = now.getSeconds(); var ms = now.getMilliseconds();
timestamp = now.getFullYear().toString()
+ (m <= 9 ? '0' : '') + m.toString()
+( d <= 9 ? '0' : '') + d.toString()
+ (h <= 9 ? '0' : '') + h.toString()
+ (i <= 9 ? '0' : '') + i.toString()
+ (s <= 9 ? '0' : '') + s.toString()
+ (ms <= 9 ? '00' : (ms <= 99 ? '0' : '')) + ms.toString()
+ window.uniqCounter;
return timestamp;
};
window.getUniqNr();
let now = new Date();
let timestamp = now.getFullYear().toString();
let month = now.getMonth() + 1;
timestamp += (month < 10 ? '0' : '') + month.toString();
timestamp += (now.getDate() < 10 ? '0' : '') + now.getDate().toString();
timestamp += (now.getHours() < 10 ? '0' : '') + now.getHours().toString();
timestamp += (now.getMinutes() < 10 ? '0' : '') + now.getMinutes().toString();
timestamp += (now.getSeconds() < 10 ? '0' : '') + now.getSeconds().toString();
timestamp += (now.getMilliseconds() < 100 ? '0' : '') + now.getMilliseconds().toString();
Easy and always get unique value :
const uniqueValue = (new Date()).getTime() + Math.trunc(365 * Math.random());
**OUTPUT LIKE THIS** : 1556782842762
I have done this way
function uniqeId() {
var ranDom = Math.floor(new Date().valueOf() * Math.random())
return _.uniqueId(ranDom);
}
function getUniqueNumber() {
function shuffle(str) {
var a = str.split("");
var n = a.length;
for(var i = n - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
return a.join("");
}
var str = new Date().getTime() + (Math.random()*999 +1000).toFixed() //string
return Number.parseInt(shuffle(str));
}
in reference to #Marcelo Lazaroni solution above
Date.now() + Math.random()
returns a number such as this 1567507511939.4558 (limited to 4 decimals), and will give non-unique numbers (or collisions) every 0.1%.
adding toString() fixes this
Date.now() + Math.random().toString()
returns '15675096840820.04510962122198503' (a string), and
is further so 'slow' that you never get the 'same' millisecond, anyway.