in src/com/amazon/ion/Timestamp.java [938:1116]
public static Timestamp valueOf(CharSequence ionFormattedTimestamp)
{
final CharSequence in = ionFormattedTimestamp;
int pos;
final int length = in.length();
if (length == 0)
{
throw fail(in);
}
// check for 'null.timestamp'
if (in.charAt(0) == 'n') {
if (length >= LEN_OF_NULL_IMAGE
&& NULL_TIMESTAMP_IMAGE.contentEquals(in.subSequence(0, LEN_OF_NULL_IMAGE)))
{
if (length > LEN_OF_NULL_IMAGE) {
if (!isValidFollowChar(in.charAt(LEN_OF_NULL_IMAGE))) {
throw fail(in);
}
}
return null;
}
throw fail(in);
}
int year = 1;
int month = 1;
int day = 1;
int hour = 0;
int minute = 0;
int seconds = 0;
BigDecimal fraction = null;
Precision precision;
// fake label to turn goto's into a break so Java is happy :) enjoy
do {
// otherwise we expect yyyy-mm-ddThh:mm:ss.ssss+hh:mm
if (length < END_OF_YEAR + 1) { // +1 for the "T"
throw fail(in, "year is too short (must be at least yyyyT)");
}
pos = END_OF_YEAR;
precision = Precision.YEAR;
year = read_digits(in, 0, 4, -1, "year");
char c = in.charAt(END_OF_YEAR);
if (c == 'T') break;
if (c != '-') {
throw fail(in,
"expected \"-\" between year and month, found "
+ printCodePointAsString(c));
}
if (length < END_OF_MONTH + 1) { // +1 for the "T"
throw fail(in, "month is too short (must be yyyy-mmT)");
}
pos = END_OF_MONTH;
precision = Precision.MONTH;
month = read_digits(in, END_OF_YEAR + 1, 2, -1, "month");
c = in.charAt(END_OF_MONTH);
if (c == 'T') break;
if (c != '-') {
throw fail(in,
"expected \"-\" between month and day, found "
+ printCodePointAsString(c));
}
if (length < END_OF_DAY) {
throw fail(in, "too short for yyyy-mm-dd");
}
pos = END_OF_DAY;
precision = Precision.DAY;
day = read_digits(in, END_OF_MONTH + 1, 2, -1, "day");
if (length == END_OF_DAY) break;
c = in.charAt(END_OF_DAY);
if (c != 'T') {
throw fail(in,
"expected \"T\" after day, found "
+ printCodePointAsString(c));
}
if (length == END_OF_DAY + 1) break;
// now lets see if we have a time value
if (length < END_OF_MINUTES) {
throw fail(in, "too short for yyyy-mm-ddThh:mm");
}
hour = read_digits(in, 11, 2, ':', "hour");
minute = read_digits(in, 14, 2, -1, "minutes");
pos = END_OF_MINUTES;
precision = Precision.MINUTE;
// we may have seconds
if (length <= END_OF_MINUTES || in.charAt(END_OF_MINUTES) != ':')
{
break;
}
if (length < END_OF_SECONDS) {
throw fail(in, "too short for yyyy-mm-ddThh:mm:ss");
}
seconds = read_digits(in, 17, 2, -1, "seconds");
pos = END_OF_SECONDS;
precision = Precision.SECOND;
if (length <= END_OF_SECONDS || in.charAt(END_OF_SECONDS) != '.')
{
break;
}
pos = END_OF_SECONDS + 1;
while (length > pos && Character.isDigit(in.charAt(pos))) {
pos++;
}
if (pos <= END_OF_SECONDS + 1) {
throw fail(in,
"must have at least one digit after decimal point");
}
fraction = new BigDecimal(in.subSequence(19, pos).toString());
} while (false);
Integer offset;
// now see if they included a timezone offset
char timezone_start = pos < length ? in.charAt(pos) : '\n';
if (timezone_start == 'Z') {
offset = 0;
pos++;
}
else if (timezone_start == '+' || timezone_start == '-')
{
if (length < pos + 5) {
throw fail(in, "local offset too short");
}
// +/- hh:mm
pos++;
int tzdHours = read_digits(in, pos, 2, ':', "local offset hours");
if (tzdHours < 0 || tzdHours > 23) {
throw fail(in,
"local offset hours must be between 0 and 23 inclusive");
}
pos += 3;
int tzdMinutes = read_digits(in, pos, 2, -1, "local offset minutes");
if (tzdMinutes > 59) {
throw fail(in,
"local offset minutes must be between 0 and 59 inclusive");
}
pos += 2;
int temp = tzdHours * 60 + tzdMinutes;
if (timezone_start == '-') {
temp = -temp;
}
if (temp == 0 && timezone_start == '-') {
// int doesn't do negative zero very elegantly
offset = null;
}
else {
offset = temp;
}
}
else {
switch (precision) {
case YEAR:
case MONTH:
case DAY:
break;
default:
throw fail(in, "missing local offset");
}
offset = null;
}
if (length > (pos + 1) && !isValidFollowChar(in.charAt(pos + 1)))
{
throw fail(in, "invalid excess characters");
}
Timestamp ts =
new Timestamp(precision, year, month, day,
hour, minute, seconds, fraction, offset, APPLY_OFFSET_YES);
return ts;
}