@typespec/http-server-js
Version:
TypeSpec HTTP server code generator for JavaScript
236 lines (210 loc) • 7.12 kB
text/typescript
// Copyright (c) Microsoft Corporation
// Licensed under the MIT license.
// #region Duration
/**
* Regular expression for matching ISO8601 duration strings.
*
* Yields:
* - 0: the full match
* - 1: the sign (optional)
* - 2: years (optional)
* - 3: months (optional)
* - 4: weeks (optional)
* - 5: days (optional)
* - 6: hours (optional)
* - 7: minutes (optional)
* - 8: seconds (optional)
*/
const ISO8601_DURATION_REGEX =
/^(-)?P(?:((?:\d*[.,])?\d+)Y)?(?:((?:\d*[.,])?\d+)M)?(?:((?:\d*[.,])?\d+)W)?(?:((?:\d*[.,])?\d+)D)?(?:T(?:((?:\d*[.,])?\d+)H)?(?:((?:\d*[.,])?\d+)M)?(?:((?:\d*[.,])?\d+)S)?)?$/;
/**
* A duration of time, measured in years, months, weeks, days, hours, minutes, and seconds.
*
* The values may be fractional and are not normalized (e.g. 36 hours is not the same duration as 1 day and 12 hours
* when accounting for Daylight Saving Time changes or leap seconds).
*
* @see https://en.wikipedia.org/wiki/ISO_8601#Durations
*/
export interface Duration {
/**
* "+" if the duration is positive, "-" if the duration is negative.
*/
sign: "+" | "-";
/**
* The number of years in the duration.
*/
years: number;
/**
* The number of months in the duration.
*/
months: number;
/**
* The number of weeks in the duration.
*/
weeks: number;
/**
* The number of days in the duration.
*/
days: number;
/**
* The number of hours in the duration.
*/
hours: number;
/**
* The number of minutes in the duration.
*/
minutes: number;
/**
* The number of seconds in the duration.
*/
seconds: number;
}
export const Duration = Object.freeze({
/**
* Parses an ISO8601 duration string into an object.
*
* @see https://en.wikipedia.org/wiki/ISO_8601#Durations
*
* @param duration - the duration string to parse
* @returns an object containing the parsed duration
*/
parseISO8601(duration: string, maxLength: number = 100): Duration {
duration = duration.trim();
if (duration.length > maxLength)
throw new Error(`ISO8601 duration string is too long: ${duration}`);
const match = duration.match(ISO8601_DURATION_REGEX);
if (!match) throw new Error(`Invalid ISO8601 duration: ${duration}`);
return {
sign: match[1] === undefined ? "+" : (match[1] as Duration["sign"]),
years: parseFloatNormal(match[2]),
months: parseFloatNormal(match[3]),
weeks: parseFloatNormal(match[4]),
days: parseFloatNormal(match[5]),
hours: parseFloatNormal(match[6]),
minutes: parseFloatNormal(match[7]),
seconds: parseFloatNormal(match[8]),
};
function parseFloatNormal(match: string | undefined): number {
if (match === undefined) return 0;
const normalized = match.replace(",", ".");
const parsed = parseFloat(normalized);
if (isNaN(parsed))
throw new Error(`Unreachable: Invalid number in ISO8601 duration string: ${match}`);
return parsed;
}
},
/**
* Writes a Duration to an ISO8601 duration string.
*
* @see https://en.wikipedia.org/wiki/ISO_8601#Durations
*
* @param duration - the duration to write to a string
* @returns a string in ISO8601 duration format
*/
toISO8601(duration: Duration): string {
const sign = duration.sign === "+" ? "" : "-";
const years =
duration.years !== 0 && !isNaN(Number(duration.years)) ? `${duration.years}Y` : "";
const months =
duration.months !== 0 && !isNaN(Number(duration.months)) ? `${duration.months}M` : "";
const weeks =
duration.weeks !== 0 && !isNaN(Number(duration.weeks)) ? `${duration.weeks}W` : "";
const days = duration.days !== 0 && !isNaN(Number(duration.days)) ? `${duration.days}D` : "";
let time = "";
const _hours = duration.hours !== 0 && !isNaN(Number(duration.hours));
const _minutes = duration.minutes !== 0 && !isNaN(Number(duration.minutes));
const _seconds = duration.seconds !== 0 && !isNaN(Number(duration.seconds));
if (_hours || _minutes || _seconds) {
const hours = _hours ? `${duration.hours}H` : "";
const minutes = _minutes ? `${duration.minutes}M` : "";
const seconds = _seconds ? `${duration.seconds}S` : "";
time = `T${hours}${minutes}${seconds}`;
}
return `${sign}P${years}${months}${weeks}${days}${time}`;
},
/**
* Gets the total number of seconds in a duration.
*
* This method will throw an Error if the duration contains any years, months, weeks, or days, as those require a reference
* point to calculate the total number of seconds.
*
* WARNING: If the total number of seconds is larger than the maximum safe integer in JavaScript, this method will
* lose precision. @see Duration.totalSecondsBigInt for a BigInt alternative.
*
* @param duration - the duration to calculate the total number of seconds for
* @returns the total number of seconds in the duration
*/
totalSeconds(duration: Duration): number {
if (
duration.years !== 0 ||
duration.months !== 0 ||
duration.weeks !== 0 ||
duration.days !== 0
) {
throw new Error(
"Cannot calculate total seconds for a duration with years, months, weeks, or days.",
);
}
return (
duration.seconds +
duration.minutes * 60 +
duration.hours * 60 * 60 +
duration.weeks * 7 * 24 * 60 * 60
);
},
/**
* Gets the total number of seconds in a duration.
*
* This method will throw an Error if the duration contains any years, months, weeks, or days, as those require a reference
* point to calculate the total number of seconds. It will also throw an error if any of the components are not integers.
*
* @param duration - the duration to calculate the total number of seconds for
* @returns the total number of seconds in the duration
*/
totalSecondsBigInt(duration: Duration): bigint {
if (
duration.years !== 0 ||
duration.months !== 0 ||
duration.weeks !== 0 ||
duration.days !== 0
) {
throw new Error(
"Cannot calculate total seconds for a duration with years, months, weeks, or days.",
);
}
if (
!Number.isInteger(duration.seconds) ||
!Number.isInteger(duration.minutes) ||
!Number.isInteger(duration.hours) ||
!Number.isInteger(duration.weeks)
) {
throw new Error(
"Cannot calculate total seconds as a BigInt for a duration with non-integer components.",
);
}
return (
BigInt(duration.seconds) +
BigInt(duration.minutes) * 60n +
BigInt(duration.hours) * 60n * 60n +
BigInt(duration.weeks) * 7n * 24n * 60n * 60n
);
},
/**
* Creates a duration from a total number of seconds.
*
* The result is not normalized, so it will only contain a seconds field.
*/
fromTotalSeconds(seconds: number): Duration {
return {
sign: seconds < 0 ? "-" : "+",
years: 0,
months: 0,
weeks: 0,
days: 0,
hours: 0,
minutes: 0,
seconds: Math.abs(seconds),
};
},
});
// #endregion