Refactor repeating from current date into a new repeat mode

This commit is contained in:
kolaente 2021-04-13 22:29:30 +02:00
parent f6e775a7f1
commit ab98351c68
Signed by: konrad
GPG Key ID: F40E70337AB24C9B
3 changed files with 165 additions and 131 deletions

View File

@ -22,7 +22,8 @@ import (
)
type tasks20210413131057 struct {
RepeatMode int `xorm:"not null default 0" json:"repeat_mode"`
RepeatFromCurrentDate bool `xorm:"null" json:"repeat_from_current_date"`
RepeatMode int `xorm:"not null default 0" json:"repeat_mode"`
}
func (tasks20210413131057) TableName() string {
@ -34,7 +35,15 @@ func init() {
ID: "20210413131057",
Description: "Add repeat mode column to tasks",
Migrate: func(tx *xorm.Engine) error {
return tx.Sync2(tasks20210413131057{})
err := tx.Sync2(tasks20210413131057{})
if err != nil {
return err
}
_, err = tx.
Where("repeat_from_current_date = ?", true).
Update(&tasks20210413131057{RepeatMode: 2})
return err
},
Rollback: func(tx *xorm.Engine) error {
return nil

View File

@ -41,6 +41,7 @@ type TaskRepeatMode int
const (
TaskRepeatModeDefault TaskRepeatMode = iota
TaskRepeatModeMonth
TaskRepeatModeFromCurrentDate
)
// Task represents an task in a todolist
@ -65,7 +66,7 @@ type Task struct {
// An amount in seconds this task repeats itself. If this is set, when marking the task as done, it will mark itself as "undone" and then increase all remindes and the due date by its amount.
RepeatAfter int64 `xorm:"bigint INDEX null" json:"repeat_after"`
// If specified, a repeating task will repeat from the current date rather than the last set date.
RepeatFromCurrentDate bool `xorm:"null" json:"repeat_from_current_date"`
//RepeatFromCurrentDate bool `xorm:"null" json:"repeat_from_current_date"`
RepeatMode TaskRepeatMode `xorm:"not null default 0" json:"repeat_mode"`
// The task priority. Can be anything you want, it is possible to sort by this later.
@ -1044,8 +1045,8 @@ func (t *Task) Update(s *xorm.Session, a web.Auth) (err error) {
ot.Position = 0
}
// Repeat from current date
if !t.RepeatFromCurrentDate {
ot.RepeatFromCurrentDate = false
if t.RepeatMode == TaskRepeatModeDefault {
ot.RepeatMode = TaskRepeatModeDefault
}
// Is Favorite
if !t.IsFavorite {
@ -1084,6 +1085,127 @@ func addOneMonthToDate(d time.Time) time.Time {
return time.Date(d.Year(), d.Month()+1, d.Day(), d.Hour(), d.Minute(), d.Second(), d.Nanosecond(), config.GetTimeZone())
}
func setTaskDatesDefault(oldTask, newTask *Task) {
if oldTask.RepeatAfter == 0 {
return
}
// Current time in an extra variable to base all calculations on the same time
now := time.Now()
repeatDuration := time.Duration(oldTask.RepeatAfter) * time.Second
// assuming we'll merge the new task over the old task
if !oldTask.DueDate.IsZero() {
// Always add one instance of the repeating interval to catch cases where a due date is already in the future
// but not the repeating interval
newTask.DueDate = oldTask.DueDate.Add(repeatDuration)
// Add the repeating interval until the new due date is in the future
for !newTask.DueDate.After(now) {
newTask.DueDate = newTask.DueDate.Add(repeatDuration)
}
}
newTask.Reminders = oldTask.Reminders
// When repeating from the current date, all reminders should keep their difference to each other.
// To make this easier, we sort them first because we can then rely on the fact the first is the smallest
if len(oldTask.Reminders) > 0 {
for in, r := range oldTask.Reminders {
newTask.Reminders[in] = r.Add(repeatDuration)
for !newTask.Reminders[in].After(now) {
newTask.Reminders[in] = newTask.Reminders[in].Add(repeatDuration)
}
}
}
// If a task has a start and end date, the end date should keep the difference to the start date when setting them as new
if !oldTask.StartDate.IsZero() {
newTask.StartDate = oldTask.StartDate.Add(repeatDuration)
for !newTask.StartDate.After(now) {
newTask.StartDate = newTask.StartDate.Add(repeatDuration)
}
}
if !oldTask.EndDate.IsZero() {
newTask.EndDate = oldTask.EndDate.Add(repeatDuration)
for !newTask.EndDate.After(now) {
newTask.EndDate = newTask.EndDate.Add(repeatDuration)
}
}
}
func setTaskDatesMonthRepeat(oldTask, newTask *Task) {
if !oldTask.DueDate.IsZero() {
newTask.DueDate = addOneMonthToDate(oldTask.DueDate)
}
newTask.Reminders = oldTask.Reminders
if len(oldTask.Reminders) > 0 {
for in, r := range oldTask.Reminders {
newTask.Reminders[in] = addOneMonthToDate(r)
}
}
if !oldTask.StartDate.IsZero() && !oldTask.EndDate.IsZero() {
diff := oldTask.EndDate.Sub(oldTask.StartDate)
newTask.StartDate = addOneMonthToDate(oldTask.StartDate)
newTask.EndDate = newTask.StartDate.Add(diff)
} else {
if !oldTask.StartDate.IsZero() {
newTask.StartDate = addOneMonthToDate(oldTask.StartDate)
}
if !oldTask.EndDate.IsZero() {
newTask.EndDate = addOneMonthToDate(oldTask.EndDate)
}
}
}
func setTaskDatesFromCurrentDateRepeat(oldTask, newTask *Task) {
if oldTask.RepeatAfter == 0 {
return
}
// Current time in an extra variable to base all calculations on the same time
now := time.Now()
repeatDuration := time.Duration(oldTask.RepeatAfter) * time.Second
// assuming we'll merge the new task over the old task
if !oldTask.DueDate.IsZero() {
newTask.DueDate = now.Add(repeatDuration)
}
newTask.Reminders = oldTask.Reminders
// When repeating from the current date, all reminders should keep their difference to each other.
// To make this easier, we sort them first because we can then rely on the fact the first is the smallest
if len(oldTask.Reminders) > 0 {
sort.Slice(oldTask.Reminders, func(i, j int) bool {
return oldTask.Reminders[i].Unix() < oldTask.Reminders[j].Unix()
})
first := oldTask.Reminders[0]
for in, r := range oldTask.Reminders {
diff := r.Sub(first)
newTask.Reminders[in] = now.Add(repeatDuration + diff)
}
}
// If a task has a start and end date, the end date should keep the difference to the start date when setting them as new
if !oldTask.StartDate.IsZero() && !oldTask.EndDate.IsZero() {
diff := oldTask.EndDate.Sub(oldTask.StartDate)
newTask.StartDate = now.Add(repeatDuration)
newTask.EndDate = now.Add(repeatDuration + diff)
} else {
if !oldTask.StartDate.IsZero() {
newTask.StartDate = now.Add(repeatDuration)
}
if !oldTask.EndDate.IsZero() {
newTask.EndDate = now.Add(repeatDuration)
}
}
}
// This helper function updates the reminders, doneAt, start and end dates of the *old* task
// and saves the new values in the newTask object.
// We make a few assumtions here:
@ -1091,116 +1213,19 @@ func addOneMonthToDate(d time.Time) time.Time {
// 2. Because of 1., this functions should not be used to update values other than Done in the same go
func updateDone(oldTask *Task, newTask *Task) {
if !oldTask.Done && newTask.Done {
// Current time in an extra variable to base all calculations on the same time
now := time.Now()
if oldTask.RepeatMode == TaskRepeatModeMonth {
if !oldTask.DueDate.IsZero() {
newTask.DueDate = addOneMonthToDate(oldTask.DueDate)
}
newTask.Reminders = oldTask.Reminders
if len(oldTask.Reminders) > 0 {
for in, r := range oldTask.Reminders {
newTask.Reminders[in] = addOneMonthToDate(r)
}
}
if !oldTask.StartDate.IsZero() && !oldTask.EndDate.IsZero() {
diff := oldTask.EndDate.Sub(oldTask.StartDate)
newTask.StartDate = addOneMonthToDate(oldTask.StartDate)
newTask.EndDate = newTask.StartDate.Add(diff)
} else {
if !oldTask.StartDate.IsZero() {
newTask.StartDate = addOneMonthToDate(oldTask.StartDate)
}
if !oldTask.EndDate.IsZero() {
newTask.EndDate = addOneMonthToDate(oldTask.EndDate)
}
}
switch oldTask.RepeatMode {
case TaskRepeatModeMonth:
setTaskDatesMonthRepeat(oldTask, newTask)
case TaskRepeatModeFromCurrentDate:
setTaskDatesFromCurrentDateRepeat(oldTask, newTask)
default:
setTaskDatesDefault(oldTask, newTask)
}
if oldTask.RepeatAfter > 0 {
repeatDuration := time.Duration(oldTask.RepeatAfter) * time.Second
// assuming we'll merge the new task over the old task
if !oldTask.DueDate.IsZero() {
if oldTask.RepeatFromCurrentDate {
newTask.DueDate = now.Add(repeatDuration)
} else {
// Always add one instance of the repeating interval to catch cases where a due date is already in the future
// but not the repeating interval
newTask.DueDate = oldTask.DueDate.Add(repeatDuration)
// Add the repeating interval until the new due date is in the future
for !newTask.DueDate.After(now) {
newTask.DueDate = newTask.DueDate.Add(repeatDuration)
}
}
}
newTask.Reminders = oldTask.Reminders
// When repeating from the current date, all reminders should keep their difference to each other.
// To make this easier, we sort them first because we can then rely on the fact the first is the smallest
if len(oldTask.Reminders) > 0 {
if oldTask.RepeatFromCurrentDate {
sort.Slice(oldTask.Reminders, func(i, j int) bool {
return oldTask.Reminders[i].Unix() < oldTask.Reminders[j].Unix()
})
first := oldTask.Reminders[0]
for in, r := range oldTask.Reminders {
diff := r.Sub(first)
newTask.Reminders[in] = now.Add(repeatDuration + diff)
}
} else {
for in, r := range oldTask.Reminders {
newTask.Reminders[in] = r.Add(repeatDuration)
for !newTask.Reminders[in].After(now) {
newTask.Reminders[in] = newTask.Reminders[in].Add(repeatDuration)
}
}
}
}
// If a task has a start and end date, the end date should keep the difference to the start date when setting them as new
if oldTask.RepeatFromCurrentDate && !oldTask.StartDate.IsZero() && !oldTask.EndDate.IsZero() {
diff := oldTask.EndDate.Sub(oldTask.StartDate)
newTask.StartDate = now.Add(repeatDuration)
newTask.EndDate = now.Add(repeatDuration + diff)
} else {
if !oldTask.StartDate.IsZero() {
if oldTask.RepeatFromCurrentDate {
newTask.StartDate = now.Add(repeatDuration)
} else {
newTask.StartDate = oldTask.StartDate.Add(repeatDuration)
for !newTask.StartDate.After(now) {
newTask.StartDate = newTask.StartDate.Add(repeatDuration)
}
}
}
if !oldTask.EndDate.IsZero() {
if oldTask.RepeatFromCurrentDate {
newTask.EndDate = now.Add(repeatDuration)
} else {
newTask.EndDate = oldTask.EndDate.Add(repeatDuration)
for !newTask.EndDate.After(now) {
newTask.EndDate = newTask.EndDate.Add(repeatDuration)
}
}
}
}
newTask.Done = false
}
}
// Update the "done at" timestamp
if !oldTask.Done && newTask.Done {
newTask.DoneAt = time.Now()
newTask.Done = false
}
// When unmarking a task as done, reset the timestamp
if oldTask.Done && !newTask.Done {
newTask.DoneAt = time.Time{}

View File

@ -456,10 +456,10 @@ func TestUpdateDone(t *testing.T) {
t.Run("repeat from current date", func(t *testing.T) {
t.Run("due date", func(t *testing.T) {
oldTask := &Task{
Done: false,
RepeatAfter: 8600,
RepeatFromCurrentDate: true,
DueDate: time.Unix(1550000000, 0),
Done: false,
RepeatAfter: 8600,
RepeatMode: TaskRepeatModeFromCurrentDate,
DueDate: time.Unix(1550000000, 0),
}
newTask := &Task{
Done: true,
@ -471,9 +471,9 @@ func TestUpdateDone(t *testing.T) {
})
t.Run("reminders", func(t *testing.T) {
oldTask := &Task{
Done: false,
RepeatAfter: 8600,
RepeatFromCurrentDate: true,
Done: false,
RepeatAfter: 8600,
RepeatMode: TaskRepeatModeFromCurrentDate,
Reminders: []time.Time{
time.Unix(1550000000, 0),
time.Unix(1555000000, 0),
@ -493,10 +493,10 @@ func TestUpdateDone(t *testing.T) {
})
t.Run("start date", func(t *testing.T) {
oldTask := &Task{
Done: false,
RepeatAfter: 8600,
RepeatFromCurrentDate: true,
StartDate: time.Unix(1550000000, 0),
Done: false,
RepeatAfter: 8600,
RepeatMode: TaskRepeatModeFromCurrentDate,
StartDate: time.Unix(1550000000, 0),
}
newTask := &Task{
Done: true,
@ -508,10 +508,10 @@ func TestUpdateDone(t *testing.T) {
})
t.Run("end date", func(t *testing.T) {
oldTask := &Task{
Done: false,
RepeatAfter: 8600,
RepeatFromCurrentDate: true,
EndDate: time.Unix(1560000000, 0),
Done: false,
RepeatAfter: 8600,
RepeatMode: TaskRepeatModeFromCurrentDate,
EndDate: time.Unix(1560000000, 0),
}
newTask := &Task{
Done: true,
@ -523,11 +523,11 @@ func TestUpdateDone(t *testing.T) {
})
t.Run("start and end date", func(t *testing.T) {
oldTask := &Task{
Done: false,
RepeatAfter: 8600,
RepeatFromCurrentDate: true,
StartDate: time.Unix(1550000000, 0),
EndDate: time.Unix(1560000000, 0),
Done: false,
RepeatAfter: 8600,
RepeatMode: TaskRepeatModeFromCurrentDate,
StartDate: time.Unix(1550000000, 0),
EndDate: time.Unix(1560000000, 0),
}
newTask := &Task{
Done: true,