/* * ----------------------------------------------------------------------- * Copyright © 2013-2016 Meno Hochschild, <http://www.menodata.de/> * ----------------------------------------------------------------------- * This file (TwoDigitYearProcessor.java) is part of project Time4J. * * Time4J is free software: You can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 2.1 of the License, or * (at your option) any later version. * * Time4J is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Time4J. If not, see <http://www.gnu.org/licenses/>. * ----------------------------------------------------------------------- */ package net.time4j.format.expert; import net.time4j.base.GregorianMath; import net.time4j.base.MathUtils; import net.time4j.engine.AttributeQuery; import net.time4j.engine.ChronoDisplay; import net.time4j.engine.ChronoElement; import net.time4j.engine.EpochDays; import net.time4j.format.Attributes; import net.time4j.format.Leniency; import java.io.IOException; import java.util.Set; /** * <p>Formatroutine zur Verarbeitung von zweistelligen Jahreszahlen. </p> * * @author Meno Hochschild * @since 3.0 */ final class TwoDigitYearProcessor implements FormatProcessor<Integer> { //~ Statische Felder/Initialisierungen -------------------------------- private static final Integer DEFAULT_PIVOT_YEAR; static { long mjd = EpochDays.MODIFIED_JULIAN_DATE.transform( MathUtils.floorDivide(System.currentTimeMillis(), 86400 * 1000), EpochDays.UNIX); DEFAULT_PIVOT_YEAR = Integer.valueOf(GregorianMath.readYear(GregorianMath.toPackedDate(mjd)) + 20); } //~ Instanzvariablen -------------------------------------------------- private final ChronoElement<Integer> element; private final boolean protectedMode; // quick path optimization private final int reserved; private final char zeroDigit; private final Leniency lenientMode; private final int protectedLength; private final int pivotYear; //~ Konstruktoren ----------------------------------------------------- /** * <p>Konstruiert eine neue benutzerdefinierte Formatverarbeitung. </p> * * @param element year element to be formatted * @throws IllegalArgumentException if no year element is given */ TwoDigitYearProcessor( ChronoElement<Integer> element, boolean protectedMode ) { super(); if (element.name().startsWith("YEAR")) { this.element = element; this.protectedMode = protectedMode; } else { throw new IllegalArgumentException( "Year element required: " + element); } this.reserved = 0; this.zeroDigit = '0'; this.lenientMode = Leniency.SMART; this.protectedLength = 0; this.pivotYear = DEFAULT_PIVOT_YEAR; } private TwoDigitYearProcessor( ChronoElement<Integer> element, boolean protectedMode, int reserved, char zeroDigit, Leniency lenientMode, int protectedLength, int pivotYear ) { super(); this.element = element; this.protectedMode = protectedMode; // quick path members this.reserved = reserved; this.zeroDigit = zeroDigit; this.lenientMode = lenientMode; this.protectedLength = protectedLength; this.pivotYear = pivotYear; } //~ Methoden ---------------------------------------------------------- @Override public void print( ChronoDisplay formattable, Appendable buffer, AttributeQuery attributes, Set<ElementPosition> positions, // optional boolean quickPath ) throws IOException { int year = formattable.getInt(this.element); if (year < 0) { if (year == Integer.MIN_VALUE) { throw new IllegalArgumentException( "Format context has no year: " + formattable); } else { throw new IllegalArgumentException( "Negative year cannot be printed as two-digit-year: " + year); } } int yy = MathUtils.floorModulo(year, 100); String digits = Integer.toString(yy); char zeroChar = ( quickPath ? this.zeroDigit : attributes.get(Attributes.ZERO_DIGIT, Character.valueOf('0')).charValue()); if (zeroChar != '0') { int diff = zeroChar - '0'; char[] characters = digits.toCharArray(); for (int i = 0; i < characters.length; i++) { characters[i] = (char) (characters[i] + diff); } digits = new String(characters); } int start = -1; int printed = 0; if (buffer instanceof CharSequence) { start = ((CharSequence) buffer).length(); } if (yy < 10) { buffer.append(zeroChar); printed++; } buffer.append(digits); printed += digits.length(); if ( (start != -1) && (printed > 0) && (positions != null) ) { positions.add(new ElementPosition(this.element, start, start + printed)); } } @Override public void parse( CharSequence text, ParseLog status, AttributeQuery attributes, ParsedEntity<?> parsedResult, boolean quickPath ) { int len = text.length(); int start = status.getPosition(); int protectedChars = (quickPath ? this.protectedLength : attributes.get(Attributes.PROTECTED_CHARACTERS, 0)); if (protectedChars > 0) { len -= protectedChars; } if (start >= len) { status.setError( start, "Missing digits for: " + this.element.name()); status.setWarning(); return; } Leniency leniency = (quickPath ? this.lenientMode : attributes.get(Attributes.LENIENCY, Leniency.SMART)); int effectiveMax = leniency.isStrict() ? 2 : 9; char zeroChar = ( quickPath ? this.zeroDigit : attributes.get(Attributes.ZERO_DIGIT, Character.valueOf('0')).charValue()); if ((this.reserved > 0) && (protectedChars <= 0)) { int digitCount = 0; // Wieviele Ziffern hat der ganze Ziffernblock? for (int i = start; i < len; i++) { int digit = text.charAt(i) - zeroChar; if ((digit >= 0) && (digit <= 9)) { digitCount++; } else { break; } } effectiveMax = Math.min(effectiveMax, digitCount - this.reserved); } int minPos = start + 2; int maxPos = Math.min(len, start + effectiveMax); int yearOfCentury = 0; boolean first = true; int pos = start; while (pos < maxPos) { int digit = text.charAt(pos) - zeroChar; if ((digit >= 0) && (digit <= 9)) { yearOfCentury = yearOfCentury * 10 + digit; pos++; first = false; } else if (first) { status.setError(start, "Digit expected."); return; } else { break; } } if (pos < minPos) { status.setError( start, "Not enough digits found for: " + this.element.name()); return; } int value; if (pos == start + 2) { int py = (quickPath ? this.pivotYear : attributes.get(Attributes.PIVOT_YEAR, DEFAULT_PIVOT_YEAR)); assert ((yearOfCentury >= 0) && (yearOfCentury <= 99)); value = toYear(yearOfCentury, py); } else { value = yearOfCentury; // absolutes Jahr (kein Kippjahr) } parsedResult.put(this.element, value); status.setPosition(pos); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } else if (obj instanceof TwoDigitYearProcessor) { TwoDigitYearProcessor that = (TwoDigitYearProcessor) obj; return this.element.equals(that.element); } else { return false; } } @Override public int hashCode() { return this.element.hashCode(); } @Override public String toString() { StringBuilder sb = new StringBuilder(64); sb.append(this.getClass().getName()); sb.append("[element="); sb.append(this.element.name()); sb.append(']'); return sb.toString(); } @Override public ChronoElement<Integer> getElement() { return this.element; } @Override public FormatProcessor<Integer> withElement(ChronoElement<Integer> e) { if (this.protectedMode || (this.element == e)) { return this; } return new TwoDigitYearProcessor(e, false); } @Override public boolean isNumerical() { return true; } @Override public FormatProcessor<Integer> quickPath( ChronoFormatter<?> formatter, AttributeQuery attributes, int reserved ) { return new TwoDigitYearProcessor( this.element, this.protectedMode, reserved, attributes.get(Attributes.ZERO_DIGIT, Character.valueOf('0')).charValue(), attributes.get(Attributes.LENIENCY, Leniency.SMART), attributes.get(Attributes.PROTECTED_CHARACTERS, 0).intValue(), attributes.get(Attributes.PIVOT_YEAR, DEFAULT_PIVOT_YEAR).intValue() ); } private static int toYear( int yearOfCentury, int pivotYear ) { int century; if (yearOfCentury >= (pivotYear % 100)) { century = (((pivotYear / 100) - 1) * 100); } else { century = ((pivotYear / 100) * 100); } return (century + yearOfCentury); } }