i have an array in javascript that looks like this
Array[9]
0: "01/06/2016"
1: "02/06/2016"
2: "23/05/2016"
3: "24/05/2016"
4: "25/05/2016"
5: "26/05/2016"
6: "27/05/2016"
7: "28/05/2016"
8: "31/05/2016"
length: 9__proto__: Array[0]
i want to order them so the oldest date is first and the most recent is last.
i have tried
days.sort(function(a,b) {
return new Date(a).getTime() - new Date(b).getTime()
});
but i guess because of the format of the date? this doesn't work.
what else could i try?
expected output
Array[9]
0: "23/05/2016"
1: "24/05/2016"
2: "25/05/2016"
3: "26/05/2016"
4: "27/05/2016"
5: "28/05/2016"
6: "31/05/2016"
7: "01/06/2016"
8: "02/06/2016"
length: 9__proto__: Array[0]
You can slit the string to year, month, date and use that to create the date for comparing.
new Date("01/06/2016") is wont be parsed as you think. The result actually is Jan 06 2016.
days = ["01/06/2016", "02/06/2016", "23/05/2016", "24/05/2016", "25/05/2016", "26/05/2016", "27/05/2016", "28/05/2016", "31/05/2016"];
days.sort(function(a, b) {
aArr = a.split('/');
bArr = b.split('/');
return new Date(aArr[2], Number(aArr[1])-1, aArr[0]).getTime() - new Date(bArr[2], Number(bArr[1])-1, bArr[0]).getTime()
});
console.log(days);
It's because the format Date uses in your case is MM/DD/YYYY
new Date("01/06/2016");
> Wed Jan 06 2016 00:00:00 GMT+0100 (Mitteleuropäische Zeit)
See also http://www.w3schools.com/js/js_date_formats.asp
But from your list i assume your dates are in DD/MM/YYYY format.
3 possible solutions:
use a different format in your list
You could split your string up and create the Date via new Date(year, month, day); in your sort function.
use an advanced date/time library, i recommend moment.js http://momentjs.com/
var array= [ "01/06/2016" ,
"02/06/2016" ,
"23/05/2016" ,
"24/05/2016" ,
"25/05/2016" ,
"26/05/2016" ,
"27/05/2016" ,
"28/05/2016" ,
"31/05/2016" ];
array.sort(function (a, b) {
var dateParts1 = a.split("/");
var dateParts2 = b.split("/");
var dateA=dateParts1[2]*360+ dateParts1[1]*30+ dateParts1[0];
var dateB=dateParts2[2]*360+ dateParts2[1]*30+ dateParts2[0];
if (dateA > dateB) {
return 1;
}
if (dateA < dateB) {
return -1;
}
return 0;
});
please separate the parsing and the sorting-operations and cache the intermediate-values.
days = ["01/06/2016", "02/06/2016", "23/05/2016", "24/05/2016", "25/05/2016", "26/05/2016", "27/05/2016", "28/05/2016", "31/05/2016"];
days.map(v => { //parsing
var a = v.split("/");
return {
value: v,
ts: +new Date(+a[2], a[1]-1, +a[0])
}
})
.sort((a,b) => a.ts - b.ts) //sorting
.map(o => o.value); //returning the associated (input-)values
For n items in the input-array, the sorting-function may be called up to n * (n-1) times, depending on the implemented sorting-algorithm.
That's in this case, up to 144 times parsing such a string, in the worst case.
In the very best case (array already sorted) the other implementations here have to parse at least 16 times for this 9 items. (8 comparisons * 2 strings to parse)
This may not sound like much (yet), but these numbers increase exponentially.
Please try this:
var array = ["01/06/2016", "02/06/2016", "23/05/2016", "24/05/2016", "25/05/2016", "26/05/2016", "27/05/2016", "28/05/2016", "31/05/2016"];
function dateString2Date(dateString) {
var dt = dateString.split(/\//);
return new Date(dt[2]+"-"+dt[1]+"-"+dt[0]);
}
for(var i =0 ; i<=array.length-2;i++){
for(var j=i+1; j<=array.length-1;j++){
if(dateString2Date((array[i])).getTime()/1000> dateString2Date((array[j])).getTime()/1000){
var temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
}
console.log(array)
Related
I need to make a given number in a div, the numbers are different for every day of the week, but at the end of the last day of the week, the number should be the one from the first day. I tried to make this script, but I only can show the day 1 number, day 2, 4 etc. doesn't change at all, what am I doing wrong?
Btw I have a few divs, and the numbers must be different for each div, so I use Content.makeNumber = function (day1, day2, day3, day4, day5, day6, day7 for it. And the number should be up even after reload, so I take date from localStorage
const Content = {};
Content.makeNumber = function (day1, day2, day3, day4, day5, day6, day7) {
let date = localStorage.getItem("date");
const week = 60*60*24*7;
if (date === null) {
const now = Date.now();
localStorage.setItem('date', 'now');
localStorage.setItem('dateWeekEnd', 'now' + 'week');
return day1;
} else {
}
date = Number(localStorage.getItem('date'));
const dateNow = Number(localStorage.getItem('dateWeekEnd'));
const dateCount = (dateNow - date) / week;
let res
switch (dateCount) {
case 1:
res = day1;
break;
case 2:
res = day2;
break;
case 3:
res = day3;
break;
case 4:
res = day4;
break;
case 5:
res = day5;
break;
case 6:
res = day6;
break;
case 7:
res = day7;
break;
default:
res = day1;
break;
}
return res;
};
export default Content;
JavaScript's date has a getDay function that returns a number from 1 to 7, you could use that.
Alternatively, the modulo (%) will also work for you
switch (dateCount % 7 + 1) {
...
}
might work for you
for(let i = 0; i <= 32; i ++){
console.log(i, " becomes: ", i % 7 + 1)
}
You're calling Date.now() only if the "date" variable from local storage is set. When you do this, you set "dateWeekEnd" in localstorage and don't update it again. This causes it to not change.
I think what you want is to replace
const dateNow = Number(localStorage.getItem('dateWeekEnd'));
with
const dateNow = Date.now()
so that it's updated based off the current date.
We also want to change the math on DateCount a bit. It should be
const dateCount = Math.floor(((dateNow - date) / week)*7)%7;
because (dateNow-date) gives us how many milliseconds have passed since date, dividing by week tells us what portion of a week has passed. Multiply by 7 to get the day. Floor it to get an integer instead of a decimal. Take it modulo 7 so it loops back to 0 if it reaches 7. Then dateCount is an integer from 0-6. To keep your current switch statement, add 1 to make it 1-7.
However, I'd recommend using an array to store the day words, and using dateCount as an index of that array to avoid the long switch statements. Altogether this gives us
const Content = {};
Content.makeNumber = function (dateWords) {
let date = localStorage.getItem("date");
const week = 60*60*24*7;
if (date === null) {
const now = Date.now();
localStorage.setItem('date', 'now');
localStorage.setItem('dateWeekEnd', 'now' + 'week');
}
date = Number(localStorage.getItem('date'));
const dateNow = Date.now();
const dateCount = Math.floor(((dateNow - date) / week)*7)%7;
let res = dateWords[dateCount]
return res;
};
export default Content;
where dateWords is an array containing [day1, day2, ... day7]
To clean it further, consider how replacing the week variable with a day variable would change the math, and whether you need to set the dateWeekEnd in localstorage.
For the following Month/Day/Year datestring array...
const array1 = ["05/31/2022", "06/01/2022", "06/02/2022"]
...I am attempting to configure the array to slice and remove all datestring array items (starting with 01 as Day) if they follow after datestring array items with 31 as Day. Same goes for instances of Day 30 followed by Day 01.
To handle this, I set up a for statement to loop through all of the strings in the array. I then used a split method to remove "/" from each array item, thus breaking MM,DD,YYYY into separate variables.
for (let i = 0; i < array1.length; i++) {
var [month, day, year] = array1[i].split('/');
console.log(month, day, year)
}
My intention for the next step is to set up a conditional that checks if an array item that includes 30 or 31 as "day" is followed by an array item that includes 01 as "day", then use slice to remove subsequent dates faster 30th or 31st. For this part, I attempted to re-consolidate month, day and year into individual array items, like so:
const newArray = []
for (let i = 0; i < array1.length; i++) {
var [month, day, year] = array1[i].split('/');
newArray.push(month + day + year)
console.log(newArray)
}
with output:
['05312022', '06012022', '06022022']
However, I'm not sure how to set up a conditional that checks if an array item that includes 30 or 31 as "day" is followed by an array item that includes 01 as "day". How can I go about the functionality for such a check?
You can loop over the dates array and grab the currDay and prevDay and if the condition is satisfied, slice the dates array and return it.
const solution = (dates) => {
for (let i = 1; i < dates.length; i++) {
const currDay = dates[i].slice(3, 5);
const prevDay = dates[i - 1].slice(3, 5);
if ((currDay === "01") & (prevDay === "31" || prevDay === "30")) {
return dates.slice(0, i);
}
}
return dates;
};
console.log(solution(["05/31/2022", "06/01/2022", "06/02/2022"]));
The following us es Array#reduce to
retain all elements unless
the number of elements retained equals the index being considered and the current element has date 01 and follows a 30 or a 31
if index in consideration is greater than the number of items retained, meaning at least one element has been skipped, then the current element is skipped as well.
const array1 = ["05/31/2022", "06/01/2022", "06/02/2022"],
output = array1.reduce(
(prev,cur,i) =>
prev.length && ["30","31"].includes( prev.slice(-1)[0].slice(3,5) ) &&
(prev.length === i && cur.slice(3,5) === "01" || i > prev.length) ?
prev :
[...prev, cur],
[]
);
console.log( output );
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 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 have an array of arrays which the first field is a date (in string format). I want to sort them by the date (asceding), so I can use it for further calculations.
I identify two tasks on my problem. First, parse strings as dates and then sort.
a = new Date(Date.parse('1/11/2014 13:42:54'));
console.log(a)
Return 11th of January whereas I need 1st of November
Then, I the sorting should work like this:
function compare(a,b) {
if (a[0] < b[0])
return -1;
if (a[0] > b[0])
return 1;
return 0;
}
myarray.sort(compare);
So, how can I solve the problem with the dates to make it works on sorting function?
If your dates are in ISO format you can use such a code:
myarray.sort(function (a, b) {
return (new Date(a[0])).getTime() - (new Date(b[0])).getTime();
});
Just capture the date and month part, swap them and do Date.parse and new Date, like this
function getFormattedDate(dateString) {
var result = dateString.replace(/(\d+)\/(\d+)(.*)/, function (m, g1, g2, g3) {
return g2 + "/" + g1 + g3;
});
return new Date(Date.parse(result));
}
console.log(getFormattedDate('1/11/2014 13:42:54'));
// Sat Nov 01 2014 13:42:54 GMT+0000 (GMT)
Here, the regular expression, (\d+)\/(\d+)(.*) will capture three parts of the string, first (\d+) captures the date part followed by / (escaped as \/) and the month part with another (\d+) and the rest of the string is captured with (.*). Then we return a new string by swapping the positions of g2 and g1 (month and date part).
Note: If all you are trying to do is sorting, then you don't need to create a new Date object. You can simply use the result of Date.parse which is epoch time, like this
function getEpochTime(dateString) {
var result = dateString.replace(/(\d+)\/(\d+)(.*)/, function (m, g1, g2, g3) {
return g2 + "/" + g1 + g3;
});
return Date.parse(result);
}
function comparator(firstDate, secondDate) {
return getEpochTime(firstDate) - getEpochTime(secondDate);
}
and then sort like this
var arr = ['3/11/2014 13:42:54',
'2/11/2014 13:42:54',
'1/12/2014 13:42:54',
'1/11/2014 13:43:54'
];
arr.sort(comparator);
console.log(arr);
would give you
[ '1/11/2014 13:43:54',
'2/11/2014 13:42:54',
'3/11/2014 13:42:54',
'1/12/2014 13:42:54' ]
With moment.js you can create a moment object using the String+Format constructor
moment('1/11/2014 13:42:54', 'DD/MM/YYYY HH:mm:ss')
so, if you have an array of arrays which the first field is a date (in string format):
array_of_arrays = [
['1/11/2014 13:42:54', 'val'],
['2/11/2014 13:42:54', true]
];
for(array in array_of_arrays){
epoch = moment(array.shift,'DD/MM/YYYY HH:mm:ss').unix();
array.unshift(epoch);
}
now, you can just do new Date(epoch) as instead of complex date objects, we have Unix Epoch which can be easily sorted with inbuid Array.sort something like this
function Comparator(a,b){
if (a[0] < b[0]) return -1;
if (a[0] > b[0]) return 1;
return 0;
}
array_of_arrays.sort(Comparator);
so now you have array_of_arrays sorted as per date
Lastly, if you need more precise answer than this, please share some more sample code.
This is problem:
a = new Date(Date.parse('1/11/2014 13:42:54'));
console.log(a)
Return 11th of January whereas I need 1st of November`
Your date format is dd/mm/yyy; Date.parse('1/11/2014 13:42:54') accepts mm/dd/yyyy
Try a date parsing as following:
function parseDate(str) {
var ds = str.match(/(\d+)\/(\d+)\/(\d+)\s+(\d+):(\d+):(\d+)/);
// Convert to format: mm/dd/yyyy
return new Date(ds[3], ds[2] - 1, // month is 0-based
ds[1], ds[4], ds[5], ds[6]);
}
var arr = [parseDate('3/11/2014 13:42:54'),
parseDate('2/11/2014 13:42:54'),
parseDate('1/12/2014 13:42:54'),
parseDate('1/11/2014 13:43:54')
];
arr.sort();
Or:
function parseDate(str) {
var ds = str.match(/(\d+)\/(\d+)\/(\d+)\s+(\d+):(\d+):(\d+)/);
// Convert to format: mm/dd/yyyy
return new Date(ds[3], ds[2] - 1, // month is 0-based
ds[1], ds[4], ds[5], ds[6]);
}
var arr = ['3/11/2014 13:42:54',
'2/11/2014 13:42:54',
'1/12/2014 13:42:54',
'1/11/2014 13:43:54'
];
arr.sort(function(a, b) {
return parseDate(a) - parseDate(b);
})