Skip to content

Commit

Permalink
implement human readable time converter and metric utility functions
Browse files Browse the repository at this point in the history
  • Loading branch information
dmonad committed Mar 2, 2020
1 parent 374d627 commit 8aa9e62
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 5 deletions.
9 changes: 9 additions & 0 deletions math.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,12 @@ export const min = (a, b) => a < b ? a : b
export const max = (a, b) => a > b ? a : b

export const isNaN = Number.isNaN

export const pow = Math.pow
/**
* Base 10 exponential function. Returns the value of 10 raised to the power of pow.
*
* @param {number} exp
* @return {number}
*/
export const exp10 = exp => Math.pow(10, exp)
48 changes: 48 additions & 0 deletions metric.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import * as math from './math.js'

export const yotta = 1e24
export const zetta = 1e21
export const exa = 1e18
export const peta = 1e15
export const tera = 1e12
export const giga = 1e9
export const mega = 1e6
export const kilo = 1e3
export const hecto = 1e2
export const deca = 10
export const deci = .1
export const centi = .01
export const milli = 1e-3
export const micro = 1e-6
export const nano = 1e-9
export const pico = 1e-12
export const femto = 1e-15
export const atto = 1e-18
export const zepto = 1e-21
export const yocto = 1e-24

const prefixUp = ['', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']
const prefixDown = ['', 'm', 'μ', 'n', 'p', 'f', 'a', 'z', 'y']

/**
* @param {number} n
* @param {number} [baseMultiplier] Multiplier of the base (10^(3*baseMultiplier)). E.g. `convert(time, -3)` if time is already in milli seconds
* @return {{n:number,prefix:string}}
*/
export const prefix = (n, baseMultiplier = 0) => {
const nPow = math.log10(n)
let mult = 0
while (nPow < mult * 3 && baseMultiplier > -8) {
baseMultiplier--
mult--
}
while (nPow >= 3 + mult * 3 && baseMultiplier < 8) {
baseMultiplier++
mult++
}
const prefix = baseMultiplier < 0 ? prefixDown[-baseMultiplier] : prefixUp[baseMultiplier]
return {
n: math.round((mult > 0 ? n / math.exp10(mult * 3) : n * math.exp10(mult * -3)) * 1e12) / 1e12,
prefix
}
}
34 changes: 34 additions & 0 deletions metric.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import * as t from './testing.js'
import * as metric from './metric.js'

/**
* @param {t.TestCase} tc
*/
export const testMetricPrefix = tc => {
t.compare(metric.prefix(1, -1), { n: 1, prefix: 'm' })
t.compare(metric.prefix(1.5), { n: 1.5, prefix: '' })
t.compare(metric.prefix(100.5), { n: 100.5, prefix: '' })
t.compare(metric.prefix(1000.5), { n: 1.0005, prefix: 'k' })
t.compare(metric.prefix(.3), { n: 300, prefix: 'm' })
t.compare(metric.prefix(.001), { n: 1, prefix: 'm' })
// up
t.compare(metric.prefix(10000), { n: 10, prefix: 'k' })
t.compare(metric.prefix(1e7), { n: 10, prefix: 'M' })
t.compare(metric.prefix(1e11), { n: 100, prefix: 'G' })
t.compare(metric.prefix(1e12 + 3), { n: (1e12 + 3) / 1e12, prefix: 'T' })
t.compare(metric.prefix(1e15), { n: 1, prefix: 'P' })
t.compare(metric.prefix(1e20), { n: 100, prefix: 'E' })
t.compare(metric.prefix(1e22), { n: 10, prefix: 'Z' })
t.compare(metric.prefix(1e24), { n: 1, prefix: 'Y' })
t.compare(metric.prefix(1e28), { n: 10000, prefix: 'Y' })
// down
t.compare(metric.prefix(.01), { n: 10, prefix: 'm' })
t.compare(metric.prefix(1e-4), { n: 100, prefix: 'μ' })
t.compare(metric.prefix(1e-9), { n: 1, prefix: 'n' })
t.compare(metric.prefix(1e-12), { n: 1, prefix: 'p' })
t.compare(metric.prefix(1e-14), { n: 10, prefix: 'f' })
t.compare(metric.prefix(1e-18), { n: 1, prefix: 'a' })
t.compare(metric.prefix(1e-21), { n: 1, prefix: 'z' })
t.compare(metric.prefix(1e-22), { n: 100, prefix: 'y' })
t.compare(metric.prefix(1e-30), { n: .000001, prefix: 'y' })
}
4 changes: 3 additions & 1 deletion test.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import * as number from './number.test.js'
import * as buffer from './buffer.test.js'
import * as sort from './sort.test.js'
import * as url from './url.test.js'
import * as metric from './metric.test.js'

import { isBrowser, isNode } from './environment.js'

Expand Down Expand Up @@ -52,7 +53,8 @@ runTests({
number,
buffer,
sort,
url
url,
metric
}).then(success => {
/* istanbul ignore next */
if (isNode) {
Expand Down
9 changes: 5 additions & 4 deletions testing.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import * as statistics from './statistics.js'
import * as array from './array.js'
import * as env from './environment.js'
import * as json from './json.js'
import * as time from './time.js'

import { performance } from './isomorphic.js'

Expand Down Expand Up @@ -129,8 +130,8 @@ export const run = async (moduleName, name, f, i, numberOfTests) => {
? ` - ${window.location.href}?filter=\\[${i + 1}/${tc._seed === null ? '' : `&seed=${tc._seed}`}`
: `\nrepeat: npm run test -- --filter "\\[${i + 1}/" ${tc._seed === null ? '' : `--seed ${tc._seed}`}`
const timeInfo = (repeat && err === null)
? ` - ${times.length} repititions in ${duration.toFixed(2)}ms (best: ${times[0].toFixed(2)}ms, worst: ${array.last(times).toFixed(2)}ms, median: ${statistics.median(times).toFixed(2)}ms, average: ${statistics.average(times).toFixed(2)}ms)`
: ` in ${duration.toFixed(2)}ms`
? ` - ${times.length} repititions in ${time.humanizeDuration(duration)} (best: ${time.humanizeDuration(times[0])}, worst: ${time.humanizeDuration(array.last(times))}, median: ${time.humanizeDuration(statistics.median(times))}, average: ${time.humanizeDuration(statistics.average(times))})`
: ` in ${time.humanizeDuration(duration)}`
if (err !== null) {
/* istanbul ignore else */
if (err.constructor === SkipError) {
Expand Down Expand Up @@ -190,7 +191,7 @@ export const measureTime = async (message, f) => {
iterations++
}
const iterationsInfo = iterations > 1 ? `, ${iterations} repititions` : ''
log.print(log.PURPLE, message, log.GREY, ` ${(duration / iterations)}ms${iterationsInfo}`)
log.print(log.PURPLE, message, log.GREY, ` ${time.humanizeDuration(duration / iterations)}${iterationsInfo}`)
}

/**
Expand Down Expand Up @@ -411,7 +412,7 @@ export const runTests = async tests => {
const success = successfulTests === numberOfTests
/* istanbul ignore else */
if (success) {
log.print(log.GREEN, log.BOLD, 'All tests successful!', log.GREY, log.UNBOLD, ` in ${(end - start).toFixed(2)}ms`)
log.print(log.GREEN, log.BOLD, 'All tests successful!', log.GREY, log.UNBOLD, ` in ${time.humanizeDuration(end - start)}`)
/* istanbul ignore next */
log.printImgBase64(nyanCatImage, 50)
} else {
Expand Down
26 changes: 26 additions & 0 deletions time.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,29 @@

import * as metric from './metric.js'
import * as math from './math.js'

export const getDate = () => new Date()
export const getUnixTime = Date.now

/**
* @param {number} d duration in milliseconds
* @return {string} humanized approximation of time
*/
export const humanizeDuration = d => {
if (d < 60000) {
const p = metric.prefix(d, -1)
return math.round(p.n * 100) / 100 + p.prefix + 's'
}
d = math.floor(d / 1000)
const seconds = d % 60
const minutes = math.floor(d / 60) % 60
const hours = math.floor(d / 3600) % 24
const days = math.floor(d / 86400)
if (days > 0) {
return days + 'd' + (hours > 0 ? ' ' + (minutes > 30 ? hours + 1 : hours) + 'h' : '')
}
if (hours > 0 ) {
return hours + 'h' + (minutes > 0 ? ' ' + (seconds > 30 ? minutes + 1 : minutes) + 'min' : '')
}
return minutes + 'min' + (seconds > 0 ? ' ' + seconds + 's' : '')
}
16 changes: 16 additions & 0 deletions time.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,19 @@ export const testTime = tc => {
const r = time.getUnixTime()
t.assert(math.abs(l - r) < 10, 'Times generated are roughly the same')
}

/**
* @param {t.TestCase} tc
*/
export const testHumanDuration = tc => {
t.assert(time.humanizeDuration(10) === '10ms')
t.assert(time.humanizeDuration(.1) === '100μs')
t.assert(time.humanizeDuration(61030) === '1min 1s')
t.assert(time.humanizeDuration(60030) === '1min')
t.assert(time.humanizeDuration(3600001) === '1h')
t.assert(time.humanizeDuration(3660000) === '1h 1min')
t.assert(time.humanizeDuration(3600000 * 25) === '1d 1h')
t.assert(time.humanizeDuration(3600000 * 24 * 400) === '400d')
// test round
t.assert(time.humanizeDuration(6001) === '6s')
}

0 comments on commit 8aa9e62

Please sign in to comment.