Skip to content

Instantly share code, notes, and snippets.

@jodastephen
Created January 31, 2013 21:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jodastephen/4686522 to your computer and use it in GitHub Desktop.
Save jodastephen/4686522 to your computer and use it in GitHub Desktop.
REJECTED: Manipulator. Patch to discuss ThreeTen issue #115
diff --git a/src/share/classes/java/time/format/DateTimeFormatterBuilder.java b/src/share/classes/java/time/format/DateTimeFormatterBuilder.java
--- a/src/share/classes/java/time/format/DateTimeFormatterBuilder.java
+++ b/src/share/classes/java/time/format/DateTimeFormatterBuilder.java
@@ -110,6 +110,7 @@
import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Function;
import sun.util.locale.provider.TimeZoneNameUtility;
@@ -1449,6 +1450,54 @@
//-----------------------------------------------------------------------
/**
+ * Appends a manipulator to the builder which has the ability to manipulate
+ * the temporal being formatted or parsed.
+ * <p>
+ * The manipulator function is invoked during parsing and can alter the
+ * map of parsed fields, such as adding or removing fields.
+ * This is very powerful behavior, which can be used is many different ways.
+ * <p>
+ * For example, consider a formatter that parses the year, month, day-of-month
+ * and day-of-week. In this case, the day-of-week is additional information
+ * and not essential to construct a date. But if the day-of-week is not the
+ * correct day for the date produced by the year, month and day, then an exception
+ * will be thrown. The manipulator could be used to remove the day-of-week,
+ * effectively ignoring the parsed value, and thus creating a more lenient parse.
+ * <p>
+ * Another example, consider a formatter that parses the year, month and day-of-month.
+ * Users may enter a day-of-month that is invalid for the year and month.
+ * A manipulator could be used to check the year and month and change the day-of-month
+ * to the last valid day of the month, effectively converting "June 31st" to "Jun 30th".
+ * <p>
+ * Using this method can make the formatter thread-unsafe.
+ * If the manipulator is thread-safe, then the formatter will remain thread-safe.
+ * If the manipulator is not thread-safe, then the formatter must not be shared.
+ * <p>
+ * The function receives a temporal representing the current state of the parse.
+ * The temporal must be queried using the methods of {@code TemporalAccessor},
+ * not using the {@code getFrom}, {@code isSupportedBy} or {@code rangeRefinedBy}
+ * methods on {@code TemporalField}. Any exception will terminate the parse.
+ * Thus, before querying a field, function implementations should ensure it is supported.
+ * <p>
+ * The function returns a map. If the map is null, the parse is terminated.
+ * If the map is empty, no changes are made.
+ * If the map is non-empty, then each entry in the map is processed.
+ * If the key is null, a {@code NullPointerException} is thrown.
+ * If the value is non-null, then the state of the parser is altered as though
+ * using {@code Map.put}. If the value is null, then the state of the parser
+ * is altered as though using {@code Map.remove}.
+ *
+ * @param manipulator the manipulator to add, not null
+ * @return this, for chaining, not null
+ */
+ public DateTimeFormatterBuilder manipulateParse(Function<TemporalAccessor, Map<TemporalField, Long>> manipulator) {
+ Objects.requireNonNull(manipulator, "manipulator");
+ appendInternal(new Manipulator(manipulator));
+ return this;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
* Appends a printer and/or parser to the internal list handling padding.
*
* @param pp the printer-parser to add, not null
@@ -3444,6 +3493,27 @@
}
}
+ //-------------------------------------------------------------------------
+ /**
+ * Manipulator of temporals.
+ */
+ static class Manipulator implements DateTimePrinterParser {
+ private Function<TemporalAccessor, Map<TemporalField, Long>> manipulator;
+
+ Manipulator(Function<TemporalAccessor, Map<TemporalField, Long>> manipulator) {
+ this.manipulator = manipulator;
+ }
+
+ @Override
+ public boolean format(DateTimePrintContext context, StringBuilder buf) {
+ return true;
+ }
+
+ @Override
+ public int parse(DateTimeParseContext context, CharSequence text, int position) {
+ return (context.manipulate(manipulator) ? position : ~position);
+ }
+ }
//-------------------------------------------------------------------------
/**
diff --git a/src/share/classes/java/time/format/DateTimeParseContext.java b/src/share/classes/java/time/format/DateTimeParseContext.java
--- a/src/share/classes/java/time/format/DateTimeParseContext.java
+++ b/src/share/classes/java/time/format/DateTimeParseContext.java
@@ -74,6 +74,7 @@
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
+import java.util.function.Function;
/**
* Context object used during date and time parsing.
@@ -322,6 +323,31 @@
//-----------------------------------------------------------------------
/**
+ * Manipulates the parsed temporal.
+ *
+ * @param manipulator the manipulator of temporals, not null
+ * @return true if successful, false if fails
+ */
+ boolean manipulate(Function<TemporalAccessor, Map<TemporalField, Long>> manipulator) {
+ Parsed data = currentParsed();
+ Map<TemporalField, Long> changes = manipulator.apply(this);
+ if (changes == null) {
+ return false;
+ }
+ for (Map.Entry<TemporalField, Long> change : changes.entrySet()) {
+ TemporalField changeField = Objects.requireNonNull(change.getKey(), "changeField");
+ Long changeValue = change.getValue();
+ if (changeValue != null) {
+ data.fieldValues.put(changeField, changeValue);
+ } else {
+ data.fieldValues.remove(changeField);
+ }
+ }
+ return true;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
* Returns a {@code DateTimeBuilder} that can be used to interpret
* the results of the parse.
* <p>
diff --git a/test/java/time/tck/java/time/format/TCKDateTimeFormatterBuilder.java b/test/java/time/tck/java/time/format/TCKDateTimeFormatterBuilder.java
--- a/test/java/time/tck/java/time/format/TCKDateTimeFormatterBuilder.java
+++ b/test/java/time/tck/java/time/format/TCKDateTimeFormatterBuilder.java
@@ -68,6 +68,7 @@
import java.text.ParsePosition;
import java.time.LocalDate;
+import java.time.YearMonth;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
@@ -75,6 +76,8 @@
import java.time.format.TextStyle;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAccessor;
+import java.time.temporal.TemporalField;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
@@ -522,6 +525,86 @@
//-----------------------------------------------------------------------
//-----------------------------------------------------------------------
//-----------------------------------------------------------------------
+ @Test
+ public void test_appendManipulator_removeOne() {
+ builder.appendValue(MONTH_OF_YEAR).appendLiteral('-')
+ .appendValue(DAY_OF_MONTH)
+ .manipulateParse(t -> Collections.<TemporalField, Long>singletonMap(MONTH_OF_YEAR, null));
+ DateTimeFormatter f = builder.toFormatter();
+ TemporalAccessor parsed = f.parse("6-8");
+ assertEquals(parsed.isSupported(MONTH_OF_YEAR), false);
+ assertEquals(parsed.isSupported(DAY_OF_MONTH), true);
+ assertEquals(parsed.getLong(DAY_OF_MONTH), 8);
+ }
+
+ @Test
+ public void test_appendManipulator_addOne() {
+ builder.appendValue(MONTH_OF_YEAR).appendLiteral('-')
+ .appendValue(DAY_OF_MONTH)
+ .manipulateParse(t -> Collections.<TemporalField, Long>singletonMap(YEAR, 2012L));
+ DateTimeFormatter f = builder.toFormatter();
+ TemporalAccessor parsed = f.parse("6-8");
+ assertEquals(parsed.isSupported(MONTH_OF_YEAR), true);
+ assertEquals(parsed.isSupported(DAY_OF_MONTH), true);
+ assertEquals(parsed.isSupported(YEAR), true);
+ assertEquals(parsed.getLong(MONTH_OF_YEAR), 6);
+ assertEquals(parsed.getLong(DAY_OF_MONTH), 8);
+ assertEquals(parsed.getLong(YEAR), 2012);
+ }
+
+ @Test
+ public void test_appendManipulator_emptyMapNoChange() {
+ builder.appendValue(MONTH_OF_YEAR).appendLiteral('-')
+ .appendValue(DAY_OF_MONTH)
+ .manipulateParse(t -> Collections.emptyMap());
+ DateTimeFormatter f = builder.toFormatter();
+ TemporalAccessor parsed = f.parse("6-8");
+ assertEquals(parsed.isSupported(MONTH_OF_YEAR), true);
+ assertEquals(parsed.isSupported(DAY_OF_MONTH), true);
+ assertEquals(parsed.getLong(MONTH_OF_YEAR), 6);
+ assertEquals(parsed.getLong(DAY_OF_MONTH), 8);
+ }
+
+ @Test
+ public void test_appendManipulator_nullMapEndParse() {
+ builder.appendValue(MONTH_OF_YEAR).appendLiteral('-')
+ .appendValue(DAY_OF_MONTH)
+ .manipulateParse(t -> null);
+ DateTimeFormatter f = builder.toFormatter();
+ ParsePosition pos = new ParsePosition(0);
+ TemporalAccessor parsed = f.parseUnresolved("6-8", pos);
+ assertEquals(parsed, null);
+ assertEquals(pos.getErrorIndex(), 3);
+ }
+
+ @Test
+ public void test_appendManipulator_checkAndAct() {
+ builder.appendValue(YEAR).appendLiteral('-')
+ .appendValue(MONTH_OF_YEAR).appendLiteral('-')
+ .appendValue(DAY_OF_MONTH)
+ .manipulateParse(t -> {
+ YearMonth yearMonth = YearMonth.from(t);
+ if (yearMonth.isValidDay(t.get(DAY_OF_MONTH))) {
+ return Collections.emptyMap();
+ } else {
+ return Collections.<TemporalField, Long>singletonMap(DAY_OF_MONTH, (long) yearMonth.lengthOfMonth());
+ }
+ });
+ DateTimeFormatter f = builder.toFormatter();
+ assertEquals(LocalDate.parse("2012-2-15", f), LocalDate.of(2012, 2, 15));
+ assertEquals(LocalDate.parse("2012-2-31", f), LocalDate.of(2012, 2, 29));
+ assertEquals(LocalDate.parse("2012-2-28", f), LocalDate.of(2012, 2, 28));
+ assertEquals(LocalDate.parse("2012-6-31", f), LocalDate.of(2012, 6, 30));
+ }
+
+ @Test(expectedExceptions=NullPointerException.class)
+ public void test_appendManipulator_null() throws Exception {
+ builder.manipulateParse(null);
+ }
+
+ //-----------------------------------------------------------------------
+ //-----------------------------------------------------------------------
+ //-----------------------------------------------------------------------
@Test(groups={"tck"})
public void test_padNext_1arg() throws Exception {
builder.appendValue(MONTH_OF_YEAR).padNext(2).appendValue(DAY_OF_MONTH).appendValue(DAY_OF_WEEK);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment