React Intl with relativeTime formatting - reactjs

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

Related

Trying to use setInterval inside a scheduleJob and it doesn't work

I'm having this the following code, and I'm trying to use a function inside setInterval but it just won't work... what I'm doing wrong?
const job = schedule.scheduleJob({ hour: 01, minute: 56 }, () => {
loop();
});
function loop() {
var checkminutes = 5,
checkthe_interval = checkminutes * 60 * 1000;
var i = 0;
var interval = setInterval(() => {
i++;
console.log(i); // Just trying here to see if the i value gets printed in console.. but nope..
//uploadFile();
}, checkthe_interval);
}
Had to remove the last 2 lines from the code in order to make it work.

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.

Limit react-day-picker range to x days

I want to limit the range length in react-day-picker to e.g. 10 days. How should this be done, is something availlable in the package already?
This is an example range selector: https://react-day-picker.js.org/examples/selected-range/
Could be easily implemented, I've just added some lines to the handleDayClick function:
Instead of this:
handleDayClick(day) {
const range = DateUtils.addDayToRange(day, this.state);
this.setState(range);
}
Implement this:
handleDayClick(day) {
const oneDay = 24 * 60 * 60 * 1000; // hours*minutes*seconds*milliseconds
const diffDays = Math.round(Math.abs((day - this.state.from) / oneDay));
const range = DateUtils.addDayToRange(day, undefined);
if(diffDays <= 10){
const range = DateUtils.addDayToRange(day, this.state);
this.setState(range);
}
else {
this.setState(range);
}
}

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

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
});

How to push an array to a file without keeping old data when looping in setInterval?

sorry for the title, I was trying to be specific but it's a bit confusing. Actually the piece of code should be the better way to show you what my problem is:
var i = 0
var jsonArray = []
var startTime = new Date().getTime()
var writeFile = setInterval(function () {
var currentTime = new Date().getTime()
var stopTime = 31 * 1000
var executeScript = setInterval(function () {
// the script will stop after some time
if (currentTime - startTime > stopTime) { // time in ms: 60 * 60 * 1000 = 1 hour
process.exit()
}
// making request and fetching data
http.get(request, function (response) {
response.pipe(bl(function (err, data) {
if (err) return console.error(err)
response.setEncoding('utf8')
parseString(data, function (err, result) {
result = result.SoccerFeed.SoccerDocument[0].MatchData[0].TeamData
// creating object and filling it with received data
result = createJsObject(result)
// converting object in json format
var jsonObject = JSON.stringify(result, null, 4)
jsonArray.push(jsonObject)
// copying json into a new file without overwriting previous written data
})
}))
}).on('error', console.error)
}, 1 * 1000) // interval in ms: 90 * 1000 = 90 seconds / 1 min 30
fs.writeFile('API - Opta - ' + moment().format('YYYY - DD.MM (HH:mm:ss)') + '.txt', jsonArray, function(err) {
if (err) throw err
})
jsonArray = []
i++
}, 10 * 1000)
The problem is that the array I'm pushing in the file is keeping data from the old one, even when I clean it with jsonArray = []. I know I'm doing something wrong but I don't know what.
Thanks in advance for your help
So, at first I thought it was an asynchronous problem with fs.writeFile getting a blank copy of jsonArray. This is not the case due to the way that javascript handles jsonArray = []. When you make the call to fs.writeFile, you pass it jsonArray which is passed by "reference." When you set it to jsonArray = [] you actually create an entire new object. The object that was passed to fs.writeFile is no longer referenced by jsonArray and it's only scope is in the call to fs.writeFile thus it maintains the state that you passed in. If you had instead called jsonArray.length = 0 it would have always output an empty array to the file because that would have overwritten the actual array fs.writeFile. Check out this post to see that: How do I empty an array in JavaScript?
In short, that's not the problem.
But the actual problem is that http.get is receiving data many times after the file was written. Those things won't get written until your next loop. Your described sequence that things are always one behind makes perfect sense.
Your sequence is this inside your interval executeScript:
Check to make sure it's not quitting time
Start http.get
sometime in the future the response is received and added to jsonArray
Start fs.writeFile, contents of jsonArray locked in at this point in time as to what is going to be written to the file
sometime in future the file is written
Set jsonArray = []
I would propose you have two separate intervals that aren't nested like so:
var i = 0
var jsonArray = []
var startTime = new Date().getTime()
var writeFile = setInterval(function () {
fs.writeFile('API - Opta - ' + moment().format('YYYY - DD.MM (HH:mm:ss)') + '.txt', jsonArray, function(err) {
if (err) throw err
})
jsonArray = []
i++
}, 10 * 1000)
var executeScript = setInterval(function () {
var currentTime = new Date().getTime()
var stopTime = 31 * 1000
// the script will stop after some time
if (currentTime - startTime > stopTime) { // time in ms: 60 * 60 * 1000 = 1 hour
process.exit()
}
// making request and fetching data
http.get(request, function (response) {
response.pipe(bl(function (err, data) {
if (err) return console.error(err)
response.setEncoding('utf8')
parseString(data, function (err, result) {
result = result.SoccerFeed.SoccerDocument[0].MatchData[0].TeamData
// creating object and filling it with received data
result = createJsObject(result)
// converting object in json format
var jsonObject = JSON.stringify(result, null, 4)
jsonArray.push(jsonObject)
// copying json into a new file without overwriting previous written data
})
}))
}).on('error', console.error)
}, 1 * 1000) // interval in ms: 90 * 1000 = 90 seconds / 1 min 30
Now you have two intervals going at the same time. The 1 second loop on executing the script now has lots of data queued up for when the 10 second loop on writing the file kicks off. It writes the file and then immediately clears jsonArray for it to keep adding in subsequent 1 second loops of the executeScript interval.
Not sure if it helps, but place this line
var jsonArray = []
after this
var writeFile = setInterval(function () {
and you won't need to clear jsonArray.
so it will be
var writeFile = setInterval(function () {
var jsonArray = []
// ...

Resources