Stop re-parsing dates in isStatusStillActive (#11574)

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Tomasz Zawadzki
2026-08-31 15:44:43 +02:00
committed by GitHub
parent 89509073f5
commit 5fabad1b5d
3 changed files with 53 additions and 10 deletions
+5 -10
View File
@@ -6,7 +6,6 @@ import {moderateStatus} from '@bsky/sdk/moderation'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
import {isAfter, parseISO} from 'date-fns'
import {uploadBlob} from '#/lib/api'
import {imageToThumb} from '#/lib/api/resolve'
@@ -23,7 +22,11 @@ import {useTickEveryMinute} from '#/state/shell'
import {useDialogContext} from '#/components/Dialog'
import * as Toast from '#/components/Toast'
import {useAnalytics} from '#/analytics'
import {getLiveNowHost, getLiveServiceNames} from '#/features/liveNow/utils'
import {
getLiveNowHost,
getLiveServiceNames,
isStatusStillActive,
} from '#/features/liveNow/utils'
import {app, com} from '#/lexicons'
import * as bsky from '#/types/bsky'
@@ -151,14 +154,6 @@ export function useActorStatus(actor?: bsky.profile.AnyProfileView) {
}, [shadowed, config, tick, moderation])
}
export function isStatusStillActive(timeStr: string | undefined) {
if (!timeStr) return false
const now = new Date()
const expiry = parseISO(timeStr)
return isAfter(expiry, now)
}
/**
* Validates whether the live status is valid for display in the app. Does NOT
* validate if the status is valid for the acting user e.g. as they go live.
@@ -0,0 +1,35 @@
import {isStatusStillActive} from '#/features/liveNow/utils'
describe('isStatusStillActive', () => {
const iso = (offsetMs: number) =>
new Date(Date.now() + offsetMs).toISOString()
it('is active for a future expiry', () => {
expect(isStatusStillActive(iso(60_000))).toBe(true)
})
it('is not active for a past expiry', () => {
expect(isStatusStillActive(iso(-60_000))).toBe(false)
})
it('is not active without a value', () => {
expect(isStatusStillActive(undefined)).toBe(false)
expect(isStatusStillActive('')).toBe(false)
})
/*
* The predicate reads an atproto `datetime`, which always carries an offset.
* An unparseable value yields NaN, and every NaN comparison is false, so a
* bad timestamp reads as "not live" rather than throwing or showing a stale
* live badge.
*/
it('is not active for an unparseable value', () => {
expect(isStatusStillActive('not-a-date')).toBe(false)
})
it('handles offsets and fractional seconds', () => {
const future = new Date(Date.now() + 3_600_000)
expect(isStatusStillActive(future.toISOString())).toBe(true)
expect(isStatusStillActive('2000-01-01T00:00:00.123+00:00')).toBe(false)
})
})
+13
View File
@@ -97,3 +97,16 @@ export function getLiveNowHost(url: string) {
const {hostname} = new URL(url)
return sanitizeLiveNowHost(hostname)
}
/**
* Whether a live status has not yet expired.
*
* Called per post, per render while scrolling, so this stays on primitives -
* `parseISO` plus two `Date` objects showed up in the scroll profile.
* `Date.parse` returns `NaN` for an unparseable value and `NaN > n` is `false`,
* which is the same "not active" answer the date-fns version gave.
*/
export function isStatusStillActive(timeStr: string | undefined) {
if (!timeStr) return false
return Date.parse(timeStr) > Date.now()
}