Skip to content

Instantly share code, notes, and snippets.

@H4ad
Last active January 12, 2023 17:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save H4ad/eed6e6490337a7849ccb75be5e0e1230 to your computer and use it in GitHub Desktop.
Save H4ad/eed6e6490337a7849ccb75be5e0e1230 to your computer and use it in GitHub Desktop.
Fast ISO String Generation

Results:

fromUnixToISOString x 3,258,470 ops/sec ±1.86% (85 runs sampled)
new Date().toISOString() x 1,408,158 ops/sec ±0.96% (90 runs sampled)
import * as Benchmark from 'benchmark';
import { fromUnixToISOString } from './fast-isostring';
import { fromUnixToISOStringWithoutCache } from './fast-isostring-without-cache';
const suite = new Benchmark.Suite();
suite.add('fromUnixToISOString', function () {
const v = fromUnixToISOString(Date.now());
});
suite.add('fromUnixToISOStringWithoutCache', function () {
const v = fromUnixToISOStringWithoutCache(Date.now());
});
suite.add('new Date().toISOString()', function () {
const v = new Date().toISOString();
});
const results = [];
suite
// add listeners
.on('cycle', function (event) {
results.push(String(event.target));
})
.on('complete', function () {
results.forEach(console.log);
console.log('Fastest is ' + this.filter('fastest').map('name'));
})
.run({
async: true,
});
const kMsPerMin = 60 * 1000;
const kSecPerDay = 24 * 60 * 60;
const kMsPerDay = kSecPerDay * 1000;
const kMsPerMonth = kMsPerDay * 30;
const kMsPerHour = 60 * 60 * 1000;
const kDaysIn4Years = 4 * 365 + 1;
const kDaysIn100Years = 25 * kDaysIn4Years - 1;
const kDaysIn400Years = 4 * kDaysIn100Years + 1;
const kDays1970to2000 = 30 * 365 + 7;
const kDaysOffset = 1000 * kDaysIn400Years + 5 * kDaysIn400Years - kDays1970to2000;
const kYearsOffset = 400000;
const kDaysInMonths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
const pad = n => (n < 10 ? `0${n}` : n);
const padMs = n => (n >= 100 ? n : n >= 10 ? `0${n}` : `00${n}`);
function getYearMonthDayFromDays(days: number): string {
days += kDaysOffset;
let year = 400 * Math.floor(days / kDaysIn400Years) - kYearsOffset;
days %= kDaysIn400Years;
days--;
let yd1 = Math.floor(days / kDaysIn100Years);
days %= kDaysIn100Years;
year += 100 * yd1;
days++;
let yd2 = Math.floor(days / kDaysIn4Years);
days %= kDaysIn4Years;
year += 4 * yd2;
days--;
let yd3 = Math.floor(days / 365);
days %= 365;
year += yd3;
const is_leap = (!yd1 || yd2) && !yd3;
if (is_leap) days++;
let month = 0;
let day = 0;
// Check if the date is after February.
if (days >= 31 + 28 + (is_leap ? 1 : 0)) {
days -= 31 + 28 + (is_leap ? 1 : 0);
// Find the date starting from March.
for (let i = 2; i < 12; i++) {
if (days < kDaysInMonths[i]) {
month = i;
day = days + 1;
break;
}
days -= kDaysInMonths[i];
}
} else {
// Check January and February.
if (days < 31) {
month = 0;
day = days + 1;
} else {
month = 1;
day = days - 31 + 1;
}
}
return `${year}-${pad(month + 1)}-${pad(day)}`;
}
export function fromUnixToISOStringWithoutCache(unix: number): string {
const days = unix < 0 ? unix - kMsPerDay - 1 : Math.floor(unix / kMsPerDay);
const timeInDayMs = unix - days * kMsPerDay;
const hour = Math.floor(timeInDayMs / kMsPerHour);
const min = Math.floor((timeInDayMs / kMsPerMin) % 60);
const sec = Math.floor((timeInDayMs / 1000) % 60);
const ms = Math.floor(timeInDayMs % 1000);
const datePart = getYearMonthDayFromDays(days);
return `${datePart}T${pad(hour)}:${pad(min)}:${pad(sec)}.${padMs(ms)}Z`;
}
const kMsPerMin = 60 * 1000;
const kSecPerDay = 24 * 60 * 60;
const kMsPerDay = kSecPerDay * 1000;
const kMsPerMonth = kMsPerDay * 30;
const kMsPerHour = 60 * 60 * 1000;
const kDaysIn4Years = 4 * 365 + 1;
const kDaysIn100Years = 25 * kDaysIn4Years - 1;
const kDaysIn400Years = 4 * kDaysIn100Years + 1;
const kDays1970to2000 = 30 * 365 + 7;
const kDaysOffset = 1000 * kDaysIn400Years + 5 * kDaysIn400Years - kDays1970to2000;
const kYearsOffset = 400000;
const kDaysInMonths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
const pad = n => (n < 10 ? `0${n}` : n);
const padMs = n => (n >= 100 ? n : n >= 10 ? `0${n}` : `00${n}`);
let cachedValid: boolean = false;
let cachedDays: number | undefined;
let cachedYear: number | undefined;
let cachedMonth: number | undefined;
let cachedDay: number | undefined;
function getYearMonthDayFromDays(days: number): string {
if (cachedValid) {
// Check conservatively if the given 'days' has
// the same year and month as the cached 'days'.
const new_day = cachedDay + (days - cachedDays);
if (new_day >= 1 && new_day <= 28) {
cachedDay = new_day;
cachedDays = days;
return `${cachedYear}-${pad(cachedMonth)}-${pad(new_day)}`;
}
}
days += kDaysOffset;
let year = 400 * Math.floor(days / kDaysIn400Years) - kYearsOffset;
days %= kDaysIn400Years;
days--;
let yd1 = Math.floor(days / kDaysIn100Years);
days %= kDaysIn100Years;
year += 100 * yd1;
days++;
let yd2 = Math.floor(days / kDaysIn4Years);
days %= kDaysIn4Years;
year += 4 * yd2;
days--;
let yd3 = Math.floor(days / 365);
days %= 365;
year += yd3;
const is_leap = (!yd1 || yd2) && !yd3;
if (is_leap) days++;
let month = 0;
let day = 0;
// Check if the date is after February.
if (days >= 31 + 28 + (is_leap ? 1 : 0)) {
days -= 31 + 28 + (is_leap ? 1 : 0);
// Find the date starting from March.
for (let i = 2; i < 12; i++) {
if (days < kDaysInMonths[i]) {
month = i;
day = days + 1;
break;
}
days -= kDaysInMonths[i];
}
} else {
// Check January and February.
if (days < 31) {
month = 0;
day = days + 1;
} else {
month = 1;
day = days - 31 + 1;
}
}
cachedValid = true;
cachedYear = year;
cachedMonth = month + 1;
cachedDay = day;
cachedDays = days;
return `${cachedYear}-${pad(cachedMonth)}-${pad(cachedDay)}`;
}
export function fromUnixToISOString(unix: number): string {
const days = unix < 0 ? unix - kMsPerDay - 1 : Math.floor(unix / kMsPerDay);
const timeInDayMs = unix - days * kMsPerDay;
const hour = Math.floor(timeInDayMs / kMsPerHour);
const min = Math.floor((timeInDayMs / kMsPerMin) % 60);
const sec = Math.floor((timeInDayMs / 1000) % 60);
const ms = Math.floor(timeInDayMs % 1000);
const datePart = getYearMonthDayFromDays(days);
return `${datePart}T${pad(hour)}:${pad(min)}:${pad(sec)}.${padMs(ms)}Z`;
}
export function fastISOString(): string {
return fromUnixToISOString(Date.now());
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment