Using RxJS's `generate` to create a non-drifiting timer, how to use the `scheduler`? - timer

In search for a timer - triggered every minute, on the minute - that doesn't drift, I found this solution. However I'm having difficulties understanding how to use / construct the last parameter scheduler?: SchedulerLike.
const noDrift = generate(
0,
_ => true, // start condition
i => ++i, // iterate
i => i, // result selector
i => ... // scheduler?: SchedulerLike; every minute on the minute, but how?
);
noDrift.subscribe(() => {
// action
});
original solution that drifts:
const date = new Date();
// calculate how many ms are left till the full minute is reached
const tillNextFullMinute = (60 - date.getSeconds()) * 1000 - date.getMilliseconds();
// start on the next full minute, then just every minute
this.currentTime = timer(tillNextFullMinute, 60 * 1000);
this.currentTime.subscribe(value => {
// action
});

Related

React - how to prevent a function from being called at short intervals

I have a function that should not be called many times over a short period of time (eg 1 second). My function should be called at least every 2 seconds. For example function setNextPage() should be called immediately and then after at least 2 seconds. I want to prevent calling function setNextPage or setPrevPage for example 5 times in a second. How to do that?
My function
<Scheduler
onMouseMove={handleMouseMove}
/>
const handleMouseMove = (event: any) => {
if (dragMode) { // code should be called only when starting a drag mode
setDragMode(false);
const bounds = event.currentTarget.getBoundingClientRect();
const xPosition = event.clientX - bounds.left;
if (
xPosition >
calendarContainerRef?.current?.clientWidth - 20
) {
setNextPage(); //this function should be called not more often than every 2 seconds
}
if (xPosition < 90) {
setPrevPage(); //this function should be called not more often than every 2 seconds
}
}
};

I'm able to calculate time, but it keeps resetting and does not add up time

const { Collection } = require("discord.js");
// ...
client.userStudyTime = new Collection();
client.on("messageCreate", (message) => {
if (message.author.bot) return;
if (message.content === "!login") {
// Check if the message's content was !login
const time = new Date(); // Get the login time
client.userStudyTime.set(message.author.id, time.getTime()); // Set the time
}
if (message.content === "!logout") {
// Check if the message's content was !logout
const userTime = client.userStudyTime.get(message.author.id); // Get the user's login time
if (!userTime) return message.channel.send("You are not logged in!"); // Check if the user had logged in
const time = new Date().getTime(); // Get the logout time
const diff = time - userTime; // Calculate the difference
const hours = Math.round(diff / 1000 / 60 / 60); // Get the hours
const minutes = Math.round(diff / 1000 / 60); // Get the minutes
const seconds = Math.round(diff / 1000); // Get the seconds
message.channel.send(
`You studied for ${hours}hrs, ${minutes}min, ${seconds}sec`
); // Reply back
client.userStudyTime.set(message.author.id, null); // Set the collection to null
}
});
// .

React Native - Resetting setInterval duration when array.length.current == 0 or when array becomes empty

