diff --git a/EcAlerter.h b/EcAlerter.h
index 325f1f0..c8fc66a 100644
--- a/EcAlerter.h
+++ b/EcAlerter.h
@@ -119,6 +119,42 @@
* of enhanced posix regular expressions. If this is not present,
* any message text will match.
*
+ * ActiveFrom
+ * A date/time specifying the earliest point at which this rule
+ * may match any alarm. If the current date/time is earlier than
+ * this (optional) value then the rule simply can't match.
+ *
+ * ActiveTo
+ * A date/time specifying the latest point at which this rule
+ * may match any alarm. If the current date/time is later than
+ * this (optional) value then the rule simply can't match.
+ *
+ * ActiveTimes
+ * Either a string containing a comma separated list of ranges
+ * of times specified using a 24 hour clock, or a dictionary of such
+ * strings keyed on the days of the week (Monday, Tuesday, Wednesday,
+ * Thursday, Friday, Saturday, Sunday), possibly with an asterisk used
+ * as the key for a default string to be used for any day not listed.
+ * If a simple string, the list applies to all days of the week
+ * (equivalent to a dictionary containing a single string keyed on
+ * an asterisk).
+ * The times are in HH:MM format or may simply be the hours (HH) with
+ * a minute value of zero implied.
+ * The range separator is a dash/minus, and the range includes the
+ * first value and excludes the second one.
+ * eg. 10:45-11 means that the rule is active in the 15 minute interval
+ * from 10:45 until 11:00 but becomes inactive again at 11:00
+ * The end of each range must be later than the start of the range,
+ * and the starts of each successive range must be later than or equal
+ * to the end of the preceding range.
+ * As a special case the time 24:00 may be used as the end of the
+ * last range in a list.
+ * If this field is simply omitted, the rule is active all day every day.
+ *
+ * ActiveTimezone
+ * The formal name of the time zone in which the system checks
+ * active date/time information. If omitted, the GMT timezone is assumed.
+ *
* Stop
* A boolean (YES or NO) saying whether rule matching should
* stop if this rule is matched. If this is NO (the default) then
diff --git a/EcAlerter.m b/EcAlerter.m
index 95fa94c..89b9dd7 100644
--- a/EcAlerter.m
+++ b/EcAlerter.m
@@ -253,12 +253,13 @@ replaceFields(NSDictionary *fields, NSString *template)
- (BOOL) setRules: (NSArray*)ra
{
- NSUInteger i = 0;
NSMutableArray *r = AUTORELEASE([ra mutableCopy]);
+ NSUInteger i;
for (i = 0; i < [r count]; i++)
{
NSMutableDictionary *md;
+ NSObject *obj;
NSString *str;
Regex *val;
@@ -344,6 +345,204 @@ replaceFields(NSDictionary *fields, NSString *template)
RELEASE(val);
}
+ str = [md objectForKey: @"ActiveFrom"];
+ if (nil != str)
+ {
+ NSDate *d = [NSDate dateWithString: str];
+
+ if (nil == d)
+ {
+ NSLog(@"ActiveFrom='%@' is not a valid date/time", str);
+ return NO;
+ }
+ else
+ {
+ [md setObject: d forKey: @"ActiveFrom"];
+ }
+ }
+ str = [md objectForKey: @"ActiveTo"];
+ if (nil != str)
+ {
+ NSDate *d = [NSDate dateWithString: str];
+
+ if (nil == d)
+ {
+ NSLog(@"ActiveTo='%@' is not a valid date/time", str);
+ return NO;
+ }
+ else
+ {
+ [md setObject: d forKey: @"ActiveTo"];
+ }
+ }
+ str = [md objectForKey: @"ActiveTimezone"];
+ if (nil != str)
+ {
+ NSTimeZone *d = [NSTimeZone timeZoneWithName: str];
+
+ if (nil == d)
+ {
+ NSLog(@"ActiveTimezone='%@' is not a valid time zone", str);
+ return NO;
+ }
+ [md setObject: d forKey: @"ActiveTimeZone"];
+ }
+ obj = [md objectForKey: @"ActiveTimes"];
+ if ([obj isKindOfClass: [NSString class]])
+ {
+ obj = (NSString*)[NSDictionary dictionaryWithObjectsAndKeys:
+ obj, @"*", nil];
+ }
+ if ([obj isKindOfClass: [NSDictionary class]])
+ {
+ NSMutableDictionary *t = [[obj mutableCopy] autorelease];
+ NSEnumerator *e = [[t allKeys] objectEnumerator];
+
+ while (nil != (str = [e nextObject]))
+ {
+ NSString *k = [str stringByTrimmingSpaces];
+ NSMutableArray *a;
+ NSUInteger j;
+ NSInteger lastMinute = 0;
+
+ if (YES == [k isEqual: @"*"])
+ {
+ k = @"*";
+ }
+ else if (YES == [k caseInsensitiveCompare: @"Monday"])
+ {
+ k = @"Monday";
+ }
+ else if (YES == [k caseInsensitiveCompare: @"Tuesday"])
+ {
+ k = @"Tuesday";
+ }
+ else if (YES == [k caseInsensitiveCompare: @"Wednesday"])
+ {
+ k = @"Wednesday";
+ }
+ else if (YES == [k caseInsensitiveCompare: @"Thursday"])
+ {
+ k = @"Thursday";
+ }
+ else if (YES == [k caseInsensitiveCompare: @"Friday"])
+ {
+ k = @"Friday";
+ }
+ else if (YES == [k caseInsensitiveCompare: @"Saturday"])
+ {
+ k = @"Saturday";
+ }
+ else if (YES == [k caseInsensitiveCompare: @"Sunday"])
+ {
+ k = @"Sunday";
+ }
+ else
+ {
+ NSLog(@"ActiveTimes='%@' with bad day of week", obj);
+ return NO;
+ }
+ a = [[[[t objectForKey: str] componentsSeparatedByString: @","]
+ mutableCopy] autorelease];
+ j = [a count];
+ while (j-- > 0)
+ {
+ NSMutableArray *r;
+ int from;
+ int to;
+ int h;
+ int m;
+ int c;
+
+ str = [[a objectAtIndex: j] stringByTrimmingSpaces];
+ if ([str length] == 0)
+ {
+ [a removeObjectAtIndex: j];
+ continue;
+ }
+ r = [[[str componentsSeparatedByString: @"-"]
+ mutableCopy] autorelease];
+ if ([r count] != 2)
+ {
+ NSLog(@"ActiveTimes='%@' with missing '-' in time range",
+ obj);
+ return NO;
+ }
+ str = [r objectAtIndex: 0];
+ c = sscanf([str UTF8String], "%d:%d", &h, &m);
+ if (0 == c)
+ {
+ NSLog(@"ActiveTimes='%@' with missing HH:MM", obj);
+ return NO;
+ }
+ if (1 != c) m = 0;
+ if (h < 0 || h > 23)
+ {
+ NSLog(@"ActiveTimes='%@' with hour out of range", obj);
+ }
+ if (m < 0 || m > 59)
+ {
+ NSLog(@"ActiveTimes='%@' with minute out of range", obj);
+ return NO;
+ }
+ from = (h * 60) + m;
+
+ str = [r objectAtIndex: 1];
+ c = sscanf([str UTF8String], "%d:%d", &h, &m);
+ if (0 == c)
+ {
+ NSLog(@"ActiveTimes='%@' with missing HH:MM", obj);
+ return NO;
+ }
+ if (1 != c) m = 0;
+ if (h < 0 || h > 24 || (24 == h && 0 != m))
+ {
+ NSLog(@"ActiveTimes='%@' with hour out of range", obj);
+ }
+ if (m < 0 || m > 59)
+ {
+ NSLog(@"ActiveTimes='%@' with minute out of range", obj);
+ return NO;
+ }
+ if (0 == h && 0 == m)
+ {
+ h = 24;
+ }
+ to = (h * 60) + m;
+
+ if (to <= from)
+ {
+ NSLog(@"ActiveTimes='%@' range end earlier than start",
+ obj);
+ return NO;
+ }
+ if (from < lastMinute)
+ {
+ NSLog(@"ActiveTimes='%@' range start earlier than"
+ @" preceding one", obj);
+ return NO;
+ }
+ lastMinute = to;
+ [r replaceObjectAtIndex: 0
+ withObject: [NSNumber numberWithInt: from]];
+ [r replaceObjectAtIndex: 1
+ withObject: [NSNumber numberWithInt: to]];
+ [a replaceObjectAtIndex: j withObject: r];
+ }
+ if (0 == [a count])
+ {
+ NSLog(@"ActiveTimes='%@' with empty time range", obj);
+ return NO;
+ }
+ [t setObject: a forKey: k];
+ }
+ [md setObject: obj forKey: @"ActiveTimes"];
+ }
+ else if (obj != nil)
+ {
+ NSLog(@"ActiveTimes='%@' is not valid", obj);
+ return NO;
+ }
}
ASSIGN(rules, r);
return YES;
@@ -440,12 +639,17 @@ replaceFields(NSDictionary *fields, NSString *template)
- (void) applyRules: (NSArray*)rulesArray
toEvent: (EcAlerterEvent*)event
{
- CREATE_AUTORELEASE_POOL(pool);
- BOOL found = NO;
- NSUInteger i;
+ NSAutoreleasePool *pool = nil;
+ NSTimeZone *tz = nil;
+ BOOL found = NO;
+ NSCalendarDate *now = [NSCalendarDate date];
+ NSUInteger minuteOfDay = 0;
+ NSUInteger dayOfWeek = 0;
+ NSUInteger i;
for (i = 0; i < [rulesArray count]; i++)
{
+ NSDictionary *times;
NSDictionary *d;
NSString *match = nil;
Regex *e;
@@ -456,6 +660,80 @@ replaceFields(NSDictionary *fields, NSString *template)
pool = [NSAutoreleasePool new];
d = [rulesArray objectAtIndex: i];
+ times = [d objectForKey: @"ActiveTimes"];
+ if (nil != times)
+ {
+ NSDate *from = [d objectForKey: @"ActiveFrom"];
+ NSDate *to = [d objectForKey: @"ActiveTo"];
+ BOOL match = NO;
+
+ if ((nil == from || [from earlierDate: now] == from)
+ && (nil == to || [to laterDate: now] == to))
+ {
+ NSTimeZone *z = [d objectForKey: @"ActiveTimezone"];
+ NSArray *ranges;
+ NSUInteger index;
+
+ if (nil == z)
+ {
+ static NSTimeZone *gmt = nil;
+
+ if (nil == gmt)
+ {
+ gmt = [[NSTimeZone timeZoneWithName: @"GMT"] retain];
+ }
+ z = gmt;
+ }
+ if (NO == [z isEqual: tz])
+ {
+ ASSIGN(tz, z);
+ [now setTimeZone: tz];
+ minuteOfDay = [now hourOfDay] * 60 + [now minuteOfHour];
+ dayOfWeek = [now dayOfWeek];
+ }
+
+ switch (dayOfWeek)
+ {
+ case 0: ranges = [times objectForKey: @"Sunday"]; break;
+ case 1: ranges = [times objectForKey: @"Monday"]; break;
+ case 2: ranges = [times objectForKey: @"Tuesday"]; break;
+ case 3: ranges = [times objectForKey: @"Wednesday"]; break;
+ case 4: ranges = [times objectForKey: @"Thursday"]; break;
+ case 5: ranges = [times objectForKey: @"Friday"]; break;
+ default: ranges = [times objectForKey: @"Saturday"]; break;
+ }
+ if (nil == ranges)
+ {
+ ranges = [times objectForKey: @"*"];
+ }
+ index = [ranges count];
+ while (index-- > 0)
+ {
+ NSArray *range;
+ NSUInteger start;
+
+ range = [ranges objectAtIndex: index];
+ start = [[range objectAtIndex: 0] unsignedIntegerValue];
+
+ if (minuteOfDay >= start)
+ {
+ NSUInteger end;
+
+ end = [[range objectAtIndex: 1] unsignedIntegerValue];
+ if (minuteOfDay < end)
+ {
+ match = YES;
+ }
+ break;
+ }
+ }
+ }
+ if (NO == match)
+ {
+ continue;
+ }
+ }
+
s = [d objectForKey: @"Tagged"];
if (s != nil && NO == [s isEqual: [event->m objectForKey: @"Tag"]])
{
@@ -816,6 +1094,7 @@ replaceFields(NSDictionary *fields, NSString *template)
NSLog(@"No match of %@ with %@", event->m, rulesArray);
}
}
+ DESTROY(tz);
RELEASE(pool);
}