How to make requests using rxjs at the certain time? - javascript

I need to make request three times a day at a certain time (8 AM, 12 AM, 4 PM).
What is the best way to implement this?

This feels a bit like a strange requirement for a JS task and I think you should rather look into making a Cron task for that. But for the sake of example, here's how you can do it:
import { of, Observable, timer, EMPTY } from "rxjs";
import { map, filter, timestamp, switchMap } from "rxjs/operators";
type Hour = number;
type Minutes = number;
const atTimes = (times: [Hour, Minutes][]): Observable<[Hour, Minutes]> =>
timer(0, 1000 * 60).pipe(
switchMap(() => {
const date: Date = new Date();
const [currentHour, currentMinutes] = [
date.getHours(),
date.getMinutes()
];
const time = times.find(
([hour, minutes]) => hour === currentHour && minutes === currentMinutes
);
if (!time) {
return EMPTY;
}
return of(time);
})
);
atTimes([[11,48]]).subscribe(x => console.log(x));
Live demo: https://stackblitz.com/edit/rxjs-sxxe41
PS: I've assumed that the update doesn't need to be triggered as soon as the new minute starts. If that's the case, then timer should tick every second and you should use distinctUntilChanged on the current hour/minutes to wait until they're differents.

You should look into asyncScheduler from RxJS which uses setInterval for time based operations.
Refer to this link: https://rxjs-dev.firebaseapp.com/guide/scheduler
Ideally cron jobs are best on the server side to deal with functions need executing at different times, might be worth looking in that as well.

Related

How to write a function that can add hours and days correctly to the target time zone with date-fns?

Lets say I have a timestamp with the IANA time zone identifier:
2021-11-07T00:00:00-04:00[America/New_York]
Note: This timestamp is 2 hours away from transitioning from DST to standard time.
I want to write a function that can take the timestamp, time zone identifier and a Duration, and return a new "future" timestamp in the target timezone.
For instance:
zonedAddDuration("2021-11-07T00:00:00-04:00", "America/New_York", {
days: 1
})
// => "2021-11-08T00:00:00-05:00"
zonedAddDuration("2021-11-07T00:00:00-04:00", "America/New_York", {
hours: 5
})
// "2021-11-07T04:00:00-05:00"
I seem to have found a way to make zonedAddDuration handle both these scenarios, but not at the same time:
import { add, Duration, parseISO } from "date-fns";
import {
utcToZonedTime,
format as formatTz
} from "date-fns-tz";
// Handles "hours" scenario (also minutes and seconds)
export const zonedAddDuration1 = (
isoDateStr: string,
zoneIana: string,
duration: Duration
) => {
const startDate = parseISO(isoDateStr);
const futureDate = add(startDate, duration);
const zonedTime = utcToZonedTime(futureDate, zoneIana);
return formatTz(zonedTime, "yyyy-MM-dd'T'HH:mm:ssXXXzzz", {
timeZone: zoneIana
});
};
// handles "days" scenario (also months, weeks, years)
export const zonedAddDuration2 = (
isoDateStr: string,
zoneIana: string,
duration: Duration
) => {
const startDate = parseISO(isoDateStr);
const zonedTime = utcToZonedTime(startDate, zoneIana);
const futureDate = add(zonedTime, duration);
return formatTz(futureDate, "yyyy-MM-dd'T'HH:mm:ssXXXzzz", {
timeZone: zoneIana
});
};
As you might notice, the only difference between zonedAddDuration1 and zonedAddDuration2 is the order in which I use utcToZonedTime. I need this function to be general purpose, and should handle all kinds of durations correctly, including DST transitions. I should add that the local timezone of the system should not matter, I want the same results regardless of where I run this code.
I think my understanding of date-fns-tz might be lacking, I've read the documentation many times and still not sure I'm grasping it correctly.
If it is not possible to write such a function, then any help understanding why it behaves as it does would be appreciated (why the ordering matters).
I've been experimenting in this Codesandbox (which includes the 2 test scenarios):
https://codesandbox.io/s/exciting-fog-tke86?file=/src/dateUtil.ts
I found a way that will add durations correctly for both dates and times. What I did was to split the input duration in to a time duration and a date duration, and add those to the input date separately. This way I can apply the utcToZonedTime in the correct order for both scenarios.
All this feels like a hack though, and I'm still not sure why it works (or even if it works as expected for all concievable scenarios).
import { add, Duration, parseISO } from "date-fns";
import {
utcToZonedTime,
format as formatTz
} from "date-fns-tz";
export const zonedAddDuration = (
isoDateStr: string,
zoneIana: string,
duration: Duration
) => {
const startDate = parseISO(isoDateStr);
const dateDuration: Duration = {
years: duration.years ?? 0,
months: duration.months ?? 0,
weeks: duration.weeks ?? 0,
days: duration.days ?? 0
};
const timeDuration: Duration = {
hours: duration.hours ?? 0,
minutes: duration.minutes ?? 0,
seconds: duration.seconds ?? 0
};
const futureDateTime = add(startDate, timeDuration);
const zonedTime = utcToZonedTime(futureDateTime, zoneIana);
const futureDate = add(zonedTime, dateDuration);
return formatTz(futureDate, "yyyy-MM-dd'T'HH:mm:ssXXXzzz", {
timeZone: zoneIana
});
};
Here is a Codesandbox with the working version plus tests.

