All events will be updated on entering/leaving daylight saving time.

This commit is contained in:
Stefan Persson 2011-01-04 11:42:36 +00:00
parent 62e8b27514
commit 49cefee642
3 changed files with 259 additions and 31 deletions

View file

@ -5,5 +5,6 @@ SET(REQUIRE_PLUGIN_SUNCALCULATOR TRUE PARENT_SCOPE)
SET( Plugin_NAME "scheduler" ) SET( Plugin_NAME "scheduler" )
SET( Plugin_PATH "com.telldus.scheduler" ) SET( Plugin_PATH "com.telldus.scheduler" )
SET( Plugin_EXTRA "DaylightSavingTime.js")
INCLUDE( ../TelldusCenterPlugin.cmake NO_POLICY_SCOPE ) INCLUDE( ../TelldusCenterPlugin.cmake NO_POLICY_SCOPE )

View file

@ -0,0 +1,113 @@
/*
Copyright (C) 2009 by Michael Khalili
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
function TimezoneDetect(){
var dtDate = new Date('1/1/' + (new Date()).getUTCFullYear());
var intOffset = 10000; //set initial offset high so it is adjusted on the first attempt
var intMonth;
var intHoursUtc;
var intHours;
var intDaysMultiplyBy;
//go through each month to find the lowest offset to account for DST
for (intMonth=0;intMonth<12;intMonth++){
//go to the next month
dtDate.setUTCMonth(dtDate.getUTCMonth() + 1);
//To ignore daylight saving time look for the lowest offset.
//Since, during DST, the clock moves forward, it'll be a bigger number.
if (intOffset > (dtDate.getTimezoneOffset() * (-1))){
intOffset = (dtDate.getTimezoneOffset() * (-1));
}
}
return intOffset;
}
//Find start and end of DST
function DstDetect(){
var dtDstDetect = new Date();
var dtDstStart = '';
var dtDstEnd = '';
var dtDstStartHold = ''; //Temp date hold
var intYearDayCount = 732; //366 (include leap year) * 2 (for two years)
var intHourOfYear = 1;
var intDayOfYear;
var intOffset = TimezoneDetect(); //Custom function. Make sure you include it.
//Start from a year ago to make sure we include any previously starting DST
dtDstDetect = new Date()
dtDstDetect.setUTCFullYear(dtDstDetect.getUTCFullYear() - 1);
dtDstDetect.setUTCHours(0,0,0,0);
//Going hour by hour through the year will detect DST with shorter code but that could result in 8760
//FOR loops and several seconds of script execution time. Longer code narrows this down a little.
//Go one day at a time and find out approx time of DST and if there even is DST on this computer.
//Also need to make sure we catch the most current start and end cycle.
for(intDayOfYear = 1; intDayOfYear <= intYearDayCount; intDayOfYear++){
dtDstDetect.setUTCDate(dtDstDetect.getUTCDate() + 1);
if ((dtDstDetect.getTimezoneOffset() * (-1)) != intOffset && dtDstStartHold == ''){
dtDstStartHold = new Date(dtDstDetect);
}
if ((dtDstDetect.getTimezoneOffset() * (-1)) == intOffset && dtDstStartHold != ''){
dtDstStart = new Date(dtDstStartHold);
dtDstEnd = new Date(dtDstDetect);
dtDstStartHold = '';
//DST is being used in this timezone. Narrow the time down to the exact hour the change happens
//Remove 48 hours (a few extra to be on safe side) from the start/end date and find the exact change point
//Go hour by hour until a change in the timezone offset is detected.
dtDstStart.setUTCHours(dtDstStart.getUTCHours() - 48);
dtDstEnd.setUTCHours(dtDstEnd.getUTCHours() - 48);
//First find when DST starts
for(intHourOfYear=1; intHourOfYear <= 48; intHourOfYear++){
dtDstStart.setUTCHours(dtDstStart.getUTCHours() + 1);
//If we found it then exit the loop. dtDstStart will have the correct value left in it.
if ((dtDstStart.getTimezoneOffset() * (-1)) != intOffset){
break;
}
}
//Now find out when DST ends
for(intHourOfYear=1; intHourOfYear <= 48; intHourOfYear++){
dtDstEnd.setUTCHours(dtDstEnd.getUTCHours() + 1);
//If we found it then exit the loop. dtDstEnd will have the correct value left in it.
if ((dtDstEnd.getTimezoneOffset() * (-1)) != (intOffset + 60)){
break;
}
}
//Check if DST is currently on for this time frame. If it is then return these values.
//If not then keep going. The function will either return the last values collected
//or another value that is currently in effect
if ((new Date()).getTime() >= dtDstStart.getTime() && (new Date()).getTime() <= dtDstEnd.getTime()){
return new Array(dtDstStart,dtDstEnd);
}
}
}
return new Array(dtDstStart,dtDstEnd);
}

View file

@ -1,5 +1,7 @@
__setupPackage__( __extension__ ); __setupPackage__( __extension__ );
include("DaylightSavingTime.js");
__postInit__ = function() { __postInit__ = function() {
application.allDoneLoading.connect( com.telldus.scheduler.init ); application.allDoneLoading.connect( com.telldus.scheduler.init );
} }
@ -77,6 +79,8 @@ com.telldus.scheduler = function() {
//TODO inte ladda jobb här, bara "add job" här... //TODO inte ladda jobb här, bara "add job" här...
//TODO hur ska event användas med det här nya? Add event och sådär liksom? //TODO hur ska event användas med det här nya? Add event och sådär liksom?
//TODO testa execute-funktionen med annat... //TODO testa execute-funktionen med annat...
//TODO borde inte använda associative arrays... Objects istället...
//TODO startdate
//det enda varje jobb har är en updateLastRun-method (som kan override:as) och en getNextRunTime (som ska override:as) //det enda varje jobb har är en updateLastRun-method (som kan override:as) och en getNextRunTime (som ska override:as)
//Schemaläggaren (denna) exponerar metoderna "addJob", "removeJob" och updateJob? //Schemaläggaren (denna) exponerar metoderna "addJob", "removeJob" och updateJob?
@ -86,9 +90,7 @@ com.telldus.scheduler = function() {
// //
function init(){ function init(){
loadJobs(); //load jobs from permanent storage loadJobs(); //load jobs from permanent storage TODO move
//calculateJobs(); //TODO recalculate jobs on every run/change, reload jobs on every job change
//runNextJob();
} }
function runNextJob(){ function runNextJob(){
@ -119,7 +121,7 @@ com.telldus.scheduler = function() {
var runJobFunc = function(){ runJob(job.id); }; var runJobFunc = function(){ runJob(job.id); };
var now = new Date().getTime(); var now = new Date().getTime();
var delay = nextRunTime - now; var delay = nextRunTime - now;
print("Will run " + storedJobs[job.id].v.name + " at " + new Date(nextRunTime)); //Note not all will have a name print("Will run " + storedJobs.get(job.id).v.name + " at " + new Date(nextRunTime)); //Note not all will have a name
print("(Now is " + new Date() + ")"); print("(Now is " + new Date() + ")");
print("Delay: " + delay); print("Delay: " + delay);
timerid = setTimeout(runJobFunc, delay); //start the timer timerid = setTimeout(runJobFunc, delay); //start the timer
@ -129,7 +131,7 @@ com.telldus.scheduler = function() {
function runJob(id){ function runJob(id){
print("Running job, will execute"); print("Running job, will execute");
queuedJob = null; queuedJob = null;
var success = storedJobs[id].execute(); var success = storedJobs.get(id).execute();
if(success){ if(success){
updateLastRun(id, new Date().getTime()); updateLastRun(id, new Date().getTime());
} }
@ -145,28 +147,47 @@ com.telldus.scheduler = function() {
settings.setValue("jobs", jobs); settings.setValue("jobs", jobs);
*/ */
//TODO... this will not be stored of course, how to do that? //TODO... this will not be stored of course, how to do that?
storedJobs[id].lastrun = lastRun; //update current list storedJobs.get(id).lastrun = lastRun; //update current list
} }
function updateJobInList(id){ function updateJobInList(id){
var job = storedJobs[id]; if(!joblist){
joblist = new Array();
}
if(!storedJobs.contains(id)){
removeFromJobList(id);
runNextJob();
return;
}
var job = storedJobs.get(id);
var nextRunTime = job.getNextRunTime(); var nextRunTime = job.getNextRunTime();
print("Time updated to: " + new Date(nextRunTime)); print("Time updated to: " + new Date(nextRunTime));
if(nextRunTime == 0){ if(nextRunTime == 0){
removeFromJobList(id); //remove from joblist if it exists there (run time may have been updated to something invalid/already passed)
runNextJob(); //resort list (may have changed), run next
return; return;
} }
if(!joblist){
joblist = new Array();
}
joblist.push(new RunJob(id, nextRunTime)); //, job.v.type, job.v.device, job.v.method, job.v.value)); joblist.push(new RunJob(id, nextRunTime)); //, job.v.type, job.v.device, job.v.method, job.v.value));
joblist.sort(compareTime); joblist.sort(compareTime);
runNextJob(); runNextJob();
} }
function removeFromJobList(id){
if(!joblist){
return;
}
for(i=0;i<joblist.length;i++){
if(id==joblist[i].id){
joblist.splice(i, 1);
return;
}
}
}
/* /*
function calculateJobs(){ function calculateJobs(){
print("Calculate jobs"); print("Calculate jobs");
@ -189,6 +210,26 @@ com.telldus.scheduler = function() {
} }
*/ */
function recalculateAllJobs(){
print("Recalculating all jobs");
joblist = new Array();
for(var key in storedJobs.container){
var job = storedJobs.get(key);
var nextRunTime = job.getNextRunTime();
print("Run time: " + new Date(nextRunTime));
if(nextRunTime == 0){
print("Will not run");
continue;
}
joblist.push(new RunJob(key, nextRunTime));
}
joblist.sort(compareTime);
runNextJob();
}
function loadJobs(){ function loadJobs(){
print("Loading jobs"); print("Loading jobs");
//TODO detta ska inte göras från denna plugin, utan från respektive... //TODO detta ska inte göras från denna plugin, utan från respektive...
@ -198,20 +239,21 @@ com.telldus.scheduler = function() {
var now = new Date(); var now = new Date();
var time1 = now.getHours() * 3600 + now.getMinutes() * 60 + now.getSeconds(); var time1 = now.getHours() * 3600 + now.getMinutes() * 60 + now.getSeconds();
print(time1); // 48880 print(time1); // 48880
time2 = time1 + 30; time2 = time1 + 300;
time3 = time1 + 60; time3 = time1 + 600;
time1 = time1 + 50; time1 = time1 + 500;
var newRecurringMonthJob = getJob({id: 4, name: "testnamn14", type: JOBTYPE_RECURRING_WEEK, startdate: "2010-01-01", lastrun: 0, device: 1, method: 1, value: ""}); var newRecurringMonthJob = getJob({id: 4, name: "testnamn14", type: JOBTYPE_RECURRING_WEEK, startdate: "2010-01-01", lastrun: 0, device: 1, method: 1, value: ""});
newRecurringMonthJob.addEvent(new Event({id: 0, value: 1, fuzzinessBefore: 0, fuzzinessAfter: 0, type: EVENTTYPE_ABSOLUTE, offset: 0, time: time1})); newRecurringMonthJob.addEvent(new Event({id: 0, value: 2, fuzzinessBefore: 0, fuzzinessAfter: 0, type: EVENTTYPE_ABSOLUTE, offset: 0, time: time1}));
newRecurringMonthJob.save(); newRecurringMonthJob.save();
var newAbsoluteJob = getJob({id: 5, name: "testnamn15", type: JOBTYPE_RECURRING_MONTH, startdate: "2010-01-01", lastrun: 0, device: 1, method: 1, value: ""}); var newAbsoluteJob = getJob({id: 5, name: "testnamn15", type: JOBTYPE_RECURRING_MONTH, startdate: "2010-01-01", lastrun: 0, device: 1, method: 1, value: ""});
newAbsoluteJob.addEvent(new Event({id: 1, value: "00-03", fuzzinessBefore: 0, fuzzinessAfter: 0, type: EVENTTYPE_ABSOLUTE, offset: 0, time: time2})); newAbsoluteJob.addEvent(new Event({id: 1, value: "00-04", fuzzinessBefore: 0, fuzzinessAfter: 0, type: EVENTTYPE_ABSOLUTE, offset: 0, time: time2}));
newAbsoluteJob.addEvent(new Event({id: 2, value: "00-03", fuzzinessBefore: 0, fuzzinessAfter: 0, type: EVENTTYPE_ABSOLUTE, offset: 0, time: time3})); newAbsoluteJob.addEvent(new Event({id: 2, value: "00-04", fuzzinessBefore: 0, fuzzinessAfter: 0, type: EVENTTYPE_ABSOLUTE, offset: 0, time: time3}));
newAbsoluteJob.save(); newAbsoluteJob.save();
storedJobs = new Array(); storedJobs = new MappedList();
//get all jobs from permanent storage //get all jobs from permanent storage
var settings = new com.telldus.settings(); var settings = new com.telldus.settings();
var storedJobsData = settings.value("jobs", ""); var storedJobsData = settings.value("jobs", "");
@ -219,10 +261,8 @@ com.telldus.scheduler = function() {
for(var key in storedJobsData){ for(var key in storedJobsData){
var jobdata = storedJobsData[key]; var jobdata = storedJobsData[key];
var job = getJob(jobdata); var job = getJob(jobdata);
com.telldus.scheduler.addJob(job); //TODO, use different function than this = only sort list afterwards (one time) and dont start timer until all initial are added var tempkey = com.telldus.scheduler.addJob(job); //TODO, use different function than this = only sort list afterwards (one time) and dont start timer until all initial are added
} }
//updateLastRun(newRecurringMonthJob.v.id, "2005-05-05"); //TODO remove
} }
function getJob(jobdata){ function getJob(jobdata){
@ -244,7 +284,6 @@ com.telldus.scheduler = function() {
case JOBTYPE_RECURRING_MONTH: case JOBTYPE_RECURRING_MONTH:
job = new JobRecurringMonth(jobdata); job = new JobRecurringMonth(jobdata);
break; break;
//TODO reload-job
default: default:
break; break;
} }
@ -253,21 +292,38 @@ com.telldus.scheduler = function() {
} }
function addJob(job){ function addJob(job){
var key = storedJobs.push(job)-1; if(storedJobs.length == 0){
print("Adding daylight saving time");
var daylightSavingReloadKey = storedJobs.push(getDaylightSavingReloadJob());
updateJobInList(daylightSavingReloadKey);
}
var key = storedJobs.push(job);
print("Add job"); print("Add job");
updateJobInList(key); updateJobInList(key);
return key; return key;
} }
function removeJob(id){ function removeJob(id){
storedJobs.splice(id,1); storedJobs.remove(id);
updateJobInList(id);
if(storedJobs.length == 1){
//only one job left, it's only the DaylightSaving reload job, remove that too
for(var key in storedJobs.container){
storedJobs.remove(key);
updateJobInList(key);
}
}
} }
function updateJob(key, job){ function updateJob(key, job){
storedJobs[key] = job; storedJobs.update(key, job);
updateJobInList(key); updateJobInList(key);
} }
function getDaylightSavingReloadJob(){
return new JobDaylightSavingReload();
}
function getNextLeapYearSafeDate(year, month, day, event){ function getNextLeapYearSafeDate(year, month, day, event){
if(month == 1 && day == 29){ if(month == 1 && day == 29){
//get leap year //get leap year
@ -324,7 +380,7 @@ com.telldus.scheduler = function() {
function fuzzify(currentTimestamp, fuzzinessBefore, fuzzinessAfter){ function fuzzify(currentTimestamp, fuzzinessBefore, fuzzinessAfter){
if(fuzzinessAfter != 0 || fuzzinessBefore != 0){ if(fuzzinessAfter != 0 || fuzzinessBefore != 0){
var interval = fuzzinessAfter + fuzzinessBefore; var interval = fuzzinessAfter + fuzzinessBefore;
var rand = Math.random(); //TODO random enough? var rand = Math.random(); //Random enough at the moment
var fuzziness = Math.floor((interval+1) * rand); var fuzziness = Math.floor((interval+1) * rand);
fuzziness = fuzziness - fuzzinessBefore; fuzziness = fuzziness - fuzzinessBefore;
currentTimestamp += (fuzziness * 1000); currentTimestamp += (fuzziness * 1000);
@ -398,7 +454,7 @@ com.telldus.scheduler = function() {
return 0; //default return 0; //default
} }
//TODO move //TODO move (or? maybe this could be here as default? maybe not, but keep the concept)
Job.prototype.save = function(){ Job.prototype.save = function(){
//TODO set properties //TODO set properties
var settings = new com.telldus.settings(); var settings = new com.telldus.settings();
@ -455,10 +511,39 @@ com.telldus.scheduler = function() {
*/ */
} }
//keep here
function JobDaylightSavingReload(){
}
JobAbsolute.prototype = new Job(); //Job.prototype; JobAbsolute.prototype = new Job(); //Job.prototype;
JobRecurringDay.prototype = new Job(); //Job.prototype; JobRecurringDay.prototype = new Job(); //Job.prototype;
JobRecurringWeek.prototype = new Job(); //Job.prototype; JobRecurringWeek.prototype = new Job(); //Job.prototype;
JobRecurringMonth.prototype = new Job(); //Job.prototype; JobRecurringMonth.prototype = new Job(); //Job.prototype;
JobDaylightSavingReload.prototype = new Job();
JobDaylightSavingReload.prototype.getNextRunTime = function(){
print("getNextRunTime DaylightSaving");
var dst = DstDetect();
var now = new Date().getTime();
var time = dst[0].getTime();
if(now > time){
//already passed
time = dst[1].getTime();
}
return time;
}
JobDaylightSavingReload.prototype.execute = function(){
//override default
print("Daylight savings job");
//1. Make sure this job is run "last", if other jobs are set to be runt the same second
//2. sleep for one second, to avoid strange calculations
setTimeout(recalculateAllJobs(), 1);
//this may lead to that things that should be executed exactly at 3.00 when moving time forward one hour won't run, but that
//is the only case
//TODO recalculate all stored jobs
return 0;
};
JobAbsolute.prototype.getNextRunTime = function(){ JobAbsolute.prototype.getNextRunTime = function(){
print("getNextRunTime absolute"); print("getNextRunTime absolute");
@ -545,7 +630,7 @@ com.telldus.scheduler = function() {
} }
for(var key in this.v.events){ for(var key in this.v.events){
//get next x day of month, may be today, for the current month //get next x day of month, may be today, for the current month
if(this.v.events[key].d.value.toString().indexOf("-") == -1){ //make sure value is of correct format if(!this.v.events[key].d.value || this.v.events[key].d.value.toString().indexOf("-") == -1){ //make sure value is of correct format
continue; continue;
} }
var daymonth = this.v.events[key].d.value.split('-'); //month-day var daymonth = this.v.events[key].d.value.split('-'); //month-day
@ -582,6 +667,36 @@ com.telldus.scheduler = function() {
this.d = {}; this.d = {};
} }
} }
function MappedList() {
this.container = {};
this.length = 0;
}
MappedList.prototype.push = function(element){
//TODO reusing keys at the moment, that's ok, right?
var length = this.length;
this.container[length] = element;
this.length = length + 1;
return length;
}
MappedList.prototype.get = function(key){
return this.container[key];
}
MappedList.prototype.update = function(key, element){
this.container[key] = element;
}
MappedList.prototype.remove = function(key){
delete this.container[key];
this.length--;
}
MappedList.prototype.contains = function(key){
return !(this.container[key] === undefined);
}
function RunJob(id, nextRunTime, type, device, method, value){ function RunJob(id, nextRunTime, type, device, method, value){
this.id = id; this.id = id;
@ -595,8 +710,7 @@ com.telldus.scheduler = function() {
return { //Public functions return { //Public functions
addJob: addJob, addJob: addJob,
removeJob: removeJob, removeJob: removeJob,
//runNextJob: runNextJob, //TODO which methods should be exposed? Should some of these methods always call another? init:init //TODO change this
init:init
} }
}(); }();