I have a function that adds numbers to an array and then plays the sounds of the numbers and then removes the numbers after they are played at an interval called 'delay'. However, I have trouble figuring out where to reset the delay back to 0. That is - when all the numbers are played and removed from the array. The whole point of this is so that the first number being added will always be played immediately (at 0 second delay), while numbers being added afterwards will be played and removed at an interval like 4 seconds. I have spent a lot of time on solving this problem, but can anyone figure out where to reset the delay back to 0 so that when the array is empty, the first number being added will always be played immediately? Remember that if you keep adding numbers, the numbers will wait at an interval like 4 seconds to being played, like a sound queue.
const [arr, setArr] = React.useState([]); //Sound queue array
const interval = React.useRef(null);
const arr_length = React.useRef(null);
const [delay, setDelay] = useState(0);
function set_remove_arr_interval(num) {
setArr((currentNumbers) => [...currentNumbers, num]);
if (!interval.current) {
interval.current = setInterval(() => {
// Sets an ongoing interval for the sound queue
console.log("Array current position: " + arr_length.current);
if (arr_length.current > 0) {
playSound(arr[0][0], arr[0][1], arr[0][2]);
setDelay(4000); //My delay duration at 4 seconds
}
if (arr_length.current === 0) {
// setDelay(0); // <-- I tried resetting the delay duration here to 0 but it resets to 0 every time a number is entered immediately and plays it, which is not what I want.
clearInterval(interval.current);
interval.current = null;
return;
}
arr_length.current = arr_length.current - 1;
setArr((currentNumbers) => [...currentNumbers.slice(1)]);
}, delay);
}
}
function add(num) {
arr_length.current = arr.length + 1;
set_remove_arr_interval(num);
}
Edit: I also tried initialising a new variable called isFirst, but it only works at alternating times of the interval:
const [isFirst, setIsFirst] = useState(true);
if (isFirst == false && arr_length.current == 0) {
setDelay(0);
setIsFirst(true);
}
if (arr_length.current > 0) {
playSound(arr[0][0], arr[0][1], arr[0][2]);
if (isFirst == true) {
setDelay(4000);
setIsFirst(false);
}
}
I now solved the problem by simply resetting the delay back to 0 using the useState hook when the array's length is at 1. Does anybody have a better solution than this? Because I believe hooks are not encouraged to be used inside functions and I also found out that sometimes console.logging them does not log the new state when changed.
sound.setOnPlaybackStatusUpdate(async (status) => {
if (status.didJustFinish === true) {
const { sound } = await Audio.Sound.createAsync(data[0], {
shouldPlay: true,
});
setSound(sound);
await sound.playAsync();
if (arr.length == 1) {
setDelay(0);
}
}
});

React Intl with relativeTime formatting

I'm working on a kind of dynamic timestamp for messages using Intl.
I want the timestamps to be dynamic in the way that it automatically transitions from ".. seconds ago" to "... minutes ago" to "... hours ago" to "today", after which it'll just return the date it's been posted. I know there's the <RelativeFormat> component, but I want to use the API instead.
The API has a method called intl.relativeFormat, but can't seem to figure out how to use it...
I'm a junior programmer so it's all still a bit new to me 😅😅
I appreciate your time :)
If you need more info, please let me know. I'll try to provide you with more.
Thanks!
Documentation for the RelativeFormat function can be found here - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/RelativeTimeFormat.
The idea is that you create an instance of relative time format function, with some pre-defined settings that you want the output to follow. For example, you can set your relative time format function to return English strings in a shortened format.
const rtf = new Intl.RelativeTimeFormat('en', { style: 'narrow' });
console.log(rtf.format(3, 'quarters'));
//expected output: "in 3 qtrs."
You also need to pass negative values in order to get labels intended for the past.
const rtf = new Intl.RelativeTimeFormat('en', { style: 'narrow' });
console.log(rtf.format(-3, 'quarters'));
//expected output: "3 qtrs. ago"
The next part leverages an answer given by #fearofawhackplanet here on StackOverflow
//The 'timestamp' function parameter is your timestamp passed in milliseconds.
function timeDifference(timestamp, locale) {
const msPerMinute = 60 * 1000;
const msPerHour = msPerMinute * 60;
const msPerDay = msPerHour * 24;
const msPerMonth = msPerDay * 30;
const msPerYear = msPerDay * 365;
const current = Date.now();
const elapsed = current - timestamp;
const rtf = new Intl.RelativeTimeFormat(locale, { numeric: "auto" });
if (elapsed < msPerMinute) {
return rtf.format(-Math.floor(elapsed/1000), 'seconds');
}
else if (elapsed < msPerHour) {
return rtf.format(-Math.floor(elapsed/msPerMinute), 'minutes');
}
else if (elapsed < msPerDay) {
return rtf.format(-Math.floor(elapsed/msPerHour), 'hours');
}
else {
return new Date(timestamp).toLocaleDateString(locale);
}
}
//
// code to test the above function
//
const fifteenSecondsAgo = new Date();
const tenMinutesAgo = new Date();
const twoHoursAgo = new Date();
fifteenSecondsAgo.setSeconds(fifteenSecondsAgo.getSeconds() - 15);
tenMinutesAgo.setMinutes(tenMinutesAgo.getMinutes() - 10);
twoHoursAgo.setHours(twoHoursAgo.getHours() - 2);
console.log(timeDifference(fifteenSecondsAgo.getTime(), 'en'));
console.log(timeDifference(fifteenSecondsAgo.getTime(), 'es'));
console.log(timeDifference(tenMinutesAgo.getTime(), 'en'));
console.log(timeDifference(tenMinutesAgo.getTime(), 'es'));
console.log(timeDifference(twoHoursAgo.getTime(), 'en'));
console.log(timeDifference(twoHoursAgo.getTime(), 'es'));
Here is a JSFiddle link to see the code running - https://jsfiddle.net/mhzya237/1/
Here is a similar idea, it also deals with future/present/past times.
function getRelativeTime(time) {
const now = new Date();
const diff = Math.abs(time - now);
const mark = (time - now) >> -1 || 1;
if (diff === 0) return new Intl.RelativeTimeFormat('en').format(0,"second");
const times = [
{ type: 'second', seconds: 1000 },
{ type: 'minute', seconds: 60 * 1000 },
{ type: 'hour', seconds: 60 * 60 * 1000 },
{ type: 'day', seconds: 24 * 60 * 60 * 1000 },
{ type: 'week', seconds: 7 * 24 * 60 * 60 * 1000 },
{ type: 'month', seconds: 30 * 24 * 60 * 60 * 1000 },
{ type: 'year', seconds: 12 * 30 * 24 * 60 * 60 * 1000 },
];
let params = [];
for (let t of times) {
const segment = Math.round(diff / t.seconds);
if (segment >= 0 && segment < 10) {
params = [(segment * mark) | 0, t.type];
break;
}
}
return new Intl.RelativeTimeFormat('en').format(...params);
}
const time = getRelativeTime(new Date(new Date().getTime() - 2 * 1000));
console.info('relative time is', time);
The function takes a time param, finds the seconds difference relative to now, uses the array map to calculate which type yields the closest match and uses it as a param for Intl.RelativeTimeFormat
You can improve getRelativeTime(time) function by either returning the params array and call Intl.RelativeTimeFormat from outside the function or also pass the locale (and options) to the function.
I'm sure there are smarter ways to get rid of the times array, perhaps by creating a wrapping closure but it will force you to "initialize" this utility function first