How to execute a function right after state gets updated with Hooks in React Native?

As my trivial experience in Javascript and React Native, I've been struggling with how to execute a function call checkValidDate right after my state for the dates have been updated.
I'm using react-native-modal-date-time-picker to choose the date.
Here's my code:
const [chosenStartDate, setChosenStartDate] = useState('');
const [chosenStartDate_unix, setChosenStartDate_unix] = useState(null);
const [chosenEndDate, setChosenEndDate] = useState('');
const [chosenEndDate_unix, setChosenEndDate_unix] = useState(null);
const handleConfirm = (day) => {
hideDatePicker(); // Set isDatePickerVisible to false
if(chosenMode){ // If true, calendar shows up for choosing starting date, false --> for choosing ending date
setChosenStartDate(moment(day).format('MMMM Do YYYY, h:mm:ss a'));
setChosenStartDate_unix(parseInt((new Date(moment(day).format()).getTime())/60000));
// Convert date to epoch time
}else{
setChosenEndDate(moment(day).format('MMMM Do YYYY, h:mm:ss a'));
setChosenEndDate_unix(parseInt((new Date(moment(day).format()).getTime())/60000));
checkValidDate(chosenStartDate_unix,chosenEndDate_unix)
// --> I know that the dates only get updated after the handleConfirm has been executed
// --> So basically, the chosenEndDate_unix passed in checkValidDate at this moment is still
// null. Therefore, I can't check it correctly
}
};
const checkValidDate = (start, end) => {
console.log('Checking')
console.log('chosenEndDate_unix', chosenEndDate_unix);
console.log('chosenStartDate_unix', chosenStartDate_unix);
if(start && end){
((end - start) >= 5)
? (console.log('VALID'))
: (alert('Please travel aleast 5 minutes, life is worth explored!'), setChosenEndDate(''))
}
}
//...
return(
//...
{isDatePickerVisible && (
<DateTimePickerModal
isVisible={isDatePickerVisible}
mode={mode}
onConfirm={(day) => {
handleConfirm(day)
// I tried to execute the checkValidDate here, but it also doesn't work
}}
onCancel={hideDatePicker}
/>
)}
)
Basically, I have 2 buttons.
One for choosing a startDate which doesn't need to be checked.
Another for choosing an endDate which needs to be checked whether It's longer that startDate for at least 5 minutes or not
When pressing startDate button, chosenMode will be set to true and vice versa for endDate button
handleConfirm is the function we'll execute when we press the OK button on the calendar. As designed in react-native-modal-date-time-picker, only when we press OK will the date chosen be passed to the onConfirm prop
How can we execute the checkValidDate right after the chosenEndDate and chosenEndDate_unix has been updated?
PLEASE HELP ME
You can use useEffect. The checkValiddate is called when chosenEndDate or chosenEndDate_unix changed.
useEffect(()=>{
checkValiddate()
}, [chosenEndDate, chosenEndDate_unix]);
The official document has more info of how it works: https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects
Thank you very much Yupeng Li. Thank to your suggestion I've come up with the solution
By the way, when we place 2 dependencies which will change at the same time like that. checkValidDate() will be executed twice. So we'll put only 1 dependency which is chosenEndDate_unix
useEffect(() => {
if(!chosenMode) checkValidDate(chosenStartDate_unix, chosenEndDate_unix)
}, [chosenEndDate_unix])

