Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Advanced repeat rules #8456

Merged
merged 33 commits into from
Feb 13, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
811ac1b
Prototype event generation using ByRules
andrehgdias Nov 28, 2024
2b6983c
Implements event expansion for daily BYDAY and BYMONTH
Dec 2, 2024
9b10f95
Implements event expansion for Weekly interval
Dec 3, 2024
7b1b3f3
Implements event expansion for Monthly interval
Dec 3, 2024
971d6c8
Implements expansion for Yearly interval
Dec 9, 2024
9f7e6bd
Add export of advanced repeat rules
Patrik-wav Dec 10, 2024
60ff001
Implements BYMONTHDAY filtering
andrehgdias Dec 12, 2024
cac89f0
Filter events happening before progenitor
andrehgdias Dec 12, 2024
3827ed9
Implements BYSETPOS filtering
andrehgdias Dec 9, 2024
d7e0e8b
Fixes BYWEEKNO expansion
andrehgdias Dec 11, 2024
38c799a
Implements BYYEARDAY filtering
Dec 16, 2024
1aacef8
Fixes SETPOS filtering
Dec 17, 2024
7ce09c1
Simplifies generator with advanced rules
Dec 17, 2024
b1121c8
[SDK] Implements BYMONTH expansion
Dec 18, 2024
f2a6781
[SDK] Implements BYWEEKNO expansion
Dec 18, 2024
c0956a4
[SDK] Implements BYYEARDAY expansion
Dec 19, 2024
5af07b1
[SDK] Implements BYMONTHDAY expansion
Dec 19, 2024
4d3d066
[SDK] Implements BYDAY expansion
Dec 20, 2024
ea406c6
[SDK] Implements tests for the complete recurrence generation flow
Jan 6, 2025
d61281c
[SDK] Exposes EventFacade to uniffi
Jan 8, 2025
c8dfd98
[Android] Integrates SDK event expansion during alarm scheduling
Jan 8, 2025
a06961a
[iOS] Integrates SDK event expansion during alarm scheduling
murilopereirame Jan 13, 2025
7ce738c
Fixes BYSETPOS on Web/Desktop
Jan 14, 2025
fead3ed
[Desktop] Integrates event expansion during alarm scheduling
Jan 14, 2025
0e0c0e1
[iOS] Adds BYSETPOS handling during alarm schedule
murilopereirame Jan 15, 2025
bfc06b0
[Android] Fixes BYSETPOS during alarm schedule
Jan 15, 2025
52662bf
Adds info banner for unsupported rules
Jan 16, 2025
4785dd7
Adds translations to Advanced Repeat Rules
murilopereirame Jan 13, 2025
dfa4faa
Fixes styling and linting
Feb 3, 2025
1cff871
[Android] Update encryption tests
Feb 5, 2025
5d5d261
[Android] Adds dexmaker to mock classes during Instrumented Tests
Feb 4, 2025
3901d4c
[iOS] Fix iOS tests and SDK event expansion
Feb 6, 2025
945ddb4
Changes after review
murilopereirame Feb 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
[iOS] Fix iOS tests and SDK event expansion
The way that we count events and occurrences changed with the
implementation of the advanced repeat rules, given that, we've had to
fix tests that relies on these counts.

This commit address one corner case for weekly events expansion on SDK
and fixes the tests on Swift.
  • Loading branch information
