text: import getUTCWeekYear from '../../../_lib/getUTCWeekYear/index'
  import setUTCDay from '../../../_lib/setUTCDay/index'
  import setUTCISODay from '../../../_lib/setUTCISODay/index'
  import setUTCISOWeek from '../../../_lib/setUTCISOWeek/index'
  import setUTCWeek from '../../../_lib/setUTCWeek/index'
  import startOfUTCISOWeek from '../../../_lib/startOfUTCISOWeek/index'
  import startOfUTCWeek from '../../../_lib/startOfUTCWeek/index'
  
  var MILLISECONDS_IN_HOUR = 3600000
  var MILLISECONDS_IN_MINUTE = 60000
  var MILLISECONDS_IN_SECOND = 1000
  
  var numericPatterns = {
    month: /^(1[0-2]|0?\d)/, // 0 to 12
    date: /^(3[0-1]|[0-2]?\d)/, // 0 to 31
    dayOfYear: /^(36[0-6]|3[0-5]\d|[0-2]?\d?\d)/, // 0 to 366
    week: /^(5[0-3]|[0-4]?\d)/, // 0 to 53
    hour23h: /^(2[0-3]|[0-1]?\d)/, // 0 to 23
    hour24h: /^(2[0-4]|[0-1]?\d)/, // 0 to 24
    hour11h: /^(1[0-1]|0?\d)/, // 0 to 11
    hour12h: /^(1[0-2]|0?\d)/, // 0 to 12
    minute: /^[0-5]?\d/, // 0 to 59
    second: /^[0-5]?\d/, // 0 to 59
  
    singleDigit: /^\d/, // 0 to 9
    twoDigits: /^\d{1,2}/, // 0 to 99
    threeDigits: /^\d{1,3}/, // 0 to 999
    fourDigits: /^\d{1,4}/, // 0 to 9999
  
    anyDigitsSigned: /^-?\d+/,
    singleDigitSigned: /^-?\d/, // 0 to 9, -0 to -9
    twoDigitsSigned: /^-?\d{1,2}/, // 0 to 99, -0 to -99
    threeDigitsSigned: /^-?\d{1,3}/, // 0 to 999, -0 to -999
    fourDigitsSigned: /^-?\d{1,4}/ // 0 to 9999, -0 to -9999
  }
  
  var timezonePatterns = {
    basicOptionalMinutes: /^([+-])(\d{2})(\d{2})?|Z/,
    basic: /^([+-])(\d{2})(\d{2})|Z/,
    basicOptionalSeconds: /^([+-])(\d{2})(\d{2})((\d{2}))?|Z/,
    extended: /^([+-])(\d{2}):(\d{2})|Z/,
    extendedOptionalSeconds: /^([+-])(\d{2}):(\d{2})(:(\d{2}))?|Z/
  }
  
  function parseNumericPattern(pattern, string, valueCallback) {
    var matchResult = string.match(pattern)
  
    if (!matchResult) {
      return null
    }
  
    var value = parseInt(matchResult[0], 10)
  
    return {
      value: valueCallback ? valueCallback(value) : value,
      rest: string.slice(matchResult[0].length)
    }
  }
  
  function parseTimezonePattern(pattern, string) {
    var matchResult = string.match(pattern)
  
    if (!matchResult) {
      return null
    }
  
    // Input is 'Z'
    if (matchResult[0] === 'Z') {
      return {
        value: 0,
        rest: string.slice(1)
      }
    }
  
    var sign = matchResult[1] === '+' ? 1 : -1
    var hours = matchResult[2] ? parseInt(matchResult[2], 10) : 0
    var minutes = matchResult[3] ? parseInt(matchResult[3], 10) : 0
    var seconds = matchResult[5] ? parseInt(matchResult[5], 10) : 0
  
    return {
      value:
        sign *
        (hours * MILLISECONDS_IN_HOUR +
          minutes * MILLISECONDS_IN_MINUTE +
          seconds * MILLISECONDS_IN_SECOND),
      rest: string.slice(matchResult[0].length)
    }
  }
  
  function parseAnyDigitsSigned(string, valueCallback) {
    return parseNumericPattern(
      numericPatterns.anyDigitsSigned,
      string,
      valueCallback
    )
  }
  
  function parseNDigits(n, string, valueCallback) {
    switch (n) {
      case 1:
        return parseNumericPattern(
          numericPatterns.singleDigit,
          string,
          valueCallback
        )
      case 2:
        return parseNumericPattern(
          numericPatterns.twoDigits,
          string,
          valueCallback
        )
      case 3:
        return parseNumericPattern(
          numericPatterns.threeDigits,
          string,
          valueCallback
        )
      case 4:
        return parseNumericPattern(
          numericPatterns.fourDigits,
          string,
          valueCallback
        )
      default:
        return parseNumericPattern(
          new RegExp('^\\d{1,' + n + '}'),
          string,
          valueCallback
        )
    }
  }
  
  function parseNDigitsSigned(n, string, valueCallback) {
    switch (n) {
      case 1:
        return parseNumericPattern(
          numericPatterns.singleDigitSigned,
          string,
          valueCallback
        )
      case 2:
        return parseNumericPattern(
          numericPatterns.twoDigitsSigned,
          string,
          valueCallback
        )
      case 3:
        return parseNumericPattern(
          numericPatterns.threeDigitsSigned,
          string,
          valueCallback
        )
      case 4:
        return parseNumericPattern(
          numericPatterns.fourDigitsSigned,
          string,
          valueCallback
        )
      default:
        return parseNumericPattern(
          new RegExp('^-?\\d{1,' + n + '}'),
          string,
          valueCallback
        )
    }
  }
  
  function dayPeriodEnumToHours(enumValue) {
    switch (enumValue) {
      case 'morning':
        return 4
      case 'evening':
        return 17
      case 'pm':
      case 'noon':
      case 'afternoon':
        return 12
      case 'am':
      case 'midnight':
      case 'night':
      default:
        return 0
    }
  }
  
  function normalizeTwoDigitYear(twoDigitYear, currentYear) {
    var isCommonEra = currentYear > 0
    // Absolute number of the current year:
    // 1 -> 1 AC
    // 0 -> 1 BC
    // -1 -> 2 BC
    var absCurrentYear = isCommonEra ? currentYear : 1 - currentYear
  
    var result
    if (absCurrentYear <= 50) {
      result = twoDigitYear || 100
    } else {
      var rangeEnd = absCurrentYear + 50
      var rangeEndCentury = Math.floor(rangeEnd / 100) * 100
      var isPreviousCentury = twoDigitYear >= rangeEnd % 100
      result = twoDigitYear + rangeEndCentury - (isPreviousCentury ? 100 : 0)
    }
  
    return isCommonEra ? result : 1 - result
  }
  
  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]
  
  // User for validation
  function isLeapYearIndex(year) {
    return year % 400 === 0 || (year % 4 === 0 && year % 100 !== 0)
  }
  
  /*
   * |     | Unit                           |     | Unit                           |
   * |-----|--------------------------------|-----|--------------------------------|
   * |  a  | AM, PM                         |  A* | Milliseconds in day            |
   * |  b  | AM, PM, noon, midnight         |  B  | Flexible day period            |
   * |  c  | Stand-alone local day of week  |  C* | Localized hour w/ day period   |
   * |  d  | Day of month                   |  D  | Day of year                    |
   * |  e  | Local day of week              |  E  | Day of week                    |
   * |  f  |                                |  F* | Day of week in month           |
   * |  g* | Modified Julian day            |  G  | Era                            |
   * |  h  | Hour [1-12]                    |  H  | Hour [0-23]                    |
   * |  i! | ISO day of week                |  I! | ISO week of year               |
   * |  j* | Localized hour w/ day period   |  J* | Localized hour w/o day period  |
   * |  k  | Hour [1-24]                    |  K  | Hour [0-11]                    |
   * |  l* | (deprecated)                   |  L  | Stand-alone month              |
   * |  m  | Minute                         |  M  | Month                          |
   * |  n  |                                |  N  |                                |
   * |  o! | Ordinal number modifier        |  O* | Timezone (GMT)                 |
   * |  p  |                                |  P  |                                |
   * |  q  | Stand-alone quarter            |  Q  | Quarter                        |
   * |  r* | Related Gregorian year         |  R! | ISO week-numbering year        |
   * |  s  | Second                         |  S  | Fraction of second             |
   * |  t! | Seconds timestamp              |  T! | Milliseconds timestamp         |
   * |  u  | Extended year                  |  U* | Cyclic year                    |
   * |  v* | Timezone (generic non-locat.)  |  V* | Timezone (location)            |
   * |  w  | Local week of year             |  W* | Week of month                  |
   * |  x  | Timezone (ISO-8601 w/o Z)      |  X  | Timezone (ISO-8601)            |
   * |  y  | Year (abs)                     |  Y  | Local week-numbering year      |
   * |  z* | Timezone (specific non-locat.) |  Z* | Timezone (aliases)             |
   *
   * Letters marked by * are not implemented but reserved by Unicode standard.
   *
   * Letters marked by ! are non-standard, but implemented by date-fns:
   * - `o` modifies the previous token to turn it into an ordinal (see `parse` docs)
   * - `i` is ISO day of week. For `i` and `ii` is returns numeric ISO week days,
   *   i.e. 7 for Sunday, 1 for Monday, etc.
   * - `I` is ISO week of year, as opposed to `w` which is local week of year.
   * - `R` is ISO week-numbering year, as opposed to `Y` which is local week-numbering year.
   *   `R` is supposed to be used in conjunction with `I` and `i`
   *   for universal ISO week-numbering date, whereas
   *   `Y` is supposed to be used in conjunction with `w` and `e`
   *   for week-numbering date specific to the locale.
   */
  var parsers = {
    // Era
    G: {
      priority: 140,
  
      parse: function(string, token, match, _options) {
        switch (token) {
          // AD, BC
          case 'G':
          case 'GG':
          case 'GGG':
            return (
              match.era(string, { width: 'abbreviated' }) ||
              match.era(string, { width: 'narrow' })
            )
          // A, B
          case 'GGGGG':
            return match.era(string, { width: 'narrow' })
          // Anno Domini, Before Christ
          case 'GGGG':
          default:
            return (
              match.era(string, { width: 'wide' }) ||
              match.era(string, { width: 'abbreviated' }) ||
              match.era(string, { width: 'narrow' })
            )
        }
      },
  
      set: function(date, flags, value, _options) {
        flags.era = value
        date.setUTCFullYear(value, 0, 1)
        date.setUTCHours(0, 0, 0, 0)
        return date
      },
  
      incompatibleTokens: ['R', 'u', 't', 'T']
    },
  
    // Year
    y: {
      // From http://www.unicode.org/reports/tr35/tr35-31/tr35-dates.html#Date_Format_Patterns
      // | Year     |     y | yy |   yyy |  yyyy | yyyyy |
      // |----------|-------|----|-------|-------|-------|
      // | AD 1     |     1 | 01 |   001 |  0001 | 00001 |
      // | AD 12    |    12 | 12 |   012 |  0012 | 00012 |
      // | AD 123   |   123 | 23 |   123 |  0123 | 00123 |
      // | AD 1234  |  1234 | 34 |  1234 |  1234 | 01234 |
      // | AD 12345 | 12345 | 45 | 12345 | 12345 | 12345 |
  
      priority: 130,
  
      parse: function(string, token, match, _options) {
        var valueCallback = function(year) {
          return {
            year: year,
            isTwoDigitYear: token === 'yy'
          }
        }
  
        switch (token) {
          case 'y':
            return parseNDigits(4, string, valueCallback)
          case 'yo':
            return match.ordinalNumber(string, {
              unit: 'year',
              valueCallback: valueCallback
            })
          default:
            return parseNDigits(token.length, string, valueCallback)
        }
      },
  
      validate: function(_date, value, _options) {
        return value.isTwoDigitYear || value.year > 0
      },
  
      set: function(date, flags, value, _options) {
        var currentYear = date.getUTCFullYear()
  
        if (value.isTwoDigitYear) {
          var normalizedTwoDigitYear = normalizeTwoDigitYear(
            value.year,
            currentYear
          )
          date.setUTCFullYear(normalizedTwoDigitYear, 0, 1)
          date.setUTCHours(0, 0, 0, 0)
          return date
        }
  
        var year =
          !('era' in flags) || flags.era === 1 ? value.year : 1 - value.year
        date.setUTCFullYear(year, 0, 1)
        date.setUTCHours(0, 0, 0, 0)
        return date
      },
  
      incompatibleTokens: ['Y', 'R', 'u', 'w', 'I', 'i', 'e', 'c', 't', 'T']
    },
  
    // Local week-numbering year
    Y: {
      priority: 130,
  
      parse: function(string, token, match, _options) {
        var valueCallback = function(year) {
          return {
            year: year,
            isTwoDigitYear: token === 'YY'
          }
        }
  
        switch (token) {
          case 'Y':
            return parseNDigits(4, string, valueCallback)
          case 'Yo':
            return match.ordinalNumber(string, {
              unit: 'year',
              valueCallback: valueCallback
            })
          default:
            return parseNDigits(token.length, string, valueCallback)
        }
      },
  
      validate: function(_date, value, _options) {
        return value.isTwoDigitYear || value.year > 0
      },
  
      set: function(date, flags, value, options) {
        var currentYear = getUTCWeekYear(date, options)
  
        if (value.isTwoDigitYear) {
          var normalizedTwoDigitYear = normalizeTwoDigitYear(
            value.year,
            currentYear
          )
          date.setUTCFullYear(
            normalizedTwoDigitYear,
            0,
            options.firstWeekContainsDate
          )
          date.setUTCHours(0, 0, 0, 0)
          return startOfUTCWeek(date, options)
        }
  
        var year =
          !('era' in flags) || flags.era === 1 ? value.year : 1 - value.year
        date.setUTCFullYear(year, 0, options.firstWeekContainsDate)
        date.setUTCHours(0, 0, 0, 0)
        return startOfUTCWeek(date, options)
      },
  
      incompatibleTokens: [
        'y',
        'R',
        'u',
        'Q',
        'q',
        'M',
        'L',
        'I',
        'd',
        'D',
        'i',
        't',
        'T'
      ]
    },
  
    // ISO week-numbering year
    R: {
      priority: 130,
  
      parse: function(string, token, _match, _options) {
        if (token === 'R') {
          return parseNDigitsSigned(4, string)
        }
  
        return parseNDigitsSigned(token.length, string)
      },
  
      set: function(_date, _flags, value, _options) {
        var firstWeekOfYear = new Date(0)
        firstWeekOfYear.setUTCFullYear(value, 0, 4)
        firstWeekOfYear.setUTCHours(0, 0, 0, 0)
        return startOfUTCISOWeek(firstWeekOfYear)
      },
  
      incompatibleTokens: [
        'G',
        'y',
        'Y',
        'u',
        'Q',
        'q',
        'M',
        'L',
        'w',
        'd',
        'D',
        'e',
        'c',
        't',
        'T'
      ]
    },
  
    // Extended year
    u: {
      priority: 130,
  
      parse: function(string, token, _match, _options) {
        if (token === 'u') {
          return parseNDigitsSigned(4, string)
        }
  
        return parseNDigitsSigned(token.length, string)
      },
  
      set: function(date, _flags, value, _options) {
        date.setUTCFullYear(value, 0, 1)
        date.setUTCHours(0, 0, 0, 0)
        return date
      },
  
      incompatibleTokens: ['G', 'y', 'Y', 'R', 'w', 'I', 'i', 'e', 'c', 't', 'T']
    },
  
    // Quarter
    Q: {
      priority: 120,
  
      parse: function(string, token, match, _options) {
        switch (token) {
          // 1, 2, 3, 4
          case 'Q':
          case 'QQ': // 01, 02, 03, 04
            return parseNDigits(token.length, string)
          // 1st, 2nd, 3rd, 4th
          case 'Qo':
            return match.ordinalNumber(string, { unit: 'quarter' })
          // Q1, Q2, Q3, Q4
          case 'QQQ':
            return (
              match.quarter(string, {
                width: 'abbreviated',
                context: 'formatting'
              }) ||
              match.quarter(string, { width: 'narrow', context: 'formatting' })
            )
          // 1, 2, 3, 4 (narrow quarter; could be not numerical)
          case 'QQQQQ':
            return match.quarter(string, {
              width: 'narrow',
              context: 'formatting'
            })
          // 1st quarter, 2nd quarter, ...
          case 'QQQQ':
          default:
            return (
              match.quarter(string, { width: 'wide', context: 'formatting' }) ||
              match.quarter(string, {
                width: 'abbreviated',
                context: 'formatting'
              }) ||
              match.quarter(string, { width: 'narrow', context: 'formatting' })
            )
        }
      },
  
      validate: function(_date, value, _options) {
        return value >= 1 && value <= 4
      },
  
      set: function(date, _flags, value, _options) {
        date.setUTCMonth((value - 1) * 3, 1)
        date.setUTCHours(0, 0, 0, 0)
        return date
      },
  
      incompatibleTokens: [
        'Y',
        'R',
        'q',
        'M',
        'L',
        'w',
        'I',
        'd',
        'D',
        'i',
        'e',
        'c',
        't',
        'T'
      ]
    },
  
    // Stand-alone quarter
    q: {
      priority: 120,
  
      parse: function(string, token, match, _options) {
        switch (token) {
          // 1, 2, 3, 4
          case 'q':
          case 'qq': // 01, 02, 03, 04
            return parseNDigits(token.length, string)
          // 1st, 2nd, 3rd, 4th
          case 'qo':
            return match.ordinalNumber(string, { unit: 'quarter' })
          // Q1, Q2, Q3, Q4
          case 'qqq':
            return (
              match.quarter(string, {
                width: 'abbreviated',
                context: 'standalone'
              }) ||
              match.quarter(string, { width: 'narrow', context: 'standalone' })
            )
          // 1, 2, 3, 4 (narrow quarter; could be not numerical)
          case 'qqqqq':
            return match.quarter(string, {
              width: 'narrow',
              context: 'standalone'
            })
          // 1st quarter, 2nd quarter, ...
          case 'qqqq':
          default:
            return (
              match.quarter(string, { width: 'wide', context: 'standalone' }) ||
              match.quarter(string, {
                width: 'abbreviated',
                context: 'standalone'
              }) ||
              match.quarter(string, { width: 'narrow', context: 'standalone' })
            )
        }
      },
  
      validate: function(_date, value, _options) {
        return value >= 1 && value <= 4
      },
  
      set: function(date, _flags, value, _options) {
        date.setUTCMonth((value - 1) * 3, 1)
        date.setUTCHours(0, 0, 0, 0)
        return date
      },
  
      incompatibleTokens: [
        'Y',
        'R',
        'Q',
        'M',
        'L',
        'w',
        'I',
        'd',
        'D',
        'i',
        'e',
        'c',
        't',
        'T'
      ]
    },
  
    // Month
    M: {
      priority: 110,
  
      parse: function(string, token, match, _options) {
        var valueCallback = function(value) {
          return value - 1
        }
  
        switch (token) {
          // 1, 2, ..., 12
          case 'M':
            return parseNumericPattern(
              numericPatterns.month,
              string,
              valueCallback
            )
          // 01, 02, ..., 12
          case 'MM':
            return parseNDigits(2, string, valueCallback)
          // 1st, 2nd, ..., 12th
          case 'Mo':
            return match.ordinalNumber(string, {
              unit: 'month',
              valueCallback: valueCallback
            })
          // Jan, Feb, ..., Dec
          case 'MMM':
            return (
              match.month(string, {
                width: 'abbreviated',
                context: 'formatting'
              }) ||
              match.month(string, { width: 'narrow', context: 'formatting' })
            )
          // J, F, ..., D
          case 'MMMMM':
            return match.month(string, { width: 'narrow', context: 'formatting' })
          // January, February, ..., December
          case 'MMMM':
          default:
            return (
              match.month(string, { width: 'wide', context: 'formatting' }) ||
              match.month(string, {
                width: 'abbreviated',
                context: 'formatting'
              }) ||
              match.month(string, { width: 'narrow', context: 'formatting' })
            )
        }
      },
  
      validate: function(_date, value, _options) {
        return value >= 0 && value <= 11
      },
  
      set: function(date, _flags, value, _options) {
        date.setUTCMonth(value, 1)
        date.setUTCHours(0, 0, 0, 0)
        return date
      },
  
      incompatibleTokens: [
        'Y',
        'R',
        'q',
        'Q',
        'L',
        'w',
        'I',
        'D',
        'i',
        'e',
        'c',
        't',
        'T'
      ]
    },
  
    // Stand-alone month
    L: {
      priority: 110,
  
      parse: function(string, token, match, _options) {
        var valueCallback = function(value) {
          return value - 1
        }
  
        switch (token) {
          // 1, 2, ..., 12
          case 'L':
            return parseNumericPattern(
              numericPatterns.month,
              string,
              valueCallback
            )
          // 01, 02, ..., 12
          case 'LL':
            return parseNDigits(2, string, valueCallback)
          // 1st, 2nd, ..., 12th
          case 'Lo':
            return match.ordinalNumber(string, {
              unit: 'month',
              valueCallback: valueCallback
            })
          // Jan, Feb, ..., Dec
          case 'LLL':
            return (
              match.month(string, {
                width: 'abbreviated',
                context: 'standalone'
              }) ||
              match.month(string, { width: 'narrow', context: 'standalone' })
            )
          // J, F, ..., D
          case 'LLLLL':
            return match.month(string, { width: 'narrow', context: 'standalone' })
          // January, February, ..., December
          case 'LLLL':
          default:
            return (
              match.month(string, { width: 'wide', context: 'standalone' }) ||
              match.month(string, {
                width: 'abbreviated',
                context: 'standalone'
              }) ||
              match.month(string, { width: 'narrow', context: 'standalone' })
            )
        }
      },
  
      validate: function(_date, value, _options) {
        return value >= 0 && value <= 11
      },
  
      set: function(date, _flags, value, _options) {
        date.setUTCMonth(value, 1)
        date.setUTCHours(0, 0, 0, 0)
        return date
      },
  
      incompatibleTokens: [
        'Y',
        'R',
        'q',
        'Q',
        'M',
        'w',
        'I',
        'D',
        'i',
        'e',
        'c',
        't',
        'T'
      ]
    },
  
    // Local week of year
    w: {
      priority: 100,
  
      parse: function(string, token, match, _options) {
        switch (token) {
          case 'w':
            return parseNumericPattern(numericPatterns.week, string)
          case 'wo':
            return match.ordinalNumber(string, { unit: 'week' })
          default:
            return parseNDigits(token.length, string)
        }
      },
  
      validate: function(_date, value, _options) {
        return value >= 1 && value <= 53
      },
  
      set: function(date, _flags, value, options) {
        return startOfUTCWeek(setUTCWeek(date, value, options), options)
      },
  
      incompatibleTokens: [
        'y',
        'R',
        'u',
        'q',
        'Q',
        'M',
        'L',
        'I',
        'd',
        'D',
        'i',
        't',
        'T'
      ]
    },
  
    // ISO week of year
    I: {
      priority: 100,
  
      parse: function(string, token, match, _options) {
        switch (token) {
          case 'I':
            return parseNumericPattern(numericPatterns.week, string)
          case 'Io':
            return match.ordinalNumber(string, { unit: 'week' })
          default:
            return parseNDigits(token.length, string)
        }
      },
  
      validate: function(_date, value, _options) {
        return value >= 1 && value <= 53
      },
  
      set: function(date, _flags, value, options) {
        return startOfUTCISOWeek(setUTCISOWeek(date, value, options), options)
      },
  
      incompatibleTokens: [
        'y',
        'Y',
        'u',
        'q',
        'Q',
        'M',
        'L',
        'w',
        'd',
        'D',
        'e',
        'c',
        't',
        'T'
      ]
    },
  
    // Day of the month
    d: {
      priority: 90,
  
      subPriority: 1,
  
      parse: function(string, token, match, _options) {
        switch (token) {
          case 'd':
            return parseNumericPattern(numericPatterns.date, string)
          case 'do':
            return match.ordinalNumber(string, { unit: 'date' })
          default:
            return parseNDigits(token.length, string)
        }
      },
  
      validate: function(date, value, _options) {
        var year = date.getUTCFullYear()
        var isLeapYear = isLeapYearIndex(year)
        var month = date.getUTCMonth()
        if (isLeapYear) {
          return value >= 1 && value <= DAYS_IN_MONTH_LEAP_YEAR[month]
        } else {
          return value >= 1 && value <= DAYS_IN_MONTH[month]
        }
      },
  
      set: function(date, _flags, value, _options) {
        date.setUTCDate(value)
        date.setUTCHours(0, 0, 0, 0)
        return date
      },
  
      incompatibleTokens: [
        'Y',
        'R',
        'q',
        'Q',
        'w',
        'I',
        'D',
        'i',
        'e',
        'c',
        't',
        'T'
      ]
    },
  
    // Day of year
    D: {
      priority: 90,
  
      subPriority: 1,
  
      parse: function(string, token, match, _options) {
        switch (token) {
          case 'D':
          case 'DD':
            return parseNumericPattern(numericPatterns.dayOfYear, string)
          case 'Do':
            return match.ordinalNumber(string, { unit: 'date' })
          default:
            return parseNDigits(token.length, string)
        }
      },
  
      validate: function(date, value, _options) {
        var year = date.getUTCFullYear()
        var isLeapYear = isLeapYearIndex(year)
        if (isLeapYear) {
          return value >= 1 && value <= 366
        } else {
          return value >= 1 && value <= 365
        }
      },
  
      set: function(date, _flags, value, _options) {
        date.setUTCMonth(0, value)
        date.setUTCHours(0, 0, 0, 0)
        return date
      },
  
      incompatibleTokens: [
        'Y',
        'R',
        'q',
        'Q',
        'M',
        'L',
        'w',
        'I',
        'd',
        'E',
        'i',
        'e',
        'c',
        't',
        'T'
      ]
    },
  
    // Day of week
    E: {
      priority: 90,
  
      parse: function(string, token, match, _options) {
        switch (token) {
          // Tue
          case 'E':
          case 'EE':
          case 'EEE':
            return (
              match.day(string, {
                width: 'abbreviated',
                context: 'formatting'
              }) ||
              match.day(string, { width: 'short', context: 'formatting' }) ||
              match.day(string, { width: 'narrow', context: 'formatting' })
            )
          // T
          case 'EEEEE':
            return match.day(string, { width: 'narrow', context: 'formatting' })
          // Tu
          case 'EEEEEE':
            return (
              match.day(string, { width: 'short', context: 'formatting' }) ||
              match.day(string, { width: 'narrow', context: 'formatting' })
            )
          // Tuesday
          case 'EEEE':
          default:
            return (
              match.day(string, { width: 'wide', context: 'formatting' }) ||
              match.day(string, {
                width: 'abbreviated',
                context: 'formatting'
              }) ||
              match.day(string, { width: 'short', context: 'formatting' }) ||
              match.day(string, { width: 'narrow', context: 'formatting' })
            )
        }
      },
  
      validate: function(_date, value, _options) {
        return value >= 0 && value <= 6
      },
  
      set: function(date, _flags, value, options) {
        date = setUTCDay(date, value, options)
        date.setUTCHours(0, 0, 0, 0)
        return date
      },
  
      incompatibleTokens: ['D', 'i', 'e', 'c', 't', 'T']
    },
  
    // Local day of week
    e: {
      priority: 90,
      parse: function(string, token, match, options) {
        var valueCallback = function(value) {
          var wholeWeekDays = Math.floor((value - 1) / 7) * 7
          return ((value + options.weekStartsOn + 6) % 7) + wholeWeekDays
        }
  
        switch (token) {
          // 3
          case 'e':
          case 'ee': // 03
            return parseNDigits(token.length, string, valueCallback)
          // 3rd
          case 'eo':
            return match.ordinalNumber(string, {
              unit: 'day',
              valueCallback: valueCallback
            })
          // Tue
          case 'eee':
            return (
              match.day(string, {
                width: 'abbreviated',
                context: 'formatting'
              }) ||
              match.day(string, { width: 'short', context: 'formatting' }) ||
              match.day(string, { width: 'narrow', context: 'formatting' })
            )
          // T
          case 'eeeee':
            return match.day(string, { width: 'narrow', context: 'formatting' })
          // Tu
          case 'eeeeee':
            return (
              match.day(string, { width: 'short', context: 'formatting' }) ||
              match.day(string, { width: 'narrow', context: 'formatting' })
            )
          // Tuesday
          case 'eeee':
          default:
            return (
              match.day(string, { width: 'wide', context: 'formatting' }) ||
              match.day(string, {
                width: 'abbreviated',
                context: 'formatting'
              }) ||
              match.day(string, { width: 'short', context: 'formatting' }) ||
              match.day(string, { width: 'narrow', context: 'formatting' })
            )
        }
      },
      validate: function(_date, value, _options) {
        return value >= 0 && value <= 6
      },
      set: function(date, _flags, value, options) {
        date = setUTCDay(date, value, options)
        date.setUTCHours(0, 0, 0, 0)
        return date
      },
  
      incompatibleTokens: [
        'y',
        'R',
        'u',
        'q',
        'Q',
        'M',
        'L',
        'I',
        'd',
        'D',
        'E',
        'i',
        'c',
        't',
        'T'
      ]
    },
  
    // Stand-alone local day of week
    c: {
      priority: 90,
  
      parse: function(string, token, match, options) {
        var valueCallback = function(value) {
          var wholeWeekDays = Math.floor((value - 1) / 7) * 7
          return ((value + options.weekStartsOn + 6) % 7) + wholeWeekDays
        }
  
        switch (token) {
          // 3
          case 'c':
          case 'cc': // 03
            return parseNDigits(token.length, string, valueCallback)
          // 3rd
          case 'co':
            return match.ordinalNumber(string, {
              unit: 'day',
              valueCallback: valueCallback
            })
          // Tue
          case 'ccc':
            return (
              match.day(string, {
                width: 'abbreviated',
                context: 'standalone'
              }) ||
              match.day(string, { width: 'short', context: 'standalone' }) ||
              match.day(string, { width: 'narrow', context: 'standalone' })
            )
          // T
          case 'ccccc':
            return match.day(string, { width: 'narrow', context: 'standalone' })
          // Tu
          case 'cccccc':
            return (
              match.day(string, { width: 'short', context: 'standalone' }) ||
              match.day(string, { width: 'narrow', context: 'standalone' })
            )
          // Tuesday
          case 'cccc':
          default:
            return (
              match.day(string, { width: 'wide', context: 'standalone' }) ||
              match.day(string, {
                width: 'abbreviated',
                context: 'standalone'
              }) ||
              match.day(string, { width: 'short', context: 'standalone' }) ||
              match.day(string, { width: 'narrow', context: 'standalone' })
            )
        }
      },
  
      validate: function(_date, value, _options) {
        return value >= 0 && value <= 6
      },
  
      set: function(date, _flags, value, options) {
        date = setUTCDay(date, value, options)
        date.setUTCHours(0, 0, 0, 0)
        return date
      },
  
      incompatibleTokens: [
        'y',
        'R',
        'u',
        'q',
        'Q',
        'M',
        'L',
        'I',
        'd',
        'D',
        'E',
        'i',
        'e',
        't',
        'T'
      ]
    },
  
    // ISO day of week
    i: {
      priority: 90,
  
      parse: function(string, token, match, _options) {
        var valueCallback = function(value) {
          if (value === 0) {
            return 7
          }
          return value
        }
  
        switch (token) {
          // 2
          case 'i':
          case 'ii': // 02
            return parseNDigits(token.length, string)
          // 2nd
          case 'io':
            return match.ordinalNumber(string, { unit: 'day' })
          // Tue
          case 'iii':
            return (
              match.day(string, {
                width: 'abbreviated',
                context: 'formatting',
                valueCallback: valueCallback
              }) ||
              match.day(string, {
                width: 'short',
                context: 'formatting',
                valueCallback: valueCallback
              }) ||
              match.day(string, {
                width: 'narrow',
                context: 'formatting',
                valueCallback: valueCallback
              })
            )
          // T
          case 'iiiii':
            return match.day(string, {
              width: 'narrow',
              context: 'formatting',
              valueCallback: valueCallback
            })
          // Tu
          case 'iiiiii':
            return (
              match.day(string, {
                width: 'short',
                context: 'formatting',
                valueCallback: valueCallback
              }) ||
              match.day(string, {
                width: 'narrow',
                context: 'formatting',
                valueCallback: valueCallback
              })
            )
          // Tuesday
          case 'iiii':
          default:
            return (
              match.day(string, {
                width: 'wide',
                context: 'formatting',
                valueCallback: valueCallback
              }) ||
              match.day(string, {
                width: 'abbreviated',
                context: 'formatting',
                valueCallback: valueCallback
              }) ||
              match.day(string, {
                width: 'short',
                context: 'formatting',
                valueCallback: valueCallback
              }) ||
              match.day(string, {
                width: 'narrow',
                context: 'formatting',
                valueCallback: valueCallback
              })
            )
        }
      },
  
      validate: function(_date, value, _options) {
        return value >= 1 && value <= 7
      },
  
      set: function(date, _flags, value, options) {
        date = setUTCISODay(date, value, options)
        date.setUTCHours(0, 0, 0, 0)
        return date
      },
  
      incompatibleTokens: [
        'y',
        'Y',
        'u',
        'q',
        'Q',
        'M',
        'L',
        'w',
        'd',
        'D',
        'E',
        'e',
        'c',
        't',
        'T'
      ]
    },
  
    // AM or PM
    a: {
      priority: 80,
  
      parse: function(string, token, match, _options) {
        switch (token) {
          case 'a':
          case 'aa':
          case 'aaa':
            return (
              match.dayPeriod(string, {
                width: 'abbreviated',
                context: 'formatting'
              }) ||
              match.dayPeriod(string, { width: 'narrow', context: 'formatting' })
            )
          case 'aaaaa':
            return match.dayPeriod(string, {
              width: 'narrow',
              context: 'formatting'
            })
          case 'aaaa':
          default:
            return (
              match.dayPeriod(string, { width: 'wide', context: 'formatting' }) ||
              match.dayPeriod(string, {
                width: 'abbreviated',
                context: 'formatting'
              }) ||
              match.dayPeriod(string, { width: 'narrow', context: 'formatting' })
            )
        }
      },
  
      set: function(date, _flags, value, _options) {
        date.setUTCHours(dayPeriodEnumToHours(value), 0, 0, 0)
        return date
      },
  
      incompatibleTokens: ['b', 'B', 'H', 'K', 'k', 't', 'T']
    },
  
    // AM, PM, midnight
    b: {
      priority: 80,
  
      parse: function(string, token, match, _options) {
        switch (token) {
          case 'b':
          case 'bb':
          case 'bbb':
            return (
              match.dayPeriod(string, {
                width: 'abbreviated',
                context: 'formatting'
              }) ||
              match.dayPeriod(string, { width: 'narrow', context: 'formatting' })
            )
          case 'bbbbb':
            return match.dayPeriod(string, {
              width: 'narrow',
              context: 'formatting'
            })
          case 'bbbb':
          default:
            return (
              match.dayPeriod(string, { width: 'wide', context: 'formatting' }) ||
              match.dayPeriod(string, {
                width: 'abbreviated',
                context: 'formatting'
              }) ||
              match.dayPeriod(string, { width: 'narrow', context: 'formatting' })
            )
        }
      },
  
      set: function(date, _flags, value, _options) {
        date.setUTCHours(dayPeriodEnumToHours(value), 0, 0, 0)
        return date
      },
  
      incompatibleTokens: ['a', 'B', 'H', 'K', 'k', 't', 'T']
    },
  
    // in the morning, in the afternoon, in the evening, at night
    B: {
      priority: 80,
  
      parse: function(string, token, match, _options) {
        switch (token) {
          case 'B':
          case 'BB':
          case 'BBB':
            return (
              match.dayPeriod(string, {
                width: 'abbreviated',
                context: 'formatting'
              }) ||
              match.dayPeriod(string, { width: 'narrow', context: 'formatting' })
            )
          case 'BBBBB':
            return match.dayPeriod(string, {
              width: 'narrow',
              context: 'formatting'
            })
          case 'BBBB':
          default:
            return (
              match.dayPeriod(string, { width: 'wide', context: 'formatting' }) ||
              match.dayPeriod(string, {
                width: 'abbreviated',
                context: 'formatting'
              }) ||
              match.dayPeriod(string, { width: 'narrow', context: 'formatting' })
            )
        }
      },
  
      set: function(date, _flags, value, _options) {
        date.setUTCHours(dayPeriodEnumToHours(value), 0, 0, 0)
        return date
      },
  
      incompatibleTokens: ['a', 'b', 't', 'T']
    },
  
    // Hour [1-12]
    h: {
      priority: 70,
  
      parse: function(string, token, match, _options) {
        switch (token) {
          case 'h':
            return parseNumericPattern(numericPatterns.hour12h, string)
          case 'ho':
            return match.ordinalNumber(string, { unit: 'hour' })
          default:
            return parseNDigits(token.length, string)
        }
      },
  
      validate: function(_date, value, _options) {
        return value >= 1 && value <= 12
      },
  
      set: function(date, _flags, value, _options) {
        var isPM = date.getUTCHours() >= 12
        if (isPM && value < 12) {
          date.setUTCHours(value + 12, 0, 0, 0)
        } else if (!isPM && value === 12) {
          date.setUTCHours(0, 0, 0, 0)
        } else {
          date.setUTCHours(value, 0, 0, 0)
        }
        return date
      },
  
      incompatibleTokens: ['H', 'K', 'k', 't', 'T']
    },
  
    // Hour [0-23]
    H: {
      priority: 70,
  
      parse: function(string, token, match, _options) {
        switch (token) {
          case 'H':
            return parseNumericPattern(numericPatterns.hour23h, string)
          case 'Ho':
            return match.ordinalNumber(string, { unit: 'hour' })
          default:
            return parseNDigits(token.length, string)
        }
      },
  
      validate: function(_date, value, _options) {
        return value >= 0 && value <= 23
      },
  
      set: function(date, _flags, value, _options) {
        date.setUTCHours(value, 0, 0, 0)
        return date
      },
  
      incompatibleTokens: ['a', 'b', 'h', 'K', 'k', 't', 'T']
    },
  
    // Hour [0-11]
    K: {
      priority: 70,
  
      parse: function(string, token, match, _options) {
        switch (token) {
          case 'K':
            return parseNumericPattern(numericPatterns.hour11h, string)
          case 'Ko':
            return match.ordinalNumber(string, { unit: 'hour' })
          default:
            return parseNDigits(token.length, string)
        }
      },
  
      validate: function(_date, value, _options) {
        return value >= 0 && value <= 11
      },
  
      set: function(date, _flags, value, _options) {
        var isPM = date.getUTCHours() >= 12
        if (isPM && value < 12) {
          date.setUTCHours(value + 12, 0, 0, 0)
        } else {
          date.setUTCHours(value, 0, 0, 0)
        }
        return date
      },
  
      incompatibleTokens: ['a', 'b', 'h', 'H', 'k', 't', 'T']
    },
  
    // Hour [1-24]
    k: {
      priority: 70,
  
      parse: function(string, token, match, _options) {
        switch (token) {
          case 'k':
            return parseNumericPattern(numericPatterns.hour24h, string)
          case 'ko':
            return match.ordinalNumber(string, { unit: 'hour' })
          default:
            return parseNDigits(token.length, string)
        }
      },
  
      validate: function(_date, value, _options) {
        return value >= 1 && value <= 24
      },
  
      set: function(date, _flags, value, _options) {
        var hours = value <= 24 ? value % 24 : value
        date.setUTCHours(hours, 0, 0, 0)
        return date
      },
  
      incompatibleTokens: ['a', 'b', 'h', 'H', 'K', 't', 'T']
    },
  
    // Minute
    m: {
      priority: 60,
  
      parse: function(string, token, match, _options) {
        switch (token) {
          case 'm':
            return parseNumericPattern(numericPatterns.minute, string)
          case 'mo':
            return match.ordinalNumber(string, { unit: 'minute' })
          default:
            return parseNDigits(token.length, string)
        }
      },
  
      validate: function(_date, value, _options) {
        return value >= 0 && value <= 59
      },
  
      set: function(date, _flags, value, _options) {
        date.setUTCMinutes(value, 0, 0)
        return date
      },
  
      incompatibleTokens: ['t', 'T']
    },
  
    // Second
    s: {
      priority: 50,
  
      parse: function(string, token, match, _options) {
        switch (token) {
          case 's':
            return parseNumericPattern(numericPatterns.second, string)
          case 'so':
            return match.ordinalNumber(string, { unit: 'second' })
          default:
            return parseNDigits(token.length, string)
        }
      },
  
      validate: function(_date, value, _options) {
        return value >= 0 && value <= 59
      },
  
      set: function(date, _flags, value, _options) {
        date.setUTCSeconds(value, 0)
        return date
      },
  
      incompatibleTokens: ['t', 'T']
    },
  
    // Fraction of second
    S: {
      priority: 30,
  
      parse: function(string, token, _match, _options) {
        var valueCallback = function(value) {
          return Math.floor(value * Math.pow(10, -token.length + 3))
        }
        return parseNDigits(token.length, string, valueCallback)
      },
  
      set: function(date, _flags, value, _options) {
        date.setUTCMilliseconds(value)
        return date
      },
  
      incompatibleTokens: ['t', 'T']
    },
  
    // Timezone (ISO-8601. +00:00 is `'Z'`)
    X: {
      priority: 10,
  
      parse: function(string, token, _match, _options) {
        switch (token) {
          case 'X':
            return parseTimezonePattern(
              timezonePatterns.basicOptionalMinutes,
              string
            )
          case 'XX':
            return parseTimezonePattern(timezonePatterns.basic, string)
          case 'XXXX':
            return parseTimezonePattern(
              timezonePatterns.basicOptionalSeconds,
              string
            )
          case 'XXXXX':
            return parseTimezonePattern(
              timezonePatterns.extendedOptionalSeconds,
              string
            )
          case 'XXX':
          default:
            return parseTimezonePattern(timezonePatterns.extended, string)
        }
      },
  
      set: function(date, flags, value, _options) {
        if (flags.timestampIsSet) {
          return date
        }
        return new Date(date.getTime() - value)
      },
  
      incompatibleTokens: ['t', 'T', 'x']
    },
  
    // Timezone (ISO-8601)
    x: {
      priority: 10,
  
      parse: function(string, token, _match, _options) {
        switch (token) {
          case 'x':
            return parseTimezonePattern(
              timezonePatterns.basicOptionalMinutes,
              string
            )
          case 'xx':
            return parseTimezonePattern(timezonePatterns.basic, string)
          case 'xxxx':
            return parseTimezonePattern(
              timezonePatterns.basicOptionalSeconds,
              string
            )
          case 'xxxxx':
            return parseTimezonePattern(
              timezonePatterns.extendedOptionalSeconds,
              string
            )
          case 'xxx':
          default:
            return parseTimezonePattern(timezonePatterns.extended, string)
        }
      },
  
      set: function(date, flags, value, _options) {
        if (flags.timestampIsSet) {
          return date
        }
        return new Date(date.getTime() - value)
      },
  
      incompatibleTokens: ['t', 'T', 'X']
    },
  
    // Seconds timestamp
    t: {
      priority: 40,
  
      parse: function(string, _token, _match, _options) {
        return parseAnyDigitsSigned(string)
      },
  
      set: function(_date, _flags, value, _options) {
        return [new Date(value * 1000), { timestampIsSet: true }]
      },
  
      incompatibleTokens: '*'
    },
  
    // Milliseconds timestamp
    T: {
      priority: 20,
  
      parse: function(string, _token, _match, _options) {
        return parseAnyDigitsSigned(string)
      },
  
      set: function(_date, _flags, value, _options) {
        return [new Date(value), { timestampIsSet: true }]
      },
  
      incompatibleTokens: '*'
    }
  }
  
  export default parsers
  