Timer - Count up timer for Angular 8

I am trying to display a timer as shown below in this screenshot. To count up constantly to show how long since this entry has been made.
I couldn't find too many options for doing it in Angular. I found this solution in Angular JS. https://siddii.github.io/angular-timer/ I need the timer exactly like this. But I was not able to port it into Angular or I couldn't. So tried the other alternatives available and found these still they are all also not working.
https://www.npmjs.com/package/ng2-simple-timer
https://www.npmjs.com/package/ngx-timer
The closest I got to was this ngx-timer's Countup-timer. https://www.npmjs.com/package/ngx-timer the timer works fine and its able to start from a given date as per my requirement. startTimer(startDate). But it has an known issue which is still unresolved that is it applies itself to the last known index of the timer element thereby only one timer running in a whole list of timers.You can notice that in the above given screenshot itself.
its a known bug. https://github.com/Y4SHVINE/ngx-timer-lib/issues
So can somebody help me with a solution or some tweaks to one of these solutions to make it work.
Thanks.
I would do this by writing a function to return the difference between the current time and the creation time of an object.
My initial thought was to return the difference as a Date object and then format the result. I soon ran into problems when formatting different time spans, as a date is not the same thing as a time span.
So instead of trying to format a Date that is pretending to be a time span, I would create my own interface.
export interface TimeSpan {
hours: number;
minutes: number;
seconds: number;
}
By doing this we retain control of how time spans >= 1 day are handled, and avoid time zone issues.
I would use OnPush change detection to keep control over when change detection is run:
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AppComponent {
constructor(private changeDetector: ChangeDetectorRef) {}
}
I would then kick off an RxJS interval in ngOnInit(), making sure to unsubscribe when we're finished:
private destroyed$ = new Subject();
ngOnInit() {
interval(1000).subscribe(() => {
this.changeDetector.detectChanges();
});
}
ngOnDestroy() {
this.destroyed$.next();
this.destroyed$.complete();
}
The HTML would then get the elapsed time from a function on my component:
getElapsedTime(entry: Entry): TimeSpan {
let totalSeconds = Math.floor((new Date().getTime() - entry.created.getTime()) / 1000);
let hours = 0;
let minutes = 0;
let seconds = 0;
if (totalSeconds >= 3600) {
hours = Math.floor(totalSeconds / 3600);
totalSeconds -= 3600 * hours;
}
if (totalSeconds >= 60) {
minutes = Math.floor(totalSeconds / 60);
totalSeconds -= 60 * minutes;
}
seconds = totalSeconds;
return {
hours: hours,
minutes: minutes,
seconds: seconds
};
}
This function starts off by getting the total number of seconds elapsed since the creation date, and then works out the hour, minute, and second components.
Where entry is the object that contains some Date instance indicating its creation time. For my demo, I am using this interface:
export interface Entry {
created: Date;
id: string;
}
The elapsed time span can then be retrieved for each instance inside an *ngFor like this:
<span *ngIf="getElapsedTime(entry) as elapsed">
{{elapsed.hours}} h {{elapsed.minutes}} m {{elapsed.seconds}} s
</span>
DEMO: https://stackblitz.com/edit/angular-p1b9af
You could do this with RxJS like this:
const startDate = new Date('2020-03-08 14:12:23');
timer(1000, 1000)
.pipe(
map((x: number) => {
const newDate = new Date(startDate.getTime());
newDate.setSeconds(newDate.getSeconds() + x);
return newDate;
})
)
.subscribe(t => this.myDate = t);
Then in your component you do:
<div>{{ mydate | date: "hh 'h' mm 'm' ss 's'" }}</div>