mup committed Feb 12, 2025
commit 3901d4c39feee59c74a230491674c2c3d7500737
5 changes: 3 additions & 2 deletions app-ios/TutanotaSharedFramework/Alarms/AlarmModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -129,14 +129,15 @@ private struct LazyEventSequence: Sequence, IteratorProtocol {

mutating func next() -> EventOccurrence? {
if case let .count(n) = repeatRule.endCondition, occurrenceNumber >= n { return nil }
if intervalNumber > EVENTS_SCHEDULED_AHEAD { return nil }

if expandedEvents.isEmpty {
let nextExpansionProgenitor = cal.date(byAdding: self.calendarComponent, value: repeatRule.interval * intervalNumber, to: calcEventStart)!
let progenitorTime = UInt64(nextExpansionProgenitor.timeIntervalSince1970)
let eventFacade = EventFacade()
let byRules = repeatRule.advancedRules.map { $0.toSDKRule() }
let generatedEvents = eventFacade.generateFutureInstances(
date: progenitorTime * 1000,
date: progenitorTime,
repeatRule: EventRepeatRule(frequency: repeatRule.frequency.toSDKPeriod(), byRules: byRules)
)
self.expandedEvents.append(contentsOf: generatedEvents)
Expand All @@ -162,7 +163,7 @@ private struct LazyEventSequence: Sequence, IteratorProtocol {

return true
}
.map { (_, event) in event }
.map { (_, event) in event }.sorted { $1 < $0 }

if let date = expandedEvents.popLast() {
occurrenceNumber += 1
Expand Down
8 changes: 4 additions & 4 deletions app-ios/tutanotaTests/AlarmManagerTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -126,15 +126,15 @@ class AlarmManagerTest: XCTestCase {
[
ScheduledAlarmInfo(
alarmTime: start2.advanced(by: 24, .hours).advanced(by: -10, .minutes),
occurrence: 1,
identifier: ocurrenceIdentifier(alarmIdentifier: alarm2.identifier, occurrence: 1),
occurrence: 2,
identifier: ocurrenceIdentifier(alarmIdentifier: alarm2.identifier, occurrence: 2),
summary: alarm2.summary,
eventDate: start2.advanced(by: 24, .hours)
),
ScheduledAlarmInfo(
alarmTime: start2.advanced(by: -10, .minutes),
occurrence: 0,
identifier: ocurrenceIdentifier(alarmIdentifier: alarm2.identifier, occurrence: 0),
occurrence: 1,
identifier: ocurrenceIdentifier(alarmIdentifier: alarm2.identifier, occurrence: 1),
summary: alarm2.summary,
eventDate: start2
),
Expand Down
8 changes: 4 additions & 4 deletions app-ios/tutanotaTests/AlarmModelTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ class AlarmModelTest: XCTestCase {
let result = plan(alarms: [alarm])

XCTAssertEqual(result.count, 3)
XCTAssertEqual(result[2].occurrenceNumber, 2)
XCTAssertEqual(result[2].occurrenceNumber, 3)
}

func testWhenRepeatedAlarmStartsBeforeNowOnlyFutureOcurrencesArePlanned() {
Expand All @@ -93,7 +93,7 @@ class AlarmModelTest: XCTestCase {
let result = plan(alarms: [alarm])

XCTAssertEqual(result.count, 2)
XCTAssertEqual(result[1].occurrenceNumber, 2)
XCTAssertEqual(result[1].occurrenceNumber, 3)
}

func testWhenMultipleAlarmsArePresentOnlyTheNewestOccurrencesArePlanned() {
Expand Down Expand Up @@ -169,8 +169,8 @@ class AlarmModelTest: XCTestCase {
let occurrences = prefix(seq: seq, 5).map { $0.eventOccurrenceTime }

let expected = [
date(2025, 2, 2, 11, timeZone), date(2025, 2, 3, 11, timeZone), date(2025, 2, 4, 11, timeZone), date(2025, 2, 10, 11, timeZone),
date(2025, 2, 11, 11, timeZone),
date(2025, 2, 2, 12, timeZone), date(2025, 2, 3, 12, timeZone), date(2025, 2, 4, 12, timeZone), date(2025, 2, 10, 12, timeZone),
date(2025, 2, 11, 12, timeZone),
]
XCTAssertEqual(occurrences, expected)
}
Expand Down
117 changes: 101 additions & 16 deletions tuta-sdk/rust/sdk/src/date/event_facade.rs
Original file line number Diff line number Diff line change
Expand Up @@ -538,31 +538,51 @@ impl EventFacade {
{
new_dates.push(*date)
} else if frequency == &RepeatPeriod::Weekly && target_week_day.is_some() {
let mut new_date = date.replace_date(
Date::from_iso_week_date(
date.year(),
date.iso_week(),
Weekday::from_short(target_week_day.unwrap().as_str()),
)
.unwrap(),
);
let interval_start = date.replace_date(
Date::from_iso_week_date(date.year(), date.iso_week(), week_start).unwrap(),
);
let parsed_target_week_day =
Weekday::from_short(target_week_day.unwrap().as_str());
let mut interval_start = *date;
while interval_start.date().weekday() != week_start {
interval_start = interval_start.sub(Duration::days(1));
}

let mut new_date = interval_start;
while new_date.weekday() != parsed_target_week_day {
new_date = new_date.add(Duration::days(1))
}

/*
if interval_start.assume_utc().unix_timestamp()
< date.assume_utc().unix_timestamp()
{
interval_start = interval_start.add(Duration::weeks(1))
}
*/
let next_event = date.add(Duration::weeks(1)).assume_utc().unix_timestamp();

if new_date.assume_utc().unix_timestamp()
> interval_start
>= interval_start
.add(Duration::weeks(1))
.assume_utc()
.unix_timestamp()
{
continue;
} else if new_date.assume_utc().unix_timestamp()
< interval_start.assume_utc().unix_timestamp()
< date.assume_utc().unix_timestamp()
{
new_date = new_date.add(Duration::weeks(1));
}

if (new_date.assume_utc().unix_timestamp() >= next_event)
|| (week_start != Weekday::Monday // We have WKST
&& new_date.assume_utc().unix_timestamp()
>= interval_start
.add(Duration::weeks(1))
.assume_utc()
.unix_timestamp())
{
continue;
}

if valid_months.is_empty()
|| valid_months.contains(&new_date.month().to_number())
{
Expand Down Expand Up @@ -2221,7 +2241,39 @@ mod tests {
}

#[test]
fn test_flow_weekly_with_by_day_and_wkst() {
fn test_flow_weekly_with_by_day_edge() {
let time = Time::from_hms(13, 23, 00).unwrap();
let date = PrimitiveDateTime::new(
Date::from_calendar_date(2025, Month::February, 2).unwrap(),
time,
);

let repeat_rule = EventRepeatRule {
frequency: RepeatPeriod::Weekly,
by_rules: vec![
ByRule {
by_rule: ByRuleType::ByDay,
interval: "MO".to_string(),
},
ByRule {
by_rule: ByRuleType::ByDay,
interval: "TU".to_string(),
},
],
};

let event_recurrence = EventFacade {};
assert_eq!(
event_recurrence.generate_future_instances(date.to_date_time(), repeat_rule.clone()),
[
date.replace_day(3).unwrap().to_date_time(),
date.replace_day(4).unwrap().to_date_time()
]
);
}

#[test]
fn test_flow_weekly_with_by_day_and_wkst_edge() {
let time = Time::from_hms(13, 23, 00).unwrap();
let date = PrimitiveDateTime::new(
Date::from_calendar_date(2025, Month::February, 10).unwrap(),
Expand All @@ -2246,12 +2298,45 @@ mod tests {
],
};

let event_recurrence = EventFacade {};
assert_eq!(
event_recurrence.generate_future_instances(date.to_date_time(), repeat_rule.clone()),
[date.replace_day(13).unwrap().to_date_time(),]
);
}

#[test]
fn test_flow_weekly_with_by_day_and_wkst() {
let time = Time::from_hms(13, 23, 00).unwrap();
let date = PrimitiveDateTime::new(
Date::from_calendar_date(2025, Month::February, 7).unwrap(),
time,
);

let repeat_rule = EventRepeatRule {
frequency: RepeatPeriod::Weekly,
by_rules: vec![
ByRule {
by_rule: ByRuleType::Wkst,
interval: "FR".to_string(),
},
ByRule {
by_rule: ByRuleType::ByDay,
interval: "TH".to_string(),
},
ByRule {
by_rule: ByRuleType::ByDay,
interval: "FR".to_string(),
},
],
};

let event_recurrence = EventFacade {};
assert_eq!(
event_recurrence.generate_future_instances(date.to_date_time(), repeat_rule.clone()),
[
date.replace_day(14).unwrap().to_date_time(),
date.replace_day(20).unwrap().to_date_time()
date.replace_day(7).unwrap().to_date_time(),
date.replace_day(13).unwrap().to_date_time(),
]
);
}
Expand Down