Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
   /*
    *  Copyright 2001-2009 Stephen Colebourne
    *
    *  Licensed under the Apache License, Version 2.0 (the "License");
    *  you may not use this file except in compliance with the License.
    *  You may obtain a copy of the License at
    *
    *      http://www.apache.org/licenses/LICENSE-2.0
    *
   *  Unless required by applicable law or agreed to in writing, software
   *  distributed under the License is distributed on an "AS IS" BASIS,
   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   *  See the License for the specific language governing permissions and
   *  limitations under the License.
   */
  package org.joda.time.format;
  
  import java.io.Writer;
  import java.util.List;
  import java.util.Locale;
  import java.util.TreeSet;
  
Factory that creates complex instances of PeriodFormatter via method calls.

Period formatting is performed by the PeriodFormatter class. Three classes provide factory methods to create formatters, and this is one. The others are PeriodFormat and ISOPeriodFormat.

PeriodFormatterBuilder is used for constructing formatters which are then used to print or parse. The formatters are built by appending specific fields or other formatters to an instance of this builder.

For example, a formatter that prints years and months, like "15 years and 8 months", can be constructed as follows:

 PeriodFormatter yearsAndMonths = new PeriodFormatterBuilder()
     .printZeroAlways()
     .appendYears()
     .appendSuffix(" year", " years")
     .appendSeparator(" and ")
     .printZeroRarelyLast()
     .appendMonths()
     .appendSuffix(" month", " months")
     .toFormatter();
 

PeriodFormatterBuilder itself is mutable and not thread-safe, but the formatters that it builds are thread-safe and immutable.

Author(s):
Brian S O'Neill
Since:
1.0
See also:
PeriodFormat
  
  public class PeriodFormatterBuilder {
      private static final int PRINT_ZERO_RARELY_FIRST = 1;
      private static final int PRINT_ZERO_RARELY_LAST = 2;
      private static final int PRINT_ZERO_IF_SUPPORTED = 3;
      private static final int PRINT_ZERO_ALWAYS = 4;
      private static final int PRINT_ZERO_NEVER = 5;
      
      private static final int YEARS = 0;
      private static final int MONTHS = 1;
      private static final int WEEKS = 2;
      private static final int DAYS = 3;
      private static final int HOURS = 4;
      private static final int MINUTES = 5;
      private static final int SECONDS = 6;
      private static final int MILLIS = 7;
      private static final int SECONDS_MILLIS = 8;
      private static final int SECONDS_OPTIONAL_MILLIS = 9;
      private static final int MAX_FIELD = ;
  
      private int iMinPrintedDigits;
      private int iPrintZeroSetting;
      private int iMaxParsedDigits;
      private boolean iRejectSignedValues;
  
      private PeriodFieldAffix iPrefix;
  
      // List of Printers and Parsers used to build a final formatter.
      private List<ObjectiElementPairs;
    
Set to true if the formatter is not a printer.
  
      private boolean iNotPrinter;
    
Set to true if the formatter is not a parser.
  
      private boolean iNotParser;
  
      // Last PeriodFormatter appended of each field type.
      private FieldFormatter[] iFieldFormatters;
 
     public PeriodFormatterBuilder() {
         clear();
     }
 
     //-----------------------------------------------------------------------
     
Constructs a PeriodFormatter using all the appended elements.

This is the main method used by applications at the end of the build process to create a usable formatter.

Subsequent changes to this builder do not affect the returned formatter.

The returned formatter may not support both printing and parsing. The methods PeriodFormatter.isPrinter() and PeriodFormatter.isParser() will help you determine the state of the formatter.

Returns:
the newly created formatter
Throws:
java.lang.IllegalStateException if the builder can produce neither a printer nor a parser
 
     public PeriodFormatter toFormatter() {
         PeriodFormatter formatter = toFormatter();
         return formatter;
     }

    
Internal method to create a PeriodPrinter instance using all the appended elements.

Most applications will not use this method. If you want a printer in an application, call toFormatter() and just use the printing API.

Subsequent changes to this builder do not affect the returned printer.

Returns:
the newly created printer, null if builder cannot create a printer
 
     public PeriodPrinter toPrinter() {
         if () {
             return null;
         }
         return toFormatter().getPrinter();
     }

    
Internal method to create a PeriodParser instance using all the appended elements.

Most applications will not use this method. If you want a printer in an application, call toFormatter() and just use the printing API.

Subsequent changes to this builder do not affect the returned parser.

Returns:
the newly created parser, null if builder cannot create a parser
 
     public PeriodParser toParser() {
         if () {
             return null;
         }
         return toFormatter().getParser();
     }
 
     //-----------------------------------------------------------------------
     
Clears out all the appended elements, allowing this builder to be reused.
 
     public void clear() {
          = 1;
          = 10;
          = false;
          = null;
         if ( == null) {
              = new ArrayList<Object>();
         } else {
             .clear();
         }
          = false;
          = false;
          = new FieldFormatter[10];
     }

    
Appends another formatter.

Returns:
this PeriodFormatterBuilder
 
     public PeriodFormatterBuilder append(PeriodFormatter formatter) {
         if (formatter == null) {
             throw new IllegalArgumentException("No formatter supplied");
         }
         clearPrefix();
         append0(formatter.getPrinter(), formatter.getParser());
         return this;
     }

    
Appends a printer parser pair.

Either the printer or the parser may be null, in which case the builder will be unable to produce a parser or printer repectively.

Parameters:
printer appends a printer to the builder, null if printing is not supported
parser appends a parser to the builder, null if parsing is not supported
Returns:
this PeriodFormatterBuilder
Throws:
java.lang.IllegalArgumentException if both the printer and parser are null
 
     public PeriodFormatterBuilder append(PeriodPrinter printerPeriodParser parser) {
         if (printer == null && parser == null) {
             throw new IllegalArgumentException("No printer or parser supplied");
         }
         clearPrefix();
         append0(printerparser);
         return this;
     }

    
Instructs the printer to emit specific text, and the parser to expect it. The parser is case-insensitive.

Returns:
this PeriodFormatterBuilder
Throws:
java.lang.IllegalArgumentException if text is null
 
     public PeriodFormatterBuilder appendLiteral(String text) {
         if (text == null) {
             throw new IllegalArgumentException("Literal must not be null");
         }
         clearPrefix();
         Literal literal = new Literal(text);
         append0(literalliteral);
         return this;
     }

    
Set the minimum digits printed for the next and following appended fields. By default, the minimum digits printed is one. If the field value is zero, it is not printed unless a printZero rule is applied.

Returns:
this PeriodFormatterBuilder
 
     public PeriodFormatterBuilder minimumPrintedDigits(int minDigits) {
          = minDigits;
         return this;
     }

    
Set the maximum digits parsed for the next and following appended fields. By default, the maximum digits parsed is ten.

Returns:
this PeriodFormatterBuilder
 
     public PeriodFormatterBuilder maximumParsedDigits(int maxDigits) {
          = maxDigits;
         return this;
     }

    
Reject signed values when parsing the next and following appended fields.

Returns:
this PeriodFormatterBuilder
 
     public PeriodFormatterBuilder rejectSignedValues(boolean v) {
          = v;
         return this;
     }

    
Never print zero values for the next and following appended fields, unless no fields would be printed. If no fields are printed, the printer forces the last "printZeroRarely" field to print a zero.

This field setting is the default.

Returns:
this PeriodFormatterBuilder
 
         return this;
     }

    
