docs: protected List<Rule> parsePattern() {
  		final DateFormatSymbols symbols = new DateFormatSymbols(locale);
  		final List<Rule> rules = new ArrayList<>();
  
  		final String[] ERAs = symbols.getEras();
  		final String[] months = symbols.getMonths();
  		final String[] shortMonths = symbols.getShortMonths();
  		final String[] weekdays = symbols.getWeekdays();
  		final String[] shortWeekdays = symbols.getShortWeekdays();
  		final String[] AmPmStrings = symbols.getAmPmStrings();
  
  		final int length = pattern.length();
  		final int[] indexRef = new int[1];
  
  		for (int i = 0; i < length; i++) {
  			indexRef[0] = i;
  			final String token = parseToken(pattern, indexRef);
  			i = indexRef[0];
  
  			final int tokenLen = token.length();
  			if (tokenLen == 0) {
  				break;
  			}
  
  			Rule rule;
  			final char c = token.charAt(0);
  
  			switch (c) {
  				case 'G': // era designator (text)
  					rule = new TextField(Calendar.ERA, ERAs);
  					break;
  				case 'y': // year (number)
  				case 'Y': // week year
  					if (tokenLen == 2) {
  						rule = TwoDigitYearField.INSTANCE;
  					} else {
  						rule = selectNumberRule(Calendar.YEAR, tokenLen < 4 ? 4 : tokenLen);
  					}
  					if (c == 'Y') {
  						rule = new WeekYear((NumberRule) rule);
  					}
  					break;
  				case 'M': // month in year (text and number)
  					if (tokenLen >= 4) {
  						rule = new TextField(Calendar.MONTH, months);
  					} else if (tokenLen == 3) {
  						rule = new TextField(Calendar.MONTH, shortMonths);
  					} else if (tokenLen == 2) {
  						rule = TwoDigitMonthField.INSTANCE;
  					} else {
  						rule = UnpaddedMonthField.INSTANCE;
  					}
  					break;
  				case 'd': // day in month (number)
  					rule = selectNumberRule(Calendar.DAY_OF_MONTH, tokenLen);
  					break;
  				case 'h': // hour in am/pm (number, 1..12)
  					rule = new TwelveHourField(selectNumberRule(Calendar.HOUR, tokenLen));
  					break;
  				case 'H': // hour in day (number, 0..23)
  					rule = selectNumberRule(Calendar.HOUR_OF_DAY, tokenLen);
  					break;
  				case 'm': // minute in hour (number)
  					rule = selectNumberRule(Calendar.MINUTE, tokenLen);
  					break;
  				case 's': // second in minute (number)
  					rule = selectNumberRule(Calendar.SECOND, tokenLen);
  					break;
  				case 'S': // millisecond (number)
  					rule = selectNumberRule(Calendar.MILLISECOND, tokenLen);
  					break;
  				case 'E': // day in week (text)
  					rule = new TextField(Calendar.DAY_OF_WEEK, tokenLen < 4 ? shortWeekdays : weekdays);
  					break;
  				case 'u': // day in week (number)
  					rule = new DayInWeekField(selectNumberRule(Calendar.DAY_OF_WEEK, tokenLen));
  					break;
  				case 'D': // day in year (number)
  					rule = selectNumberRule(Calendar.DAY_OF_YEAR, tokenLen);
  					break;
  				case 'F': // day of week in month (number)
  					rule = selectNumberRule(Calendar.DAY_OF_WEEK_IN_MONTH, tokenLen);
  					break;
  				case 'w': // week in year (number)
  					rule = selectNumberRule(Calendar.WEEK_OF_YEAR, tokenLen);
  					break;
  				case 'W': // week in month (number)
  					rule = selectNumberRule(Calendar.WEEK_OF_MONTH, tokenLen);
  					break;
  				case 'a': // am/pm marker (text)
  					rule = new TextField(Calendar.AM_PM, AmPmStrings);
  					break;
  				case 'k': // hour in day (1..24)
  					rule = new TwentyFourHourField(selectNumberRule(Calendar.HOUR_OF_DAY, tokenLen));
  					break;
  				case 'K': // hour in am/pm (0..11)
  					rule = selectNumberRule(Calendar.HOUR, tokenLen);
  					break;
  				case 'X': // ISO 8601
  					rule = Iso8601_Rule.getRule(tokenLen);
  					break;
  				case 'z': // time zone (text)
  					if (tokenLen >= 4) {
  						rule = new TimeZoneNameRule(timeZone, locale, TimeZone.LONG);
  					} else {
  						rule = new TimeZoneNameRule(timeZone, locale, TimeZone.SHORT);
  					}
  					break;
  				case 'Z': // time zone (value)
  					if (tokenLen == 1) {
  						rule = TimeZoneNumberRule.INSTANCE_NO_COLON;
  					} else if (tokenLen == 2) {
  						rule = Iso8601_Rule.ISO8601_HOURS_COLON_MINUTES;
  					} else {
  						rule = TimeZoneNumberRule.INSTANCE_COLON;
  					}
  					break;
  				case '\'': // literal text
  					final String sub = token.substring(1);
  					if (sub.length() == 1) {
  						rule = new CharacterLiteral(sub.charAt(0));
  					} else {
  						rule = new StringLiteral(sub);
  					}
  					break;
  				default:
  					throw new IllegalArgumentException("Illegal pattern component: " + token);
  			}
  
  			rules.add(rule);
  		}
  
  		return rules;
  	}