timestamp.js 3.7 KB

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