Trying to understand map in Javascript with moment library timestamps - javascript

I have following code to get list of object with new timestamp. I am using momemt lib to update time. Basic logic is to loop N times and collect list of times adding 1 min to each.
var moment = require('moment');
var _ = require('lodash');
var startDate = moment("1995-12-25");
var currentDate = startDate;
var result = _.range(5).map(function () {
const nextTimestamp = currentDate.add(60, 's');
console.log('nextTimestamp: ' + JSON.stringify(nextTimestamp));
currentDate = nextTimestamp;
return {
timestamp: nextTimestamp
}
});
console.log('result: ' + JSON.stringify(result, null, 2));
I am expecting it will give a array on timestamp with 1 min difference. The output I am seeing is:
nextTimestamp: "1995-12-25T06:01:00.000Z"
nextTimestamp: "1995-12-25T06:02:00.000Z"
nextTimestamp: "1995-12-25T06:03:00.000Z"
nextTimestamp: "1995-12-25T06:04:00.000Z"
nextTimestamp: "1995-12-25T06:05:00.000Z"
result: [
{
"timestamp": "1995-12-25T06:05:00.000Z"
},
{
"timestamp": "1995-12-25T06:05:00.000Z"
},
{
"timestamp": "1995-12-25T06:05:00.000Z"
},
{
"timestamp": "1995-12-25T06:05:00.000Z"
},
{
"timestamp": "1995-12-25T06:05:00.000Z"
}
]
Can anyone help me understand why array is coming with last timestamp for all array element.
Thanks.

This happens because you work with one currentDate object, which you keep mutating. Even when you have put that object in the array, it does not stop from getting mutated by what you do in the next iteration.
You need to create separate objects, and for this you can use the clone method available in momentjs:
currentDate = nextTimestamp.clone();
Or alternatively, call moment():
currentDate = moment(nextTimestamp);

Your problem has nothing to do with map.
See the documentation for moment:
It should be noted that moments are mutable. Calling any of the manipulation methods will change the original moment.
Each entry in the array is a reference to the same moment, which you just modify the value of repeatedly.
You need to use a new moment object (you can get one by clone()ing the old one) each time you add the time to it:
var startDate = moment("1995-12-25");
var nextTimestamp = startDate;
var result = _.range(5).map(function () {
nextTimestamp = nextTimestamp.clone().add(60, 's');
console.log('nextTimestamp: ' + JSON.stringify(nextTimestamp));
return {
timestamp: nextTimestamp
}
});

The Array.map function returns a new array with the results being equal to the given function being applied to every element in the array. Also you are not getting any of the elements to change them. Map takes in a parameter for the current element that needs to be changed.
//I didn't use lodash but the theory is the same
var range = [0, 1, 2, 3, 4, 5];
var result = range.map(function (element) {
return element += 1;
});
console.log(result); //1, 2, 3, 4, 5, 6

Related

How to store a variable to be used by a then promise?

