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");
}