Paul King created GROOVY-12124:
----------------------------------

             Summary: Modernize the TimeCategory date/time DSL: add a java.time 
flavor and a dequirked java.util.Date flavor
                 Key: GROOVY-12124
                 URL: https://issues.apache.org/jira/browse/GROOVY-12124
             Project: Groovy
          Issue Type: Improvement
            Reporter: Paul King


h1. Modernize the TimeCategory date/time DSL

h2. Summary

Introduce a {{java.time}}-based parallel to the legacy 
{{groovy.time.TimeCategory}}
DSL, and give the existing {{java.util.Date}}-based DSL a cleaned-up 
("dequirked")
home in the module that already owns Date support. Net result: one familiar DSL
({{1.hour.ago}}, {{date + 3.months}}, {{2.days.from.now}}) available in two 
flavors —
modern {{java.time}} output or classic {{java.util.Date}} output — both 
quirk-free.

h2. Background

{{groovy.time.TimeCategory}} (a {{use()}} category in *groovy core*) layers 
duration
arithmetic onto {{java.util.Date}}/{{Calendar}}: {{Integer}} producers 
({{1.hour}},
{{2.months}}), operators ({{date + duration}}, {{date - date}}), and 
relative-time
properties ({{.ago}}, {{.from.now}}). It is backed by the 
{{groovy.time.Duration}}
hierarchy (Duration / TimeDuration / DatumDependentDuration / 
TimeDatumDependentDuration).

Two problems motivate this work:
* *No java.time equivalent.* Modern code uses {{java.time}}, but there is no
  {{1.hour}}/{{2.months}} producer for it. The {{groovy-datetime}} module 
already
  provides all the arithmetic (plus/minus/next/multiply/between/upto…) on 
{{java.time}}
  types — the only gap is the number→amount producers and 
{{.ago}}/{{.from.now}}.
* *The Date DSL is split and quirky.* All other {{java.util.Date}} DSL methods
  ({{date + int}}, {{date.next()}}, {{date[YEAR]}}, {{clearTime}}, {{format}},
  {{date - date -> int}}) live in the optional *groovy-dateutil* module, while
  TimeCategory lives in core — so half of Date arithmetic is core, half is 
optional,
  and {{date - date}} even resolves differently between them.

h2. Proposal

Add two categories and deprecate the legacy one:

|| Class || Module || Output type || Status ||
| {{org.apache.groovy.datetime.TimeCategory}} | groovy-datetime | {{java.time}} 
(Duration/Period, LocalDate/LocalDateTime) | new |
| {{org.apache.groovy.dateutil.TimeCategory}} | groovy-dateutil | 
{{java.util.Date}} | new (dequirked) |
| {{groovy.time.TimeCategory}} (+ Duration hierarchy) | core | 
{{java.util.Date}} | deprecated, frozen |

The two new categories share one DSL surface and one semantics spec; only the 
terminal
return types differ. The legacy class stays byte-for-byte behavior-compatible 
(frozen),
so existing users are unaffected until they choose to migrate.

h3. Producer mapping (both flavors)

* {{seconds, minutes, hours, millis, nanos}} → {{java.time.Duration}} 
(datetime) /
  {{TimeDuration}} (dateutil)
* {{days, weeks, months, years}} → {{java.time.Period}} (datetime) /
  {{Duration}}/{{DatumDependentDuration}} (dateutil)
* {{.ago}} / {{.from.now}} → {{LocalDate}}/{{LocalDateTime}} (datetime) /
  {{java.util.Date}} (dateutil)

Note: {{java.time}} deliberately keeps date-based ({{Period}}) and time-based
({{Duration}}) amounts separate, so {{date + 2.months + 3.hours}} works by
left-associative chaining rather than a combined amount type.

h2. Design decisions

* *(proposed, pending team review)* The dateutil flavor uses its *own* 
dequirked value
  classes (option "B1"), not {{java.time}} internally (option "B2"). Rationale: 
B1 keeps
  the DSL return shape ({{.seconds}}/{{.days}} component accessors, bespoke 
{{toString}},
  the type lattice), so existing tests and user code port ~1:1; it is fully 
