taskcheck 0.1.1__tar.gz → 0.1.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,13 +1,12 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: taskcheck
3
- Version: 0.1.1
3
+ Version: 0.1.3
4
4
  Summary: Automatic scheduler for taskwarrior
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
7
7
  License-File: LICENSE
8
8
  Requires-Dist: appdirs>=1.4.4
9
9
  Requires-Dist: icalendar>=6.0.1
10
- Requires-Dist: requests-cache>=1.2.1
11
10
  Requires-Dist: requests>=2.32.3
12
11
 
13
12
 
@@ -46,6 +45,8 @@ You can exclude a task from being scheduled by removing the `time_map` or `estim
46
45
 
47
46
  You can see tasks that you can execute now with the `task ready` report.
48
47
 
48
+ You can see the schedule for a task in the `scheduling` UDA.
49
+
49
50
  ## Configuration
50
51
 
51
52
  1. Create a TOML file at `~/.config/task/taskcheck.toml` with the following format:
@@ -72,6 +73,7 @@ days_ahead = 1000 # how far go with the schedule (lower values make the computat
72
73
  [calendars.1]
73
74
  url = "https://your/url/to/calendar.ics"
74
75
  expiration = 0.08 # in hours (0.08 hours =~ 5 minutes)
76
+ timezone = "Europe/Rome" # if set, force timezone for this calendar; timezone values are TZ identifiers (https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)
75
77
 
76
78
  [calendars.holidays]
77
79
  url = "https://www.officeholidays.com/ics-clean/italy/milan"
@@ -90,18 +92,25 @@ uda.estimated.type = duration
90
92
  uda.estimated.label = Estimated Time
91
93
  uda.completion_date.type = date
92
94
  uda.completion_date.label = Expected Completion Date
95
+ uda.scheduling.type = string
96
+ uda.scheduling.label = Scheduling
93
97
 
94
98
  # Adjust urgency for taskcheck
95
- urgency.uda.estimated.coefficient = 1.0 # Increase urgency for tasks with longer estimated durations
96
- urgency.waiting.coefficient = 0.0 # you will use `wait:` to avoid scheduling tasks too much sooner
99
+ # you will use `wait:` to avoid scheduling tasks too much sooner
100
+ urgency.waiting.coefficient = 0.0
101
+ # you will use `active:` to prioritize tasks already started even if the estimated amount is decreased
102
+ urgency.active.coefficient = 3.0
103
+ urgency.scheduled.coefficient = 0.0 # tasks that are not scheduled may still be urgent
97
104
 
98
105
  # we need to be able to modify recurrent tasks without prompt
99
106
  recurrence.confirmation=no
100
107
 
101
108
  # suggested
102
- urgency.inherit=1
109
+ urgency.inherit=1 # use dependencies to automatically prioritize tasks
103
110
  urgency.blocking.coefficient=2
104
111
 
112
+ # give urgeny values to the estimated time
113
+ # this is log(estimation) + 1, with base so that 36 hours has ~12 urgency
105
114
  urgency.uda.estimated.PT1H.coefficient = 1
106
115
  urgency.uda.estimated.PT2H.coefficient = 2.32
107
116
  urgency.uda.estimated.PT3H.coefficient = 3.67
@@ -138,11 +147,14 @@ urgency.uda.estimated.PT33H.coefficient = 11.73
138
147
  urgency.uda.estimated.PT34H.coefficient = 11.81
139
148
  urgency.uda.estimated.PT35H.coefficient = 11.89
140
149
  urgency.uda.estimated.PT36H.coefficient = 11.96
150
+ # maximum duration is 36 hours
151
+
141
152
  ```
142
153
 
143
154
  ## CLI Options
144
155
 
145
156
  ```
157
+
146
158
  -v, --verbose: increase output verbosity
147
- -t, --today: how much time has been already used today (default: 0)
159
+
148
160
  ```
