timestamp.js 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. import { stringifyNumber } from '../../stringify/stringifyNumber.js';
  2. /** Internal types handle bigint as number, because TS can't figure it out. */
  3. function parseSexagesimal(str, asBigInt) {
  4. const sign = str[0];
  5. const parts = sign === '-' || sign === '+' ? str.substring(1) : str;
  6. const num = (n) => asBigInt ? BigInt(n) : Number(n);
  7. const res = parts
  8. .replace(/_/g, '')
  9. .split(':')
  10. .reduce((res, p) => res * num(60) + num(p), num(0));
  11. return (sign === '-' ? num(-1) * res : res);
  12. }
  13. /**
  14. * hhhh:mm:ss.sss
  15. *
  16. * Internal types handle bigint as number, because TS can't figure it out.
  17. */
  18. function stringifySexagesimal(node) {
  19. let { value } = node;
  20. let num = (n) => n;
  21. if (typeof value === 'bigint')
  22. num = n => BigInt(n);
  23. else if (isNaN(value) || !isFinite(value))
  24. return stringifyNumber(node);
  25. let sign = '';
  26. if (value < 0) {
  27. sign = '-';
  28. value *= num(-1);
  29. }
  30. const _60 = num(60);
  31. const parts = [value % _60]; // seconds, including ms
  32. if (value < 60) {
  33. parts.unshift(0); // at least one : is required
  34. }
  35. else {
  36. value = (value - parts[0]) / _60;
  37. parts.unshift(value % _60); // minutes
  38. if (value >= 60) {
  39. value = (value - parts[0]) / _60;
  40. parts.unshift(value); // hours
  41. }
  42. }
  43. return (sign +
  44. parts
  45. .map(n => String(n).padStart(2, '0'))
  46. .join(':')
  47. .replace(/000000\d*$/, '') // % 60 may introduce error
  48. );
  49. }
  50. const intTime = {
  51. identify: value => typeof value === 'bigint' || Number.isInteger(value),
  52. default: true,
  53. tag: 'tag:yaml.org,2002:int',
  54. format: 'TIME',
  55. test: /^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+$/,
  56. resolve: (str, _onError, { intAsBigInt }) => parseSexagesimal(str, intAsBigInt),
  57. stringify: stringifySexagesimal
  58. };
  59. const floatTime = {
  60. identify: value => typeof value === 'number',
  61. default: true,
  62. tag: 'tag:yaml.org,2002:float',
  63. format: 'TIME',
  64. test: /^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\.[0-9_]*$/,
  65. resolve: str => parseSexagesimal(str, false),
  66. stringify: stringifySexagesimal
  67. };
  68. const timestamp = {
  69. identify: value => value instanceof Date,
  70. default: true,
  71. tag: 'tag:yaml.org,2002:timestamp',
  72. // If the time zone is omitted, the timestamp is assumed to be specified in UTC. The time part
  73. // may be omitted altogether, resulting in a date format. In such a case, the time part is
  74. // assumed to be 00:00:00Z (start of day, UTC).
  75. test: RegExp('^([0-9]{4})-([0-9]{1,2})-([0-9]{1,2})' + // YYYY-Mm-Dd
  76. '(?:' + // time is optional
  77. '(?:t|T|[ \\t]+)' + // t | T | whitespace
  78. '([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2}(\\.[0-9]+)?)' + // Hh:Mm:Ss(.ss)?
  79. '(?:[ \\t]*(Z|[-+][012]?[0-9](?::[0-9]{2})?))?' + // Z | +5 | -03:30
  80. ')?$'),
  81. resolve(str) {
  82. const match = str.match(timestamp.test);
  83. if (!match)
  84. throw new Error('!!timestamp expects a date, starting with yyyy-mm-dd');
  85. const [, year, month, day, hour, minute, second] = match.map(Number);
  86. const millisec = match[7] ? Number((match[7] + '00').substr(1, 3)) : 0;
  87. let date = Date.UTC(year, month - 1, day, hour || 0, minute || 0, second || 0, millisec);
  88. const tz = match[8];
  89. if (tz && tz !== 'Z') {
  90. let d = parseSexagesimal(tz, false);
  91. if (Math.abs(d) < 30)
  92. d *= 60;
  93. date -= 60000 * d;
  94. }
  95. return new Date(date);
  96. },
  97. stringify: ({ value }) => value.toISOString().replace(/((T00:00)?:00)?\.000Z$/, '')
  98. };
  99. export { floatTime, intTime, timestamp };