I am wondering if it is possible to use weekly trigger to fire once and then go to a weekly schedule
Trigger trigger = TriggerUtils.MakeWeeklyTrigger(
"TriggerName",
dayOfweek,
timeOfDay.Hours,
timeOfDay.Minutes);
So I basically want when my service first starts my trigger to fire and run it's job. After that I want it to work on a weekly basis based on user settings(So maybe after first run a user set it to Monday 5:00am).
You can have multiple triggers associated with one job. One would fire immediately and only once and the other will fire weekly:
var jobDetail = new JobDetail("MyJob", "MyGroup", typeof(T));
// run in ten seconds, only once.
var once = new SimpleTrigger("Once",
null,
DateTime.UtcNow.AddSeconds(10),
null,
0,
TimeSpan.Zero);
// run weekly, start in 5 minutes to give 'once' trigger some time to run
Trigger weekly = TriggerUtils.MakeWeeklyTrigger(
"Weekly",
dayOfweek,
timeOfDay.Hours,
timeOfDay.Minutes);
weekly.StartTimeUtc = DateTime.UtcNow.Add(TimeSpan.FromMinutes(5));
once.JobName = jobDetail.Name;
weekly.JobGroup = jobDetail.Group;
once.JobName = jobDetail.Name;
weekly.JobGroup = jobDetail.Group;
_quartzScheduler.ScheduleJob(jobDetail, once);
_quartzScheduler.ScheduleJob(weekly);
Or you can schedule immediate trigger and when it fires reschedule to weekly:
private static volatile Boolean firstRun = false;
public void Execute(JobExecutionContext context) {
if(firstRun) {
Trigger weekly = TriggerUtils.MakeWeeklyTrigger(
"Weekly",
dayOfweek,
timeOfDay.Hours,
timeOfDay.Minutes);
context.Scheduler.RescheduleJob("Once", "MyGroup", weekly);
firstRun = false;
}
}
Another option is to just run your code on start up without involving Quartz. Just use ThreadPool. And then let Quartz handle weekly schedule only. This is possible if your code does not depend on Quartz which might be a good idea in general.
Related
I am calling one API, but that data is not changing frequently and I am storing data in core data. I want that API should call only once in 30 min. is there any better approach of calling API only if it exceed 30 min from last API call. I can think of Timer based, but like to know if there is any other better way to do same?
If you are calling the api randomly then holding a variable in memory for the last successful call might be good enough. If you want the API to be called automatically you'll best approach would be to set up a timer.
If you want your app to prohibit to make a new call in 30 min, then this would be a quick example:
(wrote this for a playground)
var lastCheck: Date?
let minimumMinutes = 60.0
func makeNetworkCall() {
if let lastCheckDate = lastCheck, lastCheckDate.timeIntervalSinceNow < (30 * minimumMinutes) {
debugPrint("Not making call, Didn't go 30 min yet")
return
}
lastCheck = Date()
debugPrint("Making network call!")
// ... make call
}
makeNetworkCall() // Should make call
makeNetworkCall() // Should not make call
makeNetworkCall() // Should not make call
I haven't tested the code above, but it should work.
To just limit service calling for a specific time (ex 30 minutes), you can store last service called date and use it to decide to do a call or not. You can store the date in memory or persistent storage depend on you need.
UserDefaults can be an option to store last date for persistance. There is a sample implementation below;
func saveLastServiceCalledDate() {
UserDefaults.standard.set(Date(), forKey: "lastServiceCallDate")
}
func isCalledInLast30Min() -> Bool {
guard let lastDate = UserDefaults.standard.value(forKey: "lastServiceCallDate") as? Date else { return false }
let timeElapsed: Int = Int(Date().timeIntervalSince(lastDate))
return timeElapsed < 30 * 60 // 30 minutes
}
func serviceCall() {
// ignore if called in last 30 minutes
if isCalledInLast30Min() { return }
// save current date
saveLastServiceCalledDate()
// do service call
}
My suggestion is to use DispatchSourceTimer because it can be restarted at any time.
Call startTimer() in viewDidLoad and in applicationWillBecomeActive to get the most recent data when the application becomes active
var timer : DispatchSourceTimer!
func startTimer()
{
if timer == nil {
timer = DispatchSource.makeTimerSource(queue: DispatchQueue.global())
timer.schedule(deadline: .now(), repeating: 30.0)
timer.setEventHandler {
self.callAPI()
}
timer.activate()
} else {
timer.schedule(deadline:.now(, repeating: 30.0)
}
}
There is no way but timer
1- Create a Timer with 1 minute schedule
2- Timer function checks current timeStamp against a stored 1 say in defaults
3- If stored value is nil or exceeded 30 minutes gap between the current call the api
4- When you call the api update the stored value with the current 1
The reason behind making it a stored value not global is freguently opening and closing the app won't cause non-new api calls
let current = Date().timeIntervalSince1970
let stored = UserDefaults.standard.double(forKey:"stored")
if stored == 0 || current - stored >= 30.0 {
// call the api && update stored value
}
You haven't mentioned whether this should happen in background or foreground? Because based on that only we need to go for the solution. In case if u are wondering about update the data in the background, you should check apples BGTaskBackground. But the problem with this is, you can't decide the time to trigger. You can only give minimumFetchingInterval, which is not guaranteed but will be decided by the system/is.
Incase if you are looking to update in the foreground, just go with the timer approach you are talking about. Use any background queues to do that job. Queues will help you out in dispatching specific task at specific time with delay method.
Test Timer Spreadsheet
My understanding is that it isn't possible due to the limitations of Spreadsheet Settings > Calculations. I have successfully implemented Days, Hours, and Minutes into my timer - but it would be cool to have the Seconds as well.
Formulae:
=IFERROR(DAY(AH9-NOW()),0)
=IFERROR(HOUR(AH9-NOW()),0)
=IFERROR(MINUTE(AH9-NOW()),0)
*AH9 refers to the grey date cell in the timer
The Seconds timer isn't essential, more aesthetic - but I was interested to see if it was possible or not.
I have added some script into your workbook which does run a seconds timer.
The script that I have added is subject to the limitations of Apps Script run time but I have added various triggers in which should hopefully fire up the script once it has ended. I have also added a button to start the script as well but you may want to delete this out?
The script on its own will run for 4-5 mins, the triggers should set this so that there is only a small interval when the seconds won't be working.
Please note that to make this script run it needs to add a date into a cell - if you set A1 to colour black, you will see this there. The script will add this in if you delete it out so no real need to worry about leaving it there etc.
If you need to change the cell where this is, just change the global variable in the apps script project.
Hope this helps!!
The script I used was as below. The triggers that I used were a time driven trigger based on a minute timer then set it to every minute. As each function of the script works for 15 seconds, this creates a loop for a minute, which is triggered every minute.
I did play around but unfortunately I couldn't get the maths right to make it run for any longer :)...
var CELL_RANGE = 'A1'
function updateCell() {
for (i=0; i<15; i++){
Utilities.sleep(1000);
var date = new Date();
SpreadsheetApp.getActiveSheet().getRange(CELL_RANGE).setValue(date);
SpreadsheetApp.flush();
updateCell1()
}
}
function updateCell1() {
for (i=0; i<15; i++){
Utilities.sleep(1000);
var date = new Date();
SpreadsheetApp.getActiveSheet().getRange(CELL_RANGE).setValue(date);
SpreadsheetApp.flush();
updateCell2()
}
}
function updateCell2() {
for (i=0; i<15; i++){
Utilities.sleep(1000);
var date = new Date();
SpreadsheetApp.getActiveSheet().getRange(CELL_RANGE).setValue(date);
SpreadsheetApp.flush();
updateCell3()
}
}
function updateCell3() {
for (i=0; i<15; i++){
Utilities.sleep(1000);
var date = new Date();
SpreadsheetApp.getActiveSheet().getRange(CELL_RANGE).setValue(date);
SpreadsheetApp.flush();
updateCell()
}
}
So I've been searching Stackoverflow for a good solution on how to update label if the time change, but so far the results have been unsatisfactory. Most use a timer, and that's not what I want.
What I want is if the status bar time is 8:52 PM and if some arbitrary bus leaves at 9:00 PM then I want the label to show 8 min. Then, if the time changes to 8:53 PM I want the label to show 7 min.
I'd prefer some sort of notification or delegation method rather than setting a timer.
If anyone has any suggestions on what I should do (or any 3rd party libraries that could notify if the time changes) that would be great!
You only have limited choices. If your app is not running in the foreground, you're limited to local notifications, which display an alert to the user. Using those once a minute would be awful for the user.
When your app comes to the foreground you can start a timer that is timed to fire every minute on the minute and use that to update a label in your app:
weak var timer: Timer?
func startTimer() {
timer?.invalidate()
let interval = Double(Date().timeIntervalSinceReferenceDate)
let delay = 60 - fmod(interval, 60.0)
message.text = "Delay = \(delay)"
//Create a "one-off" timer that fires on the next even minute
let _ = Timer.scheduledTimer(withTimeInterval: delay, repeats: false ) { timer in
self.message.text = "\(Date())"
self.timer = Timer.scheduledTimer(withTimeInterval: 60.0,
repeats: true ) { timer in
//Put your repeating code here.
self.message.text = "\(Date())"
}
}
}
I want to measure how long (in seconds) it takes users to do certain things in my app. Some examples are logging in, pressing a button on a certain page, etc.
I am using an NSTimer for that. I am starting it in the viewDidLoad of a specific page, and stopping it at the point that I want to measure.
I also want to measure cumulative time for certain things. I would like to start the timer on the log-in screen, and then continue the timer until the user gets to the next view controller and clicks on a certain button.
I'm not sure how to do this. Should create a global variable in my app delegate? Or is there some other better way?
No need for an NSTimer, you just need to record the start times and compare them to the stop times. Try using a little helper class such as:
class MyTimer {
static let shared = MyTimer()
var startTimes = [String : Date]()
func start(withKey key: String) {
startTimes[key] = Date()
}
func measure(key: String) -> TimeInterval? {
if let start = startTimes[key] {
return Date().timeIntervalSince(start)
}
return nil
}
}
To use this, just call start(withKey:) right before you start a long-running task.
MyTimer.shared.start(withKey: "login")
Do something that takes a while and then call measure(key:) when you're done. Because MyTimer is a singleton, it can be called from anywhere in your code.
if let interval = MyTimer.shared.measure("login") {
print("Logging in time: \(interval)")
}
If you're using multiple threads, you may to to add some thread safety to this, but it should work as is in simple scenarios.
Generally speaking I want to query my DB for all new FeedItems in the last 10 minutes. This Feed should have a nice batch of new FeedItems every 10 minutes.
I've written a function:
func getRecentFeedItems() {
// Set up timing
let date = NSDate()
let calendar = NSCalendar.autoupdatingCurrentCalendar()
let dateMinusTenMin = calendar.dateByAddingUnit(.Minute, value: -10, toDate: date, options: [])
//Query the DB
let getRecentFeedItems = FeedItem.query()
getRecentFeedItems!.whereKey("createdAt", greaterThan: dateMinusTenMin!)
let newBadgeCount: Int = (getRecentFeedItems?.countObjects())!
if newBadgeCount > 0 {
self.navigationController?.tabBarItem.badgeValue = String(newBadgeCount) //update the Badge with the new count
print("update the badge with \(newBadgeCount)")
} else {
self.navigationController?.tabBarItem.badgeValue = nil
}
}
This function works to update the Badge Notification however when I set a timer for it to run every ten minutes:
var updateBadgeQueryTimer = NSTimer.scheduledTimerWithTimeInterval(600.0, target: self, selector: Selector("updateBadgeQuery"), userInfo: nil, repeats: true)
It does work, but I get the following warning which I know to be a serious one:
Warning: A long-running operation is being executed on the main thread.
Given the following parameters:
If user is on Tab1 where the FeedItems, the query runs and the badge is populated, I want the Badge to show up and then disappear when the user reloads the feed via UIRefreshControl()
If user is on Tab2 or another View, I want the Badge to show up and only disappear when user presses Tab1.
I want the query for the amount of new items to run every 10 minutes.
I've tried running getRecentFeedItems() in viewWillAppear as well as viewDidAppear().
Is there a better way to do this?
As far as I can tell the only thing you'll need to change is to run getRecentFeedItems in the background and only update the badge value after you get a success or failure message from the query. This will prevent the warning of an operation occurring on the main thread.
Hope this helps!