blob: 0cedd9a6d88b8ac5b3f9289acf406af640679b99 [file] [log] [blame]
// Copyright (c) 2014, 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.
import 'package:string_scanner/string_scanner.dart';
import 'utils.dart';
const _weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
const _months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec'
];
final _shortWeekdayRegExp = RegExp(r'Mon|Tue|Wed|Thu|Fri|Sat|Sun');
final _longWeekdayRegExp =
RegExp(r'Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday');
final _monthRegExp = RegExp(r'Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec');
final _digitRegExp = RegExp(r'\d+');
/// Return a HTTP-formatted string representation of [date].
///
/// This follows [RFC 822](http://tools.ietf.org/html/rfc822) as updated by
/// [RFC 1123](http://tools.ietf.org/html/rfc1123).
String formatHttpDate(DateTime date) {
date = date.toUtc();
final buffer = StringBuffer()
..write(_weekdays[date.weekday - 1])
..write(', ')
..write(date.day <= 9 ? '0' : '')
..write(date.day.toString())
..write(' ')
..write(_months[date.month - 1])
..write(' ')
..write(date.year.toString())
..write(date.hour <= 9 ? ' 0' : ' ')
..write(date.hour.toString())
..write(date.minute <= 9 ? ':0' : ':')
..write(date.minute.toString())
..write(date.second <= 9 ? ':0' : ':')
..write(date.second.toString())
..write(' GMT');
return buffer.toString();
}
/// Parses an HTTP-formatted date into a UTC [DateTime].
///
/// This follows [RFC 2616](http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3).
/// It will throw a [FormatException] if [date] is invalid.
DateTime parseHttpDate(String date) =>
wrapFormatException('HTTP date', date, () {
final scanner = StringScanner(date);
if (scanner.scan(_longWeekdayRegExp)) {
// RFC 850 starts with a long weekday.
scanner.expect(', ');
final day = _parseInt(scanner, 2);
scanner.expect('-');
final month = _parseMonth(scanner);
scanner.expect('-');
final year = 1900 + _parseInt(scanner, 2);
scanner.expect(' ');
final time = _parseTime(scanner);
scanner.expect(' GMT');
scanner.expectDone();
return _makeDateTime(year, month, day, time);
}
// RFC 1123 and asctime both start with a short weekday.
scanner.expect(_shortWeekdayRegExp);
if (scanner.scan(', ')) {
// RFC 1123 follows the weekday with a comma.
final day = _parseInt(scanner, 2);
scanner.expect(' ');
final month = _parseMonth(scanner);
scanner.expect(' ');
final year = _parseInt(scanner, 4);
scanner.expect(' ');
final time = _parseTime(scanner);
scanner.expect(' GMT');
scanner.expectDone();
return _makeDateTime(year, month, day, time);
}
// asctime follows the weekday with a space.
scanner.expect(' ');
final month = _parseMonth(scanner);
scanner.expect(' ');
final day =
scanner.scan(' ') ? _parseInt(scanner, 1) : _parseInt(scanner, 2);
scanner.expect(' ');
final time = _parseTime(scanner);
scanner.expect(' ');
final year = _parseInt(scanner, 4);
scanner.expectDone();
return _makeDateTime(year, month, day, time);
});
/// Parses a short-form month name to a form accepted by [DateTime].
int _parseMonth(StringScanner scanner) {
scanner.expect(_monthRegExp);
// DateTime uses 1-indexed months.
return _months.indexOf(scanner.lastMatch![0]!) + 1;
}
/// Parses an int an enforces that it has exactly [digits] digits.
int _parseInt(StringScanner scanner, int digits) {
scanner.expect(_digitRegExp);
if (scanner.lastMatch![0]!.length != digits) {
scanner.error('expected a $digits-digit number.');
}
return int.parse(scanner.lastMatch![0]!);
}
/// Parses an timestamp of the form "HH:MM:SS" on a 24-hour clock.
DateTime _parseTime(StringScanner scanner) {
final hours = _parseInt(scanner, 2);
if (hours >= 24) scanner.error('hours may not be greater than 24.');
scanner.expect(':');
final minutes = _parseInt(scanner, 2);
if (minutes >= 60) scanner.error('minutes may not be greater than 60.');
scanner.expect(':');
final seconds = _parseInt(scanner, 2);
if (seconds >= 60) scanner.error('seconds may not be greater than 60.');
return DateTime(1, 1, 1, hours, minutes, seconds);
}
/// Returns a UTC [DateTime] from the given components.
///
/// Validates that [day] is a valid day for [month]. If it's not, throws a
/// [FormatException].
DateTime _makeDateTime(int year, int month, int day, DateTime time) {
final dateTime =
DateTime.utc(year, month, day, time.hour, time.minute, time.second);
// If [day] was too large, it will cause [month] to overflow.
if (dateTime.month != month) {
throw FormatException("invalid day '$day' for month '$month'.");
}
return dateTime;
}