Never print zero values for the next and following appended fields, unless no fields would be printed. If no fields are printed, the printer forces the first "printZeroRarely" field to print a zero.

Returns:
this PeriodFormatterBuilder
 
         return this;
     }

    
Print zero values for the next and following appened fields only if the period supports it.

Returns:
this PeriodFormatterBuilder
 
         return this;
     }

    
Always print zero values for the next and following appended fields, even if the period doesn't support it. The parser requires values for fields that always print zero.

Returns:
this PeriodFormatterBuilder
 
         return this;
     }

    
Never print zero values for the next and following appended fields, unless no fields would be printed. If no fields are printed, the printer forces the last "printZeroRarely" field to print a zero.

This field setting is the default.

Returns:
this PeriodFormatterBuilder
 
          = ;
         return this;
     }
 
     //-----------------------------------------------------------------------
     
Append a field prefix which applies only to the next appended field. If the field is not printed, neither is the prefix.

Parameters:
text text to print before field only if field is printed
Returns:
this PeriodFormatterBuilder
See also:
appendSuffix(java.lang.String)
 
     public PeriodFormatterBuilder appendPrefix(String text) {
         if (text == null) {
             throw new IllegalArgumentException();
         }
         return appendPrefix(new SimpleAffix(text));
     }

    
Append a field prefix which applies only to the next appended field. If the field is not printed, neither is the prefix.

During parsing, the singular and plural versions are accepted whether or not the actual value matches plurality.

Parameters:
singularText text to print if field value is one
pluralText text to print if field value is not one
Returns:
this PeriodFormatterBuilder
See also:
appendSuffix(java.lang.String)
 
     public PeriodFormatterBuilder appendPrefix(String singularText,
                                                  String pluralText) {
         if (singularText == null || pluralText == null) {
             throw new IllegalArgumentException();
         }
         return appendPrefix(new PluralAffix(singularTextpluralText));
     }

    
