2024-01-29 09:26:07 +08:00

453 lines
11 KiB
JavaScript

import toInteger from 'date-fns/_lib/toInteger/index.js'
import getTimezoneOffsetInMilliseconds from 'date-fns/_lib/getTimezoneOffsetInMilliseconds/index.js'
import tzParseTimezone from '../_lib/tzParseTimezone/index.js'
import tzPattern from '../_lib/tzPattern/index.js'
var MILLISECONDS_IN_HOUR = 3600000
var MILLISECONDS_IN_MINUTE = 60000
var DEFAULT_ADDITIONAL_DIGITS = 2
var patterns = {
dateTimePattern: /^([0-9W+-]+)(T| )(.*)/,
datePattern: /^([0-9W+-]+)(.*)/,
plainTime: /:/,
// year tokens
YY: /^(\d{2})$/,
YYY: [
/^([+-]\d{2})$/, // 0 additional digits
/^([+-]\d{3})$/, // 1 additional digit
/^([+-]\d{4})$/, // 2 additional digits
],
YYYY: /^(\d{4})/,
YYYYY: [
/^([+-]\d{4})/, // 0 additional digits
/^([+-]\d{5})/, // 1 additional digit
/^([+-]\d{6})/, // 2 additional digits
],
// date tokens
MM: /^-(\d{2})$/,
DDD: /^-?(\d{3})$/,
MMDD: /^-?(\d{2})-?(\d{2})$/,
Www: /^-?W(\d{2})$/,
WwwD: /^-?W(\d{2})-?(\d{1})$/,
HH: /^(\d{2}([.,]\d*)?)$/,
HHMM: /^(\d{2}):?(\d{2}([.,]\d*)?)$/,
HHMMSS: /^(\d{2}):?(\d{2}):?(\d{2}([.,]\d*)?)$/,
// time zone tokens (to identify the presence of a tz)
timeZone: tzPattern,
}
/**
* @name toDate
* @category Common Helpers
* @summary Convert the given argument to an instance of Date.
*
* @description
* Convert the given argument to an instance of Date.
*
* If the argument is an instance of Date, the function returns its clone.
*
* If the argument is a number, it is treated as a timestamp.
*
* If an argument is a string, the function tries to parse it.
* Function accepts complete ISO 8601 formats as well as partial implementations.
* ISO 8601: http://en.wikipedia.org/wiki/ISO_8601
* If the function cannot parse the string or the values are invalid, it returns Invalid Date.
*
* If the argument is none of the above, the function returns Invalid Date.
*
* **Note**: *all* Date arguments passed to any *date-fns* function is processed by `toDate`.
* All *date-fns* functions will throw `RangeError` if `options.additionalDigits` is not 0, 1, 2 or undefined.
*
* @param {Date|String|Number} argument - the value to convert
* @param {OptionsWithTZ} [options] - the object with options. See [Options]{@link https://date-fns.org/docs/Options}
* @param {0|1|2} [options.additionalDigits=2] - the additional number of digits in the extended year format
* @param {String} [options.timeZone=''] - used to specify the IANA time zone offset of a date String.
* @returns {Date} the parsed date in the local time zone
* @throws {TypeError} 1 argument required
* @throws {RangeError} `options.additionalDigits` must be 0, 1 or 2
*
* @example
* // Convert string '2014-02-11T11:30:30' to date:
* var result = toDate('2014-02-11T11:30:30')
* //=> Tue Feb 11 2014 11:30:30
*
* @example
* // Convert string '+02014101' to date,
* // if the additional number of digits in the extended year format is 1:
* var result = toDate('+02014101', {additionalDigits: 1})
* //=> Fri Apr 11 2014 00:00:00
*/
export default function toDate(argument, dirtyOptions) {
if (arguments.length < 1) {
throw new TypeError('1 argument required, but only ' + arguments.length + ' present')
}
if (argument === null) {
return new Date(NaN)
}
var options = dirtyOptions || {}
var additionalDigits =
options.additionalDigits == null
? DEFAULT_ADDITIONAL_DIGITS
: toInteger(options.additionalDigits)
if (additionalDigits !== 2 && additionalDigits !== 1 && additionalDigits !== 0) {
throw new RangeError('additionalDigits must be 0, 1 or 2')
}
// Clone the date
if (
argument instanceof Date ||
(typeof argument === 'object' && Object.prototype.toString.call(argument) === '[object Date]')
) {
// Prevent the date to lose the milliseconds when passed to new Date() in IE10
return new Date(argument.getTime())
} else if (
typeof argument === 'number' ||
Object.prototype.toString.call(argument) === '[object Number]'
) {
return new Date(argument)
} else if (
!(
typeof argument === 'string' || Object.prototype.toString.call(argument) === '[object String]'
)
) {
return new Date(NaN)
}
var dateStrings = splitDateString(argument)
var parseYearResult = parseYear(dateStrings.date, additionalDigits)
var year = parseYearResult.year
var restDateString = parseYearResult.restDateString
var date = parseDate(restDateString, year)
if (isNaN(date)) {
return new Date(NaN)
}
if (date) {
var timestamp = date.getTime()
var time = 0
var offset
if (dateStrings.time) {
time = parseTime(dateStrings.time)
if (isNaN(time)) {
return new Date(NaN)
}
}
if (dateStrings.timeZone || options.timeZone) {
offset = tzParseTimezone(dateStrings.timeZone || options.timeZone, new Date(timestamp + time))
if (isNaN(offset)) {
return new Date(NaN)
}
} else {
// get offset accurate to hour in time zones that change offset
offset = getTimezoneOffsetInMilliseconds(new Date(timestamp + time))
offset = getTimezoneOffsetInMilliseconds(new Date(timestamp + time + offset))
}
return new Date(timestamp + time + offset)
} else {
return new Date(NaN)
}
}
function splitDateString(dateString) {
var dateStrings = {}
var parts = patterns.dateTimePattern.exec(dateString)
var timeString
if (!parts) {
parts = patterns.datePattern.exec(dateString)
if (parts) {
dateStrings.date = parts[1]
timeString = parts[2]
} else {
dateStrings.date = null
timeString = dateString
}
} else {
dateStrings.date = parts[1]
timeString = parts[3]
}
if (timeString) {
var token = patterns.timeZone.exec(timeString)
if (token) {
dateStrings.time = timeString.replace(token[1], '')
dateStrings.timeZone = token[1].trim()
} else {
dateStrings.time = timeString
}
}
return dateStrings
}
function parseYear(dateString, additionalDigits) {
var patternYYY = patterns.YYY[additionalDigits]
var patternYYYYY = patterns.YYYYY[additionalDigits]
var token
// YYYY or ±YYYYY
token = patterns.YYYY.exec(dateString) || patternYYYYY.exec(dateString)
if (token) {
var yearString = token[1]
return {
year: parseInt(yearString, 10),
restDateString: dateString.slice(yearString.length),
}
}
// YY or ±YYY
token = patterns.YY.exec(dateString) || patternYYY.exec(dateString)
if (token) {
var centuryString = token[1]
return {
year: parseInt(centuryString, 10) * 100,
restDateString: dateString.slice(centuryString.length),
}
}
// Invalid ISO-formatted year
return {
year: null,
}
}
function parseDate(dateString, year) {
// Invalid ISO-formatted year
if (year === null) {
return null
}
var token
var date
var month
var week
// YYYY
if (dateString.length === 0) {
date = new Date(0)
date.setUTCFullYear(year)
return date
}
// YYYY-MM
token = patterns.MM.exec(dateString)
if (token) {
date = new Date(0)
month = parseInt(token[1], 10) - 1
if (!validateDate(year, month)) {
return new Date(NaN)
}
date.setUTCFullYear(year, month)
return date
}
// YYYY-DDD or YYYYDDD
token = patterns.DDD.exec(dateString)
if (token) {
date = new Date(0)
var dayOfYear = parseInt(token[1], 10)
if (!validateDayOfYearDate(year, dayOfYear)) {
return new Date(NaN)
}
date.setUTCFullYear(year, 0, dayOfYear)
return date
}
// yyyy-MM-dd or YYYYMMDD
token = patterns.MMDD.exec(dateString)
if (token) {
date = new Date(0)
month = parseInt(token[1], 10) - 1
var day = parseInt(token[2], 10)
if (!validateDate(year, month, day)) {
return new Date(NaN)
}
date.setUTCFullYear(year, month, day)
return date
}
// YYYY-Www or YYYYWww
token = patterns.Www.exec(dateString)
if (token) {
week = parseInt(token[1], 10) - 1
if (!validateWeekDate(year, week)) {
return new Date(NaN)
}
return dayOfISOWeekYear(year, week)
}
// YYYY-Www-D or YYYYWwwD
token = patterns.WwwD.exec(dateString)
if (token) {
week = parseInt(token[1], 10) - 1
var dayOfWeek = parseInt(token[2], 10) - 1
if (!validateWeekDate(year, week, dayOfWeek)) {
return new Date(NaN)
}
return dayOfISOWeekYear(year, week, dayOfWeek)
}
// Invalid ISO-formatted date
return null
}
function parseTime(timeString) {
var token
var hours
var minutes
// hh
token = patterns.HH.exec(timeString)
if (token) {
hours = parseFloat(token[1].replace(',', '.'))
if (!validateTime(hours)) {
return NaN
}
return (hours % 24) * MILLISECONDS_IN_HOUR
}
// hh:mm or hhmm
token = patterns.HHMM.exec(timeString)
if (token) {
hours = parseInt(token[1], 10)
minutes = parseFloat(token[2].replace(',', '.'))
if (!validateTime(hours, minutes)) {
return NaN
}
return (hours % 24) * MILLISECONDS_IN_HOUR + minutes * MILLISECONDS_IN_MINUTE
}
// hh:mm:ss or hhmmss
token = patterns.HHMMSS.exec(timeString)
if (token) {
hours = parseInt(token[1], 10)
minutes = parseInt(token[2], 10)
var seconds = parseFloat(token[3].replace(',', '.'))
if (!validateTime(hours, minutes, seconds)) {
return NaN
}
return (hours % 24) * MILLISECONDS_IN_HOUR + minutes * MILLISECONDS_IN_MINUTE + seconds * 1000
}
// Invalid ISO-formatted time
return null
}
function dayOfISOWeekYear(isoWeekYear, week, day) {
week = week || 0
day = day || 0
var date = new Date(0)
date.setUTCFullYear(isoWeekYear, 0, 4)
var fourthOfJanuaryDay = date.getUTCDay() || 7
var diff = week * 7 + day + 1 - fourthOfJanuaryDay
date.setUTCDate(date.getUTCDate() + diff)
return date
}
// Validation functions
var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
var DAYS_IN_MONTH_LEAP_YEAR = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
function isLeapYearIndex(year) {
return year % 400 === 0 || (year % 4 === 0 && year % 100 !== 0)
}
function validateDate(year, month, date) {
if (month < 0 || month > 11) {
return false
}
if (date != null) {
if (date < 1) {
return false
}
var isLeapYear = isLeapYearIndex(year)
if (isLeapYear && date > DAYS_IN_MONTH_LEAP_YEAR[month]) {
return false
}
if (!isLeapYear && date > DAYS_IN_MONTH[month]) {
return false
}
}
return true
}
function validateDayOfYearDate(year, dayOfYear) {
if (dayOfYear < 1) {
return false
}
var isLeapYear = isLeapYearIndex(year)
if (isLeapYear && dayOfYear > 366) {
return false
}
if (!isLeapYear && dayOfYear > 365) {
return false
}
return true
}
function validateWeekDate(year, week, day) {
if (week < 0 || week > 52) {
return false
}
if (day != null && (day < 0 || day > 6)) {
return false
}
return true
}
function validateTime(hours, minutes, seconds) {
if (hours != null && (hours < 0 || hours >= 25)) {
return false
}
if (minutes != null && (minutes < 0 || minutes >= 60)) {
return false
}
if (seconds != null && (seconds < 0 || seconds >= 60)) {
return false
}
return true
}