Checking for part of a key value pair - javascript

I am currently trying to develop an app that displays the parking tickets in NYC. The API I am using is https://data.cityofnewyork.us/resource/ati4-9cgt.json. I am trying to create a graph in which it shows how many tickets were distributed per month. There is a column called "issue_date", and it has year, month, followed by a bunch of numbers I am unsure of. Is it possible to only check the year and month part, instead of the whole value? For example, if I only want to see if the ticket was issued in January, I would check for 2017-01, but how would I make it so that the rest of the value doesn't get accounted and break the code? Thank you!
I forgot to include that I am a first year in college. I have limited knowledge on programming, sorry for the inconvenience.

You can just grab the first 7 characters from the date.
Here is some code to count the number of entries per month and put that in an array:
fetch("https://data.cityofnewyork.us/resource/ati4-9cgt.json")
.then(resp => resp.json()).then(data => {
const byMonth = Object.entries(data.reduce( (acc, row) => {
const month = row.issue_date.slice(0,7); // <--- just taking YYYY-MM
acc[month] = acc[month] || 0;
acc[month]++; // <--- counting
return acc;
}, {})).sort((a, b) => a[0].localeCompare(b[0])); // <--- sorting months
console.log(byMonth); // <--- output [[month, count], [month, count], ...]
});

Match issuedate with regexp, e.g. /^2017-01/
So if you have this whole array, you could:onlyOnJanuary2017 = wholeArray.filter(object => object.issue_date.match(/^2017-01/)); as here: https://jsfiddle.net/twpq6nvd/
Additionaly, if you want to bucket themm try this
var result = arr.reduce((accumulator, item) => {
const date = new Date(item.issue_date)
var key = `${date.getFullYear()}-${date.getMonth()}`
accumulator[key] = accumulator[key] || [];
accumulator[key].push(item);
return accumulator;
}, {})
https://jsfiddle.net/d3qa96bg/1/

Related

Team calendar mapping to find common available open slots

I am using a calendar application that shows the available timeslots for a group. For example if the team has 2 members, and if each member has their own workingHours and own existing bookings; to show the team calendar where the array output is only giving all slots that are commonly (collectively) open among all the members. Code below is incorrectly providing returns that are not consistent with the common open slots.
userSchedule has the working hours and busy times of the 2 members of the team
userSchedule = [{"workingHours":[{"days":[1,2,3,4,5],"startTime":60,"endTime":540}],"busy":[]},{"workingHours":[{"days":[2,3],"startTime":60,"endTime":540},{"days":[5],"startTime":60,"endTime":450},{"days":[1],"startTime":180,"endTime":540}],"busy":[]}]
From this I want to get the working hours of the combined team when both are available first. Then i want to removed the blocked times and only show in team calendar (in an array) the actual days and slots that the combined team is available.
The below logic is not working and showing output where the workingHours are not all days that both are available
const workingHours = userSchedules?.reduce(
(currentValue: ValuesType<typeof userSchedules>["workingHours"], s) => {
// Collective needs to be exclusive of overlap throughout - others inclusive.
if (eventType.schedulingType === SchedulingType.COLLECTIVE) {
// taking the first item as a base
if (!currentValue.length) {
currentValue.push(...s.workingHours);
return currentValue;
}
// the remaining logic subtracts
return s.workingHours.reduce((compare, workingHour) => {
return compare.map((c) => {
const intersect = workingHour.days.filter((day) => c.days.includes(day));
return intersect.length
? {
days: intersect,
startTime: Math.max(workingHour.startTime, c.startTime),
endTime: Math.min(workingHour.endTime, c.endTime),
}
: c;
});
}, currentValue);
} else {
// flatMap for ROUND_ROBIN and individuals
currentValue.push(...s.workingHours);
}
return currentValue;
},
[]
);

Javascript Compare two arrays based on partial match and update to fully match

