Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(274)

Unified Diff: lib/i18n/date_format.dart

Issue 10807096: Add date formatting and parsing code. (Closed) Base URL: http://dart.googlecode.com/svn/branches/bleeding_edge/dart/
Patch Set: Created 8 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | lib/i18n/date_symbol_data.dart » ('j') | lib/i18n/date_symbol_data.dart » ('J')
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: lib/i18n/date_format.dart
===================================================================
--- lib/i18n/date_format.dart (revision 8510)
+++ lib/i18n/date_format.dart (working copy)
@@ -9,24 +9,82 @@
* as specify a customized pattern under certain locales. Date elements that
* vary across locales include month name, week name, field order, etc.
* <!-- TODO(efortuna): Customized pattern system -- suggested by i18n needs
- *feedback on appropriateness. -->
+ * feedback on appropriateness. -->
* We also allow the user to use any customized pattern to parse or format
* date-time strings under certain locales. Date elements that vary across
* locales include month name, weekname, field, order, etc.
*
- * This library uses the ICU/JDK date/time pattern specification as described
- * below.
+ * This library uses the ICU/JDK date/time pattern specification both for
+ * complete format specifications and also the abbreviated "skeleton" form
+ * which can also adapt to different locales and is preferred where available.
*
- * Time Format Syntax: To specify the time format use a time pattern string.
- * In this pattern, following letters are reserved as pattern letters, which
- * are defined in the following manner:
+ * Skeletons: These can be specified either as the ICU constant name or as the
+ * skeleton to which it resolves. The supported set of skeletons is as follows
+ * ICU Name Skeleton
+ * -------- --------
+ * DAY d
+ * ABBR_WEEKDAY E
+ * WEEKDAY EEEE
+ * ABBR_STANDALONE_MONTH LLL
+ * STANDALONE_MONTH LLLL
+ * NUM_MONTH M
+ * NUM_MONTH_DAY Md
+ * NUM_MONTH_WEEKDAY_DAY MEd
+ * ABBR_MONTH MMM
+ * ABBR_MONTH_DAY MMMd
+ * ABBR_MONTH_WEEKDAY_DAY MMMEd
+ * MONTH MMMM
+ * MONTH_DAY MMMMd
+ * MONTH_WEEKDAY_DAY MMMMEEEEd
+ * ABBR_QUARTER QQQ
+ * QUARTER QQQQ
+ * YEAR y
+ * YEAR_NUM_MONTH yM
+ * YEAR_NUM_MONTH_DAY yMd
+ * YEAR_NUM_MONTH_WEEKDAY_DAY yMEd
+ * YEAR_ABBR_MONTH yMMM
+ * YEAR_ABBR_MONTH_DAY yMMMd
+ * YEAR_ABBR_MONTH_WEEKDAY_DAY yMMMEd
+ * YEAR_MONTH yMMMM
+ * YEAR_MONTH_DAY yMMMMd
+ * YEAR_MONTH_WEEKDAY_DAY yMMMMEEEEd
+ * YEAR_ABBR_QUARTER yQQQ
+ * YEAR_QUARTER yQQQQ
+ * HOUR24 H
+ * HOUR24_MINUTE Hm
+ * HOUR24_MINUTE_SECOND Hms
+ * HOUR j
+ * HOUR_MINUTE jm
+ * HOUR_MINUTE_SECOND jms
+ * HOUR_MINUTE_GENERIC_TZ jmv
+ * HOUR_MINUTE_TZ jmz
+ * HOUR_GENERIC_TZ jv
+ * HOUR_TZ jz
+ * MINUTE m
+ * MINUTE_SECOND ms
+ * SECOND s
*
+ * Examples Using the US Locale:
+ *
+ * Pattern Result
+ * ---------------- -------
+ * "yMd" ->07/10/1996
+ * "yMMMMd" ->July 10, 1996
+ * "Hm" ->12:08 PM
+ *
+ * Explicit Pattern Syntax: Formats can also be specified with a pattern string.
+ * The skeleton forms will resolve to explicit patterns of this form, but will
+ * also adapt to different patterns in different locales.
+ * The following characters are reserved:
+ *
* Symbol Meaning Presentation Example
* ------ ------- ------------ -------
* G era designator (Text) AD
* y# year (Number) 1996
* M month in year (Text & Number) July & 07
+ * L standalone month (Text & Number) July & 07
* d day in month (Number) 10
+ * c standalone day (Number) 10
* h hour in am/pm (1~12) (Number) 12
* H hour in day (0~23) (Number) 0
* m minute in hour (Number) 30
@@ -40,17 +98,18 @@
* z time zone (Text) Pacific Standard Time
* Z time zone (RFC 822) (Number) -0800
* v time zone (generic) (Text) Pacific Time
+ * Q quarter (Text) Q3
* ' escape for text (Delimiter) 'Date='
* '' single quote (Literal) 'o''clock'
*
* Items marked with '#' work differently than in Java.
Emily Fortuna 2012/07/31 05:52:53 Now that this is implemented, does it in fact work
Alan Knight 2012/08/03 23:02:15 Good question. Shanjian believes this refers to is
*
* The count of pattern letters determine the format.
- * **Text**:
+ * **Text**:
+ * * 5 pattern letters--use narrow form for standalone. Otherwise does not apply
* * 4 or more pattern letters--use full form,
- * * less than 4--use short or abbreviated form if one exists.
- * In parsing, we will always try long format, then short.
- * (e.g., "EEEE" produces "Monday", "EEE" produces "Mon")
+ * * 3 pattern letters--use short or abbreviated form if one exists
+ * * less than 3--use numeric form if one exists
*
* **Number**: the minimum number of digits. Shorter numbers are zero-padded to
* this amount (e.g. if "m" produces "6", "mm" produces "06"). Year is handled
@@ -71,15 +130,15 @@
*
* Format Pattern Result
* -------------- -------
- * "yyyy.MM.dd G 'at' HH:mm:ss vvvv"->> 1996.07.10 AD at 15:08:56 Pacific Time
Emily Fortuna 2012/07/31 05:52:53 When was the last time you sync'd? This was fixed
Alan Knight 2012/08/03 23:02:15 Yes, it was before you left.
- * "EEE, MMM d, ''yy" ->> Wed, July 10, '96
- * "h:mm a" ->> 12:08 PM
- * "hh 'o''clock' a, zzzz" ->> 12 o'clock PM, Pacific Daylight Time
- * "K:mm a, vvv" ->> 0:00 PM, PT
- * "yyyyy.MMMMM.dd GGG hh:mm aaa" ->> 01996.July.10 AD 12:08 PM
+ * "yyyy.MM.dd G 'at' HH:mm:ss vvvv"->1996.07.10 AD at 15:08:56 Pacific Time
+ * "EEE, MMM d, ''yy" ->Wed, July 10, '96
+ * "h:mm a" ->12:08 PM
+ * "hh 'o''clock' a, zzzz" ->12 o'clock PM, Pacific Daylight Time
+ * "K:mm a, vvv" ->0:00 PM, PT
+ * "yyyyy.MMMMM.dd GGG hh:mm aaa" ->01996.July.10 AD 12:08 PM
*
* When parsing a date string using the abbreviated year pattern ("yy"),
- * DateTimeParse must interpret the abbreviated year relative to some
+ * DateFormat must interpret the abbreviated year relative to some
* century. It does this by adjusting dates to be within 80 years before and 20
* years after the time the parse function is called. For example, using a
* pattern of "MM/dd/yy" and a DateTimeParse instance created on Jan 1, 1997,
@@ -107,149 +166,879 @@
*/
#library('date_format');
+#import('intl.dart');
+#import('date_time_patterns.dart');
+#import('date_symbols.dart');
+#import('date_symbol_data.dart');
class DateFormat {
- /** Definition of how this object formats dates. */
- String _formatDefinition;
+ /**
+ * The constructor accepts a [formatDefinition] which is a String. If the
Emily Fortuna 2012/07/31 05:52:53 1) No need to say formatDefinition is a string, ju
Alan Knight 2012/08/03 23:02:15 Done.
+ * [formatDefinition] matches one of the skeleton forms, it is looked up
+ * in the [locale] or in a default if none is specified, and the corresponding
+ * full format string is used. If the [formatDefinition] does not match one
+ * of the supported skeleton forms then it is used as a format directly.
+ * For example, in an en_US locale, specifying the skeleton
+ * new DateFormat('yMEd');
+ * or the explicit
+ * new DateFormat('EEE, M/d/y');
+ * would produce the same result, a date of the form
+ * Wed, 6/27/2012
+ * However, the skeleton version would also adapt to other locales.
+ * If [locale] does not exist in our set of supported locales then an
+ * IllegalArgumentException is thrown.
+ */
+ DateFormat([String newPattern, String locale]) {
+ //TODO(alanknight): It should be possible to specify multiple skeletons, e.g
Emily Fortuna 2012/07/31 05:52:53 indent these comments two more spaces. Also, add a
Alan Knight 2012/08/03 23:02:15 Done, along with other occurrences.
+ // date, time, timezone all separately. Adding many or named parameters to
+ // the constructor seems awkward, especially with the possibility of confusion
+ // with the locale. A "fluent" interface with cascading on an instance might
+ // work better? A list of patterns is also possible.
+ //TODO(alanknight): There will need to be at least setup type async operations
+ // to avoid the need to bring along every locale in every program using this.
+ _setLocale(locale);
+ pattern = newPattern;
+ }
/**
- * The locale code with which the message is to be formatted (such as en-CA).
+ * Return a string representing [date] formatted according to our locale
+ * and internal format.
*/
+ String format(Date date) {
+ // TODO(efortuna): read optional TimeZone argument (or similar)?
+ var result = new StringBuffer();
+ _formatFields.forEach(
+ (field) => result.add(field.format(date))
+ );
+ return result.toString();
+ }
+
+ /**
+ * Returns a date string indicating how long ago (3 hours, 2 minutes)
Emily Fortuna 2012/07/31 05:52:53 remove the extra whitespace here. comments should
Alan Knight 2012/08/03 23:02:15 Done.
+ * something has happened or how long in the future something will happen
+ * given a [reference] Date relative to the current time.
+ */
+ String formatDuration(Date reference) {
+ return '';
+ }
+
+ /**
+ * Formats a string indicating how long ago (negative [duration]) or how far
+ * in the future (positive [duration]) some time is with respect to a
+ * reference [date].
+ */
+ String formatDurationFrom(Duration duration, Date date) {
+ return '';
+ }
+
+ /**
+ * Given user input, attempt to parse the [inputString] into the anticipated
+ * format, using the local machine timezone.
+ */
+ Date parse(String inputString) {
+ var dateFields = new _DateBuilder();
+ var stream = new _Stream(inputString);
+ _formatFields.forEach(
Emily Fortuna 2012/07/31 05:52:53 indentation??
Alan Knight 2012/08/03 23:02:15 Done.
+ (each) {
Emily Fortuna 2012/07/31 05:52:53 this looks like a great case for (each) => each.pa
Alan Knight 2012/08/03 23:02:15 Done.
+ each.parse(stream, dateFields);
+ }
+ );
+ return dateFields.asDate();
+ }
+
+ /**
+ * Given user input, attempt to parse the [inputString] into the anticipated
+ * format, using UTC.
Emily Fortuna 2012/07/31 05:52:53 The inputString is in UTC, or we parse the result
Alan Knight 2012/08/03 23:02:15 That is rather ambiguous. Changed it so that it's
+ */
+ Date parseUTC(String inputString) {
+ return parse(inputString).toUtc();
+ }
+
+ /**
+ * Return the locale code in which we operate, e.g. 'en_US' or 'pt'.
+ */
+ String get locale() {
+ if (_locale == null) {
+ return Intl.defaultLocale;
+ } else {
+ return _locale;}
Emily Fortuna 2012/07/31 05:52:53 move } to new line.
Alan Knight 2012/08/03 23:02:15 Done.
+ }
+
+ /**
+ * Constructors for a set of predefined formats for which
+ * internationalized forms are known. These can be specified
+ * either as ICU constants, or as skeletons.
+ */
+ DateFormat.DAY([locale]) : this(DAY, locale);
+ DateFormat.ABBR_WEEKDAY([locale]) : this(ABBR_WEEKDAY, locale);
+ DateFormat.WEEKDAY([locale]) : this(WEEKDAY, locale);
+ DateFormat.ABBR_STANDALONE_MONTH([locale]) :
+ this(ABBR_STANDALONE_MONTH, locale);
+ DateFormat.STANDALONE_MONTH([locale]) : this(STANDALONE_MONTH, locale);
+ DateFormat.NUM_MONTH([locale]) : this(NUM_MONTH, locale);
+ DateFormat.NUM_MONTH_DAY([locale]) : this(NUM_MONTH_DAY, locale);
+ DateFormat.NUM_MONTH_WEEKDAY_DAY([locale]) :
+ this(NUM_MONTH_WEEKDAY_DAY, locale);
+ DateFormat.ABBR_MONTH([locale]) : this(ABBR_MONTH, locale);
+ DateFormat.ABBR_MONTH_DAY([locale]) : this(ABBR_MONTH_DAY, locale);
+ DateFormat.ABBR_MONTH_WEEKDAY_DAY([locale]) :
+ this(ABBR_MONTH_WEEKDAY_DAY, locale);
+ DateFormat.MONTH([locale]) : this(MONTH, locale);
+ DateFormat.MONTH_DAY([locale]) : this(MONTH_DAY, locale);
+ DateFormat.MONTH_WEEKDAY_DAY([locale]) : this(MONTH_WEEKDAY_DAY, locale);
+ DateFormat.ABBR_QUARTER([locale]) : this(ABBR_QUARTER, locale);
+ DateFormat.QUARTER([locale]) : this(QUARTER, locale);
+ DateFormat.YEAR([locale]) : this(YEAR, locale);
+ DateFormat.YEAR_NUM_MONTH([locale]) : this(YEAR_NUM_MONTH, locale);
+ DateFormat.YEAR_NUM_MONTH_DAY([locale]) : this(YEAR_NUM_MONTH_DAY, locale);
+ DateFormat.YEAR_NUM_MONTH_WEEKDAY_DAY([locale]) :
+ this(YEAR_NUM_MONTH_WEEKDAY_DAY, locale);
Emily Fortuna 2012/07/31 05:52:53 if you have to break a line, the next line should
Alan Knight 2012/08/03 23:02:15 Done.
+ DateFormat.YEAR_ABBR_MONTH([locale]) : this(YEAR_ABBR_MONTH, locale);
+ DateFormat.YEAR_ABBR_MONTH_DAY([locale]) : this(YEAR_ABBR_MONTH_DAY, locale);
+ DateFormat.YEAR_ABBR_MONTH_WEEKDAY_DAY([locale]) :
+ this(YEAR_ABBR_MONTH_WEEKDAY_DAY, locale);
+ DateFormat.YEAR_MONTH([locale]) : this(YEAR_MONTH, locale);
+ DateFormat.YEAR_MONTH_DAY([locale]) : this(YEAR_MONTH_DAY, locale);
+ DateFormat.YEAR_MONTH_WEEKDAY_DAY([locale]) :
+ this(YEAR_MONTH_WEEKDAY_DAY, locale);
+ DateFormat.YEAR_ABBR_QUARTER([locale]) : this(YEAR_ABBR_QUARTER, locale);
+ DateFormat.YEAR_QUARTER([locale]) : this(YEAR_QUARTER, locale);
+ DateFormat.HOUR24([locale]) : this(HOUR24, locale);
+ DateFormat.HOUR24_MINUTE([locale]) : this(HOUR24_MINUTE, locale);
+ DateFormat.HOUR24_MINUTE_SECOND([locale]) :
+ this(HOUR24_MINUTE_SECOND, locale);
+ DateFormat.HOUR([locale]) : this(HOUR, locale);
+ DateFormat.HOUR_MINUTE([locale]) : this(HOUR_MINUTE, locale);
+ DateFormat.HOUR_MINUTE_SECOND([locale]) : this(HOUR_MINUTE_SECOND, locale);
+ DateFormat.HOUR_MINUTE_GENERIC_TZ([locale]) :
+ this(HOUR_MINUTE_GENERIC_TZ, locale);
+ DateFormat.HOUR_MINUTE_TZ([locale]) : this(HOUR_MINUTE_TZ, locale);
+ DateFormat.HOUR_GENERIC_TZ([locale]) : this(HOUR_GENERIC_TZ, locale);
+ DateFormat.HOUR_TZ([locale]) : this(HOUR_TZ, locale);
+ DateFormat.MINUTE([locale]) : this(MINUTE, locale);
+ DateFormat.MINUTE_SECOND([locale]) : this(MINUTE_SECOND, locale);
+ DateFormat.SECOND([locale]) : this(SECOND, locale);
+
+ DateFormat.d([locale]) : this("d", locale);
+ DateFormat.E([locale]) : this("E", locale);
+ DateFormat.EEEE([locale]) : this("EEEE", locale);
+ DateFormat.LLL([locale]) : this("LLL", locale);
+ DateFormat.LLLL([locale]) : this("LLLL", locale);
+ DateFormat.M([locale]) : this("M", locale);
+ DateFormat.Md([locale]) : this("Md", locale);
+ DateFormat.MEd([locale]) : this("MEd", locale);
+ DateFormat.MMM([locale]) : this("MMM", locale);
+ DateFormat.MMMd([locale]) : this("MMMd", locale);
+ DateFormat.MMMEd([locale]) : this("MMMEd", locale);
+ DateFormat.MMMM([locale]) : this("MMMM", locale);
+ DateFormat.MMMMd([locale]) : this("MMMMd", locale);
+ DateFormat.MMMMEEEEd([locale]) : this("MMMMEEEEd", locale);
+ DateFormat.QQQ([locale]) : this("QQQ", locale);
+ DateFormat.QQQQ([locale]) : this("QQQQ", locale);
+ DateFormat.y([locale]) : this("y", locale);
+ DateFormat.yM([locale]) : this("yM", locale);
+ DateFormat.yMd([locale]) : this("yMd", locale);
+ DateFormat.yMEd([locale]) : this("yMEd", locale);
+ DateFormat.yMMM([locale]) : this("yMMM", locale);
+ DateFormat.yMMMd([locale]) : this("yMMMd", locale);
+ DateFormat.yMMMEd([locale]) : this("yMMMEd", locale);
+ DateFormat.yMMMM([locale]) : this("yMMMM", locale);
+ DateFormat.yMMMMd([locale]) : this("yMMMMd", locale);
+ DateFormat.yMMMMEEEEd([locale]) : this("yMMMMEEEEd", locale);
+ DateFormat.yQQQ([locale]) : this("yQQQ", locale);
+ DateFormat.yQQQQ([locale]) : this("yQQQQ", locale);
+ DateFormat.H([locale]) : this("H", locale);
+ DateFormat.Hm([locale]) : this("Hm", locale);
+ DateFormat.Hms([locale]) : this("Hms", locale);
+ DateFormat.j([locale]) : this("j", locale);
+ DateFormat.jm([locale]) : this("jm", locale);
+ DateFormat.jms([locale]) : this("jms", locale);
+ DateFormat.jmv([locale]) : this("jmv", locale);
+ DateFormat.jmz([locale]) : this("jmz", locale);
+ DateFormat.jv([locale]) : this("jv", locale);
+ DateFormat.jz([locale]) : this("jz", locale);
+ DateFormat.m([locale]) : this("m", locale);
+ DateFormat.ms([locale]) : this("ms", locale);
+ DateFormat.s([locale]) : this("s", locale);
+
+ /**
+ * ICU constants for format names, resolving to the corresponding skeletons.
+ */
+ static final String DAY = 'd';
Emily Fortuna 2012/07/31 05:52:53 Perhaps I'm missing something here. Why doesn't th
Alan Knight 2012/08/03 23:02:15 An interesting point. But apparently it doesn't. C
+ static final String ABBR_WEEKDAY = 'E';
+ static final String WEEKDAY = 'EEEE';
+ static final String ABBR_STANDALONE_MONTH = 'LLL';
+ static final String STANDALONE_MONTH = 'LLLL';
+ static final String NUM_MONTH = 'M';
+ static final String NUM_MONTH_DAY = 'Md';
+ static final String NUM_MONTH_WEEKDAY_DAY = 'MEd';
+ static final String ABBR_MONTH = 'MMM';
+ static final String ABBR_MONTH_DAY = 'MMMd';
+ static final String ABBR_MONTH_WEEKDAY_DAY = 'MMMEd';
+ static final String MONTH = 'MMMM';
+ static final String MONTH_DAY = 'MMMMd';
+ static final String MONTH_WEEKDAY_DAY = 'MMMMEEEEd';
+ static final String ABBR_QUARTER = 'QQQ';
+ static final String QUARTER = 'QQQQ';
+ static final String YEAR = 'y';
+ static final String YEAR_NUM_MONTH = 'yM';
+ static final String YEAR_NUM_MONTH_DAY = 'yMd';
+ static final String YEAR_NUM_MONTH_WEEKDAY_DAY = 'yMEd';
+ static final String YEAR_ABBR_MONTH = 'yMMM';
+ static final String YEAR_ABBR_MONTH_DAY = 'yMMMd';
+ static final String YEAR_ABBR_MONTH_WEEKDAY_DAY = 'yMMMEd';
+ static final String YEAR_MONTH = 'yMMMM';
+ static final String YEAR_MONTH_DAY = 'yMMMMd';
+ static final String YEAR_MONTH_WEEKDAY_DAY = 'yMMMMEEEEd';
+ static final String YEAR_ABBR_QUARTER = 'yQQQ';
+ static final String YEAR_QUARTER = 'yQQQQ';
+ static final String HOUR24 = 'H';
+ static final String HOUR24_MINUTE = 'Hm';
+ static final String HOUR24_MINUTE_SECOND = 'Hms';
+ static final String HOUR = 'j';
+ static final String HOUR_MINUTE = 'jm';
+ static final String HOUR_MINUTE_SECOND = 'jms';
+ static final String HOUR_MINUTE_GENERIC_TZ = 'jmv';
+ static final String HOUR_MINUTE_TZ = 'jmz';
+ static final String HOUR_GENERIC_TZ = 'jv';
+ static final String HOUR_TZ = 'jz';
+ static final String MINUTE = 'm';
+ static final String MINUTE_SECOND = 'ms';
+ static final String SECOND = 's';
+
+ /** The locale in which we operate, e.g. 'en_US', or 'pt'. */
String _locale;
+ /**
+ * The full template string. This may have been specified directly, or
+ * it may have been derived from a skeleton and the locale information
+ * on how to interpret that skeleton.
+ */
+ String _pattern;
Emily Fortuna 2012/07/31 05:52:53 -1 space indentation.
Alan Knight 2012/08/03 23:02:15 Done.
+
/**
- * Date/Time format "skeleton" patterns. Also specifiable by String, but
- * written this way so that they can be discoverable via autocomplete. These
- * follow the ICU syntax described at the top of the file. These skeletons can
- * be combined and we will attempt to find the best format for each locale
- * given the pattern.
+ * We parse the format string into individual fields and store them here.
+ * This is what is actually used to do the formatting.
*/
- // TODO(efortuna): Hear back from i18n about Time Zones and the "core set"
- // of skeleton patterns.
- // Example of how this looks in the US
- // locale.
- static final String Hm = 'Hm'; // HH:mm
- static final String Hms = 'Hms'; // HH:mm:ss
- static final String M = 'M'; // L
- static final String MEd = 'MEd'; // E, M/d
- static final String MMM = 'MMM'; // LLL
- static final String MMMEd = 'MMMEd'; // E, MMM d
- static final String MMMMEd = 'MMMMEd'; // E, MMMM d
- static final String MMMMd = 'MMMMd'; // MMMM d
- static final String MMMd = 'MMMd'; // MMM d
- static final String Md = 'Md'; // M/d
- static final String d = 'd'; // d
- static final String hm = 'hm'; // h:mm a
- static final String ms = 'ms'; // mm:ss
- static final String y = 'y'; // yyyy
- static final String yM = 'yM'; // M/yyyy
- static final String yMEd = 'yMEd'; // EEE, M/d/yyyy
- static final String yMMM = 'yMMM'; // MMM yyyy
- static final String yMMMEd = 'yMMMEd'; // EEE, MM d, yyyy
- static final String yMMMM = 'yMMMM'; // MMMM yyyy
- static final String yQ = 'yQ'; // Q yyyy
- static final String yQQQ = 'yQQQ'; // QQQ yyyy
+ List<_DateFormatField> _formatFields;
- /** Date/Time format patterns. */
- // TODO(efortuna): This are just guesses of what a full date, long date is.
- // Do the proper homework on ICU to find the proper set "Hms"/"yMMd"
- // applicable to each case.
- static final String fullDate = '$y$MMMMd';
- static final String longDate = yMMMEd;
- static final String mediumDate = '$y$Md';
- static final String shortDate = Md;
- static final String fullTime = '$Hms a';
- static final String longTime = '$Hms a zzzz';
- static final String mediumTime = Hms;
- static final String shortTime = Hm;
- static final String fullDateTime = '$fullDate$fullTime';
- static final String longDateTime = '$longDate$longTime';
- static final String mediumDateTime = '$mediumDate$mediumTime';
- static final String shortDateTime = '$shortDate$shortTime';
+ /**
+ * Given a format from the user look it up in our list of known skeletons.
+ * If it's there, then use the corresponding pattern for this locale.
+ * If it's not, then treat it as an explicit pattern.
+ */
+ set pattern(String inputPattern) {
+ //TODO(alanknight): This is an expensive operation. Caching recently used
Emily Fortuna 2012/07/31 05:52:53 this never gets used/tested? Why provide this func
Alan Knight 2012/08/03 23:02:15 The constructor calls this. It's also possible, an
+ // formats, or possibly introducing an entire "locale" object that would
+ // cache patterns for that locale could be a good optimization.
+ if (!availableSkeletons.containsKey(inputPattern)) {
+ _pattern = inputPattern;
+ } else {
+ _pattern = availableSkeletons[inputPattern];
+ }
+ _formatFields = parsePattern(_pattern);
+ }
+ /** Return the pattern that we use to format dates.*/
+ get pattern() => _pattern;
+
+ /** Return the skeletons for our current locale. */
+ Map get availableSkeletons() {
Emily Fortuna 2012/07/31 05:52:53 do we want this to be a public function? or is th
Alan Knight 2012/08/03 23:02:15 Changed to private. I had a lot more private, but
+ return dateTimePatterns[locale];
+ }
+
+ /**
+ * Return true if the locale exists, or if it is null. The null case
Emily Fortuna 2012/07/31 05:52:53 seems like all of this locale code is not unique t
Alan Knight 2012/08/03 23:02:15 As per this and later comment, moved into Intl as
+ * is interpreted to mean that we use the default locale.
+ */
+ bool _localeExists(localeName) {
Emily Fortuna 2012/07/31 05:52:53 -1 space indentation. Please check that your code
Alan Knight 2012/08/03 23:02:15 Sorry, I did try to find formatting issues, but am
Emily Fortuna 2012/08/06 22:43:00 Last I heard the editor team was going to be worki
+ if (localeName == null) return true;
+ return dateTimeSymbols.containsKey(localeName);
+ }
+
/**
- * Constructors for dates/times that use a default format.
+ * Set the locale. If the locale can't be found, we also look up
+ * based on alternative versions, e.g. if we have no 'en_CA' we will
+ * look for 'en' as a fallback. It will also translate en-ca into en_CA.
+ * Null is also considered a valid value for [newLocale], indicating
+ * to use the default.
*/
- DateFormat.Hm([this._locale]) : _formatDefinition = Hm;
- DateFormat.Hms([this._locale]) : _formatDefinition = Hms;
- DateFormat.M([this._locale]) : _formatDefinition = M;
- DateFormat.MEd([this._locale]) : _formatDefinition = MEd;
- DateFormat.MMM([this._locale]) : _formatDefinition = MMM;
- DateFormat.MMMEd([this._locale]) : _formatDefinition = MMMEd;
- DateFormat.MMMMEd([this._locale]) : _formatDefinition = MMMMEd;
- DateFormat.MMMMd([this._locale]) : _formatDefinition = MMMMd;
- DateFormat.MMMd([this._locale]) : _formatDefinition = MMMd;
- DateFormat.Md([this._locale]) : _formatDefinition = Md;
- DateFormat.d([this._locale]) : _formatDefinition = d;
- DateFormat.hm([this._locale]) : _formatDefinition = hm;
- DateFormat.ms([this._locale]) : _formatDefinition = ms;
- DateFormat.y([this._locale]) : _formatDefinition = y;
- DateFormat.yM([this._locale]) : _formatDefinition = yM;
- DateFormat.yMEd([this._locale]) : _formatDefinition = yMEd;
- DateFormat.yMMM([this._locale]) : _formatDefinition = yMMM;
- DateFormat.yMMMEd([this._locale]) : _formatDefinition = yMMMEd;
- DateFormat.yMMMM([this._locale]) : _formatDefinition = yMMMM;
- DateFormat.yQ([this._locale]) : _formatDefinition = yQ;
- DateFormat.yQQQ([this._locale]) : _formatDefinition = yQQQ;
+ _setLocale(String newLocale) {
+ //TODO(alanknight): We may want to make this available more generally in
+ // i18n
Emily Fortuna 2012/07/31 05:52:53 comments are a complete sentence and end in "." Pl
+ if (_localeExists(newLocale)) {
+ return _locale = newLocale;
+ }
+ for (var each in [_canonicalized(newLocale), _shortLocale(newLocale)]) {
+ if (_localeExists(each)) {
+ return _locale = each;
Emily Fortuna 2012/07/31 05:52:53 don't combine assignment with return values. It's
Alan Knight 2012/08/03 23:02:15 Done.
+ }
+ }
+ throw new IllegalArgumentException("Invalid locale '$newLocale'");
+ }
- DateFormat.fullDate([this._locale]) : _formatDefinition = fullDate;
- DateFormat.longDate([this._locale]) : _formatDefinition = longDate;
- DateFormat.mediumDate([this._locale]) : _formatDefinition = mediumDate;
- DateFormat.shortDate([this._locale]) : _formatDefinition = shortDate;
- DateFormat.fullTime([this._locale]) : _formatDefinition = fullTime;
- DateFormat.longTime([this._locale]) : _formatDefinition = longTime;
- DateFormat.mediumTime([this._locale]) : _formatDefinition = mediumTime;
- DateFormat.shortTime([this._locale]) : _formatDefinition = shortTime;
- DateFormat.fullDateTime([this._locale]) : _formatDefinition = fullDateTime;
- DateFormat.longDateTime([this._locale]) : _formatDefinition = longDateTime;
- DateFormat.mediumDateTime([this._locale]) : _formatDefinition = mediumDateTime;
- DateFormat.shortDateTime([this._locale]) : _formatDefinition = shortDateTime;
+ /** Return the short version of a locale name, e.g. 'en_US' => 'en' */
+ String _shortLocale(aLocale) {
+ if (aLocale.length < 2) return aLocale;
Emily Fortuna 2012/07/31 05:52:53 are there locales that are just one letter? If not
Alan Knight 2012/08/03 23:02:15 Locales should be at least two letters. This deals
+ return aLocale.substring(0,2).toLowerCase();
+ }
/**
- * Constructor accepts a [formatDefinition], which can be a String, one of the
- * predefined static forms, or a custom date format using the syntax described
- * above. An optional [locale] can be provided for specifics of the language
- * locale to be used, otherwise, we will attempt to infer it (acceptable if
- * Dart is running on the client, we can infer from the browser).
+ * Return a locale name turned into xx_YY where it might possibly be
+ * in the wrong case or with a hyphen instead of an underscore.
*/
- DateFormat([formatDefinition = fullDate, locale]) {
- this._formatDefinition = formatDefinition;
- this._locale = locale;
+ String _canonicalized(aLocale) {
+ if (aLocale.length != 5) return aLocale;
Emily Fortuna 2012/07/31 05:52:53 what other acceptable locales are there? if it's j
Alan Knight 2012/08/03 23:02:15 Locales should be of the form either 'en' or 'en_U
Emily Fortuna 2012/08/06 22:43:00 This is fine. I just wanted to understand the case
+ if (aLocale[2] != '-' && (aLocale[2] != '_')) return aLocale;
+ return '${aLocale[0]}${aLocale[1]}_${aLocale[3].toUpperCase()}'
+ '${aLocale[4].toUpperCase()}';
}
/**
- * Given user input, attempt to parse the [inputString] into the anticipated
- * format.
+ * A series of regular expression used to parse a format string into its
+ * component fields.
*/
- String parse(String inputString) {
- return inputString;
+ static var _matchers = const [
+ // Quoted String
Emily Fortuna 2012/07/31 05:52:53 an example for this?
Alan Knight 2012/08/03 23:02:15 Done.
+ const RegExp("^\'(?:[^\']|\'\')*\'"),
+ // Fields
+ const RegExp(
+ "^(?:G+|y+|M+|k+|S+|E+|a+|h+|K+|H+|c+|L+|Q+|d+|m+|s+|v+|z+|Z+)"),
+ // Everything Else
+ const RegExp("^[^\'GyMkSEahKHcLQdmsvzZ]+")
+ ];
+
+ /** Parse the template pattern and return a list of field objects.*/
+ List parsePattern(String pattern) {
+ if (pattern == null) return null;
+ return _reverse(_parsePatternHelper(pattern));
}
+ /** Recursive helper for parsing the template pattern. */
+ List _parsePatternHelper(String pattern) {
+ if (pattern.isEmpty()) return [ ];
Emily Fortuna 2012/07/31 05:52:53 []
Alan Knight 2012/08/03 23:02:15 Done.
+
+ var matched = _match(pattern);
+ if (matched == null) return [ ];
+
+ var parsed = _parsePatternHelper(pattern.substring(matched.string.length));
+ matched.patchQuotes();
+ parsed.add(matched);
+ return parsed;
+ }
+
+ /** Find elements in a string that are patterns for specific fields.*/
+ _DateFormatField _match(String pattern) {
+ for (var i = 0; i < _matchers.length; i++) {
+ var regex = _matchers[i];
+ var match = regex.firstMatch(pattern);
+ if (match != null) {
+ return new _DateFormatField(
+ match.group(0),
+ _DateFormatField._matchTypes[i],
+ this);
+ }
+ }
+ }
+
+ /** Polyfill for missing library function. */
Emily Fortuna 2012/07/31 05:52:53 is there a bug filed /feature request? If so, plea
Alan Knight 2012/08/03 23:02:15 Done.
+ List _reverse(List list) {
+ var result = new List();
+ for (var i = list.length-1; i >= 0; i--) {
+ result.addLast(list[i]);
+ }
+ return result;
+ }
+}
+
+/**
+ * This is a private class internal to DateFormat which is used for formatting
+ * particular fields in a template. e.g. if the format is hh:mm:ss then the
+ * fields would be "hh", ":", "mm", ":", and "ss". Each type of field knows
+ * how to format that portion of a date.
+ */
+
+//TODO(alanknight): Is including these all in the same file as private the best
Emily Fortuna 2012/07/31 05:52:53 source seems more readable than a really long file
Alan Knight 2012/08/03 23:02:15 OK, split out into two additional files, one for t
+// way to organize this? Would a separate library but public make it easier
+// to test them independently?
+class _DateFormatField {
+ /** The possible types of field. Any instance of this must be one of these */
+ static List<String> _matchTypes = const ['QUOTED_STRING', 'FIELD', 'LITERAL'];
Emily Fortuna 2012/07/31 05:52:53 Use an enum pattern here. See https://groups.googl
Alan Knight 2012/08/03 23:02:15 Rather than an enum, split the field class into th
+
+ /** The format string that defines us, e.g. "hh" */
+ String string;
Emily Fortuna 2012/07/31 05:52:53 can you give this variable a different name?
Alan Knight 2012/08/03 23:02:15 Done.
+
+ /** The type of field we represent. See _matchTypes for possible values. */
+ String type;
+
+ /** The DateFormat that we are part of.*/
+ DateFormat parent;
+
+ /**
+ * Return the width of our string field. Different widths represent different
Emily Fortuna 2012/07/31 05:52:53 [string]
Alan Knight 2012/08/03 23:02:15 Done.
+ * formatting options. See the comment for DateFormat for details.
Emily Fortuna 2012/07/31 05:52:53 where is this comment for DateFormat?
Alan Knight 2012/08/03 23:02:15 Lines 80 through 112, particularly the 107-112 par
+ */
+ int get width() => string.length;
+
+ _DateFormatField(this.string, this.type,this.parent);
Emily Fortuna 2012/07/31 05:52:53 space after ,
Alan Knight 2012/08/03 23:02:15 Done.
+
+ String toString() => string;
+
+ void patchQuotes() {
+ if (type != "QUOTED_STRING") return;
+ if (string == "''") {
+ string = "'";
+ } else {
+ string = string.substring(1,string.length - 1);
+ var someRegex = new RegExp("\'\'");
Emily Fortuna 2012/07/31 05:52:53 @ before a string also can denote a raw string
Alan Knight 2012/08/03 23:02:15 I'm not clear what the implications of that are. D
+ string = string.replaceAll(someRegex, "'");
+ }
+ }
+
+ /** Format date according to our specification and return the result. */
+ String format(Date date) {
+ if(type != "FIELD") {
+ return string;
+ } else {
+ return formatField(date);
+ }
+ }
+
+ /**
+ * Parse the date according to our specification and put the result
+ * into the correct place in dateFields.
+ */
+ void parse(_Stream input, _DateBuilder dateFields) {
+ if (type == 'LITERAL') return parseLiteral(input);
+ if (type == 'QUOTED_STRING') return parseLiteral(input);
+ parseField(input,dateFields);
+ }
+
+ /** Parse a literal field. We just look for the exact input. */
+ void parseLiteral(_Stream input) {
+ var found = input.read(width);
+ if (found != string) throw
+ new FormatException(input, this);
+ }
+
+ /**
+ * Parse a field representing part of a date pattern. Note that we do not
+ * return a value, but rather build up the result in [builder].
+ */
+ void parseField(_Stream input, _DateBuilder builder) {
+ try {
+ switch(string[0]) {
+ case 'a': parseAmPm(input,builder); break;
+ case 'c': parseStandaloneDay(input); break;
+ case 'd': handleNumericField(input,builder.setDay); break; // day
+ case 'E': parseDayOfWeek(input); break;
+ case 'G': break; // era
+ case 'h': parse1To12Hours(input,builder); break;
+ case 'H': handleNumericField(input,builder.setHour); break; // hour 0-23
+ case 'K': handleNumericField(input,builder.setHour); break; //hour 0-11
+ case 'k': handleNumericField(input,builder.setHour,-1); break; //hr 1-24
+ case 'L': parseStandaloneMonth(input,builder); break;
+ case 'M': parseMonth(input,builder); break;
+ case 'm': handleNumericField(input,builder.setMinute); break; // minutes
+ case 'Q': break; // quarter
+ case 'S': handleNumericField(input,builder.setFractionalSecond); break;
+ case 's': handleNumericField(input,builder.setSecond); break;
+ case 'v': break; // time zone id
+ case 'y': handleNumericField(input,builder.setYear); break;
+ case 'z': break; // time zone
+ case 'Z': break; // time zone RFC
+ default: return;
+ }
+ } catch (var e) {throw new FormatException(input,this);}
+ }
+
+ /** Formatting logic if we are of type FIELD */
+ String formatField(Date date) {
+ switch (string[0]) {
+ case 'a': return formatAmPm(date);
+ case 'c': return formatStandaloneDay(date);
+ case 'd': return formatDayOfMonth(date);
+ case 'E': return formatDayOfWeek(date);
+ case 'G': return formatEra(date);
+ case 'h': return format1To12Hours(date);
+ case 'H': return format0To23Hours(date);
+ case 'K': return format0To11Hours(date);
+ case 'k': return format24Hours(date);
+ case 'L': return formatStandaloneMonth(date);
+ case 'M': return formatMonth(date);
+ case 'm': return formatMinutes(date);
+ case 'Q': return formatQuarter(date);
+ case 'S': return formatFractionalSeconds(date);
+ case 's': return formatSeconds(date);
+ case 'v': return formatTimeZoneId(date);
+ case 'y': return formatYear(date);
+ case 'z': return formatTimeZone(date);
+ case 'Z': return formatTimeZoneRFC(date);
+ default: return '';
+ }
+ }
+
+ /** Return the symbols for our current locale. */
+ DateSymbols get symbols() => dateTimeSymbols[parent.locale];
+
+ formatEra(Date date) {
+ var era = date.year > 0 ? 1 : 0;
+ return width >= 4 ? symbols.ERANAMES[era] :
+ symbols.ERAS[era];
+ }
+
+ formatYear(Date date) {
+ var year = date.year;
+ if (year < 0) {
+ year = -year;
+ }
+ return width == 2 ? padTo(2, year % 100) : year.toString();
+ }
+
+ /**
+ * We are given [input] as a stream from which we want to read a date. We
+ * can't dynamically build up a date, so we are given a list [dateFields] of
+ * the constructor arguments and an [position] at which to set it
+ * (year,month,day,hour,minute,second,fractionalSecond)
+ * then after all parsing is done we construct a date from the arguments.
+ * This method handles reading any of the numeric fields. The [offset]
+ * argument allows us to compensate for zero-based versus one-based values.
+ */
+ void handleNumericField(
+ _Stream input,
+ Function setter,
+ [int offset = 0]) {
+ var result = input.nextInteger();
+ setter(result + offset);
+ }
+
+ /**
+ * We are given [input] as a stream from which we want to read a date. We
+ * can't dynamically build up a date, so we are given a list [dateFields] of
+ * the constructor arguments and an [position] at which to set it
+ * (year,month,day,hour,minute,second,fractionalSecond)
+ * then after all parsing is done we construct a date from the arguments.
+ * This method handles reading any of string fields from an enumerated set.
+ */
+ int parseEnumeratedString(_Stream input, List possibilities) {
+ var results = new _Stream(possibilities).findIndexes(
+ (each) => input.peek(each.length) == each);
+ if (results.isEmpty()) throw new FormatException(input,this);
+ results.sort(
+ (a, b) => possibilities[a].length.compareTo(possibilities[b].length));
+ var longestResult = results.last();
+ input.read(possibilities[longestResult].length);
+ return longestResult;
+ }
+
+ String formatMonth(Date date) {
+ switch (width) {
+ case 5: return symbols.NARROWMONTHS[date.month-1];
+ case 4: return symbols.MONTHS[date.month-1];
+ case 3: return symbols.SHORTMONTHS[date.month-1];
+ default:
+ return padTo(width, date.month);
+ }
+ }
+
+ void parseMonth(input,dateFields) {
+ var possibilities;
+ switch(width) {
+ case 5: possibilities = symbols.NARROWMONTHS; break;
+ case 4: possibilities = symbols.MONTHS; break;
+ case 3: possibilities = symbols.SHORTMONTHS; break;
+ default: return handleNumericField(input,dateFields.setMonth);
+ }
+ dateFields.month = parseEnumeratedString(input,possibilities) + 1;
+ }
+
+ String format24Hours(Date date) {
+ return padTo(width, date.hour);
+ }
+
+ String formatFractionalSeconds(Date date) {
+ // Always print at least 3 digits. If the width is greater, append 0s
+ var basic = padTo(3,date.millisecond);
+ if (width - 3 > 0) {
+ var extra = padTo(width - 3, 0);
+ return basic.concat(extra);
+ } else {
+ return basic;
+ }
+ }
+
+ String formatAmPm(Date date) {
+ var hours = date.hour;
+ var index = (date.hour >= 12) && (date.hour < 24) ? 1 : 0;
+ var ampm = symbols.AMPMS;
+ return ampm[index];
+ }
+
+ void parseAmPm(input,dateFields) {
+ // If we see a "PM" note it in an extra field.
+ var ampm = parseEnumeratedString(input,symbols.AMPMS);
+ if (ampm == 1) dateFields.pm = true;
+ }
+
+ String format1To12Hours(Date date) {
+ var hours = date.hour;
+ if (date.hour > 12) hours = hours - 12;
+ if (hours == 0) hours = 12;
+ return padTo(width,hours);
+ }
+
+ void parse1To12Hours(_Stream input, _DateBuilder dateFields) {
+ handleNumericField(input,dateFields.setHour);
+ if (dateFields.hour == 12) dateFields.hour = 0;
+ }
+
+ String format0To11Hours(Date date) {
+ return padTo(width,date.hour % 12);
+ }
+
+ String format0To23Hours(Date date) {
+ return padTo(width,date.hour);
+ }
+
+ String formatStandaloneDay(Date date) {
+ switch (width) {
+ case 5: return symbols.STANDALONENARROWWEEKDAYS[date.weekday % 7];
+ case 4: return symbols.STANDALONEWEEKDAYS[date.weekday % 7];
+ case 3: return symbols.STANDALONESHORTWEEKDAYS[date.weekday % 7];
+ default:
+ return padTo(1, date.day);
+ }
+ }
+
+ void parseStandaloneDay(_Stream input) {
+ // This is ignored, but we still have to skip over it the correct amount.
+ var possibilities;
+ switch(width) {
+ case 5: possibilities = symbols.STANDALONENARROWWEEKDAYS; break;
+ case 4: possibilities = symbols.STANDALONEWEEKDAYS; break;
+ case 3: possibilities = symbols.STANDALONESHORTWEEKDAYS; break;
+ default: return handleNumericField(input,(x)=>x);
+ }
+ parseEnumeratedString(input,possibilities);
+ }
+
+ String formatStandaloneMonth(Date date) {
+ switch (width) {
+ case 5:
+ return symbols.STANDALONENARROWMONTHS[date.month-1];
+ case 4:
+ return symbols.STANDALONEMONTHS[date.month-1];
+ case 3:
+ return symbols.STANDALONESHORTMONTHS[date.month-1];
+ default:
+ return padTo(width, date.month);
+ }
+ }
+
+ void parseStandaloneMonth(input,dateFields) {
+ var possibilities;
+ switch(width) {
+ case 5: possibilities = symbols.STANDALONENARROWMONTHS; break;
+ case 4: possibilities = symbols.STANDALONEMONTHS; break;
+ case 3: possibilities = symbols.STANDALONESHORTMONTHS; break;
+ default: return handleNumericField(input,dateFields.setMonth);
+ }
+ dateFields.month = parseEnumeratedString(input,possibilities) + 1;
+ }
+
+ String formatQuarter(Date date) {
+ var quarter = (date.month / 3).truncate().toInt();
+ if (width < 4) {
+ return symbols.SHORTQUARTERS[quarter];
+ } else {
+ return symbols.QUARTERS[quarter];
+ }
+ }
+ String formatDayOfMonth(Date date) {
+ return padTo(width,date.day);
+ }
+
+ String formatDayOfWeek(Date date) {
+ // Note that Dart's weekday returns 1 for Monday and 7 for Sunday.
+ return (width >= 4 ? symbols.WEEKDAYS :
+ symbols.SHORTWEEKDAYS)[(date.weekday) % 7];
+ }
+
+ void parseDayOfWeek(_Stream input) {
+ // This is IGNORED, but we still have to skip over it the correct amount.
+ var possibilities = width >= 4 ? symbols.WEEKDAYS : symbols.SHORTWEEKDAYS;
+ parseEnumeratedString(input,possibilities);
+ }
+
+ String formatMinutes(Date date) {
+ return padTo(width,date.minute);
+ }
+
+ String formatSeconds(Date date) {
+ return padTo(width,date.second);
+ }
+
+ //TODO(alanknight): implement time zone support
+ String formatTimeZoneId(Date date) {
+ return 'not implemented';
+ }
+
+ String formatTimeZone(Date date) {
+ return 'not implemented';
Emily Fortuna 2012/07/31 05:52:53 throw NotImplementedException
Alan Knight 2012/08/03 23:02:15 Done.
+ }
+
+ String formatTimeZoneRFC(Date date) {
+ return 'not implemented';
+ }
+
/**
- * Format the given [date] object according to preset pattern and current
- * locale and return a formated string for the given date.
+ * Return a string representation of the object padded to the left with
+ * zeros. Primarily useful for numbers.
*/
- String format(Date date) {
- // TODO(efortuna): readd optional TimeZone argument (or similar)?
- return date.toString();
+ String padTo(int width, Object toBePrinted) {
+ var basicString = toBePrinted.toString();
+ if (basicString.length >= width) return basicString;
+ var buffer = new StringBuffer();
+ for (var i = 0; i < width - basicString.length; i++) {
+ buffer.add('0');
+ }
+ buffer.add(basicString);
+ return buffer.toString();
}
+}
+/** A class for holding onto the data for a date so that it can be built
Emily Fortuna 2012/07/31 05:52:53 separate line /**
Alan Knight 2012/08/03 23:02:15 Done.
+ * up incrementally.
+ */
+class _DateBuilder {
+ int year = 0,
+ month = 0,
+ day = 0,
+ hour = 0,
+ minute = 0,
+ second = 0,
+ fractionalSecond = 0;
+ bool pm = false;
+
+ // Functions that exist just be closurized so we can pass them to a general
+ // method.
+ void setYear(x) {year = x;}
Emily Fortuna 2012/07/31 05:52:53 void setYear(x) => year = x or void setYear(x) {
Alan Knight 2012/08/03 23:02:15 Done. I'd prefer the => form, but using it with a
Emily Fortuna 2012/08/06 22:43:00 Oh.. weird. I thought that it was okay to have =>
+ 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;}
+
/**
- * Returns a date string indicating how long ago (3 hours, 2 minutes)
- * something has happened or how long in the future something will happen
- * given a [reference] Date relative to the current time.
+ * Return a date built using our values. If no date portion is set,
+ * use today's date, as otherwise the constructor will fail.
*/
- String formatDuration(Date reference) {
- return '';
+ Date asDate() {
+ if (year == 0) {
Emily Fortuna 2012/07/31 05:52:53 if year !=0 but month or day == 0?
Alan Knight 2012/08/03 23:02:15 Done.
+ var today = new Date.now();
+ year = today.year;
+ if (month == 0) month = today.month;
+ if (day == 0) day = today.day;
+ }
+ return new Date(
+ year,
+ month,
+ day,
+ pm ? hour + 12 : hour,
+ minute,
+ second,
+ fractionalSecond,
+ false);
}
+}
+/**
+ * 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.
+ */
+class _Stream {
+ var contents;
+ int index = 0;
+
+ _Stream(this.contents);
+
+ bool atEnd() => index >= contents.length;
+
+ Dynamic next() => contents[index++];
+
+ /** Return the next [howMany] items, or as many as there are remaining.
Emily Fortuna 2012/07/31 05:52:53 /** on own line
Alan Knight 2012/08/03 23:02:15 Done.
+ * Advance the stream by that many positions.
+ */
+ read([howMany = 1]) {
+ var result = peek(howMany);
+ index += howMany;
+ return result;
+ }
+
/**
- * Formats a string indicating how long ago (negative [duration]) or how far
- * in the future (positive [duration]) some time is with respect to a
- * reference [date].
+ * Return the next [howMany] items, or as many as there are remaining.
+ * Does not modify the stream position.
*/
- String formatDurationFrom(Duration duration, Date date) {
- return '';
+ peek([howMany = 1]) {
+ var result;
+ if (contents is String) {
+ result = contents.substring(
+ index,
+ Math.min(index + howMany, contents.length));
+ } else {
+ // Assume List
+ result = contents.getRange(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.*/
+ int findIndex(Function f) {
Emily Fortuna 2012/07/31 05:52:53 This also has the side effect of modifying index/a
Alan Knight 2012/08/03 23:02:15 Done.
+ while (!atEnd()) {
+ if (f(next())) return index - 1;
+ }
+ return null;
+ }
+
+ /** Find the indexes of all the elements for which [f] returns true*/
+ 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.
+ */
+ int nextInteger() {
+ var validDigits = '0123456789';
+ var digits = [];
+ while (!atEnd() && (validDigits.contains(peek()))) {
+ digits.add(next().charCodeAt(0));
Emily Fortuna 2012/07/31 05:52:53 this could be done faster with a regular expressio
Alan Knight 2012/08/03 23:02:15 It's not obvious to me how to do that without comp
Emily Fortuna 2012/08/06 22:43:00 Alrighty. Nevermind then. Seemed to make more sens
+ }
+ return Math.parseInt(new String.fromCharCodes(digits));
+ }
}
+
+/**
+ * Exception indicating that we could not parse a particular string into a
+ * Date according to the specified pattern.
+ */
+class FormatException implements Exception {
+ final _Stream input;
+ final _DateFormatField field;
+
+ const FormatException(this.input, this.field);
+
+ toString() => "FormatException: $field matching ${input.rest}";
+
Emily Fortuna 2012/07/31 05:52:53 whitespace
Alan Knight 2012/08/03 23:02:15 Done.
+}
« no previous file with comments | « no previous file | lib/i18n/date_symbol_data.dart » ('j') | lib/i18n/date_symbol_data.dart » ('J')

Powered by Google App Engine
This is Rietveld 408576698