Append a field prefix which applies only to the next appended field. If the field is not printed, neither is the prefix.

Parameters:
prefix custom prefix
Returns:
this PeriodFormatterBuilder
See also:
appendSuffix(java.lang.String)
 
         if (prefix == null) {
             throw new IllegalArgumentException();
         }
         if ( != null) {
             prefix = new CompositeAffix(prefix);
         }
          = prefix;
         return this;
     }
 
     //-----------------------------------------------------------------------
     
Instruct the printer to emit an integer years field, if supported.

The number of printed and parsed digits can be controlled using minimumPrintedDigits(int) and maximumParsedDigits(int).

Returns:
this PeriodFormatterBuilder
 
     public PeriodFormatterBuilder appendYears() {
         appendField();
         return this;
     }

    
Instruct the printer to emit an integer months field, if supported.

The number of printed and parsed digits can be controlled using minimumPrintedDigits(int) and maximumParsedDigits(int).

Returns:
this PeriodFormatterBuilder
 
     public PeriodFormatterBuilder appendMonths() {
         appendField();
         return this;
     }

    
Instruct the printer to emit an integer weeks field, if supported.

The number of printed and parsed digits can be controlled using minimumPrintedDigits(int) and maximumParsedDigits(int).

Returns:
this PeriodFormatterBuilder
 
     public PeriodFormatterBuilder appendWeeks() {
         appendField();
         return this;
     }

    
Instruct the printer to emit an integer days field, if supported.

The number of printed and parsed digits can be controlled using minimumPrintedDigits(int) and maximumParsedDigits(int).

Returns:
this PeriodFormatterBuilder
 
     public PeriodFormatterBuilder appendDays() {
         appendField();
         return this;
     }

    
Instruct the printer to emit an integer hours field, if supported.

The number of printed and parsed digits can be controlled using minimumPrintedDigits(int) and maximumParsedDigits(int).

Returns:
this PeriodFormatterBuilder
 
     public PeriodFormatterBuilder appendHours() {
         appendField();
         return this;
     }

    
Instruct the printer to emit an integer minutes field, if supported.

The number of printed and parsed digits can be controlled using minimumPrintedDigits(int) and maximumParsedDigits(int).

Returns:
this PeriodFormatterBuilder
 
         appendField();
         return this;
     }

    
Instruct the printer to emit an integer seconds field, if supported.

The number of printed and parsed digits can be controlled using minimumPrintedDigits(int) and maximumParsedDigits(int).

Returns:
this PeriodFormatterBuilder
 
         appendField();
         return this;
     }

    
Instruct the printer to emit a combined seconds and millis field, if supported. The millis will overflow into the seconds if necessary. The millis are always output.

Returns:
this PeriodFormatterBuilder
 
         appendField();
         return this;
     }

    
Instruct the printer to emit a combined seconds and millis field, if supported. The millis will overflow into the seconds if necessary. The millis are only output if non-zero.

Returns:
this PeriodFormatterBuilder
 
         return this;
     }

    
Instruct the printer to emit an integer millis field, if supported.

The number of printed and parsed digits can be controlled using minimumPrintedDigits(int) and maximumParsedDigits(int).

Returns:
this PeriodFormatterBuilder
 
     public PeriodFormatterBuilder appendMillis() {
         appendField();
         return this;
     }

    
Instruct the printer to emit an integer millis field, if supported.

The number of arsed digits can be controlled using maximumParsedDigits(int).

Returns:
this PeriodFormatterBuilder
 
         appendField(7, 3);
         return this;
     }
 
     private void appendField(int type) {
         appendField(type);
     }
 
     private void appendField(int typeint minPrinted) {
         FieldFormatter field = new FieldFormatter(minPrinted,
             typenull);
         append0(fieldfield);
         [type] = field;
          = null;
     }
 
     //-----------------------------------------------------------------------
     
Append a field suffix which applies only to the last appended field. If the field is not printed, neither is the suffix.

Parameters:
text text to print after field only if field is printed
Returns:
this PeriodFormatterBuilder
Throws:
java.lang.IllegalStateException if no field exists to append to
See also:
appendPrefix(java.lang.String)
 
     public PeriodFormatterBuilder appendSuffix(String text) {
         if (text == null) {
             throw new IllegalArgumentException();
         }
         return appendSuffix(new SimpleAffix(text));
     }

    
Append a field suffix which applies only to the last appended field. If the field is not printed, neither is the suffix.