I am using an API to download some data and I want to store it. I want to make multiple calls using different dates and store all the data together, as well as the date in question, like this:
let rankJson = [];
let rankExport = [];
let ranks = [];
function add_weeks(date, n) {
return new Date(date.setDate(date.getDate() + (n * 7)));
};
for (i = startingDate; i < new Date(); i = add_weeks(i,1)) {
// This loops starts at some date I define and adds a week until it reaches a date in the future.
let activeYear = i.getFullYear();
let activeMonth = monthNames[i.getMonth()];
let activeDay = i.getDate();
API.function({year: activeYear.toString(), month: activeMonth, day: activeDay.toString()}).then((res) => {
// This is an API call (its an NPM/Node API) that returns a promise. I then want to store the resulting json array and add the date (i) as new values for each object like so: `
rankJson = JSON.stringify(res);
ranks = JSON.parse(rankJson);
ranks.forEach(function(value){
rankExport.push({
"teamName" : value.team.name,
"teamId": value.team.id,
"position" : value.place,
"points" : value.points,
"posChange" : value.change,
"rankDay" : activeDay,
"rankMonth" : i.getMonth() + 1,
"rankYear" : activeYear,
})
});
In essence, I want all of the responses from the API to be stored in one array (don't need it to be in order) with the right date value. Problem is, I don't know how to pass the date variables so that the correct value is applied. When I pass it like this, it seems to be using the last iteration (i.e. a date in the future). I've also found this to be a bit finicky, mostly because I obviously don't have a lot of experience with promises.
Can anyone help?
Thanks
Sounds like you just need to use a closure/IIFE:
(function(i) {
let activeYear = i.getFullYear();
let activeMonth = monthNames[i.getMonth()];
let activeDay = i.getDate();
API.function({
year: activeYear.toString(),
month: activeMonth,
day: activeDay.toString()
}).then((res) => {
// ...
});
})(i);
The issue right now is that the API call is asynchronous, so by the time you get to the .then, the values of activeYear, activeMonth, activeDay, and i will all have been updated to the last value. By wrapping it in a closure each iteration of the loop will have its own scope.
You could simplify this code by adding all objects to an array and then use Promise.all
var promisses=[]
for (let i = startingDate; i < new Date(); i = add_weeks(i,1)) {
promisses.push({year: i.getFullYear().toString(), month: monthNames[i.getMonth()], day: i.getDate().toString()})}
Promise.all(promisses.map(o=>API.function(o))).then((p) => {
p.forEach(res=>{
rankJson=JSON.stringify(res)
ranks = JSON.parse(rankJson);
ranks.forEach(function(value){
rankExport.push({
"teamName" : value.team.name,
"teamId": value.team.id,
"position" : value.place,
"points" : value.points,
"posChange" : value.change,
"rankDay" : activeDay,
"rankMonth" : i.getMonth() + 1,
"rankYear" : activeYear,
})
});
})
})

forEach unexpected token adding new property to existing array

I want to add a new proeprrty call total_days calculate using date_from and date_to but my forEach got an expected token error.
let applicants = [{
date_from: '2017-05-05',
date_to: '2017-05-10'
},{
date_from: '2017-05-08',
date_to: '2017-05-12'
}]
calculateDays = applicants.forEach(obj =>
applicants['total_days'] = (obj.date_from).diff(obj.date_to, 'days')+1;
)
No clue what's wrong here.
You didn't exactly clarify what you wanted but I tried to take a guess by your code.
My guess is that you wanted to create a new array of applicants from the old array of applicants but in the new array, you wanted to add a property to each object in that array that is the difference in days of the two dates.
To do so, you can use Array.prototype.map to map each item from your array to a new array.
I'm also using Object.assign to clone each object so that the original array is unmodified.
I'm also parsing the date strings into number. The parsed number is the number of milliseconds elapsed since 1 January 1970 00:00:00 UTC. If I take the difference of the two numbers and divide that be the millisecondsInADay then I'll get how many days elapsed in between.
The result is then stored into a new array calculateDays.
Let me know if you need more clarification.
let applicants = [{
date_from: '2017-05-05',
date_to: '2017-05-10'
}, {
date_from: '2017-05-08',
date_to: '2017-05-12'
}]
const millisecondsInADay = 1000 * 60 * 60 * 24;
const calculateDays = applicants.map(obj => Object.assign({}, obj, {
total_days: ((
Date.parse(obj.date_to) - Date.parse(obj.date_from)
) / millisecondsInADay) + ' days'
}));
console.log(calculateDays);
Assuming you want to add a new property to all objects, you could use obj as variable with a new property.
applicants.forEach(obj => obj.total_days = obj.date_from.diff(obj.date_to, 'days') + 1);
// ^^^

add items into two arrays (future and past dates) based on date

I am trying to find a way to sort posts into two arrays: upcoming and current (upcoming posts are in the future and current have already been posted).
All posts have a scheduledPubDate that is a date string in the format YYYY-MM-DDT00:00:00. and todays date has to be a Date object as it will need to stay relevent (I am using moment())
Is it possible to compare these two different things without having to use a .split and compare the month / day /year separately
angular.forEach(data.items, function (key, index) {
if (moment(key.scheduledPubDate) > moment()) {
$scope.upcomingPosts.push(item[index]);
} else if (moment(key.scheduledPubDate) <= moment()) {
$scope.currentPosts.push(item[index]);
};
});
Presumably you want the string treated as UTC, a simple parser for that is:
// Expected format YYYY-MM-DDT00:00:00
function parseUTC(s) {
var b = s.split(/\D/);
return new Date(Date.UTC(b[0], b[1]-1, b[2], b[3], b[4], b[5]));
}
Note that this doesn't allow for invalid dates. If needed, an extra line if code is required. So now you can do:
if (parseUTC(key.scheduledPubDate) > new Date()) // or Date.now()
You really don't need moment.js for this.
JavaScript's built-in Date object will help you here.
var date = Date.parse('2014-01-21T12:45:13');
date < Date.now() // true
For the purpose of an example, let's assume items is an array of posts:
var items = [{
scheduledPubDate: '2014-01-21T12:45:13'
// ...other keys here
}, {
scheduledPubDate: '2017-03-01T15:21:00'
} // ...and so on
];
Then a reduce operation over items can categorize the posts:
var posts = items.reduce(function (memo, item) {
memo[Date.parse(item.scheduledPubDate) <= Date.now() ? 'current' : 'upcoming'].push(item);
return memo;
}, { current: [], upcoming: [] });
Now posts.current will contain an array of all posts from items whose scheduledPubDate is before the current date, and posts.upcoming will contain an array of all scheduled posts.
Edited to use Date.parse, to avoid unreliable behavior pointed out by RobG. This requires that all dates be in the YYYY-MM-DDT00:00:00 format you specified; if that is not the case, another solution will be required.
You have to specify the date format of the string
var format = "YYYY-MM-DDT00:00:00";
angular.forEach(data.items, function (key, index) {
if (moment(key.scheduledPubDate, format) > moment()) {
$scope.upcomingPosts.push(item[index]);
} else if (moment(key.scheduledPubDate, format) <= moment()) {
$scope.currentPosts.push(item[index]);
};
});
Working example (See the console.log): http://jsbin.com/fejaxiguce/1/edit?html,output
First create an array of elements, in any order, and then use the .sort() method.
http://www.w3schools.com/jsref/jsref_sort.asp
var points = [40, 100, 1, 5, 25, 10];
points.sort(function(a, b){return a-b});
Just substitute the above logic with your own. a and b above can be objects.

Split date value

I have a $scope object that has a date property. I am trying to split that date into dd, mm and yyyy so i can perform calculations based on the values.
JS:
$scope.expenses = [
{
amount: 100,
SpentOn: '19/04/2014'
},
{
amount: 350,
SpentOn: '01/09/2013'
}
];
$scope.MySplitDateFunction = function () {
var data = [];
var data = $scope.expenses.SpentOn.split('/');
data.day = SpentOn[0];
data.month = SpentOn[1];
data.year = SpentOn[2];
return data;
};
I am getting the following error: $scope.expenses.SpentOn is undefined
since in your program $scope.expenses has two objects and you have not specifies which is that
Try this out
$scope.MySplitDateFunction = function () {
var data = [];
var data = $scope.expenses[0].SpentOn.split('/');
return data;
};
$scope.expenses returns an array with 2 objects in it so you'll have to do something like
$scope.expenses[0].SpentOn // returns 19/04/2014
Or you can remove the [] to make it an object and not an array of objects
$scope.expenses seems to be an array with objects, you have to try
$scope.expenses[0].SpentOn

Pulling values from an array based on date?

I have an array of data points, [date, value], like so...
data_points = [[1310279340, 1], [1310279340, 1]]
I need to create an array based on "milliseconds ago", with ten values for each second, or a value for every 100ms going backwards.
The new array's values will look like, [ms ago, value], like so...
ms_ago_points = [[0,3],[100,6],[200,7]]
So the last value in that array represents [200ms ago, value of 7].
I'm having a hard time figuring out how to get "value from Xms ago" based on date values in the first array. If needed I might be able to get the initial date/values in a format other then an array if it would be easier to poll for data.
Thanks for any guidance!!
edit: I'm looking to repeat the same value between dates for every 100ms tick that lands between them.
var now = Date.now(),
l = data_points.length,
ms_ago_points = [];
for ( var c = 0; c < l; c++ ) {
ms_ago_points.push([now - data_points[c][0],data_points[c][1]);
}
Maybe you need to sort the ms_ago_points afterwards, but this should give you your desired array.
However: I'd seriously suggest you not to work with multi-dimesional arrays but objects instead:
ms_ago_points= [{ms: 0, value: 3},{ms:100, value: 6}...]
*edit: This of course assumes that your data_points are already in 100ms steps, otherwise you'd have to implement an interpolation :)
I believe this meets your requirements:
function msFromDataPoints(nowTime, dataPoints) {
var d = dataPoints.length,
i = 0,
anchor = Math.floor(nowTime.getTime() / 100) * 100,
ms = anchor,
msArr = [];
while (d--) {
while (ms >= data_points[d][0] * 1000) {
msArr.push([anchor - ms, dataPoints[d][1]];
ms -= 100;
}
}
return msArr;
}
See it in a Js Fiddle.
Note: I find the data structures to be a little strange. An object for the input seems best:
[{date: 1310279340, value: 1}, {date: 1310279340, value: 1}]
And your output array doesn't need the milliseconds at all, as simply [1, 1, 5, 5, 3, 2 4] (or whatever) would be understood to mean millisecond indexes starting at 0 and increasing by 100. If absolutely required, you could use a sparse array:
result = [];
result[0] = 1;
result[100] = 1;
result[200] = 5; // and so on

Categories