@@ -34,6 +34,8 @@ You can exclude a task from being scheduled by removing the `time_map` or `estim
34
34
 
35
35
  You can see tasks that you can execute now with the `task ready` report.
36
36
 
37
+ You can see the schedule for a task in the `scheduling` UDA.
38
+
37
39
  ## Configuration
38
40
 
39
41
  1. Create a TOML file at `~/.config/task/taskcheck.toml` with the following format:
@@ -60,6 +62,7 @@ days_ahead = 1000 # how far go with the schedule (lower values make the computat
60
62
  [calendars.1]
61
63
  url = "https://your/url/to/calendar.ics"
62
64
  expiration = 0.08 # in hours (0.08 hours =~ 5 minutes)
65
+ timezone = "Europe/Rome" # if set, force timezone for this calendar; timezone values are TZ identifiers (https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)
63
66
 
64
67
  [calendars.holidays]
65
68
  url = "https://www.officeholidays.com/ics-clean/italy/milan"
@@ -78,18 +81,25 @@ uda.estimated.type = duration
78
81
  uda.estimated.label = Estimated Time
79
82
  uda.completion_date.type = date
80
83
  uda.completion_date.label = Expected Completion Date
84
+ uda.scheduling.type = string
85
+ uda.scheduling.label = Scheduling
81
86
 
82
87
  # Adjust urgency for taskcheck
83
- urgency.uda.estimated.coefficient = 1.0 # Increase urgency for tasks with longer estimated durations
84
- urgency.waiting.coefficient = 0.0 # you will use `wait:` to avoid scheduling tasks too much sooner
88
+ # you will use `wait:` to avoid scheduling tasks too much sooner
89
+ urgency.waiting.coefficient = 0.0
90
+ # you will use `active:` to prioritize tasks already started even if the estimated amount is decreased
91
+ urgency.active.coefficient = 3.0
92
+ urgency.scheduled.coefficient = 0.0 # tasks that are not scheduled may still be urgent
85
93
 
86
94
  # we need to be able to modify recurrent tasks without prompt
87
95
  recurrence.confirmation=no
88
96
 
89
97
  # suggested
90
- urgency.inherit=1
98
+ urgency.inherit=1 # use dependencies to automatically prioritize tasks
91
99
  urgency.blocking.coefficient=2
92
100
 
101
+ # give urgeny values to the estimated time
102
+ # this is log(estimation) + 1, with base so that 36 hours has ~12 urgency
93
103
  urgency.uda.estimated.PT1H.coefficient = 1
94
104
  urgency.uda.estimated.PT2H.coefficient = 2.32
95
105
  urgency.uda.estimated.PT3H.coefficient = 3.67
@@ -126,11 +136,14 @@ urgency.uda.estimated.PT33H.coefficient = 11.73
126
136
  urgency.uda.estimated.PT34H.coefficient = 11.81
127
137
  urgency.uda.estimated.PT35H.coefficient = 11.89
128
138
  urgency.uda.estimated.PT36H.coefficient = 11.96
139
+ # maximum duration is 36 hours
140
+
129
141
  ```
130
142
 
131
143
  ## CLI Options
132
144
 
133
145
  ```
146
+
134
147
  -v, --verbose: increase output verbosity
135
- -t, --today: how much time has been already used today (default: 0)
148
+
136
149
  ```
@@ -1,15 +1,10 @@
1
1
  [project]
2
2
  name = "taskcheck"
3
- version = "0.1.1"
3
+ version = "0.1.3"
4
4
  description = "Automatic scheduler for taskwarrior"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
7
- dependencies = [
8
- "appdirs>=1.4.4",
9
- "icalendar>=6.0.1",
10
- "requests-cache>=1.2.1",
11
- "requests>=2.32.3",
12
- ]
7
+ dependencies = ["appdirs>=1.4.4", "icalendar>=6.0.1", "requests>=2.32.3"]
13
8
 
14
9
  [project.scripts]
15
10
  taskcheck = "taskcheck.__main__:main"
@@ -11,14 +11,6 @@ from taskcheck.ical import ical_to_dict
11
11
  config_dir = Path(appdirs.user_config_dir("task"))
12
12
 
13
13
  arg_parser = argparse.ArgumentParser()