During parsing, the singular and plural versions are accepted whether or not the actual value matches plurality.

Parameters:
singularText text to print if field value is one
pluralText text to print if field value is not one
Returns:
this PeriodFormatterBuilder
Throws:
java.lang.IllegalStateException if no field exists to append to
See also:
appendPrefix(java.lang.String)
 
     public PeriodFormatterBuilder appendSuffix(String singularText,
                                                String pluralText) {
         if (singularText == null || pluralText == null) {
             throw new IllegalArgumentException();
         }
         return appendSuffix(new PluralAffix(singularTextpluralText));
     }

    
Append a field suffix which applies only to the last appended field. If the field is not printed, neither is the suffix.

Parameters:
suffix custom suffix
Returns:
this PeriodFormatterBuilder
Throws:
java.lang.IllegalStateException if no field exists to append to
See also:
appendPrefix(java.lang.String)
 
         final Object originalPrinter;
         final Object originalParser;
         if (.size() > 0) {
             originalPrinter = .get(.size() - 2);
             originalParser = .get(.size() - 1);
         } else {
             originalPrinter = null;
             originalParser = null;
         }
 
         if (originalPrinter == null || originalParser == null ||
                 originalPrinter != originalParser ||
                 !(originalPrinter instanceof FieldFormatter)) {
             throw new IllegalStateException("No field to apply suffix to");
         }
 
         clearPrefix();
         FieldFormatter newField = new FieldFormatter((FieldFormatteroriginalPrintersuffix);
         .set(.size() - 2, newField);
         .set(.size() - 1, newField);
         [newField.getFieldType()] = newField;
         
         return this;
     }
 
     //-----------------------------------------------------------------------
     
Append a separator, which is output if fields are printed both before and after the separator.

For example, builder.appendDays().appendSeparator(",").appendHours() will only output the comma if both the days and hours fields are output.

The text will be parsed case-insensitively.

Note: appending a separator discontinues any further work on the latest appended field.

Parameters:
text the text to use as a separator
Returns:
this PeriodFormatterBuilder
Throws:
java.lang.IllegalStateException if this separator follows a previous one
 
     public PeriodFormatterBuilder appendSeparator(String text) {
         return appendSeparator(texttextnulltruetrue);
     }

    
Append a separator, which is output only if fields are printed after the separator.

For example, builder.appendDays().appendSeparatorIfFieldsAfter(",").appendHours() will only output the comma if the hours fields is output.

The text will be parsed case-insensitively.

Note: appending a separator discontinues any further work on the latest appended field.

Parameters:
text the text to use as a separator
Returns:
this PeriodFormatterBuilder
Throws:
java.lang.IllegalStateException if this separator follows a previous one
 
         return appendSeparator(texttextnullfalsetrue);
     }

    
Append a separator, which is output only if fields are printed before the separator.

For example, builder.appendDays().appendSeparatorIfFieldsBefore(",").appendHours() will only output the comma if the days fields is output.

The text will be parsed case-insensitively.

Note: appending a separator discontinues any further work on the latest appended field.

Parameters:
text the text to use as a separator
Returns:
this PeriodFormatterBuilder
Throws:
java.lang.IllegalStateException if this separator follows a previous one
 
         return appendSeparator(texttextnulltruefalse);
     }

    
Append a separator, which is output if fields are printed both before and after the separator.

This method changes the separator depending on whether it is the last separator to be output.

For example, builder.appendDays().appendSeparator(",", "&").appendHours().appendSeparator(",", "&").appendMinutes() will output '1,2&3' if all three fields are output, '1&2' if two fields are output and '1' if just one field is output.

The text will be parsed case-insensitively.

Note: appending a separator discontinues any further work on the latest appended field.

Parameters:
text the text to use as a separator
finalText the text used used if this is the final separator to be printed
Returns:
this PeriodFormatterBuilder
Throws:
java.lang.IllegalStateException if this separator follows a previous one
 
     public PeriodFormatterBuilder appendSeparator(String textString finalText) {
         return appendSeparator(textfinalTextnulltruetrue);
     }

    
Append a separator, which is output if fields are printed both before and after the separator.

This method changes the separator depending on whether it is the last separator to be output.

For example, builder.appendDays().appendSeparator(",", "&").appendHours().appendSeparator(",", "&").appendMinutes() will output '1,2&3' if all three fields are output, '1&2' if two fields are output and '1' if just one field is output.

The text will be parsed case-insensitively.

Note: appending a separator discontinues any further work on the latest appended field.