I'm working on a project in Node.js (Foundry Virtual Table Top), and I have two arrays. First is a list of entries, the second is the same list, but with indexing numbers prefixed to it. I need to match the two, based on the name, and update the first list to include the same numbering.
Example of 1st list:
Early Life
Circumstances of Birth
Family
Region
...
Example of 2nd list:
1.3. Early Life
1.3.1. Circumstances of Birth
1.3.2. Family
1.3.3. Region
So I want "Early Life" to be updated to "1.3. Early Life"
I have the base of it done, along with the regex I need, but I'm not sure how to actually compare, match, and then update based on the partial matches. Any help would be greatly appreciated, I have very little experience with javascript.
/** Takes a compendium of journals and matches then replaces names to those of an imported list prepended with index numbers
* New naming convention == "#.##... TEXT"
* Eg. "Special Abilities" -> "2.01.2. Special Abilities"
*
* 1. put the new name list in an array
* 2. iterate through the compendium, get index or contents
* 3. compare the entries name value vs new list(array), regex'd to remove the #s and . prior to the name, and set an update array with the new name
* 4. update the compendium
*/
//read text file of new names
let f = await fetch("/test_list.txt")
let txt = await f.text()
// Convert to array, split by line
let updatedNames = txt.toString().split("\n");
// Compendium to Update
const compendiumLabel = "Gamemastery";
(async ()=>{
const p = game.packs.entries.find(e => e.metadata.label === compendiumLabel)
// Array of current journal entries (filtering out Compendium Folders)
const currentNames = p.index.filter(x => x.name != '#[CF_tempEntity]').map(i=>{
return { name : i.name };
});
// Compare currentNames to updatedNames (regex'd to remove starting numbers and .)
// regex that excludes the numbering "#.#.#. " from updatedNames
let regex = new RegExp('[^(\d+.)+\s].*', 'g')
// update compendium
})();
Maybe something like this:
const notIndexed = `Early Life
Circumstances of Birth
Family
Region`.split('\n');
const indexed = `1.3. Early Life
1.3.1. Circumstances of Birth
1.3.2. Family
1.3.3. Region`.split('\n');
const regexp = /^((?:\d.)+) +(.+)/;
const mapping = new Map(
indexed.map((line) => {
const [, index, title] = line.match(regexp);
return [title, index];
})
);
const reIndexed = notIndexed.map(
(line) => {
if (mapping.has(line)) return `${mapping.get(line)} ${line}`;
return line;
}
).join('\n');
console.log(reIndexed);

Firestore orderBy Timestamp object