14
- arg_parser.add_argument(
15
- "-t",
16
- "--today",
17
- type=float,
18
- action="store",
19
- help="specify how many hours you have already worked today (default: 0)",
20
- default=0,
21
- )
22
14
  arg_parser.add_argument(
23
15
  "-v", "--verbose", action="store_true", help="increase output verbosity"
24
16
  )
@@ -54,11 +46,12 @@ def _hours_to_decimal(hour):
54
46
 
55
47
  def _hours_to_time(hour):
56
48
  hours = int(hour)
57
- minutes = int((hour - hours) * 60)
49
+ minutes = int((hour - hours) * 100)
58
50
  return datetime.strptime(f"{hours}:{minutes}", "%H:%M").time()
59
51
 
60
52
 
61
- def _time_to_hours(time):
53
+ def _time_to_decimal(time):
54
+ # round to 2 digits after the point
62
55
  return time.hour + time.minute / 60
63
56
 
64
57
 
@@ -75,6 +68,7 @@ def get_available_hours(time_map, date, calendars):
75
68
  # so let's convert them to datetime.time objects
76
69
  schedule_start = _hours_to_time(schedule_start)
77
70
  schedule_end = _hours_to_time(schedule_end)
71
+ schedule_blocked_hours = 0
78
72
  for calendar in calendars:
79
73
  for event in calendar:
80
74
  # we use str to make object serializable as jsons
@@ -87,13 +81,24 @@ def get_available_hours(time_map, date, calendars):
87
81
  break
88
82
  elif event["end"].date() < date:
89
83
  continue
84
+
90
85
  # check if the event overlaps with one of the working hours
91
86
  event_start = event["start"].time()
92
87
  event_end = event["end"].time()
88
+ if event["start"].date() < date:
89
+ event_start = datetime(date.year, date.month, date.day, 0, 0).time()
90
+ if event["end"].date() > date:
91
+ event_end = datetime(date.year, date.month, date.day, 23, 59).time()
92
+
93
93
  if event_start < schedule_end and event_end > schedule_start:
94
- blocked_hours += _time_to_hours(
94
+ schedule_blocked_hours += _time_to_decimal(
95
95
  min(schedule_end, event_end)
96
- ) - _time_to_hours(max(schedule_start, event_start))
96
+ ) - _time_to_decimal(max(schedule_start, event_start))
97
+ if args.verbose and schedule_blocked_hours > 0:
98
+ print(
99
+ f"Blocked hours on {date} between {schedule_start} and {schedule_end}: {schedule_blocked_hours}"
100
+ )
101
+ blocked_hours += schedule_blocked_hours
97
102
  available_hours -= blocked_hours
98
103
  return available_hours
99
104
 
@@ -130,7 +135,20 @@ def get_long_range_time_map(time_maps, time_map_names, days_ahead, calendars):
130
135
  task_time_map.append(daily_hours)
131
136
  long_range_time_map[key] = task_time_map
132
137
 
133
- return task_time_map
138
+ today_time = _time_to_decimal(datetime.now().time())
139
+ today_weekday = datetime.today().strftime("%A").lower()
140
+ today_used_hours = 0
141
+ # compare with the time_maps of today
142
+ for time_map_name in time_map_names:
143
+ time_map = time_maps[time_map_name].get(today_weekday)
144
+ if time_map:
145
+ for schedule_start, schedule_end in time_map:
146
+ if schedule_start <= today_time <= schedule_end:
147
+ today_used_hours += today_time - schedule_start
148
+ break
149
+ elif today_time > schedule_end:
150
+ today_used_hours += schedule_end - schedule_start
151
+ return task_time_map, today_used_hours
134
152
 
135
153
 
