|
1 | 1 | # redmine_issue_datetime |
2 | 2 |
|
3 | | -A Redmine plugin that adds time of day to issue start and due dates, |
| 3 | +A Redmine plugin that adds a time of day to issue start and due dates, |
4 | 4 | without changing the Redmine core schema. |
5 | 5 |
|
6 | | -Redmine stores `start_date` and `due_date` as plain dates. The ability to |
7 | | -set a time as well has been requested for a long time |
8 | | -([redmine.org #5458](https://www.redmine.org/issues/5458)). This plugin |
9 | | -solves it with parallel storage: times live in a plugin-owned table and |
10 | | -the date part is mirrored into the core columns, so filters, calendar, |
11 | | -Gantt, exports, and the REST API keep working exactly as before. |
| 6 | +Redmine stores `start_date` and `due_date` as plain dates. Adding a time |
| 7 | +has been requested for a long time |
| 8 | +([redmine.org #5458](https://www.redmine.org/issues/5458), open since |
| 9 | +2010). This plugin solves it with parallel storage: times live in a |
| 10 | +plugin-owned table, and the date part is mirrored into the core columns. |
| 11 | +Filters, calendar, Gantt, exports, and the REST API keep working exactly |
| 12 | +as before, and an issue without times behaves exactly like stock Redmine. |
12 | 13 |
|
13 | | -## Status |
| 14 | +## Features |
14 | 15 |
|
15 | | -Early development, no release yet. The core pieces (sidecar storage, |
16 | | -date mirroring, issue form integration, JSON API) are implemented and |
17 | | -covered by tests. See [docs/design.md](docs/design.md) for the full |
18 | | -design document. |
19 | | - |
20 | | -## Planned highlights |
21 | | - |
22 | | -- Time inputs next to the start and due date fields, with a configurable |
23 | | - step (15 minutes by default) |
24 | | -- Per-tracker enablement; issues without times behave exactly like stock |
25 | | - Redmine |
26 | | -- Times journalized in the issue history |
| 16 | +- Time inputs next to the start and due date fields on the issue form, |
| 17 | + with a configurable step (15 minutes by default) and an "All day" |
| 18 | + toggle |
| 19 | +- Enabled per tracker; issues of other trackers are untouched |
| 20 | +- Time changes appear in the issue history (journal) |
| 21 | +- Optional "Start time" / "Due time" columns on the issue list |
| 22 | +- A "Date and time" custom field format, offered alongside the built-in |
| 23 | + "Date" format |
27 | 24 | - JSON API for reading and writing times, including a bulk endpoint for |
28 | 25 | external consumers such as schedulers and sync clients |
| 26 | +- Consistency check and repair tasks for operators |
| 27 | + |
| 28 | +## Requirements |
| 29 | + |
| 30 | +- Redmine 6.0 or later (tested against Redmine 6.1 and 7.0, on Ruby 3.4 |
| 31 | + and 4.0) |
| 32 | +- No other plugins and no GIS stack required |
| 33 | + |
| 34 | +## Installation |
| 35 | + |
| 36 | +```bash |
| 37 | +cd /path/to/redmine/plugins |
| 38 | +git clone https://github.com/gtt-project/redmine_issue_datetime.git |
| 39 | +cd /path/to/redmine |
| 40 | +bundle exec rake redmine:plugins:migrate RAILS_ENV=production |
| 41 | +``` |
| 42 | + |
| 43 | +Restart Redmine, then open |
| 44 | +**Administration → Plugins → Redmine Issue Datetime → Configure** and |
| 45 | +enable the trackers that should have time fields. |
| 46 | + |
| 47 | +To uninstall, revert the migration and remove the plugin directory: |
| 48 | + |
| 49 | +```bash |
| 50 | +bundle exec rake redmine:plugins:migrate NAME=redmine_issue_datetime VERSION=0 RAILS_ENV=production |
| 51 | +rm -rf plugins/redmine_issue_datetime |
| 52 | +``` |
| 53 | + |
| 54 | +Uninstalling drops only the plugin's own table; core issue data is not |
| 55 | +affected. |
| 56 | + |
| 57 | +## Configuration |
| 58 | + |
| 59 | +| Setting | Meaning | Default | |
| 60 | +| --- | --- | --- | |
| 61 | +| Enabled for trackers | Trackers whose issues get time fields | none | |
| 62 | +| Time input step | Interval of the time picker; typed values are snapped to it | 15 min | |
| 63 | +| Reference time zone | The clock all times are entered and shown in (see below) | application default | |
| 64 | + |
| 65 | +## How times are shown: one clock for everyone |
| 66 | + |
| 67 | +Times are stored as instants (UTC in the database) but always entered |
| 68 | +and displayed in one time zone, the configured reference zone. They are |
| 69 | +deliberately **not** converted to each viewer's personal time zone: for |
| 70 | +work that happens at a physical place, a dispatcher and a worker must |
| 71 | +mean the same wall-clock time by "09:15". The zone is labelled in the UI |
| 72 | +so this is never ambiguous. |
| 73 | + |
| 74 | +The reference zone also decides which date is mirrored into |
| 75 | +`start_date`/`due_date`. Changing it after times have been stored can |
| 76 | +make existing rows inconsistent; run the check task below afterwards. |
| 77 | + |
| 78 | +## Custom field format "Date and time" |
| 79 | + |
| 80 | +The plugin also registers a `datetime` custom field format next to the |
| 81 | +built-in `date` one. Values are stored as naive local timestamps in the |
| 82 | +reference zone (for example `2026-08-03T09:15`) and support filtering and |
| 83 | +grouping. Unlike the start/due times, custom field values are not |
| 84 | +snapped to the step: an arbitrary datetime field may legitimately record |
| 85 | +an off-grid moment. Currently offered for issue custom fields |
| 86 | +([#15](https://github.com/gtt-project/redmine_issue_datetime/issues/15) |
| 87 | +tracks widening the scope). |
| 88 | + |
| 89 | +## REST API |
| 90 | + |
| 91 | +All endpoints are JSON and use Redmine's regular API authentication. |
| 92 | +Reading requires issue visibility; writing requires permission to edit |
| 93 | +issue attributes. |
| 94 | + |
| 95 | +``` |
| 96 | +GET /issues/:issue_id/datetime.json |
| 97 | +PUT /issues/:issue_id/datetime.json |
| 98 | +DELETE /issues/:issue_id/datetime.json |
| 99 | +GET /projects/:project_id/issue_datetimes.json?updated_since=<iso8601> |
| 100 | +``` |
| 101 | + |
| 102 | +Write timestamps must carry an explicit offset (`Z` or `+09:00`), so a |
| 103 | +request means the same thing regardless of the server's local zone: |
| 104 | + |
| 105 | +```bash |
| 106 | +curl -X PUT -H 'Content-Type: application/json' \ |
| 107 | + -H 'X-Redmine-API-Key: <key>' \ |
| 108 | + -d '{"starts_at": "2026-08-03T09:15:00+09:00", "ends_at": null}' \ |
| 109 | + https://redmine.example.org/issues/123/datetime.json |
| 110 | +``` |
| 111 | + |
| 112 | +A timestamp sets both the core date and the time; `null` clears the time |
| 113 | +and keeps the date; omitted keys are left untouched. `DELETE` clears all |
| 114 | +times (back to "all day") and keeps the dates. |
| 115 | + |
| 116 | +## Consistency tasks |
| 117 | + |
| 118 | +The date mirror is maintained by ActiveRecord callbacks. Anything that |
| 119 | +bypasses them (raw SQL, `update_column`, a restored database) can put |
| 120 | +the stored times and the core dates out of step: |
| 121 | + |
| 122 | +```bash |
| 123 | +# Report disagreements; exits non-zero when any are found (cron/monitoring friendly) |
| 124 | +bundle exec rake redmine_issue_datetime:check RAILS_ENV=production |
| 125 | + |
| 126 | +# Repair them: the core date is re-derived from the stored timestamp |
| 127 | +bundle exec rake redmine_issue_datetime:repair RAILS_ENV=production |
| 128 | +``` |
| 129 | + |
| 130 | +## Development |
| 131 | + |
| 132 | +Tests run inside a Redmine checkout, like any Redmine plugin: |
| 133 | + |
| 134 | +```bash |
| 135 | +bundle exec rails test plugins/redmine_issue_datetime/test RAILS_ENV=test |
| 136 | +``` |
| 137 | + |
| 138 | +CI covers Redmine 6.1/7.0 on Ruby 3.4/4.0 with PostgreSQL, plus a |
| 139 | +`zeitwerk:check` eager-loading gate. See |
| 140 | +[docs/design.md](docs/design.md) for the design document. |
29 | 141 |
|
30 | 142 | ## Related projects |
31 | 143 |
|
|
0 commit comments