Sync multiple coundowns o diferent devices react native

Hi I'm using React Native to create an Auctions App I have a Flat List for render each product in auction. On the Flat List component I've a interval of 1000 milliseconds for update the state on all the items the time left.
And for update the current time I use the following method:
updateTime(){
let currentTime = moment().unix();
let diffTime = this.state.product.endTime - currentTime;
let duration = moment.duration(diffTime * 1000, 'milliseconds');
return duration.hours() + ":" + duration.minutes() + ":" + duration.seconds();
}
I retrive the end time for each product from Firebase Firestore.
But for some reason that I do not understand, the time remaining in different devices varies between 2, 3 or even 4 seconds.
If you can tell me what I am doing wrong or what is the best way to do this, I would appreciate it.
moment() gets the time set on the device, so it's expected that your different devices are not set to the exact same time.
You can use a time elapsed interval instead to count down after fetching the product end time:
setInterval(() => this.setState(prevState => ({ timeElapsed: prevState.timeElapsed + 1 }), 1000);
...
const timeRemaining = this.state.product.endTime - this.state.timeElapsed

In JavaScript, how can I have a function run at a specific time?

I have a website that hosts a dashboard: I can edit the JavaScript on the page and I currently have it refreshing every five seconds.
I am trying to now get a window.print() to run every day at 8 AM.
How could I do this?
JavaScript is not the tool for this. If you want something to run at a specific time every day, you're almost certainly looking for something that runs locally, like python or applescript.
However, let's consider for a moment that JavaScript is your only option. There are a few ways that you could do this, but I'll give you the simplest.
First, you'll have to to create a new Date() and set a checking interval to see whether the hour is 8 (for 8 AM).
This will check every minute (60000 milliseconds) to see if it is eight o'clock:
window.setInterval(function(){ // Set interval for checking
var date = new Date(); // Create a Date object to find out what time it is
if(date.getHours() === 8 && date.getMinutes() === 0){ // Check the time
// Do stuff
}
}, 60000); // Repeat every 60000 milliseconds (1 minute)
It won't execute at exactly 8 o'clock (unless you start running this right on the minute) because it is checking once per minute. You could decrease the interval as much as you'd like to increase the accuracy of the check, but this is overkill as it is: it will check every minute of every hour of every day to see whether it is 8 o'clock.
The intensity of the checking is due to the nature of JavaScript: there are much better languages and frameworks for this sort of thing. Because JavaScript runs on webpages as you load them, it is not meant to handle long-lasting, extended tasks.
Also realize that this requires the webpage that it is being executed on to be open. That is, you can't have a scheduled action occur every day at 8 AM if the page isn't open doing the counting and checking every minute.
You say that you are already refreshing the page every five seconds: if that's true, you don't need the timer at all. Just check every time you refresh the page:
var date = new Date(); // Create Date object for a reference point
if(date.getHours() === 8 && date.getMinutes() === 0 && date.getSeconds() < 10){ // Check the time like above
// Do stuff
}
With this, you also have to check the seconds because you're refreshing every five seconds, so you would get duplicate tasks.
With that said, you might want to do something like this or write an Automator workflow for scheduled tasks on OS X.
If you need something more platform-agnostic, I'd seriously consider taking a look at Python or Bash.
As an update, JavaScript for Automation was introduced with OS X Yosemite, and it seems to offer a viable way to use JavaScript for this sort of thing (although obviously you're not using it in the same context; Apple is just giving you an interface for using another scripting language locally).
If you're on OS X and really want to use JavaScript, I think this is the way to go.
The release notes linked to above appear to be the only existing documentation as of this writing (which is ~2 months after Yosemite's release to the public), but they're worth a read. You can also take a look at the javascript-automation tag for some examples.
I've also found the JXA Cookbook extremely helpful.
You might have to tweak this approach a bit to adjust for your particular situation, but I'll give a general overview.
Create a blank Application in Automator.
Open Automator.app (it should be in your Applications directory) and create a new document.
From the dialog, choose "Application."
Add a JavaScript action.
The next step is to actually add the JavaScript that will be executed. To do that, start by adding a "Run JavaScript" action from the sidebar to the workflow.
Write the JavaScript.
This is where you'll have to know what you want to do before proceeding. From what you've provided, I'm assuming you want to execute window.print() on a page loaded in Safari. You can do that (or, more generally, execute arbitrary JS in a Safari tab) with this:
var safari = Application('Safari');
safari.doJavaScript('window.print();', { in: safari.windows[0].currentTab });
You might have to adjust which of the windows you're accessing depending on your setup.
Save the Application.
Save (File -> Save or ⌘+S) the file as an Application in a location you can find (or iCloud).
Schedule it to run.
Open Calendar (or iCal).
Create a new event and give it an identifiable name; then, set the time to your desired run time (8:00 AM in this case).
Set the event to repeat daily (or weekly, monthly, etc. – however often you'd like it to run).
Set the alert (or alarm, depending on your version) to custom.
Choose "Open file" and select the Application file that you saved.
Choose "At time of event" for the alert timing option.
That's it! The JavaScript code that you wrote in the Application file will run every time that event is set to run. You should be able to go back to your file in Automator and modify the code if needed.
function every8am (yourcode) {
var now = new Date(),
start,
wait;
if (now.getHours() < 7) {
start = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 8, 0, 0, 0);
} else {
start = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1, 8, 0, 0, 0);
}
wait = start.getTime() - now.getTime();
if(wait <= 0) { //If missed 8am before going into the setTimeout
console.log('Oops, missed the hour');
every8am(yourcode); //Retry
} else {
setTimeout(function () { //Wait 8am
setInterval(function () {
yourcode();
}, 86400000); //Every day
},wait);
}
}
To use it:
var yourcode = function () {
console.log('This will print evryday at 8am');
};
every8am(yourcode);
Basically, get the timestamp of now, the timestamp of today 8am if run in time, or tomorrow 8am, then set a interval of 24h to run the code everyday. You can easily change the hour it will run by setting the variable start at a different timestamp.
I don t know how it will be useful to do that thought, as other pointed out, you ll need to have the page open all day long to see that happen...
Also, since you are refreshing every 5 seconds:
function at8am (yourcode) {
var now = new Date(),
start = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 8, 0, 0, 0);
if (now.getTime() >= start.getTime() - 2500 && now.getTime() < start.getTime() + 2500) {
yourcode();
}
}
Run it the same way as every8am, it look if 8am is 2.5second ahead or behind, and run if it does.
I try to give my answer hoping it could help:
function startJobAt(hh, mm, code) {
var interval = 0;
var today = new Date();
var todayHH = today.getHours();
var todayMM = today.getMinutes();
if ((todayHH > hh) || (todayHH == hh && todayMM > mm)) {
var midnight = new Date();
midnight.setHours(24,0,0,0);
interval = midnight.getTime() - today.getTime() +
(hh * 60 * 60 * 1000) + (mm * 60 * 1000);
} else {
interval = (hh - todayHH) * 60 * 60 * 1000 + (mm - todayMM) * 60 * 1000;
}
return setTimeout(code, interval);
}
With the startJobAt you can execute only one the task you wish, but if you need to rerun your task It's up to you to recall startJobAt.
bye
Ps
If you need an automatic print operation, with no dialog box, consider to use http://jsprintsetup.mozdev.org/reference.html plugin for mozilla or other plugin for other bowsers.
I will suggest to do it in Web Worker concept, because it is independent of other scripts and runs without affecting the performance of the page.
Create a web worker (demo_worker.js)
var i = 0;
var date = new Date();
var counter = 10;
var myFunction = function(){
i = i + 1;
clearInterval(interval);
if(date.getHours() === 8 && date.getMinutes() === 0) {
counter = 26280000;
postMessage("hello"+i);
}
interval = setInterval(myFunction, counter);
}
var interval = setInterval(myFunction, counter);
Use the web worker in Ur code as follows.
var w;
function startWorker() {
if (typeof(Worker) !== "undefined") {
if (typeof(w) == "undefined") {
w = new Worker("demo_worker.js");
w.onmessage = function(event) {
window.print();
};
} else {
document.getElementById("result").innerHTML = "Sorry, your browser does not support HTML5 Web Workers";
}
}
}
I think it will help you.
I have written function which
allows expressing delay in seconds, new Date() format and string's new Date format
allows cancelling timer
Here is code:
"use strict"
/**
This function postpones execution until given time.
#delay might be number or string or `Date` object. If number, then it delay expressed in seconds; if string, then it is parsed with new Date() syntax. Example:
scheduleAt(60, function() {console.log("executed"); }
scheduleAt("Aug 27 2014 16:00:00", function() {console.log("executed"); }
scheduleAt("Aug 27 2014 16:00:00 UTC", function() {console.log("executed"); }
#code function to be executed
#context #optional `this` in function `code` will evaluate to this object; by default it is `window` object; example:
scheduleAt(1, function(console.log(this.a);}, {a: 42})
#return function which can cancel timer. Example:
var cancel=scheduleAt(60, function(console.log("executed.");});
cancel();
will never print to the console.
*/
function scheduleAt(delay, code, context) {
//create this object only once for this function
scheduleAt.conv = scheduleAt.conv || {
'number': function numberInSecsToUnixTs(delay) {
return (new Date().getTime() / 1000) + delay;
},
'string': function dateTimeStrToUnixTs(datetime) {
return new Date(datetime).getTime() / 1000;
},
'object': function dateToUnixTs(date) {
return date.getTime() / 1000;
}
};
var delayInSec = scheduleAt.conv[typeof delay](delay) - (new Date().getTime() / 1000);
if (delayInSec < 0) throw "Cannot execute in past";
if (debug) console.log('executing in', delayInSec, new Date(new Date().getTime() + delayInSec * 1000))
var id = setTimeout(
code,
delayInSec * 1000
);
//preserve as a private function variable setTimeout's id
return (function(id) {
return function() {
clearTimeout(id);
}
})(id);
}
Use this as follows:
scheduleAt(2, function() {
console.log("Hello, this function was delayed 2s.");
});
scheduleAt(
new Date().toString().replace(/:\d{2} /, ':59 '),
function() {
console.log("Hello, this function was executed (almost) at the end of the minute.")
}
);
scheduleAt(new Date(Date.UTC(2014, 9, 31)), function() {
console.log('Saying in UTC time zone, we are just celebrating Helloween!');
})
setInterval(() => {
let t = `${new Date().getHours() > 12 ? new Date().getHours() - 12 : new Date().getHours()}:${new Date().getMinutes().length < 2 ? '0' + new Date().getMinutes() : new Date().getMinutes()}:${new Date().getSeconds().length < 2 ? '0' + new Date().getSeconds() : new Date().getSeconds()} ${new Date().getHours()>12?"pm":"am"}`
console.log(t);
}, 1000);

Categories