I have this:
init(userId: string = this.authProvider.currentUserId) {
this._subscription.add(this.db.list(`meetings/${userId}`)
.subscribe((meetings: any[]) => {
this._meetings = meetings.map((meeting) => {
meeting.Date = meeting.Date;
meeting.Ends = moment.tz(meeting.Ends, 'HH:mm', meeting.Location.TimeZone).local().format('HH:mm');
meeting.Starts = moment.tz(meeting.Starts, 'HH:mm', meeting.Location.TimeZone).local().format('HH:mm');
return Meeting.Parse(meeting);
});
}));
}
On any change from firebase, meeting.Starts, meeting.Ends values are preserved after being parsed by moment.tz.
So assuming on first subscription meeting.Starts is 11:00. After doing meeting.Starts = moment.tz... which will result in say 13:00 then next time anything happens on firebase end and the subscription is triggered, the value of Starts becomes 15:00 assuming the timezone adds 2 hours.
(The value in firebase is correct...) What's happening?
You appear to be using AngularFire2.
The problem is that you are mutating the items in the list. When changes occur, AngularFire2 updates only the items that are affected by the change. However, you are mutating all of the items - so moment.tz will be re-applied to any items that have not changed.
One solution would be to not mutate the items:
init(userId: string = this.authProvider.currentUserId) {
this._subscription.add(this.db.list(`meetings/${userId}`)
.subscribe((meetings: any[]) => {
this._meetings = meetings.map(meeting => Meeting.Parse({
...meeting,
Ends: moment.tz(meeting.Ends, 'HH:mm', meeting.Location.TimeZone).local().format('HH:mm'),
Starts: moment.tz(meeting.Starts, 'HH:mm', meeting.Location.TimeZone).local().format('HH:mm');
}));
});
}
Or, without the spread properties syntax:
init(userId: string = this.authProvider.currentUserId) {
this._subscription.add(this.db.list(`meetings/${userId}`)
.subscribe((meetings: any[]) => {
this._meetings = meetings.map(meeting => Meeting.Parse(
Object.assign({}, meeting, {
Ends: moment.tz(meeting.Ends, 'HH:mm', meeting.Location.TimeZone).local().format('HH:mm'),
Starts: moment.tz(meeting.Starts, 'HH:mm', meeting.Location.TimeZone).local().format('HH:mm');
})
));
});
}
Related
I'm trying to use an if statement in my code where I want it to 'open' a Calendar Box if the date of today has occurred as well as for the past days of my calendar to open.
Here is my code where I'm using an useEffect to post it on loading the React Component:
// Call on post method via axios
useEffect(async () => {
console.log(daysData);
const daysDay = daysData.map((day) => day.day);
console.log(daysDay);
if (date + 1 >= daysDay) {
// Url where to post
await axios.post(`http://localhost:5001/open/chocolate`, {
day: date,
});
alert('New day is available to eat!');
}
setOpenCalendarBox('');
}, []);
I'm trying to get an array I've initiated a few lines above of the useEffect function (daysData) and I want the value of the 'day' item inside of the objects inside of the array and then compare the date of today if it is equal to or less than daysDay (day item inside of daysData)
Here is the code for my array:
// Here I initalize the array with useState
const [daysData, setDaysData] = useState([]);
// Here is the port I'm fetching my array from.
useEffect(() => {
fetch('http://localhost:5001/chocolates')
.then((resp) => resp.json())
.then((data) => setDaysData(data));
}, []);
And here is the date code:
// Initiate new Date
const current = new Date();
// Retrieve current day of the month
const date = current.getDate();
I can't seem to get the effect I want. I basically only want to see if the day has passed or if it is today then I want it to post to '/open/chocolate'.
That's probably because the value of daysData is set asynchronously, yet the useEffect block that depends on it does not list it as a dependency. Therefore you are invoking logic, which requires daysData to be populated asynchronously, when the component is loaded at runtime. So daysData will be empty.
A solution is to simply add daysData in the dependency array, so that you will only execute whatever logic that is in there once the array is successfully populated.
On the other hand, you are comparing a number against an array: which will give an unexpected result. If you want any of the day to meet date + 1, use daysDay.some(d => date + 1 >= d). If you want all of the days to meet date + 1, use daysDate.every(d => date + 1 >= d).
useEffect(async () => {
const daysDay = daysData.map((day) => day.day);
// This needs to be fixed, see comment for options
if (daysDay.some(d => date + 1 > d)) {
// Url where to post
await axios.post(`http://localhost:5001/open/chocolate`, {
day: date,
});
}
setOpenCalendarBox('');
}, [daysData]);
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!
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.
how to concatenate the date and time value in react js. for date and time picking i am using material UI datepicker and timepicker for selecting the date and time this my example code.
selectDate(event, date) {
this.setState({
updatedDate: moment(date).format('DD-MM-YYYY'),
});
}
selectTime(event, time) {
this.setState({
startime: moment(time).format('HH:mm')
});
}
i want to convert to YYYY-MM-DDThh:mm:ss this format
If it really has to remain a datetime string in state at all times then something along these lines should work (ie taking the previous set state string, converting to a moment object, modifying the relevant parts only, then applying back to state as a string again)
format = 'YYYY-MM-DDThh:mm:ss'
selectDate = (event, date) => this.setState( d => prevState => ({
datetimeStr: (moment(prevState.datetimeStr, this.format) || d)
.year(d.year()).month(d.month()).date(d.date())
.format(this.format)
})(moment(date)))
selectTime = (event, time) => this.setState( d => prevState => ({
datetimeStr: (moment(prevState.datetimeStr, this.format) || d)
.hour(d.hour()).minutes(d.minutes())
.format(this.format)
})(moment(time)))
But would be far more flexible if they are both stored in state as actual Date objects and only combined/formatted when needed as a string in some output, that way if need to be formatted differently in different places you can still derive it from the same state
selectDate = (event, date) => this.setState({date})
selectTime = (event, time) => this.setState({time})
getSelectedDatetimeStr = () => {
const date = moment(this.state.date || {})
const time = moment(this.state.time || {})
return moment({
year: date.year(),
month: date.month(),
day: date.date(),
hours: time.hours(),
minute: time.minutes()
}).format('YYYY-MM-DDThh:mm:ss')
}
I m trying to learn cyclejs and reactive programming, and I can't get how to manage events with values.
For example,I need to create four functions that makes some maths operations such as :
addition
substraction
divison
multiplication
Here's the code that I have :
function main ({DOM}) {
const secondNumber$ = DOM
.select('.second-number')
.events('change')
.map(ev => ev.target.value)
.startWith(0)
const firstNumber$ = DOM
.select('.first-number')
.events('change')
.map(ev => ev.target.value)
.startWith(0)
const addClick$ = DOM
.select('.add')
.events('click')
.merge(firstNumber$)
.merge(secondNumber$)
.filter(value => console.log(value))
.scan((nb1, nb2) => nb1 + nb2)
return {
DOM: addClick$.map(result =>
div([
input('.first-number',{type:'text'}),
input('.second-number',{type:'text'}),
button('.add', 'Add'),
h2('Result is : ' + result)
])
)
};
}
It doesn't work at all and I can't figure out in my mind what I m doing wrong out there.
I m looking for a simple explanation how can I make this working ? I feel just like the merging streams of secondNumber$ and firstNumber$ are not correct and I can't find why..
Any idea ?
EDIT : I got that I shouldn't use the operator I was using, but use withLatestFrom.
The fact is that I m using xstream and so I have to map / flatten :
import {
div,
h1,
input,
button
} from '#cycle/dom';
/**
* Counter
* #param {Object} sources Contains the inputs
* #return {Object} The output sinks
*/
function counter(sources) {
const input1$ = sources.DOM
.select('.input1')
.events('input')
.map(ev => ev.target.value)
.startWith(0);
const input2$ = sources.DOM
.select('.input2')
.events('input')
.map(ev => ev.target.value)
.startWith(0);
const add$ = sources.DOM
.select('.add')
.events('click');
const resultAddition$ = add$
.map(ev => input1$
.map(value => input2$
.map(value2 => Number(value) + Number(value2)))
.flatten())
.flatten()
.startWith(0);
return {
DOM: resultAddition$.map(item => {
console.log(item); // triggered each time an input is modified
return div([
h1(`Super new value : ${item}`),
input('.input1', {
attrs: {
type: 'text'
}
}),
input('.input2', {
attrs: {
type: 'text'
}
}),
button('.add', 'Ajouter')
]);
})
};
}
export default counter;
From now, I have got in mind what the code should do, mapping on each click the operation and flatten the two input$ to get my result only when clicking the button
The fact is that the result value is changing on input and not and click. And more important, it changes on input only after the first click on the add button that is not what I want to.
What am I doing wrong this time ?
Thanks for your replies
It seems like you want combineLatest, not merge.
Both combineLatest and merge are "combination operators". They bring multiple Observables together and output one Observable. However, combineLatest is for "AND" combinations, while merge is for "OR" combinations.
You probably need "AND", because you want the value from first-number AND the value from second-number. That said, you want those values only when an add click happens. In that case, there is a variant of combineLatest called withLatestFrom. It allows you to sample the values from first-number AND second-number, but only when the add click happens.
const addClick$ = DOM
.select('.add')
.events('click')
const added$ = addClick$
.withLatestFrom(firstNumber$, secondNumber$,
(click, first, second) => first + second
)
As a side note, you should never do something like .filter(value => console.log(value)). The function for filter is a predicate. It's supposed to be a "condition" function that returns a boolean. If you want to debug, use .do(value => console.log(value)).
PS: I'm assuming you were using RxJS v4.