private static Maybe parseCalendarSimpleFlexibleFormatParser()

in brooklyn-server/utils/common/src/main/java/org/apache/brooklyn/util/time/Time.java [684:838]


    private static Maybe<Calendar> parseCalendarSimpleFlexibleFormatParser(String input) {
        input = input.trim();

        String[] DATE_PATTERNS = new String[] {
            DATE_ONLY_WITH_INNER_SEPARATORS,
            DATE_ONLY_NO_SEPARATORS,
            DATE_WORDS_2,
            DATE_WORDS_3,            
        };
        String[] TIME_PATTERNS = new String[] {
            TIME_ONLY_WITH_INNER_SEPARATORS,
            TIME_ONLY_NO_SEPARATORS            
        };
        String[] TZ_PATTERNS = new String[] {
            // space then time zone with sign (+-) or code is preferred
            optionally(getDateTimeSeparatorPattern("")) + " " + TIME_ZONE_SIGNED_OFFSET,
            // then no TZ - but declare the named groups
            namedGroup("tz", namedGroup("tzOffset", "")+namedGroup("tzCode", "")),
            // then any separator then offset with sign
            getDateTimeSeparatorPattern("") + TIME_ZONE_SIGNED_OFFSET,
            
            // try parsing with enforced separators before TZ first 
            // (so e.g. in the case of DATE-0100, the -0100 is the time, not the timezone)
            // then relax below (e.g. in the case of DATE-TIME+0100)
            
            // finally match DATE-TIME-1000 as time zone -1000
            // or DATE-TIME 1000 as TZ +1000 in case a + was supplied but converted to ' ' by web
            // (but be stricter about the format, two or four digits required, and hours <= 12 so as not to confuse with a year)
            optionally(getDateTimeSeparatorPattern("")) + TIME_ZONE_OPTIONALLY_SIGNED_OFFSET
        };
        
        List<String> basePatterns = MutableList.of();
        
        // patterns with date first
        String[] DATE_PATTERNS_UNCLOSED = new String[] {
            // separator before time *required* if date had separators
            DATE_ONLY_WITH_INNER_SEPARATORS + "("+getDateTimeSeparatorPattern("Tt"),
            // separator before time optional if date did not have separators
            DATE_ONLY_NO_SEPARATORS + "("+optionally(getDateTimeSeparatorPattern("Tt")),
            // separator before time required if date has words
            DATE_WORDS_2 + "("+getDateTimeSeparatorPattern("Tt"),
            DATE_WORDS_3 + "("+getDateTimeSeparatorPattern("Tt"),
        };
        for (String tzP: TZ_PATTERNS)
            for (String dateP: DATE_PATTERNS_UNCLOSED)
                for (String timeP: TIME_PATTERNS)
                    basePatterns.add(dateP + timeP+")?" + tzP);
        
        // also allow time first, with TZ after, then before
        for (String tzP: TZ_PATTERNS)
            for (String dateP: DATE_PATTERNS)
                for (String timeP: TIME_PATTERNS)
                    basePatterns.add(timeP + getDateTimeSeparatorPattern("") + dateP + tzP);
        // also allow time first, with TZ after, then before
        for (String tzP: TZ_PATTERNS)
            for (String dateP: DATE_PATTERNS)
                for (String timeP: TIME_PATTERNS)
                    basePatterns.add(timeP + tzP + getDateTimeSeparatorPattern("") + dateP);

        Maybe<Matcher> mm = Maybe.absent();
        for (String p: basePatterns) {
            mm = match(p, input);
            if (mm.isPresent()) break;
        }
        if (mm.isPresent()) {
            Matcher m = mm.get();
            Calendar result;

            String tz = m.group("tz");
            
            int year = Integer.parseInt(m.group("year"));
            int day = Integer.parseInt(m.group("day"));
            
            String monthS = m.group("month");
            int month;
            if (monthS.matches(DIGIT+"+")) {
                month = Integer.parseInt(monthS)-1;
            } else {
                try {
                    month = new SimpleDateFormat("yyyy-MMM-dd", Locale.ROOT).parse("2015-"+monthS+"-15").getMonth();
                } catch (ParseException e) {
                    return Maybe.absent("Unknown date format '"+input+"': invalid month '"+monthS+"'; try http://yaml.org/type/timestamp.html format e.g. 2015-06-15 16:00:00 +0000");
                }
            }
            
            if (Strings.isNonBlank(tz)) {
                TimeZone tzz = null;
                String tzCode = m.group("tzCode");
                if (Strings.isNonBlank(tzCode)) {
                    tz = tzCode;
                }
                if (tz.matches(DIGIT+"+")) {
                    // stick a plus in front in case it was submitted by a web form and turned into a space
                    tz = "+"+tz;
                } else {
                    tzz = getTimeZone(tz);
                }
                if (tzz==null) {
                    Maybe<Matcher> tmm = match(" ?(?<tzH>(\\+|\\-||)"+DIGIT+optionally(DIGIT)+")"+optionally(optionally(":")+namedGroup("tzM", DIGIT+DIGIT)), tz);
                    if (tmm.isAbsent()) {
                        return Maybe.absent("Unknown date format '"+input+"': invalid timezone '"+tz+"'; try http://yaml.org/type/timestamp.html format e.g. 2015-06-15 16:00:00 +0000");
                    }
                    Matcher tm = tmm.get();
                    String tzM = tm.group("tzM");
                    int offset = (60*Integer.parseInt(tm.group("tzH")) + Integer.parseInt("0"+(tzM!=null ? tzM : "")))*60;
                    tzz = new SimpleTimeZone(offset*1000, tz);
                }
                tz = getTimeZoneOffsetString(tzz, year, month, day);
                result = new GregorianCalendar(tzz);
            } else {
                result = new GregorianCalendar();
            }
            result.clear();
            
            result.set(Calendar.YEAR, year);
            result.set(Calendar.MONTH, month);
            result.set(Calendar.DAY_OF_MONTH, day);
            if (m.group("hours")!=null) {
                int hours = Integer.parseInt(m.group("hours"));
                String meridian = m.group("meridian");
                if (Strings.isNonBlank(meridian) && meridian.toLowerCase().startsWith("p")) {
                    if (hours>12) {
                        return Maybe.absent("Unknown date format '"+input+"': can't be "+hours+" PM; try http://yaml.org/type/timestamp.html format e.g. 2015-06-15 16:00:00 +0000");
                    }
                    hours += 12;
                }
                result.set(Calendar.HOUR_OF_DAY, hours);
                String minsS = m.group("mins");
                if (Strings.isNonBlank(minsS)) {
                    result.set(Calendar.MINUTE, Integer.parseInt(minsS));
                }
                String secsS = m.group("secs");
                if (Strings.isBlank(secsS)) {
                    // leave at zero
                } else if (secsS.matches(DIGIT+DIGIT+"?")) {
                    result.set(Calendar.SECOND, Integer.parseInt(secsS));
                } else {
                    double s = Double.parseDouble(secsS);
                    if (secsS.indexOf('.')>=0) {
                        // accept
                    } else if (secsS.length()==5) {
                        // allow ssSSS with no punctuation
                        s = s/=1000;
                    } else {
                        return Maybe.absent("Unknown date format '"+input+"': invalid seconds '"+secsS+"'; try http://yaml.org/type/timestamp.html format e.g. 2015-06-15 16:00:00 +0000");
                    }
                    result.set(Calendar.SECOND, (int)s);
                    result.set(Calendar.MILLISECOND, (int)((s*1000) % 1000));
                }
            }
            
            return Maybe.of(result);
        }
        return Maybe.absent("Unknown date format '"+input+"'; try http://yaml.org/type/timestamp.html format e.g. 2015-06-15 16:00:00 +0000");
    }