// Copyright (c) 2011, 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. // @dart = 2.9 part of utilslib; /** * General purpose date/time utilities. */ class DateUtils { // TODO(jmesserly): localized strings static const WEEKDAYS = const [ 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' ]; static const YESTERDAY = 'Yesterday'; static const MS_IN_WEEK = DateTime.daysPerWeek * Duration.millisecondsPerDay; // TODO(jmesserly): workaround for missing DateTime.fromDate in Dartium // Remove this once that is implemented. See b/5055106 // Parse a string like: "Mon, 27 Jun 2011 15:22:00 -0700" static DateTime fromString(String text) { final parts = text.split(' '); if (parts.length == 1) { return _parseIsoDate(text); } if (parts.length != 6) { throw 'bad date format, expected 6 parts: $text'; } // skip parts[0], the weekday int day = int.parse(parts[1]); final months = const [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ]; int month = months.indexOf(parts[2], 0) + 1; if (month < 0) { throw 'bad month, expected 3 letter month code, got: ${parts[2]}'; } int year = int.parse(parts[3]); final timeParts = parts[4].split(':'); if (timeParts.length != 3) { throw 'bad time format, expected 3 parts: ${parts[4]}'; } int hours = int.parse(timeParts[0]); int minutes = int.parse(timeParts[1]); int seconds = int.parse(timeParts[2]); // TODO(jmesserly): TimeZone is not implemented in Dartium. This ugly // hack applies the timezone from the string to the final time int zoneOffset = int.parse(parts[5]) ~/ 100; // Pretend it's a UTC time DateTime result = new DateTime.utc(year, month, day, hours, minutes, seconds, 0); // Shift it to the proper zone, but it's still a UTC time result = result.subtract(new Duration(hours: zoneOffset)); // Then render it as a local time return result.toLocal(); } /** Parse a string like: 2011-07-19T22:03:04.000Z */ // TODO(jmesserly): workaround for DateTime.fromDate, which has issues: // * on Dart VM it doesn't handle all of ISO 8601. See b/5055106. // * on DartC it doesn't work on Safari. See b/5062557. // Remove this once that function is fully implemented static DateTime _parseIsoDate(String text) { void ensure(bool value) { if (!value) { throw 'bad date format, expected YYYY-MM-DDTHH:MM:SS.mmmZ: $text'; } } bool isUtc = text.endsWith('Z'); if (isUtc) { text = text.substring(0, text.length - 1); } final parts = text.split('T'); ensure(parts.length == 2); final date = parts[0].split('-'); ensure(date.length == 3); final time = parts[1].split(':'); ensure(time.length == 3); final seconds = time[2].split('.'); ensure(seconds.length >= 1 && seconds.length <= 2); int milliseconds = 0; if (seconds.length == 2) { milliseconds = int.parse(seconds[1]); } return new DateTime( int.parse(date[0]), int.parse(date[1]), int.parse(date[2]), int.parse(time[0]), int.parse(time[1]), int.parse(seconds[0]), milliseconds); } /** * A date/time formatter that takes into account the current date/time: * - if it's from today, just show the time * - if it's from yesterday, just show 'Yesterday' * - if it's from the same week, just show the weekday * - otherwise, show just the date */ static String toRecentTimeString(DateTime then) { bool datesAreEqual(DateTime d1, DateTime d2) { return (d1.year == d2.year) && (d1.month == d2.month) && (d1.day == d2.day); } final now = new DateTime.now(); if (datesAreEqual(then, now)) { return toHourMinutesString(new Duration( days: 0, hours: then.hour, minutes: then.minute, seconds: then.second, milliseconds: then.millisecond)); } final today = new DateTime(now.year, now.month, now.day, 0, 0, 0, 0); Duration delta = today.difference(then); if (delta.inMilliseconds < Duration.millisecondsPerDay) { return YESTERDAY; } else if (delta.inMilliseconds < MS_IN_WEEK) { return WEEKDAYS[getWeekday(then)]; } else { // TODO(jmesserly): locale specific date format String twoDigits(int n) { if (n >= 10) return "${n}"; return "0${n}"; } String twoDigitMonth = twoDigits(then.month); String twoDigitDay = twoDigits(then.day); return "${then.year}-${twoDigitMonth}-${twoDigitDay}"; } } // TODO(jmesserly): this is a workaround for unimplemented DateTime.weekday // Code inspired by v8/src/date.js static int getWeekday(DateTime dateTime) { final unixTimeStart = new DateTime(1970, 1, 1, 0, 0, 0, 0); int msSince1970 = dateTime.difference(unixTimeStart).inMilliseconds; int daysSince1970 = msSince1970 ~/ Duration.millisecondsPerDay; // 1970-1-1 was Thursday return ((daysSince1970 + DateTime.thursday) % DateTime.daysPerWeek); } /** Formats a time in H:MM A format */ // TODO(jmesserly): should get 12 vs 24 hour clock setting from the locale static String toHourMinutesString(Duration duration) { assert(duration.inDays == 0); int hours = duration.inHours; String a; if (hours >= 12) { a = 'pm'; if (hours != 12) { hours -= 12; } } else { a = 'am'; if (hours == 0) { hours += 12; } } String twoDigits(int n) { if (n >= 10) return "${n}"; return "0${n}"; } String mm = twoDigits(duration.inMinutes.remainder(Duration.minutesPerHour)); return "${hours}:${mm} ${a}"; } }