tokens.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. export const fields = {
  2. G: { field: 'era', desc: 'Era' },
  3. y: { field: 'year', desc: 'Year' },
  4. Y: { field: 'year', desc: 'Year of "Week of Year"' },
  5. u: { field: 'year', desc: 'Extended year' },
  6. U: { field: 'year', desc: 'Cyclic year name' },
  7. r: { field: 'year', desc: 'Related Gregorian year' },
  8. Q: { field: 'quarter', desc: 'Quarter' },
  9. q: { field: 'quarter', desc: 'Stand-alone quarter' },
  10. M: { field: 'month', desc: 'Month in year' },
  11. L: { field: 'month', desc: 'Stand-alone month in year' },
  12. w: { field: 'week', desc: 'Week of year' },
  13. W: { field: 'week', desc: 'Week of month' },
  14. d: { field: 'day', desc: 'Day in month' },
  15. D: { field: 'day', desc: 'Day of year' },
  16. F: { field: 'day', desc: 'Day of week in month' },
  17. g: { field: 'day', desc: 'Modified julian day' },
  18. E: { field: 'weekday', desc: 'Day of week' },
  19. e: { field: 'weekday', desc: 'Local day of week' },
  20. c: { field: 'weekday', desc: 'Stand-alone local day of week' },
  21. a: { field: 'period', desc: 'AM/PM marker' },
  22. b: { field: 'period', desc: 'AM/PM/noon/midnight marker' },
  23. B: { field: 'period', desc: 'Flexible day period' },
  24. h: { field: 'hour', desc: 'Hour in AM/PM (1~12)' },
  25. H: { field: 'hour', desc: 'Hour in day (0~23)' },
  26. k: { field: 'hour', desc: 'Hour in day (1~24)' },
  27. K: { field: 'hour', desc: 'Hour in AM/PM (0~11)' },
  28. j: { field: 'hour', desc: 'Hour in preferred cycle' },
  29. J: { field: 'hour', desc: 'Hour in preferred cycle without marker' },
  30. C: { field: 'hour', desc: 'Hour in preferred cycle with flexible marker' },
  31. m: { field: 'min', desc: 'Minute in hour' },
  32. s: { field: 'sec', desc: 'Second in minute' },
  33. S: { field: 'sec-frac', desc: 'Fractional second' },
  34. A: { field: 'ms', desc: 'Milliseconds in day' },
  35. z: { field: 'tz', desc: 'Time Zone: specific non-location' },
  36. Z: { field: 'tz', desc: 'Time Zone' },
  37. O: { field: 'tz', desc: 'Time Zone: localized' },
  38. v: { field: 'tz', desc: 'Time Zone: generic non-location' },
  39. V: { field: 'tz', desc: 'Time Zone: ID' },
  40. X: { field: 'tz', desc: 'Time Zone: ISO8601 with Z' },
  41. x: { field: 'tz', desc: 'Time Zone: ISO8601' }
  42. };
  43. const isLetter = (char) => (char >= 'A' && char <= 'Z') || (char >= 'a' && char <= 'z');
  44. function readFieldToken(src, pos) {
  45. const char = src[pos];
  46. let width = 1;
  47. while (src[++pos] === char)
  48. ++width;
  49. const field = fields[char];
  50. if (!field) {
  51. const msg = `The letter ${char} is not a valid field identifier`;
  52. return { char, error: new Error(msg), width };
  53. }
  54. return { char, field: field.field, desc: field.desc, width };
  55. }
  56. function readQuotedToken(src, pos) {
  57. let str = src[++pos];
  58. let width = 2;
  59. if (str === "'")
  60. return { char: "'", str, width };
  61. while (true) {
  62. const next = src[++pos];
  63. ++width;
  64. if (next === undefined) {
  65. const msg = `Unterminated quoted literal in pattern: ${str || src}`;
  66. return { char: "'", error: new Error(msg), str, width };
  67. }
  68. else if (next === "'") {
  69. if (src[++pos] !== "'")
  70. return { char: "'", str, width };
  71. else
  72. ++width;
  73. }
  74. str += next;
  75. }
  76. }
  77. function readToken(src, pos) {
  78. const char = src[pos];
  79. if (!char)
  80. return null;
  81. if (isLetter(char))
  82. return readFieldToken(src, pos);
  83. if (char === "'")
  84. return readQuotedToken(src, pos);
  85. let str = char;
  86. let width = 1;
  87. while (true) {
  88. const next = src[++pos];
  89. if (!next || isLetter(next) || next === "'")
  90. return { char, str, width };
  91. str += next;
  92. width += 1;
  93. }
  94. }
  95. /**
  96. * Parse an {@link http://userguide.icu-project.org/formatparse/datetime | ICU
  97. * DateFormat skeleton} string into a {@link DateToken} array.
  98. *
  99. * @remarks
  100. * Errors will not be thrown, but if encountered are included as the relevant
  101. * token's `error` value.
  102. *
  103. * @public
  104. * @param src - The skeleton string
  105. *
  106. * @example
  107. * ```js
  108. * import { parseDateTokens } from '@messageformat/date-skeleton'
  109. *
  110. * parseDateTokens('GrMMMdd', console.error)
  111. * // [
  112. * // { char: 'G', field: 'era', desc: 'Era', width: 1 },
  113. * // { char: 'r', field: 'year', desc: 'Related Gregorian year', width: 1 },
  114. * // { char: 'M', field: 'month', desc: 'Month in year', width: 3 },
  115. * // { char: 'd', field: 'day', desc: 'Day in month', width: 2 }
  116. * // ]
  117. * ```
  118. */
  119. export function parseDateTokens(src) {
  120. const tokens = [];
  121. let pos = 0;
  122. while (true) {
  123. const token = readToken(src, pos);
  124. if (!token)
  125. return tokens;
  126. tokens.push(token);
  127. pos += token.width;
  128. }
  129. }