blob: 13b38017c36aaa8ad07dbdb47b3bbf76cba3bc47 [file] [log] [blame]
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
part of intl;
/**
* A class for holding onto the data for a date so that it can be built
* up incrementally.
*/
class _DateBuilder {
// Default the date values to the EPOCH so that there's a valid date
// in case the format doesn't set them.
int year = 1970,
month = 1,
day = 1,
hour = 0,
minute = 0,
second = 0,
fractionalSecond = 0;
bool pm = false;
bool utc = false;
// Functions that exist just to be closurized so we can pass them to a general
// method.
void setYear(x) { year = x; }
void setMonth(x) { month = x; }
void setDay(x) { day = x; }
void setHour(x) { hour = x; }
void setMinute(x) { minute = x; }
void setSecond(x) { second = x; }
void setFractionalSecond(x) { fractionalSecond = x; }
get hour24 => pm ? hour + 12 : hour;
/**
* Verify that we correspond to a valid date. This will reject out of
* range values, even if the DateTime constructor would accept them. An
* invalid message will result in throwing a [FormatException].
*/
verify(String s) {
_verify(month, 1, 12, "month", s);
_verify(hour24, 0, 23, "hour", s);
_verify(minute, 0, 59, "minute", s);
_verify(second, 0, 59, "second", s);
_verify(fractionalSecond, 0, 999, "fractional second", s);
// Verifying the day is tricky, because it depends on the month. Create
// our resulting date and then verify that our values agree with it
// as an additional verification. And since we're doing that, also
// check the year, which we otherwise can't verify, and the hours,
// which will catch cases like "14:00:00 PM".
var date = asDate();
_verify(hour24, date.hour, date.hour, "hour", s);
_verify(day, date.day, date.day, "day", s);
_verify(year, date.year, date.year, "year", s);
}
_verify(int value, int min, int max, String desc, String originalInput) {
if (value < min || value > max) {
throw new FormatException(
"Error parsing $originalInput, invalid $desc value: $value");
}
}
/**
* Return a date built using our values. If no date portion is set,
* use the "Epoch" of January 1, 1970.
*/
DateTime asDate({retry: true}) {
// TODO(alanknight): Validate the date, especially for things which
// can crash the VM, e.g. large month values.
var result;
if (utc) {
result = new DateTime.utc(
year,
month,
day,
hour24,
minute,
second,
fractionalSecond);
} else {
result = new DateTime(
year,
month,
day,
hour24,
minute,
second,
fractionalSecond);
// TODO(alanknight): Issue 15560 means non-UTC dates occasionally come
// out in UTC. If that happens, retry once. This will always happen if
// the local time zone is UTC, but that's ok.
if (result.toUtc() == result) {
result = asDate(retry: false);
}
}
return result;
}
}
/**
* A simple and not particularly general stream class to make parsing
* dates from strings simpler. It is general enough to operate on either
* lists or strings.
*/
// TODO(alanknight): With the improvements to the collection libraries
// since this was written we might be able to get rid of it entirely
// in favor of e.g. aString.split('') giving us an iterable of one-character
// strings, or else make the implementation trivial. And consider renaming,
// as _Stream is now just confusing with the system Streams.
class _Stream {
var contents;
int index = 0;
_Stream(this.contents);
bool atEnd() => index >= contents.length;
next() => contents[index++];
/**
* Return the next [howMany] items, or as many as there are remaining.
* Advance the stream by that many positions.
*/
read([int howMany = 1]) {
var result = peek(howMany);
index += howMany;
return result;
}
/**
* Does the input start with the given string, if we start from the
* current position.
*/
bool startsWith(String pattern) {
if (contents is String) return contents.startsWith(pattern, index);
return pattern == peek(pattern.length);
}
/**
* Return the next [howMany] items, or as many as there are remaining.
* Does not modify the stream position.
*/
peek([int howMany = 1]) {
var result;
if (contents is String) {
result = contents.substring(
index,
min(index + howMany, contents.length));
} else {
// Assume List
result = contents.sublist(index, index + howMany);
}
return result;
}
/** Return the remaining contents of the stream */
rest() => peek(contents.length - index);
/**
* Find the index of the first element for which [f] returns true.
* Advances the stream to that position.
*/
int findIndex(Function f) {
while (!atEnd()) {
if (f(next())) return index - 1;
}
return null;
}
/**
* Find the indexes of all the elements for which [f] returns true.
* Leaves the stream positioned at the end.
*/
List findIndexes(Function f) {
var results = [];
while (!atEnd()) {
if (f(next())) results.add(index - 1);
}
return results;
}
/**
* Assuming that the contents are characters, read as many digits as we
* can see and then return the corresponding integer. Advance the stream.
*/
var digitMatcher = new RegExp(r'\d+');
int nextInteger() {
var string = digitMatcher.stringMatch(rest());
if (string == null || string.isEmpty) return null;
read(string.length);
return int.parse(string);
}
}