Parameters:
text the text to use as a separator
finalText the text used used if this is the final separator to be printed
variants set of text values which are also acceptable when parsed
Returns:
this PeriodFormatterBuilder
Throws:
java.lang.IllegalStateException if this separator follows a previous one
 
     public PeriodFormatterBuilder appendSeparator(String textString finalText,
                                                   String[] variants) {
         return appendSeparator(textfinalTextvariantstruetrue);
     }
 
     private PeriodFormatterBuilder appendSeparator(String textString finalText,
                                                    String[] variants,
                                                    boolean useBeforeboolean useAfter) {
         if (text == null || finalText == null) {
             throw new IllegalArgumentException();
         }
 
         clearPrefix();
         
         // optimise zero formatter case
         List<Objectpairs = ;
         if (pairs.size() == 0) {
             if (useAfter && useBefore == false) {
                 Separator separator = new Separator(
                         textfinalTextvariants,
                         ..useBeforeuseAfter);
                 append0(separatorseparator);
             }
             return this;
         }
         
         // find the last separator added
         int i;
         Separator lastSeparator = null;
         for (i=pairs.size(); --i>=0; ) {
             if (pairs.get(iinstanceof Separator) {
                 lastSeparator = (Separatorpairs.get(i);
                 pairs = pairs.subList(i + 1, pairs.size());
                 break;
             }
             i--;  // element pairs
         }
         
         // merge formatters
         if (lastSeparator != null && pairs.size() == 0) {
             throw new IllegalStateException("Cannot have two adjacent separators");
         } else {
             Object[] comp = createComposite(pairs);
             pairs.clear();
             Separator separator = new Separator(
                     textfinalTextvariants,
                     (PeriodPrintercomp[0], (PeriodParsercomp[1],
                     useBeforeuseAfter);
             pairs.add(separator);
             pairs.add(separator);
         }
         
         return this;
     }
 
     //-----------------------------------------------------------------------
     private void clearPrefix() throws IllegalStateException {
         if ( != null) {
             throw new IllegalStateException("Prefix not followed by field");
         }
          = null;
     }
 
     private PeriodFormatterBuilder append0(PeriodPrinter printerPeriodParser parser) {
         .add(printer);
         .add(parser);
          |= (printer == null);
          |= (parser == null);
         return this;
     }
 
     //-----------------------------------------------------------------------
     private static PeriodFormatter toFormatter(List<ObjectelementPairsboolean notPrinterboolean notParser) {
         if (notPrinter && notParser) {
             throw new IllegalStateException("Builder has created neither a printer nor a parser");
         }
         int size = elementPairs.size();
         if (size >= 2 && elementPairs.get(0) instanceof Separator) {
             Separator sep = (SeparatorelementPairs.get(0);
             if (sep.iAfterParser == null && sep.iAfterPrinter == null) {
                 PeriodFormatter f = toFormatter(elementPairs.subList(2, size), notPrinternotParser);
                 sep = sep.finish(f.getPrinter(), f.getParser());
                 return new PeriodFormatter(sepsep);
             }
         }
         Object[] comp = createComposite(elementPairs);
         if (notPrinter) {
             return new PeriodFormatter(null, (PeriodParsercomp[1]);
         } else if (notParser) {
             return new PeriodFormatter((PeriodPrintercomp[0], null);
         } else {
             return new PeriodFormatter((PeriodPrintercomp[0], (PeriodParsercomp[1]);
         }
     }
 
     private static Object[] createComposite(List<ObjectelementPairs) {
         switch (elementPairs.size()) {
             case 0:
                 return new Object[] {..};
             case 1:
                 return new Object[] {elementPairs.get(0), elementPairs.get(1)};
             default:
                 Composite comp = new Composite(elementPairs);
                 return new Object[] {compcomp};
         }
     }
 
     //-----------------------------------------------------------------------
     
Defines a formatted field's prefix or suffix text. This can be used for fields such as 'n hours' or 'nH' or 'Hour:n'.
 
     static interface PeriodFieldAffix {
         int calculatePrintedLength(int value);
         
         void printTo(StringBuffer bufint value);
         
         void printTo(Writer outint valuethrows IOException;
        
        

Returns:
new position after parsing affix, or ~position of failure
 
         int parse(String periodStrint position);

        

Returns:
position where affix starts, or original ~position if not found
 
         int scan(String periodStrint position);
     }
 
     //-----------------------------------------------------------------------
     
Implements an affix where the text does not vary by the amount.
 
     static class SimpleAffix implements PeriodFieldAffix {
         private final String iText;
 
         SimpleAffix(String text) {
              = text;
         }
 
         public int calculatePrintedLength(int value) {
             return .length();
         }
 
         public void printTo(StringBuffer bufint value) {
             buf.append();
         }
 
         public void printTo(Writer outint valuethrows IOException {
             out.write();
         }
 
         public int parse(String periodStrint position) {
             String text = ;
             int textLength = text.length();
             if (periodStr.regionMatches(truepositiontext, 0, textLength)) {
                 return position + textLength;
             }
             return ~position;
         }
 
         public int scan(String periodStrfinal int position) {
             String text = ;
             int textLength = text.length();
             int sourceLength = periodStr.length();
             search:
             for (int pos = positionpos < sourceLengthpos++) {
                 if (periodStr.regionMatches(truepostext, 0, textLength)) {
                     return pos;
                 }
                 // Only allow number characters to be skipped in search of suffix.
                 switch (periodStr.charAt(pos)) {
                 case '0'case '1'case '2'case '3'case '4':
                 case '5'case '6'case '7'case '8'case '9':
                 case '.'case ','case '+'case '-':
                     break;
                 default:
                     break search;
                 }
             }
             return ~position;
         }
     }
 
     //-----------------------------------------------------------------------
     
Implements an affix where the text varies by the amount of the field. Only singular (1) and plural (not 1) are supported.
 
     static class PluralAffix implements PeriodFieldAffix {
         private final String iSingularText;
         private final String iPluralText;
 
         PluralAffix(String singularTextString pluralText) {
              = singularText;
              = pluralText;
         }
 
         public int calculatePrintedLength(int value) {
             return (value == 1 ?  : ).length();
         }
 
         public void printTo(StringBuffer bufint value) {
             buf.append(value == 1 ?  : );
         }
 
         public void printTo(Writer outint valuethrows IOException {
             out.write(value == 1 ?  : );
         }
 
         public int parse(String periodStrint position) {
             String text1 = ;
             String text2 = 
 
             if (text1.length() < text2.length()) {
                 // Swap in order to match longer one first.
                 String temp = text1;
                 text1 = text2;
                 text2 = temp;
             }
 
             if (periodStr.regionMatches
                 (truepositiontext1, 0, text1.length())) {
                 return position + text1.length();
             }
             if (periodStr.regionMatches
                 (truepositiontext2, 0, text2.length())) {
                 return position + text2.length();
             }
 
             return ~position;
         }
 
         public int scan(String periodStrfinal int position) {
             String text1 = ;
             String text2 = 
 
             if (text1.length() < text2.length()) {
                 // Swap in order to match longer one first.
                 String temp = text1;
                 text1 = text2;
                 text2 = temp;
             }
 
             int textLength1 = text1.length();
             int textLength2 = text2.length();
 
             int sourceLength = periodStr.length();
             for (int pos = positionpos < sourceLengthpos++) {
                 if (periodStr.regionMatches(truepostext1, 0, textLength1)) {
                     return pos;
                 }
                 if (periodStr.regionMatches(truepostext2, 0, textLength2)) {
                     return pos;
                 }
             }
             return ~position;
         }
     }
 
     //-----------------------------------------------------------------------
     
Builds a composite affix by merging two other affix implementations.
 
     static class CompositeAffix implements PeriodFieldAffix {
         private final PeriodFieldAffix iLeft;
         private final PeriodFieldAffix iRight;
 
         CompositeAffix(PeriodFieldAffix leftPeriodFieldAffix right) {
              = left;
              = right;
         }
 
         public int calculatePrintedLength(int value) {
             return .calculatePrintedLength(value)
                 + .calculatePrintedLength(value);
         }
        public void printTo(StringBuffer bufint value) {
            .printTo(bufvalue);
            .printTo(bufvalue);
        }
        public void printTo(Writer outint valuethrows IOException {
            .printTo(outvalue);
            .printTo(outvalue);
        }
        public int parse(String periodStrint position) {
            position = .parse(periodStrposition);
            if (position >= 0) {
                position = .parse(periodStrposition);
            }
            return position;
        }
        public int scan(String periodStrfinal int position) {
            int pos = .scan(periodStrposition);
            if (pos >= 0) {
                return .scan(periodStrpos);
            }
            return ~position;
        }
    }
    //-----------------------------------------------------------------------
    
Formats the numeric value of a field, potentially with prefix/suffix.
    static class FieldFormatter
            implements PeriodPrinterPeriodParser {
        private final int iMinPrintedDigits;
        private final int iPrintZeroSetting;
        private final int iMaxParsedDigits;
        private final boolean iRejectSignedValues;
        
        
The index of the field type, 0=year, etc.
        private final int iFieldType;
        
The array of the latest formatter added for each type. This is shared between all the field formatters in a formatter.
        private final FieldFormatter[] iFieldFormatters;
        
        private final PeriodFieldAffix iPrefix;
        private final PeriodFieldAffix iSuffix;
        FieldFormatter(int minPrintedDigitsint printZeroSetting,
                       int maxParsedDigitsboolean rejectSignedValues,
                       int fieldTypeFieldFormatter[] fieldFormatters,
                       PeriodFieldAffix prefixPeriodFieldAffix suffix) {
             = minPrintedDigits;
             = printZeroSetting;
             = maxParsedDigits;
             = rejectSignedValues;
             = fieldType;
             = fieldFormatters;
             = prefix;
             = suffix;
        }
        FieldFormatter(FieldFormatter fieldPeriodFieldAffix suffix) {
             = field.iMinPrintedDigits;
             = field.iPrintZeroSetting;
             = field.iMaxParsedDigits;
             = field.iRejectSignedValues;
             = field.iFieldType;
             = field.iFieldFormatters;
             = field.iPrefix;
            if (field.iSuffix != null) {
                suffix = new CompositeAffix(field.iSuffixsuffix);
            }
             = suffix;
        }
        public int countFieldsToPrint(ReadablePeriod periodint stopAtLocale locale) {
            if (stopAt <= 0) {
                return 0;
            }
            if ( ==  || getFieldValue(period) != .) {
                return 1;
            }
            return 0;
        }
        public int calculatePrintedLength(ReadablePeriod periodLocale locale) {
            long valueLong = getFieldValue(period);
            if (valueLong == .) {
                return 0;
            }
            int sum = Math.max(FormatUtils.calculateDigitCount(valueLong), );
            if ( >= ) {
                // valueLong contains the seconds and millis fields
                // the minimum output is 0.000, which is 4 digits
                sum = Math.max(sum, 4);
                // plus one for the decimal point
                sum++;
                if ( ==  &&
                        (Math.abs(valueLong) % .) == 0) {
                    sum -= 4; // remove three digits and decimal point
                }
                // reset valueLong to refer to the seconds part for the prefic/suffix calculation
                valueLong = valueLong / .;
            }
            int value = (intvalueLong;
            if ( != null) {
                sum += .calculatePrintedLength(value);
            }
            if ( != null) {
                sum += .calculatePrintedLength(value);
            }
            return sum;
        }
        
        public void printTo(StringBuffer bufReadablePeriod periodLocale locale) {
            long valueLong = getFieldValue(period);
            if (valueLong == .) {
                return;
            }
            int value = (intvalueLong;
            if ( >= ) {
                value = (int) (valueLong / .);
            }
            if ( != null) {
                .printTo(bufvalue);
            }
            int minDigits = ;
            if (minDigits <= 1) {
                FormatUtils.appendUnpaddedInteger(bufvalue);
            } else {
                FormatUtils.appendPaddedInteger(bufvalueminDigits);
            }
            if ( >= ) {
                int dp = (int) (Math.abs(valueLong) % .);
                if ( ==  || dp > 0) {
                    buf.append('.');
                    FormatUtils.appendPaddedInteger(bufdp, 3);
                }
            }
            if ( != null) {
                .printTo(bufvalue);
            }
        }
        public void printTo(Writer outReadablePeriod periodLocale localethrows IOException {
            long valueLong = getFieldValue(period);
            if (valueLong == .) {
                return;
            }
            int value = (intvalueLong;
            if ( >= ) {
                value = (int) (valueLong / .);
            }
            if ( != null) {
                .printTo(outvalue);
            }
            int minDigits = ;
            if (minDigits <= 1) {
                FormatUtils.writeUnpaddedInteger(outvalue);
            } else {
                FormatUtils.writePaddedInteger(outvalueminDigits);
            }
            if ( >= ) {
                int dp = (int) (Math.abs(valueLong) % .);
                if ( ==  || dp > 0) {
                    out.write('.');
                    FormatUtils.writePaddedInteger(outdp, 3);
                }
            }
            if ( != null) {
                .printTo(outvalue);
            }
        }
        public int parseInto(
                ReadWritablePeriod periodString text
                int positionLocale locale) {
            boolean mustParse = ( == );
            // Shortcut test.
            if (position >= text.length()) {
                return mustParse ? ~position : position;
            }
            if ( != null) {
                position = .parse(textposition);
                if (position >= 0) {
                    // If prefix is found, then the parse must finish.
                    mustParse = true;
                } else {
                    // Prefix not found, so bail.
                    if (!mustParse) {
                        // It's okay because parsing of this field is not
                        // required. Don't return an error. Fields down the
                        // chain can continue on, trying to parse.
                        return ~position;
                    }
                    return position;
                }
            }
            int suffixPos = -1;
            if ( != null && !mustParse) {
                // Pre-scan the suffix, to help determine if this field must be
                // parsed.
                suffixPos = .scan(textposition);
                if (suffixPos >= 0) {
                    // If suffix is found, then parse must finish.
                    mustParse = true;
                } else {
                    // Suffix not found, so bail.
                    if (!mustParse) {
                        // It's okay because parsing of this field is not
                        // required. Don't return an error. Fields down the
                        // chain can continue on, trying to parse.
                        return ~suffixPos;
                    }
                    return suffixPos;
                }
            }
            if (!mustParse && !isSupported(period.getPeriodType(), )) {
                // If parsing is not required and the field is not supported,
                // exit gracefully so that another parser can continue on.
                return position;
            }
            int limit;
            if (suffixPos > 0) {
                limit = Math.min(suffixPos - position);
            } else {
                limit = Math.min(text.length() - position);
            }
            // validate input number
            int length = 0;
            int fractPos = -1;
            boolean hasDigits = false;
            while (length < limit) {
                char c = text.charAt(position + length);
                // leading sign
                if (length == 0 && (c == '-' || c == '+') && !) {
                    boolean negative = c == '-';
                    // Next character must be a digit.
                    if (length + 1 >= limit || 
                        (c = text.charAt(position + length + 1)) < '0' || c > '9')
                    {
                        break;
                    }
                    if (negative) {
                        length++;
                    } else {
                        // Skip the '+' for parseInt to succeed.
                        position++;
                    }
                    // Expand the limit to disregard the sign character.
                    limit = Math.min(limit + 1, text.length() - position);
                    continue;
                }
                // main number
                if (c >= '0' && c <= '9') {
                    hasDigits = true;
                } else {
                    if ((c == '.' || c == ',')
                         && ( ==  ||  == )) {
                        if (fractPos >= 0) {
                            // can't have two decimals
                            break;
                        }
                        fractPos = position + length + 1;
                        // Expand the limit to disregard the decimal point.
                        limit = Math.min(limit + 1, text.length() - position);
                    } else {
                        break;
                    }
                }
                length++;
            }
            if (!hasDigits) {
                return ~position;
            }
            if (suffixPos >= 0 && position + length != suffixPos) {
                // If there are additional non-digit characters before the
                // suffix is reached, then assume that the suffix found belongs
                // to a field not yet reached. Return original position so that
                // another parser can continue on.
                return position;
            }
            if ( !=  &&  != ) {
                // Handle common case.
                setFieldValue(periodparseInt(textpositionlength));
            } else if (fractPos < 0) {
                setFieldValue(periodparseInt(textpositionlength));
                setFieldValue(period, 0);
            } else {
                int wholeValue = parseInt(textpositionfractPos - position - 1);
                setFieldValue(periodwholeValue);
                int fractLen = position + length - fractPos;
                int fractValue;
                if (fractLen <= 0) {
                    fractValue = 0;
                } else {
                    if (fractLen >= 3) {
                        fractValue = parseInt(textfractPos, 3);
                    } else {
                        fractValue = parseInt(textfractPosfractLen);
                        if (fractLen == 1) {
                            fractValue *= 100;
                        } else {
                            fractValue *= 10;
                        }
                    }
                    if (wholeValue < 0) {
                        fractValue = -fractValue;
                    }
                }
                setFieldValue(periodfractValue);
            }
                
            position += length;
            if (position >= 0 &&  != null) {
                position = .parse(textposition);
            }
                
            return position;
        }

        

Parameters:
text text to parse
position position in text
length exact count of characters to parse
Returns:
parsed int value
        private int parseInt(String textint positionint length) {
            if (length >= 10) {
                // Since value may exceed max, use stock parser which checks for this.
                return Integer.parseInt(text.substring(positionposition + length));
            }
            if (length <= 0) {
                return 0;
            }
            int value = text.charAt(position++);
            length--;
            boolean negative;
            if (value == '-') {
                if (--length < 0) {
                    return 0;
                }
                negative = true;
                value = text.charAt(position++);
            } else {
                negative = false;
            }
            value -= '0';
            while (length-- > 0) {
                value = ((value << 3) + (value << 1)) + text.charAt(position++) - '0';
            }
            return negative ? -value : value;
        }

        

Returns:
Long.MAX_VALUE if nothing to print, otherwise value
        long getFieldValue(ReadablePeriod period) {
            PeriodType type;
            if ( == ) {
                type = null// Don't need to check if supported.