self-contained
  within groovy-dateutil (no dependency on groovy-datetime being on the 
classpath — matching
  today, where the Duration classes rely on no external extension methods). B2 
would change
  component-accessor and {{toString}} semantics and require either duplicating 
datetime's
  operator DGM or a new dateutil→datetime module dependency.
* *(proposed, pending team review)* Deprecate {{groovy.time.TimeCategory}} with 
a removal
  trajectory (future major). The dateutil copy gives Date users a 
non-deprecated home, so
  the deprecation is not coercive toward java.time.
* Keep the legacy {{groovy.time.Duration}} value classes exactly as-is (do not 
move; the
  dequirked classes are new copies in {{org.apache.groovy.dateutil}} — no split 
package).
* The legacy class is *not* a forwarder to the new one (behavior differs); it 
stays intact.

h2. Quirks removed in the new flavors

|| # || Legacy behavior || New behavior ||
| A | {{.ago}}/{{.from.now}} floor to midnight for day/month/year durations but 
keep time for hours/min/sec (inconsistent) | Time-of-day preserved uniformly 
(datetime: {{Period}}→{{LocalDate}}, {{Duration}}→{{LocalDateTime}}) |
| B | {{DatumDependentDuration.toMilliseconds()}} resolves against {{new 
Date()}} — nondeterministic | Deterministic: uses {{ChronoUnit}} estimates 
({{1.year == 12.months}} still holds exactly); {{java.time}} {{Period}} simply 
has no {{toMillis}} |
| C | Four-class Duration lattice incl. {{TimeDatumDependentDuration}} | 
Retained in dateutil (B1); not needed in datetime (chaining) |
| D | {{Duration}} = 24h/day for {{toMillis}} but DST-aware when added | 
Documented/consistent |
| E | {{getTimeZone}} (already {{@Deprecated}}), {{getDaylightSavingsOffset}} | 
Dropped from new flavors (superseded by zone-aware java.time) |

Side effect of A+B: {{getAgo}}/{{getFrom}}/{{toMilliseconds}} collapse to single
implementations on {{BaseDuration}}, removing the per-subclass overrides (~40% 
less
duplication in the value classes).

h2. Prototype validation

A working B1 prototype of the dateutil flavor (six classes) was built and 
exercised
against a port of {{TimeCategoryTest}}:
* All arithmetic/{{toString}}/comparison assertions ported with *only* the 
{{use()}}/import
  target changed — no assertion edits.
* Three added assertions demonstrate the dequirks; the same probes run against 
the legacy
  class confirm the delta (legacy {{3.days.ago}} → {{00:00:00}}; 
{{1.month.ago}} off by the
  millis-since-midnight; {{5.months.toMilliseconds()}} varies with the current 
date).
* Only the value-class suites ({{DurationTest}}, 
{{DatumDependentDurationTest}}) need edits,
  confined to the ~6 methods that assert the midnight-flooring / 
{{now}}-relative behavior.

h2. Scope / tasks

* [ ] {{org.apache.groovy.datetime.TimeCategory}} + tests + spec section 
(_working-with-datetime-types_)
* [ ] {{org.apache.groovy.dateutil.TimeCategory}} (B1, dequirked) + value 
classes + tests + spec section
* [ ] Deprecate {{groovy.time.TimeCategory}} (Javadoc {{@deprecated}} pointing 
to both replacements)
* [ ] Reconcile/document the {{date - date}} collision (int days vs Duration) 
within groovy-dateutil
* [ ] Add {{Long}}/{{nanos}} support (enhancement over the Integer-only legacy)
* [ ] Changelog + docgenerator entries

h2. Open questions (pending team review)

* B1 vs B2 for the dateutil flavor (recommendation: B1).
* Commit to eventual removal of {{groovy.time.TimeCategory}} (hard 
{{@Deprecated}}), or
  soft-deprecate in docs only?
* Category name: reuse the simple name {{TimeCategory}} in both new packages 
(symmetry, but
  dual-import hazard) vs a distinct name for the datetime flavor.
* Global DGM producers (static-compilation friendly) in addition to the 
{{use()}} category —
  now, later, or never?



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to