136
154
  def schedule_task_on_day(
@@ -143,14 +161,17 @@ def schedule_task_on_day(
143
161
  today,
144
162
  used_hours,
145
163
  wait,
164
+ scheduling_note,
146
165
  ):
147
166
  # we can schedule task on this day
148
167
  employable_hours = task_time_map[day_offset] - used_hours[day_offset]
168
+ # avoid using too small values
169
+ employable_hours = 0.01 if 0.01 > employable_hours > 0 else employable_hours
149
170
  current_date = today + timedelta(days=day_offset)
150
171
  if wait and current_date <= wait:
151
172
  if args.verbose:
152
173
  print(f"Skipping date {current_date} because of wait date {wait}")
153
- return start_date, end_date, task_remaining_hours, is_starting
174
+ return start_date, end_date, task_remaining_hours, is_starting, scheduling_note
154
175
 
155
176
  if is_starting:
156
177
  if args.verbose:
@@ -158,8 +179,12 @@ def schedule_task_on_day(
158
179
  is_starting = False
159
180
  start_date = current_date
160
181
 
161
- if task_remaining_hours <= employable_hours:
182
+ # minimum value we admit is 0.01 (1 minute)
183
+ if task_remaining_hours <= employable_hours + 0.01:
162
184
  # consume all the remaining task's hours
185
+ if scheduling_note != "":
186
+ scheduling_note += "\n"
187
+ scheduling_note += f"{current_date}: {task_remaining_hours:.2f} hours"
163
188
  used_hours[day_offset] += task_remaining_hours
164
189
  task_remaining_hours = 0
165
190
  end_date = current_date
@@ -168,14 +193,19 @@ def schedule_task_on_day(
168
193
  print(f"Used hours on {current_date}: {used_hours[day_offset]}")
169
194
  else:
170
195
  # consume all the available hours on this task
196
+ if scheduling_note != "":
197
+ scheduling_note += "\n"
198
+ scheduling_note += f"{current_date}: {employable_hours:.2f} hours"
171
199
  task_remaining_hours -= employable_hours
172
200
  used_hours[day_offset] += employable_hours
173
201
  if args.verbose:
174
202
  print(f"Working for {employable_hours} hours on task on {current_date}")
175
- return start_date, end_date, task_remaining_hours, is_starting
203
+ return start_date, end_date, task_remaining_hours, is_starting, scheduling_note
176
204
 
177
205
 
178
- def mark_end_date(due_date, end_date, start_date, id, description=None):
206
+ def mark_end_date(
207
+ due_date, end_date, start_date, scheduling_note, id, description=None
208
+ ):
179
209
  start_end_fields = [f"scheduled:{start_date}", f"completion_date:{end_date}"]
180
210
 
181
211
  subprocess.run(
@@ -184,6 +214,7 @@ def mark_end_date(due_date, end_date, start_date, id, description=None):
184
214
  str(id),
185
215
  "modify",
186
216
  *start_end_fields,
217
+ f'scheduling:"{scheduling_note}"',
187
218
  ],
188
219
  stdout=subprocess.DEVNULL,
189
220
  stderr=subprocess.DEVNULL,
@@ -204,6 +235,7 @@ def get_calendars(config):
204
235
  all_day=calendar["event_all_day_is_blocking"],
205
236
  expiration=calendar["expiration"],
206
237
  verbose=args.verbose,
238
+ tz_name=calendar.get("timezone"),
207
239
  )
208
240
  calendar.sort(key=lambda e: e["start"])
209
241
  calendars.append(calendar)
@@ -217,7 +249,7 @@ def check_tasks_sequentially(tasks, config):
217
249
  time_maps = config["time_maps"]
218
250
  today = datetime.today().date()
219
251
  todo = [True if t["status"] not in AVOID_STATUS else False for t in tasks]
220
- used_hours = [args.today] + [0] * config["scheduler"]["days_ahead"]
252
+ used_hours = [0] * (config["scheduler"]["days_ahead"] + 1)
221
253
  calendars = get_calendars(config)
222
254
 
223
255
  while any(todo):
@@ -255,33 +287,45 @@ def check_tasks_sequentially(tasks, config):
255
287
  )
256
288
 
257
289
  task_remaining_hours = estimated_hours
258
- task_time_map = get_long_range_time_map(
290
+ task_time_map, today_used_hours = get_long_range_time_map(
259
291
  time_maps, time_map_names, config["scheduler"]["days_ahead"], calendars
260
292
  )
293
+ used_hours[0] += today_used_hours
261
294
 
262
295
  # Simulate work day-by-day until task is complete or past due
263
296
  is_starting = True
297
+ scheduling_note = ""
264
298
  start_date = end_date = None
265
299
  for offset in range(len(task_time_map)):
266
300
  if task_time_map[offset] > used_hours[offset]:
267
- (start_date, end_date, task_remaining_hours, is_starting) = (
268
- schedule_task_on_day(
269
- is_starting,
270
- offset,
271
- start_date,
272
- end_date,
273
- task_remaining_hours,
274
- task_time_map,
275
- today,
276
- used_hours,
277
- wait_date,
278
- )
301
+ (
302
+ start_date,
303
+ end_date,
304
+ task_remaining_hours,
305
+ is_starting,
306
+ scheduling_note,
307
+ ) = schedule_task_on_day(
308
+ is_starting,
309
+ offset,
310
+ start_date,
311
+ end_date,
312
+ task_remaining_hours,
313
+ task_time_map,
314
+ today,
315
+ used_hours,
316
+ wait_date,
317
+ scheduling_note,
279
318
  )
280
319
 
281
320
  if end_date is not None:
282
321
  todo[i] = False
283
322
  mark_end_date(
284
- due_date, end_date, start_date, task["id"], task["description"]
323
+ due_date,
324
+ end_date,
325
+ start_date,
326
+ scheduling_note,
327
+ task["id"],
328
+ task["description"],
285
329
  )
286
330
  break
287
331
 
@@ -3,38 +3,29 @@ import json
3
3
  from icalendar import Calendar
4
4
  from datetime import datetime, timedelta
5
5
  from dateutil.rrule import rruleset, rrulestr
6
- from requests_cache import CachedSession
6
+ import requests
7
7
  from pathlib import Path
8
8
  import appdirs
9
+ import zoneinfo
9
10
 
10
11
  import time
11
12
 
12
13
  CACHE = Path(appdirs.user_cache_dir("taskcheck"))
13
14
 
14
15
 
15
- def fetch_ical_data(url, expiration):
16
- session = CachedSession(
17
- "taskcheck",
18
- use_cache_dir=True, # Save files in the default user cache dir
19
- cache_control=True, # Use Cache-Control response headers for expiration, if available
20
- urls_expire_after={url: expiration}, # Set expiration time
21
- allowable_codes=[
22
- 200,
23
- ],
24
- allowable_methods=["GET", "POST"], # Cache whatever HTTP methods you want
25
- stale_if_error=True, # In case of request errors, use stale cache data if possible
26
- )
27
- response = session.get(url)
16
+ def fetch_ical_data(url):
17
+ response = requests.get(url)
28
18
  response.raise_for_status()
29
19
  return response.text
30
20
 
31
21
 
32
- def parse_ical_events(ical_text, days_ahead, all_day):
22
+ def parse_ical_events(ical_text, days_ahead, all_day, tz_name=None, verbose=False):
33
23
  cal = Calendar.from_ical(ical_text)
34
24
  today = datetime.now().date()
35
25
  end_date = today + timedelta(days=days_ahead)
36
26
 
37
27
  events = []
28
+ exceptions = set()
38
29
 
39
30
  for component in cal.walk():
40
31
  if component.name == "VEVENT":
@@ -61,9 +52,12 @@ def parse_ical_events(ical_text, days_ahead, all_day):
61
52
  )
62
53
  event_end = event_start + timedelta(days=1) - timedelta(seconds=1)
63
54
  if recurrence_id:
64
- continue # Ignore recurrence exceptions here for simplicity
55
+ if end_date >= recurrence_id.dt.date() >= today:
56
+ # this was an occurrence of a recurring event
57
+ # but has been moved, let's record the exception that must be removed
58
+ exceptions.add(recurrence_id.dt.isoformat())
65
59
 
66
- event_dates = rruleset()
60
+ occurrences = rruleset()
67
61
  if recurrence_rule:
68
62
  # avoid handling rules that are already ended
69
63
  # and don't create events beyond end_date
@@ -72,26 +66,49 @@ def parse_ical_events(ical_text, days_ahead, all_day):
72
66
  until = until[0].date()
73
67
  if until < today:
74
68
  continue
75
- elif until > end_date:
76
- recurrence_rule["UNTIL"] = end_date
77
69
  rrule = rrulestr(
78
70
  str(recurrence_rule.to_ical(), "utf-8"),
79
71
  dtstart=event_start,
80
72
  )
81
- event_dates.rrule(rrule) # type: ignore
73
+ occurrences.rrule(rrule) # type: ignore
74
+ # exceptions to recurring events
75
+ excdates = component.get("exdate")
76
+ if excdates is not None:
77
+ if isinstance(excdates, list):
78
+ # concatenate all the .dts into one list
79
+ excdates = [e for exdate in excdates for e in exdate.dts]
80
+ else:
81
+ excdates = excdates.dts
82
+ for excdate in excdates:
83
+ if end_date >= excdate.dt.date() >= today:
84
+ exceptions.add(excdate.dt.isoformat())
82
85
  else:
83
- event_dates.rdate(event_start)
84
-
85
- for event_date in event_dates:
86
- if today <= event_date.date() <= end_date:
86
+ # if event is not recurring, add it as a single event
87
+ if event_start.date() >= today and event_end.date() <= end_date:
88
+ occurrences.rdate(event_start)
89
+
90
+ for occurrence in occurrences:
91
+ if tz_name is not None:
92
+ occurrence = occurrence.astimezone(zoneinfo.ZoneInfo(tz_name))
93
+ end = occurrence + (event_end - event_start)
94
+ if occurrence.date() >= today and end.date() <= end_date:
87
95
  events.append(
88
96
  {
89
- "summary": component.get("summary"),
90
- "start": event_date.isoformat(),
91
- "end": event_end.isoformat(),
97
+ # "summary": component.get("summary"),
98
+ "start": occurrence.isoformat(),
99
+ "end": end.isoformat(),
92
100
  }
93
101
  )
102
+ elif end.date() > end_date:
103
+ break
94
104
 
105
+ with_exc_len = len(events)
106
+ events = [event for event in events if event["start"] not in exceptions]
107
+ if verbose:
108
+ print(
109
+ f"Removed {with_exc_len - len(events)} events that are exceptions to recurring events"
110
+ )
111
+ events.sort(key=lambda x: x["start"])
95
112
  return events
96
113
 
97
114
 
@@ -100,7 +117,9 @@ def get_cache_filename(url):
100
117
  return CACHE / f"{hash_object.hexdigest()}.json"
101
118
 
102
119
 
103
- def ical_to_dict(url, days_ahead=7, all_day=False, expiration=0.25, verbose=False):
120
+ def ical_to_dict(
121
+ url, days_ahead=7, all_day=False, expiration=0.25, verbose=False, tz_name=None
122
+ ):
104
123
  CACHE.mkdir(exist_ok=True, parents=True)
105
124
  cache_file = CACHE / get_cache_filename(url)
106
125
  current_time = time.time()
@@ -117,11 +136,13 @@ def ical_to_dict(url, days_ahead=7, all_day=False, expiration=0.25, verbose=Fals
117
136
 
118
137
  # Fetch and parse iCal data
119
138
  ttt = time.time()
120
- ical_text = fetch_ical_data(url, expiration * 3600)
139
+ ical_text = fetch_ical_data(url)
121
140
  if verbose:
122
141
  print("Time taken to fetch ical data: ", time.time() - ttt)
123
142
  ttt = time.time()
124
- events = parse_ical_events(ical_text, days_ahead, all_day)
143
+ events = parse_ical_events(
144
+ ical_text, days_ahead, all_day, tz_name=tz_name, verbose=verbose
145
+ )
125
146
  if verbose:
126
147
  print("Time taken to parse ical data: ", time.time() - ttt)
127
148
 
@@ -1,13 +1,12 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: taskcheck
3
- Version: 0.1.1
3
+ Version: 0.1.3
4
4
  Summary: Automatic scheduler for taskwarrior
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
7
7
  License-File: LICENSE
8
8
  Requires-Dist: appdirs>=1.4.4
9
9
  Requires-Dist: icalendar>=6.0.1
10
- Requires-Dist: requests-cache>=1.2.1
11
10
  Requires-Dist: requests>=2.32.3
12
11
 
13
12
 
@@ -46,6 +45,8 @@ You can exclude a task from being scheduled by removing the `time_map` or `estim
46
45
 
47
46
  You can see tasks that you can execute now with the `task ready` report.
48
47
 
48
+ You can see the schedule for a task in the `scheduling` UDA.
49
+
49
50
  ## Configuration
50
51
 
51
52
  1. Create a TOML file at `~/.config/task/taskcheck.toml` with the following format:
@@ -72,6 +73,7 @@ days_ahead = 1000 # how far go with the schedule (lower values make the computat
72
73
  [calendars.1]
73
74
  url = "https://your/url/to/calendar.ics"
74
75
  expiration = 0.08 # in hours (0.08 hours =~ 5 minutes)
76
+ timezone = "Europe/Rome" # if set, force timezone for this calendar; timezone values are TZ identifiers (https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)
75
77
 
76
78
  [calendars.holidays]
77
79
  url = "https://www.officeholidays.com/ics-clean/italy/milan"
@@ -90,18 +92,25 @@ uda.estimated.type = duration
90
92
  uda.estimated.label = Estimated Time
91
93
  uda.completion_date.type = date
92
94
  uda.completion_date.label = Expected Completion Date
95
+ uda.scheduling.type = string
96
+ uda.scheduling.label = Scheduling
93
97
 
94
98
  # Adjust urgency for taskcheck
95
- urgency.uda.estimated.coefficient = 1.0 # Increase urgency for tasks with longer estimated durations
96
- urgency.waiting.coefficient = 0.0 # you will use `wait:` to avoid scheduling tasks too much sooner
99
+ # you will use `wait:` to avoid scheduling tasks too much sooner
100
+ urgency.waiting.coefficient = 0.0
101
+ # you will use `active:` to prioritize tasks already started even if the estimated amount is decreased
102
+ urgency.active.coefficient = 3.0
103
+ urgency.scheduled.coefficient = 0.0 # tasks that are not scheduled may still be urgent
97
104
 
98
105
  # we need to be able to modify recurrent tasks without prompt
99
106
  recurrence.confirmation=no
100
107
 
101
108
  # suggested
102
- urgency.inherit=1
109
+ urgency.inherit=1 # use dependencies to automatically prioritize tasks
103
110
  urgency.blocking.coefficient=2
104
111
 
112
+ # give urgeny values to the estimated time
113
+ # this is log(estimation) + 1, with base so that 36 hours has ~12 urgency
105
114
  urgency.uda.estimated.PT1H.coefficient = 1
106
115
  urgency.uda.estimated.PT2H.coefficient = 2.32
107
116
  urgency.uda.estimated.PT3H.coefficient = 3.67
@@ -138,11 +147,14 @@ urgency.uda.estimated.PT33H.coefficient = 11.73
138
147
  urgency.uda.estimated.PT34H.coefficient = 11.81
139
148
  urgency.uda.estimated.PT35H.coefficient = 11.89
140
149
  urgency.uda.estimated.PT36H.coefficient = 11.96
150
+ # maximum duration is 36 hours
151
+
141
152
  ```
142
153
 
143
154
  ## CLI Options
144
155
 
145
156
  ```
157
+
146
158
  -v, --verbose: increase output verbosity
147
- -t, --today: how much time has been already used today (default: 0)
159
+
148
160
  ```
@@ -1,4 +1,3 @@
1
1
  appdirs>=1.4.4
2
2
  icalendar>=6.0.1
3
- requests-cache>=1.2.1
4
3
  requests>=2.32.3
File without changes
File without changes