setInterval in a promise, only executes once in React

I'm trying to create a countdown timer with one second ticking away each second. The interval works only one time and does not execute. The problem is that I do not have this.state.timetill until a server call is made, then I can begin the timer. Therefore, the setInterval function has to be tucked away in a promise, where it executes only one. The timetill variable however can be a constant, once retrieved it does not change.
componentDidMount = async () => {
const that = this;
*do stuff*
this.someFunction().finally(() => {
/*This right here, below is not working */
setInterval(that.createTimer(), 1000);
});
}
My JSX is such
{this.state.setTimer}
function in question, the time has to be divided by 1000 since javascript uses miliseconds
createTimer = async timerNow => {
var timerNow = (await new Date().getTime()) / 1000;
// Find the distance between now and the count down date
var distance = this.state.timeTill - timerNow;
// Time calculations for days, hours, minutes and seconds
var days = Math.floor(distance / (1000 * 60 * 60 * 24));
var hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
var minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
var seconds = Math.floor((distance % (1000 * 60)) / 1000);
//the variable displayed
var setTimer = days + "d " + hours + "h " + minutes + "m " + seconds + "s ";
this.setState({setTimer});
};
You pass the returned value of createTimer to setInterval, instead pass the function but don't invoke it
setInterval(this.createTimer, 1000)
There's a few things you should do:
1. Have a method that will actually call setState
2. Change setInterval to call a method reference
3. Cleanup any pending request on unmounting:
4. [optional] you can drop that
private createTimer = { ... }
private setTimer = () => {
this.setState({ timer: this.createTimer() });
}
private _mounted: boolean = false;
private _intervalHandler: number | null = null;
componentDidMount = async () => {
this._mounted = true;
this.someFunction().finally(() => {
if (!this._mounted) { return; }
this._intervalHandler = setInterval(this.setTimer, 1000);
});
}
componentWillUnmoun = () => {
this._mounted = false;
if (this._intervalHandler !== null) {
clearInterval(this._intervalHandler);
}
}
This will ensure you are not performing state changes after the component was unmounted.

Resources