Skip to content

Loading…

Decide/implement lenient date building #115

Closed
jodastephen opened this Issue · 7 comments

2 participants

@jodastephen
ThreeTen member

The JDK supports the concept of lenient building of dates, such that parsing JANUARY the 32nd will yield FEBRUARY the 1st. Should JSR-310 support this?

Note that this is different to the lenient parsing concepts of #65. That deals with interpreting poor quality input. This deals with invalid dates/times.

If implemented, there must be two modes - strict and lenient, Technically, this is orthogonal to the other strict/lenient setting. However if we say that the strict/lenient setting that is active at the end of the parse is the one used for the building, it may be sufficient.

There are four basic options:

  • reject invalid dates, such as January 31st, or February 29th in a non-leap year (this is not totally possible, as week-of-month fields and Japanese date fields would be too hard to enter if this strict) ie. no strict/lenient build at all
  • accept invalid dates (in a lenient mode), treating the invalid field as an addition based on a valid value, ie. lenient is very lenient as per the JDK
  • accept invalid dates, but only within the outer range of valid values. Thus the 32nd or 0th of January would be an error, but the 31st of February would not be an error (and would be the 2nd or 3rd of March) ie. day-of-month allowed only from 1 to 31 (ISO).
  • be more field-based. Day-of-month might accept 31st February and clamp it to the last day of February, whereas other fields might be more or less lenient.

A mode where the 29th February is converted to either the 29th or the 28th depending on whether it is a leap year feels like it might be a nice feature, but it is different to the current JDK.

@jodastephen
ThreeTen member

See gist https://gist.github.com/4686522 for a patch on this topic.

Its a general purpose "manipulator" of the parsed data. This could allow the "unresolved" concept to be removed. It allows most of the above lenient behaviour to be written. Future JDKs (or even this one) could add pre-packaged manipulators.

The manipulator concept is similar to TemporalAdjuster, but we don't have enough data to fulfil that interface properly. The issue is that in the parsing code, we have a map of unresolved fields, which would be unreliable to manipulate via Temporal, especially as many TemporalAduster implementations wouldn't work or would be inappropriate.

One option is a new interface, rather than using Function. I'm not sure whether it is important enough for that.

@RogerRiggs

The gist seems a bit too much like a very advanced feature, almost too complicated to use effectively.
Developers only need a mechanism to enable/disable resolving to previous valid, end of month, or wrap to next month.

@jodastephen
ThreeTen member

This feature allows many things that are not possible today. Examples include:

  • end of month handling
  • parsing 24:00
  • parsing 23:59:60 (leap second)
  • general lenient parsing (like SimpleDateFormat)
  • peeking at the unresolved data (removing the need for parseUnresolved)
  • defaulting fields (if only year-month parsed, add in a day-of-month) and so on.

I don't love the method signature, but the concept is a high power single-method solution for a lot of issues. My expectation is that some standard cases will be supplied in third party jar files, or documented on high profile web pages. JDK 1.9 could then rationalise common cases to constants or nicer methods. And its no more complicated than the TemporalField.resolve method on which it is based (if users can understand that, then they can understand this).

Alternative signatures considered included:

  • Consumer<Map<TemporalField,Long>> = users directly edit the hash map, but they don't get a temporal which is less useful
  • adding separate methods for filtering vs adding = users cannot write pre-packaged code so easily, and not everything can be achieved
  • adding a TemporalMap interface, also implemented by MonthDay, such that users could edit using the single supplied object, but the interface seems less useful beyond this use case

We have no real alternatives right now for the use cases above beyond using parseUnresolved and duplicating all the resolution code, which is highly undesirable. Thus, ot making a change like this effectively blocks a slew of useful features.

@RogerRiggs

I don't think there is time to adequately develop this before M7; we can review and propose it after M7.

@jodastephen
ThreeTen member

Simplest way to default a field is this, but it only supports a fixed value, not "end of month":

    /**
     * Appends a default value for a field to the formatter for use in parsing.
     * <p>
     * This appends an instruction to the builder to inject a default value
     * into the parsed result.
     * For example, this could be used to default the day-of-month in a parser
     * that only receives the month and year, allowing a date to be created.
     * <p>
     * During formatting, this method has no effect.
     * <p>
     * During parsing, the current state of the parse is inspected.
     * If the specified field has no associated value, because it has not been
     * parsed successfully at that point, then the specified value is injected
     * into the parse result. Injection is immediate, thus the field-value pair
     * will be visible to any subsequent elements in the formatter.
     *
     * @return this, for chaining, not null
     */
    public DateTimeFormatterBuilder parseDefaulting(TemporalField field, long value) {
        appendInternal(new DefaultPrinterParser(field, value));
        return this;
    }

 builder.parseDefaulting(DAY_OF_MONTH, 1);

More complex ways would involve a lambda, which here is restricted to changing a single field:

 public DateTimeFormatterBuilder parseChanging(TemporalField field, Function<TemporalAccessor, Long> valueFunction) 

 builder.parseChanging(DAY_OF_MONTH, t -> t.isSupported(DAY_OF_MONTH) ? null : 1);
 builder.parseChanging(DAY_OF_MONTH, t -> t.isSupported(DAY_OF_MONTH) ? null : YearMonth.from(t).lengthOfMonth());

Being able to specify some fields as ignored once parsed is also useful (parse year-month-day plus day-of-week, you may want to ignore an inconsistent day-oif-week):

 public DateTimeFormatterBuilder parseIgnoreNext() 

 builder.parseIgnoreNext().appendText(DAY_OF_WEEK);

Parsing 24:00 could be a special case, but it gets complicated in returning the result to the user (extra day can be added in theory if we parsed a date and a time in full, but not if date was incomplete or missing):

 public DateTimeFormatterBuilder parseMidnight2400() 

All these options are less complete than a full solution, but solve some key use cases.

@RogerRiggs

Leniency needs to work with existing patterns including those from ofLocalized{Date, Time, DateTime} etc. A mode on DateTimeFormatter is the preferred API for that. For most developers, it will be most useful if there is a fixed set of leniency modes that can be selected.

@jodastephen jodastephen was assigned
@jodastephen
ThreeTen member

Closed in favour of the linked calls.

@jodastephen jodastephen closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.