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()); | |
} |