Add a Date and time custom field format - #14
Merged
Conversation
Closes #9 Registers a new 'datetime' format alongside Redmine's built-in 'date' rather than patching it, so a field either wants a time or it does not and that stays a per-field choice. Redmine 7 has no datetime format of its own (13 built-ins, date is the only temporal one). No sidecar is involved. The mirror machinery elsewhere in this plugin exists only because issues.start_date is a real date column that cannot be changed; a custom value is a string column, so a timestamp fits natively and there is nothing to keep in sync. Stored as naive local time in the reference zone (2026-08-03T09:15, no offset): it matches how the built-in date format stores, keeps ISO strings sortable as plain strings so ordering needs no special casing, and is consistent with this plugin showing one clock rather than converting per viewer. Formatting therefore avoids the view's format_time helper, which would convert to the viewer's own zone. The input carries the configured interval as its step, so the picker moves on the same grid as the start/due fields, but off-grid minutes are accepted: unlike the scheduling times, an arbitrary datetime field may legitimately record when something actually happened.
There was a problem hiding this comment.
Pull request overview
Adds a new datetime custom field format to the plugin, registered alongside Redmine’s built-in date format, storing values as naive ISO-8601 local timestamps and formatting them consistently in the configured reference time zone.
Changes:
- Introduces
RedmineIssueDatetime::DatetimeFormatas a new Redmine custom field format (field_format: 'datetime') with validation, casting, formatting, and filter typing. - Adds the admin UI partial for configuring datetime custom fields (including the reference-zone hint) plus EN/JA translations.
- Adds unit tests covering registration, round-tripping, rejection cases, formatting behavior, sorting, and query filter options.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| test/unit/datetime_format_test.rb | New unit coverage for the datetime field format behaviors and constraints. |
| lib/redmine_issue_datetime/field_format.rb | Implements the new datetime custom field format (parse/validate/format/input rendering). |
| lib/redmine_issue_datetime.rb | Ensures the new field format is loaded during plugin initialization. |
| config/locales/ja.yml | Adds Japanese translations for the new format label and zone hint text. |
| config/locales/en.yml | Adds English translations for the new format label and zone hint text. |
| app/views/custom_fields/formats/_datetime.html.erb | Adds the admin form partial for datetime custom field defaults and hinting. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
The eager-load gate caught this on its own author: the file was field_format.rb while the class is DatetimeFormat, so Zeitwerk expected RedmineIssueDatetime::FieldFormat and a production boot would have died with a NameError. Every test passed, because test and development load lazily - exactly the gap the gate exists for, and the same shape as karida-org/redmine_gtt_sync#81. Renamed to datetime_format.rb, with a comment so the next person does not reintroduce it. Also restores the previous User.current in the test teardown rather than assuming it was nil.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #9, taking the approach you suggested rather than the one the issue originally sketched.
Why a format, not a sidecar
Registers a new
datetimeformat alongside Redmine's built-indate, so a field either wants a time or it does not, and that stays a per-field choice. Redmine 7 ships 13 formats anddateis the only temporal one, so nothing is being duplicated.The important part: this needs no sidecar table and no mirroring. That machinery exists elsewhere in this plugin for exactly one reason, that
issues.start_dateis a realdatecolumn which cannot be changed. A custom value lives in a string column, so a timestamp fits natively and there is nothing to keep in sync, and no drift to check for.Deliberately not the approach of nanego/redmine_datetime_custom_field, which patches the built-in
dateformat and migrates existing date fields; keeping the built-in intact was the requirement.Storage: naive local in the reference zone
2026-08-03T09:15, no offset. Three reasons, all of which the tests pin:That third point has a consequence worth reviewing:
formatted_valuedeliberately does not use the view'sformat_timehelper, because that converts to the viewer's own zone and would reintroduce exactly the per-viewer ambiguity #5 rejected. A test asserts formatting is unaffected byUser.current's zone.The interval
The input carries the configured step, so the picker moves on the same grid as the start and due time fields. Off-grid minutes are accepted rather than snapped, which is a deliberate difference from those fields: they feed the optimizer and belong on a grid, whereas an arbitrary datetime field may legitimately record when something happened, such as an incident report time.
One thing my own test caught
The first version fell back to
I18n.l(time, format: :short)when the admin date/time settings are blank, which renders03 Aug 09:15with no year. Ambiguous for anything outside the current year. Formatting now uses explicit fallbacks (%Y-%m-%d %H:%M) and still honours the admin settings when they are set.Testing
73 tests green against Redmine 7.0.0 / Ruby 4.0.6 with the full plugin set. New coverage: registration alongside
date, round-trip through a real custom field, casting into the reference zone, rejection of a date without a time / a value carrying an offset / an impossible time, blank allowed, formatting completeness and admin settings, viewer-zone independence, string sortability, the declared filter type, and off-grid acceptance.Verified end to end in the dev instance: "Date and time" appears in the admin format picker with "Date" still present, the issue form renders
type=datetime-localwithstep=900, and the value shows as2026-08-03 09:07on the issue page. Demo field and settings removed afterwards.Follow-up filed for widening beyond issue custom fields.