I am trying to order a query by timestamp.
In my document I have a field called "date" which has this form:
date = {
nanoseconds: 963000000,
seconds: 1594917688
}
In my code I have this:
let photosArray = [];
firebase
.getDatabase()
.collection("photos")
.doc(firebase.getCurrentUser().uid)
.collection("userPhotos")
.orderBy("date", "asc") // Sorted by date in ascending direction
.onSnapshot((snapshot) => {
let changes = snapshot.docChanges();
changes.forEach((change) => {
if (change.type === "added") {
// Get the new photo
const photo = change.doc.data();
// Add the photo to the photos list
photosArray.push(photo);
}
});
// The last photo is at the top of the list
setPhotos(photosArray);
But when I render the list of photos, they are unsorted... For example: the first one taken 2 hours ago, the second one taken 1 minute ago, and the last one taken 2 years ago.
UPDATE
This is how I store the date in firestore
Firebase.js:
getTimestamp = () => firebase.firestore.FieldValue.serverTimestamp();
PhotoUploader.js
await firestore
.collection("photos")
.doc(userId)
.collection("userPhotos")
.add({
id,
date: firebase.getTimestamp(),
});
If your date field shows a map with two nested fields, that is not really a timestamp, and it won't sort the way you expect. You should take a look at the code that adds the date field to the document, and make sure it uses a timestamp correctly. Either that, or use a single timestamp numeric value that will sort the way you expect.

How to filter last day in an array?

I have an array of objects like this:
[
{
created: "2019-08-14T13:24:36Z",
email: "test1#gmail.com"
},
{
created: "2019-08-15T13:24:36Z",
email: "test2#gmail.com"
},
{
created: "2019-08-16T13:24:36Z",
email: "test1#gmail.com"
},
{
created: "2019-08-22T13:24:36Z",
email: "test4#gmail.com"
},
{
created: "2019-08-22T15:29:66Z",
email: "test1#gmail.com"
}
]
The array is sorted by created. I want to filter those records which are on the last day, irrespective of the time on that day. I added the timestamp using moment.js. Something on these lines:
router.get('/GetLastDayRecords', (req, res) => {
res.json(allRecords.filter(record => record.created.max()));
});
Split the task: first get the maximum date which you'll find at the end of the sorted array (just getting the "YYYY-MM-DD" part of it is enough) and then launch the filter:
let max = allRecords.length ? allRecords[allRecords.length-1].created.slice(0,10) : "";
res.json(allRecords.filter(({created}) => created >= max));
First you need to figure out which day is the last day. If you can assume the records are already sorted, then this is pretty simple:
// Assuming your records are stored in the variable "records"
var lastDay = records[records.length - 1].created;
Now here's where your specific answer may differ based on how you want to handle time zones. Suppose one event happened at 11 PM EST (3 AM GMT) and another event happened at 1 AM EST (5 AM GMT). Are these the same day? In Europe they are, but in America they aren't!
What you need to do is create some cipher from the date+time listed to a "day". This way you can compare two "days" to see if they're the same:
lastDay = new Date(lastDay);
// Setting hours, minutes, and seconds to 0 will give you just the "day" without the time, but by default will use the system timezone
lastDay.setHours(0);
lastDay.setMinutes(0);
lastDay.setSeconds(0);
Once you know which day was the last, it's a simple filter:
// Using a for loop
var results = []
for (var i = 0; i < records.length; i++)
{
if (records[i].created > lastDay) {
results.push(records[i]);
}
}
// Using .filter
var results = records.filter(x => x.created > lastDay);
Alternatively, since we know it's already sorted, we can do it a bit more efficiently by binary searching for the first record on the last day, then grabbing all records after that:
var test = records.length / 2;
var step = records.length / 4;
var found = false;
while (!found) {
if (records[test].created < lastDay) {
test += step;
step /= 2;
}
else if (records[test].created > lastDay) {
if (step == 1) {
// We found the exact cut-off
found = true;
}
else {
test -= step;
step /= 2;
}
}
}
var results = records.slice(test);
Because you're only interested in the "last" day, the logic is a bit simpler. If you wanted the "third" day, you would need to check if created was after the start of the third day and before the end of the third day. We can just check if it's after the start of the last day.
I would create a function to turn your created properties into data be easily compared.
I would also avoid trying to do the entire filter operation in one or two lines as it will difficult to read by other developers.
const dateToInt = date => parseInt( date.split('T').shift().replace(/-/g, '') );
The above will:
Split your created property into an array of date and time.
Select the first element, which happens to be the date.
Remove the dashes in the date.
Coerce the value into a number.
With this you can find the maximum value and filter based on that value.
const nums = foo.map( ({ created }) => dateToInt(created) )
First get a list of numbers from the dataset.
const max = Math.max( ...nums )
Get the biggest number in the list.
const lastDays = foo.filter( ({ created }) => dateToInt(created) === max )
With all that setup, getting the max date is very easy and readable.
Of course, since the list is already sorted. You could have just done this as well.
const last = foo[foo.length -1].created;
const lastDays = foo.filter( ({ created }) => created === last )
I wrote a solution using reduce and filter:
const lastDay = arr.reduce((acc, el) => {
const date = el.created.substr(0,10);
const oldDate = new Date(acc);
const nextDate = new Date(date);
if(oldDate.getTime() > nextDate.getTime()) {
return oldDate;
} else {
return nextDate;
}
}, '1900-01-01');
const lastDayArr = arr.filter(el => {
const date = el.created.substr(0,10);
const oldDate = new Date(lastDay);
const nextDate = new Date(date);
return (oldDate.getTime() === nextDate.getTime());
});
First, you find the most recent date, reducing the original array by comparing which date is the most recent, for this you drop the part of the created string that specifies the hours/minutes/seconds.
You can use a very distant in time date as initial value, or you can set it to null and add another validation in your callback function.
As a second step, you use filter, using the same technique of dropping the hours/minutes/seconds of the created string.
The end result is an array of the elements with the most recent date in your original array.
If you can assume the array is sorted, you can skip the reduce method and just do:
const lastDay = arr[arr.length - 1].created.substr(0,10);
This should work:
allRecords.filter( record => {
let last_date = allRecords[ allRecords.length - 1].created
return last_date.slice(0, 10) === record.created.slice(0, 10)
})
Basically, you are getting the last element from your array and slicing its created value down to its date. Then you are slicing your current record's created value down to its date and comparing if they are the same.
Assuming that the array is already ASC ordered:
const onLastDay = values.filter( v => {
const last = moment(values[ values.length - 1 ].created)
const differenceInDays = last.diff(moment(v.created), 'days')
return differenceInDays < 1
})
console.log(onLastDay)
NOTE: If you try with the reported array you get an error due the fact that the last date is not valid! There are 66 seconds!

Return most recent time in series of creationTimes

Not entirely sure how I word the question but my problem is Im doing an api call that returns a bunch of messages that have a creation time, now what I want to do is only return the latest creationTime for the messages with the same date so say If I have 30 messages on the 15/03/2018 I want to grab the latest time, and discard the rest.. and do that for each set of messages with the same date
So what Ive done so far is..
using lodash I have gotten all the messages, filtered out all the ones with a certain type, and I have ordered them by creationTime so the latest being at the top and going down.. now my question is how can I then make an array of the latest times for each date??
this._activityServiceProxy.getAllItems(start, end).subscribe(result => {
// this.messages = result;
// console.log(result);
let loginUnfiltered = _.filter(result, {'title': 'LOGIN'});
let loginFiltered = _.orderBy(loginUnfiltered, {'creationTime': 'desc'});
console.log(loginFiltered);
});
any help would be appreciated!
Thanks
Use .map(...) to get at array of only the latest creationTime:
this._activityServiceProxy.getAllItems(start, end).subscribe(result => {
// this.messages = result;
// console.log(result);
let loginUnfiltered = _.filter(result, {'title': 'LOGIN'});
let loginFiltered = _.orderBy(loginUnfiltered, {'creationTime': 'desc'});
const creationTimes = loginFiltered.map(l => l.creationTime);
console.log(creationTimes);
const latestTime = creationTimes[0];
console.log(latestTime);
});
You can use Underscore's groupBy function to achieve this:
const groups = _.groupBy(loginFiltered, (login) => {
const asDate = new Date(login.creationTime);
asDate.setHours(0, 0, 0, 0);
return asDate;
});
Object.keys(groups).forEach((key) => {
console.log(groups[key][0]);
});
You group by the creationDate property but remove the time component so all days get grouped together. You then loop through the result and just take the first entry per day.
Note that this assumes your creationTime property is a string, as it came from an API. If it's already a date, you don't need the new Date line.

Categories

Resources