You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
101 lines
3.6 KiB
101 lines
3.6 KiB
import { stringifyNumber } from '../../stringify/stringifyNumber.js'; |
|
|
|
/** Internal types handle bigint as number, because TS can't figure it out. */ |
|
function parseSexagesimal(str, asBigInt) { |
|
const sign = str[0]; |
|
const parts = sign === '-' || sign === '+' ? str.substring(1) : str; |
|
const num = (n) => asBigInt ? BigInt(n) : Number(n); |
|
const res = parts |
|
.replace(/_/g, '') |
|
.split(':') |
|
.reduce((res, p) => res * num(60) + num(p), num(0)); |
|
return (sign === '-' ? num(-1) * res : res); |
|
} |
|
/** |
|
* hhhh:mm:ss.sss |
|
* |
|
* Internal types handle bigint as number, because TS can't figure it out. |
|
*/ |
|
function stringifySexagesimal(node) { |
|
let { value } = node; |
|
let num = (n) => n; |
|
if (typeof value === 'bigint') |
|
num = n => BigInt(n); |
|
else if (isNaN(value) || !isFinite(value)) |
|
return stringifyNumber(node); |
|
let sign = ''; |
|
if (value < 0) { |
|
sign = '-'; |
|
value *= num(-1); |
|
} |
|
const _60 = num(60); |
|
const parts = [value % _60]; // seconds, including ms |
|
if (value < 60) { |
|
parts.unshift(0); // at least one : is required |
|
} |
|
else { |
|
value = (value - parts[0]) / _60; |
|
parts.unshift(value % _60); // minutes |
|
if (value >= 60) { |
|
value = (value - parts[0]) / _60; |
|
parts.unshift(value); // hours |
|
} |
|
} |
|
return (sign + |
|
parts |
|
.map(n => (n < 10 ? '0' + String(n) : String(n))) |
|
.join(':') |
|
.replace(/000000\d*$/, '') // % 60 may introduce error |
|
); |
|
} |
|
const intTime = { |
|
identify: value => typeof value === 'bigint' || Number.isInteger(value), |
|
default: true, |
|
tag: 'tag:yaml.org,2002:int', |
|
format: 'TIME', |
|
test: /^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+$/, |
|
resolve: (str, _onError, { intAsBigInt }) => parseSexagesimal(str, intAsBigInt), |
|
stringify: stringifySexagesimal |
|
}; |
|
const floatTime = { |
|
identify: value => typeof value === 'number', |
|
default: true, |
|
tag: 'tag:yaml.org,2002:float', |
|
format: 'TIME', |
|
test: /^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\.[0-9_]*$/, |
|
resolve: str => parseSexagesimal(str, false), |
|
stringify: stringifySexagesimal |
|
}; |
|
const timestamp = { |
|
identify: value => value instanceof Date, |
|
default: true, |
|
tag: 'tag:yaml.org,2002:timestamp', |
|
// If the time zone is omitted, the timestamp is assumed to be specified in UTC. The time part |
|
// may be omitted altogether, resulting in a date format. In such a case, the time part is |
|
// assumed to be 00:00:00Z (start of day, UTC). |
|
test: RegExp('^([0-9]{4})-([0-9]{1,2})-([0-9]{1,2})' + // YYYY-Mm-Dd |
|
'(?:' + // time is optional |
|
'(?:t|T|[ \\t]+)' + // t | T | whitespace |
|
'([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2}(\\.[0-9]+)?)' + // Hh:Mm:Ss(.ss)? |
|
'(?:[ \\t]*(Z|[-+][012]?[0-9](?::[0-9]{2})?))?' + // Z | +5 | -03:30 |
|
')?$'), |
|
resolve(str) { |
|
const match = str.match(timestamp.test); |
|
if (!match) |
|
throw new Error('!!timestamp expects a date, starting with yyyy-mm-dd'); |
|
const [, year, month, day, hour, minute, second] = match.map(Number); |
|
const millisec = match[7] ? Number((match[7] + '00').substr(1, 3)) : 0; |
|
let date = Date.UTC(year, month - 1, day, hour || 0, minute || 0, second || 0, millisec); |
|
const tz = match[8]; |
|
if (tz && tz !== 'Z') { |
|
let d = parseSexagesimal(tz, false); |
|
if (Math.abs(d) < 30) |
|
d *= 60; |
|
date -= 60000 * d; |
|
} |
|
return new Date(date); |
|
}, |
|
stringify: ({ value }) => value.toISOString().replace(/((T00:00)?:00)?\.000Z$/, '') |
|
}; |
|
|
|
export { floatTime, intTime, timestamp };
|
|
|