Skip to content

Commit

Permalink
feat: use branded UnixTimestamp instead of non-branded UnixTimestampN…
Browse files Browse the repository at this point in the history
…umber
  • Loading branch information
kirillgroshkov committed Nov 9, 2024
1 parent 3229031 commit f6a5511
Show file tree
Hide file tree
Showing 19 changed files with 196 additions and 99 deletions.
12 changes: 6 additions & 6 deletions src/datetime/localDate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import type {
IsoDateTimeString,
MonthId,
SortDirection,
UnixTimestampMillisNumber,
UnixTimestampNumber,
UnixTimestamp,
UnixTimestampMillis,
} from '../types'
import { DateObject, ISODayOfWeek, LocalTime, localTime } from './localTime'

Expand Down Expand Up @@ -491,15 +491,15 @@ export class LocalDate {
/**
* Returns unix timestamp of 00:00:00 of that date (in UTC, because unix timestamp always reflects UTC).
*/
get unix(): UnixTimestampNumber {
return Math.floor(this.toDate().valueOf() / 1000)
get unix(): UnixTimestamp {
return Math.floor(this.toDate().valueOf() / 1000) as UnixTimestamp
}

/**
* Same as .unix(), but in milliseconds.
*/
get unixMillis(): UnixTimestampMillisNumber {
return this.toDate().valueOf()
get unixMillis(): UnixTimestampMillis {
return this.toDate().valueOf() as UnixTimestampMillis
}

toJSON(): IsoDateString {
Expand Down
13 changes: 9 additions & 4 deletions src/datetime/localTime.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { dayjs } from '@naturalcycles/time-lib'
import { _range } from '../array/range'
import { expectWithMessage, isUTC } from '../test/test.util'
import { UnixTimestamp, UnixTimestampMillis } from '../types'
import { ISODayOfWeek, localTime, LocalTimeFormatter, LocalTimeUnit } from './localTime'

const units: LocalTimeUnit[] = ['year', 'month', 'day', 'hour', 'minute', 'second', 'week']
Expand Down Expand Up @@ -68,8 +69,10 @@ test('basic', () => {
expect(lt.toLocalDate().toString()).toBe('2022-01-01')

if (isUTC()) {
expect(localTime.fromUnix(1640995200).toString()).toBe(lt.toString())
expect(localTime.fromMillis(1640995200000).toString()).toBe(lt.toString())
expect(localTime.fromUnix(1640995200 as UnixTimestamp).toString()).toBe(lt.toString())
expect(localTime.fromMillis(1640995200000 as UnixTimestampMillis).toString()).toBe(
lt.toString(),
)
expect(localTime.fromInput(new Date(1640995200000)).toString()).toBe(lt.toString())
}

Expand Down Expand Up @@ -119,7 +122,7 @@ test('basic', () => {

expect(localTime.orUndefined(undefined)).toBeUndefined()
expect(localTime.orUndefined(null)).toBeUndefined()
expect(localTime.orUndefined(0)?.toPretty()).toBe('1970-01-01 00:00:00')
expect(localTime.orUndefined(0 as UnixTimestamp)?.toPretty()).toBe('1970-01-01 00:00:00')
expect(localTime.orUndefined(start)?.toISODate()).toBe('2022-01-01')

expect(localTime.now().toString()).toBeDefined()
Expand All @@ -130,7 +133,9 @@ test('basic', () => {
`"Cannot parse "undefined" into LocalTime"`,
)

expect(localTime(0).toISODateTime()).toMatchInlineSnapshot(`"1970-01-01T00:00:00"`)
expect(localTime(0 as UnixTimestamp).toISODateTime()).toMatchInlineSnapshot(
`"1970-01-01T00:00:00"`,
)

expect(localTime.getTimezone()).toBe('UTC')
expect(localTime.isTimezoneValid('Europe/Stockholm')).toBe(true)
Expand Down
28 changes: 14 additions & 14 deletions src/datetime/localTime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import type {
NumberOfHours,
NumberOfMinutes,
SortDirection,
UnixTimestampMillisNumber,
UnixTimestampNumber,
UnixTimestamp,
UnixTimestampMillis,
} from '../types'
import { LocalDate, localDate } from './localDate'
import { WallTime } from './wallTime'
Expand All @@ -26,7 +26,7 @@ export enum ISODayOfWeek {
SUNDAY = 7,
}

export type LocalTimeInput = LocalTime | Date | IsoDateTimeString | UnixTimestampNumber
export type LocalTimeInput = LocalTime | Date | IsoDateTimeString | UnixTimestamp
export type LocalTimeInputNullable = LocalTimeInput | null | undefined
export type LocalTimeFormatter = (ld: LocalTime) => string

Expand Down Expand Up @@ -638,16 +638,16 @@ export class LocalTime {
return new LocalTime(new Date(this.$date))
}

get unix(): UnixTimestampNumber {
return Math.floor(this.$date.valueOf() / 1000)
get unix(): UnixTimestamp {
return Math.floor(this.$date.valueOf() / 1000) as UnixTimestamp
}

get unixMillis(): UnixTimestampMillisNumber {
return this.$date.valueOf()
get unixMillis(): UnixTimestampMillis {
return this.$date.valueOf() as UnixTimestampMillis
}

valueOf(): UnixTimestampNumber {
return Math.floor(this.$date.valueOf() / 1000)
valueOf(): UnixTimestamp {
return Math.floor(this.$date.valueOf() / 1000) as UnixTimestamp
}

toLocalDate(): LocalDate {
Expand Down Expand Up @@ -730,7 +730,7 @@ export class LocalTime {
return this.toISODateTime()
}

toJSON(): UnixTimestampNumber {
toJSON(): UnixTimestamp {
return this.unix
}

Expand Down Expand Up @@ -772,8 +772,8 @@ class LocalTimeFactory {
Convenience function to return current Unix timestamp in seconds.
Like Date.now(), but in seconds.
*/
nowUnix(): UnixTimestampNumber {
return Math.floor(Date.now() / 1000)
nowUnix(): UnixTimestamp {
return Math.floor(Date.now() / 1000) as UnixTimestamp
}

/**
Expand Down Expand Up @@ -925,14 +925,14 @@ class LocalTimeFactory {
return new LocalTime(date)
}

fromUnix(ts: UnixTimestampNumber): LocalTime {
fromUnix(ts: UnixTimestamp): LocalTime {
return new LocalTime(new Date(ts * 1000))
}

/**
* Create LocalTime from unixTimestamp in milliseconds (not in seconds).
*/
fromMillis(millis: UnixTimestampMillisNumber): LocalTime {
fromMillis(millis: UnixTimestampMillis): LocalTime {
return new LocalTime(new Date(millis))
}

Expand Down
7 changes: 4 additions & 3 deletions src/datetime/timeInterval.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { UnixTimestamp } from '../types'
import { TimeInterval } from './timeInterval'

test('basic', () => {
const str1 = '1649267185/1649267187'
const int1 = TimeInterval.parse(str1)
expect(int1.toString()).toBe(str1)
expect(JSON.stringify(int1)).toBe(`"${str1}"`)
expect(int1.startTime.isSame(1649267185))
expect(int1.endTime.isSame(1649267187))
expect(int1.startTime.isSame(1649267185 as UnixTimestamp))
expect(int1.endTime.isSame(1649267187 as UnixTimestamp))

const int2 = TimeInterval.of(1649267185, 1649267187)
const int2 = TimeInterval.of(1649267185 as UnixTimestamp, 1649267187 as UnixTimestamp)

expect(int1.isSame(int2)).toBe(true)
expect(int1.cmp(int2)).toBe(0)
Expand Down
12 changes: 6 additions & 6 deletions src/datetime/timeInterval.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Inclusiveness, UnixTimestampNumber } from '../types'
import type { Inclusiveness, UnixTimestamp } from '../types'
import { LocalTime, localTime, LocalTimeInput } from './localTime'

export type TimeIntervalConfig = TimeInterval | TimeIntervalString
Expand All @@ -12,19 +12,19 @@ export type TimeIntervalString = string
*/
export class TimeInterval {
private constructor(
private $start: UnixTimestampNumber,
private $end: UnixTimestampNumber,
private $start: UnixTimestamp,
private $end: UnixTimestamp,
) {}

static of(start: LocalTimeInput, end: LocalTimeInput): TimeInterval {
return new TimeInterval(localTime.fromInput(start).unix, localTime.fromInput(end).unix)
}

get start(): UnixTimestampNumber {
get start(): UnixTimestamp {
return this.$start
}

get end(): UnixTimestampNumber {
get end(): UnixTimestamp {
return this.$end
}

Expand All @@ -42,7 +42,7 @@ export class TimeInterval {
static parse(d: TimeIntervalConfig): TimeInterval {
if (d instanceof TimeInterval) return d

const [start, end] = d.split('/').map(Number)
const [start, end] = d.split('/').map(Number) as UnixTimestamp[]

if (!end || !start) {
throw new Error(`Cannot parse "${d}" into TimeInterval`)
Expand Down
6 changes: 3 additions & 3 deletions src/decorators/debounce.decorator.test.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { _since, pDelay } from '..'
import { _since, pDelay, UnixTimestampMillis } from '..'
import type { AnyFunction } from '../types'
import { _Debounce } from './debounce.decorator'

class C {
// @debounce(200, {leading: true, trailing: true})
// @throttle(200, {leading: true, trailing: true})
@_Debounce(20)
fn(started: number, n: number): void {
fn(started: UnixTimestampMillis, n: number): void {
console.log(`#${n} after ${_since(started)}`)
}
}

const inst = new C()
const fn = (started: number, n: number): void => inst.fn(started, n)
const fn = (started: UnixTimestampMillis, n: number): void => inst.fn(started, n)

async function startTimer(fn: AnyFunction, interval: number, count: number): Promise<void> {
const started = Date.now()
Expand Down
6 changes: 3 additions & 3 deletions src/decorators/debounce.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { _since, pDelay } from '..'
import { _since, pDelay, UnixTimestampMillis } from '..'
import type { AnyFunction } from '../types'
import { _debounce } from './debounce'

const originalFn = (started: number, n: number): void =>
const originalFn = (started: UnixTimestampMillis, n: number): void =>
console.log(`#${n} after ${_since(started)}`)

async function startTimer(fn: AnyFunction, interval: number, count: number): Promise<void> {
const started = Date.now()
const started = Date.now() as UnixTimestampMillis

for (let i = 0; i < count; i++) {
await pDelay(interval)
Expand Down
4 changes: 2 additions & 2 deletions src/decorators/memo.util.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { _isPrimitive } from '../is.util'
import { pDelay } from '../promise/pDelay'
import type { UnixTimestampNumber } from '../types'
import type { UnixTimestamp } from '../types'
import { MISS } from '../types'

export type MemoSerializer = (args: any[]) => any
Expand All @@ -16,7 +16,7 @@ export interface MemoCacheOptions {
* If set (and if it's implemented by the driver) - will set expiry TTL for each key of the batch.
* E.g EXAT in Redis.
*/
expireAt?: UnixTimestampNumber
expireAt?: UnixTimestamp
}

export interface MemoCache<KEY = any, VALUE = any> {
Expand Down
6 changes: 3 additions & 3 deletions src/env/buildInfo.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { localTime } from '../datetime/localTime'
import type { UnixTimestampNumber } from '../types'
import type { UnixTimestamp } from '../types'

export interface BuildInfo {
/**
* Unix timestamp of when the build was made.
*/
ts: UnixTimestampNumber
ts: UnixTimestamp

/**
* Unix timestamp of commit ("committer date", not "author date")
*/
tsCommit: UnixTimestampNumber
tsCommit: UnixTimestamp

repoName: string
branchName: string
Expand Down
25 changes: 25 additions & 0 deletions src/error/assert.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { _deepEquals } from '../object/deepEquals'
import { _stringify } from '../string/stringify'
import type { Class } from '../typeFest'
import type { UnixTimestamp } from '../types'
import { TS_2000, TS_2500 } from '../zod/zod.shared.schemas'
import type { BackendErrorResponseObject, ErrorData, ErrorObject } from './error.model'
import { _isBackendErrorResponseObject, _isErrorObject, AssertionError } from './error.util'

Expand Down Expand Up @@ -141,3 +143,26 @@ export function _assertTypeOf<T>(v: any, expectedType: string, message?: string)
throw new AssertionError(msg)
}
}

/**
* Casts an arbitrary number as UnixTimestamp.
* Right now does not perform any validation (unlike `asUnixTimestamp2000`),
* but only type casting.
*/
export function asUnixTimestamp(n: number): UnixTimestamp {
return n as UnixTimestamp
}

/**
* Casts an arbitrary number as UnixTimestamp2000.
* Throws if the number is not inside 2000-01-01 and 2500-01-01 time interval,
* which would indicate a bug.
*/
export function asUnixTimestamp2000(n: number): UnixTimestamp {
if (!n || n < TS_2000 || n > TS_2500) {
throw new AssertionError(`Number is not a valid UnixTimestamp2000: ${n}`, {
fingerprint: 'asUnixTimestamp2000',
})
}
return n as UnixTimestamp
}
4 changes: 2 additions & 2 deletions src/error/tryCatch.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { CommonLogger } from '../index'
import type { CommonLogger, UnixTimestampMillis } from '../index'
import { _anyToError, _since } from '../index'
import type { AnyFunction } from '../types'

Expand Down Expand Up @@ -39,7 +39,7 @@ export function _tryCatch<T extends AnyFunction>(fn: T, opt: TryCatchOptions = {
const fname = fn.name || 'anonymous'

return async function (this: any, ...args: any[]) {
const started = Date.now()
const started = Date.now() as UnixTimestampMillis

try {
const r = await fn.apply(this, args)
Expand Down
4 changes: 2 additions & 2 deletions src/http/fetcher.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import type { ErrorData } from '../error/error.model'
import type { CommonLogger } from '../log/commonLogger'
import type { Promisable } from '../typeFest'
import type { AnyObject, NumberOfMilliseconds, Reviver, UnixTimestampMillisNumber } from '../types'
import type { AnyObject, NumberOfMilliseconds, Reviver, UnixTimestampMillis } from '../types'
import type { HttpMethod, HttpStatusFamily } from './http.model'

export interface FetcherNormalizedCfg
Expand Down Expand Up @@ -149,7 +149,7 @@ export interface FetcherRequest
retry3xx: boolean
retry4xx: boolean
retry5xx: boolean
started: UnixTimestampMillisNumber
started: UnixTimestampMillis
}

export interface FetcherOptions {
Expand Down
3 changes: 2 additions & 1 deletion src/http/fetcher.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
localTime,
pExpectedErrorString,
UnexpectedPassError,
UnixTimestampMillis,
} from '..'
import { _range } from '../array/range'
import { _assert, _assertIsError, _assertIsErrorObject } from '../error/assert'
Expand Down Expand Up @@ -64,7 +65,7 @@ test('defaults', () => {

const req: FetcherRequest = (fetcher as any).normalizeOptions({ url: 'some', logResponse: true })
expect(req.logResponse).toBe(true)
req.started = 1234
req.started = 1234 as UnixTimestampMillis

expect(req).toMatchInlineSnapshot(`
{
Expand Down
6 changes: 3 additions & 3 deletions src/http/fetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { pTimeout } from '../promise/pTimeout'
import { _jsonParse, _jsonParseIfPossible } from '../string/json.util'
import { _stringify } from '../string/stringify'
import { _ms, _since } from '../time/time.util'
import { ErrorDataTuple, NumberOfMilliseconds } from '../types'
import { ErrorDataTuple, NumberOfMilliseconds, UnixTimestampMillis } from '../types'
import type {
FetcherAfterResponseHook,
FetcherBeforeRequestHook,
Expand Down Expand Up @@ -262,7 +262,7 @@ export class Fetcher {
} as FetcherResponse<any>

while (!res.retryStatus.retryStopped) {
req.started = Date.now()
req.started = Date.now() as UnixTimestampMillis

// setup timeout
let timeoutId: number | undefined
Expand Down Expand Up @@ -708,7 +708,7 @@ export class Fetcher {
'throwHttpErrors',
'errorData',
]),
started: Date.now(),
started: Date.now() as UnixTimestampMillis,
..._omit(opt, ['method', 'headers', 'credentials']),
inputUrl: opt.url || '',
fullUrl: opt.url || '',
Expand Down
Loading

0 comments on commit f6a5511

Please sign in to comment.