diff --git a/src/sidebar/components/Annotation/test/AnnotationTimestamps-test.js b/src/sidebar/components/Annotation/test/AnnotationTimestamps-test.js index e757fbceb3f..550b8bce697 100644 --- a/src/sidebar/components/Annotation/test/AnnotationTimestamps-test.js +++ b/src/sidebar/components/Annotation/test/AnnotationTimestamps-test.js @@ -6,7 +6,9 @@ import AnnotationTimestamps, { $imports } from '../AnnotationTimestamps'; describe('AnnotationTimestamps', () => { let clock; - let fakeTime; + let fakeFormatDateTime; + let fakeFormatRelativeDate; + let fakeDecayingInterval; const createComponent = props => mount( @@ -21,15 +23,16 @@ describe('AnnotationTimestamps', () => { beforeEach(() => { clock = sinon.useFakeTimers(); - - fakeTime = { - formatDateTime: sinon.stub().returns('absolute date'), - formatRelativeDate: sinon.stub().returns('fuzzy string'), - decayingInterval: sinon.stub(), - }; + fakeFormatDateTime = sinon.stub().returns('absolute date'); + fakeFormatRelativeDate = sinon.stub().returns('fuzzy string'); + fakeDecayingInterval = sinon.stub(); $imports.$mock({ - '@hypothesis/frontend-shared': fakeTime, + '@hypothesis/frontend-shared': { + formatDateTime: fakeFormatDateTime, + formatRelativeDate: fakeFormatRelativeDate, + decayingInterval: fakeDecayingInterval, + }, }); }); @@ -58,7 +61,7 @@ describe('AnnotationTimestamps', () => { }); it('renders edited timestamp if `withEditedTimestamp` is true', () => { - fakeTime.formatRelativeDate.onCall(1).returns('another fuzzy string'); + fakeFormatRelativeDate.onCall(1).returns('another fuzzy string'); const wrapper = createComponent({ withEditedTimestamp: true }); @@ -68,7 +71,7 @@ describe('AnnotationTimestamps', () => { }); it('does not render edited relative date if equivalent to created relative date', () => { - fakeTime.formatRelativeDate.returns('equivalent fuzzy strings'); + fakeFormatRelativeDate.returns('equivalent fuzzy strings'); const wrapper = createComponent({ withEditedTimestamp: true }); @@ -78,12 +81,12 @@ describe('AnnotationTimestamps', () => { }); it('is updated after time passes', () => { - fakeTime.decayingInterval.callsFake((date, callback) => { + fakeDecayingInterval.callsFake((date, callback) => { const id = setTimeout(callback, 10); return () => clearTimeout(id); }); const wrapper = createComponent(); - fakeTime.formatRelativeDate.returns('60 jiffies'); + fakeFormatRelativeDate.returns('60 jiffies'); act(() => { clock.tick(1000); diff --git a/src/sidebar/services/annotations-exporter.tsx b/src/sidebar/services/annotations-exporter.tsx index c86bca1a836..b3f5a3955aa 100644 --- a/src/sidebar/services/annotations-exporter.tsx +++ b/src/sidebar/services/annotations-exporter.tsx @@ -15,7 +15,7 @@ import { annotationDisplayName } from '../helpers/annotation-user'; import { stripInternalProperties } from '../helpers/strip-internal-properties'; import { VersionData } from '../helpers/version-data'; import { renderMathAndMarkdown } from '../render-markdown'; -import { formatDateTime } from '../util/time'; +import { formatSortableDateTime } from '../util/time'; export type JSONExportContent = { export_date: string; @@ -91,7 +91,7 @@ export class AnnotationsExporter { const page = pageLabel(annotation); const annotationQuote = quote(annotation); const lines = [ - `Created at: ${formatDateTime(new Date(annotation.created))}`, + `Created at: ${formatSortableDateTime(new Date(annotation.created))}`, `Author: ${extractUsername(annotation)}`, page ? `Page: ${page}` : undefined, `Type: ${annotationRole(annotation)}`, @@ -108,7 +108,7 @@ export class AnnotationsExporter { }); return trimAndDedent` - ${formatDateTime(now)} + ${formatSortableDateTime(now)} ${title} ${uri} Group: ${groupName} @@ -135,7 +135,7 @@ export class AnnotationsExporter { }); const annotationToRow = (annotation: APIAnnotationData) => [ - formatDateTime(new Date(annotation.created)), + formatSortableDateTime(new Date(annotation.created)), extractUsername(annotation), pageLabel(annotation) ?? '', uri, @@ -192,7 +192,9 @@ export class AnnotationsExporter {

Summary

- +

{title} @@ -253,7 +255,9 @@ export class AnnotationsExporter { Created at: diff --git a/src/sidebar/services/test/annotations-exporter-test.js b/src/sidebar/services/test/annotations-exporter-test.js index 8b8da842eb8..e5704dbab9f 100644 --- a/src/sidebar/services/test/annotations-exporter-test.js +++ b/src/sidebar/services/test/annotations-exporter-test.js @@ -4,7 +4,7 @@ import { newReply, publicAnnotation, } from '../../test/annotation-fixtures'; -import { formatDateTime } from '../../util/time'; +import { formatSortableDateTime } from '../../util/time'; import { AnnotationsExporter } from '../annotations-exporter'; describe('AnnotationsExporter', () => { @@ -30,7 +30,7 @@ describe('AnnotationsExporter', () => { beforeEach(() => { now = new Date(); - formattedNow = formatDateTime(now); + formattedNow = formatSortableDateTime(now); baseAnnotation = { ...newAnnotation(), ...publicAnnotation(), diff --git a/src/sidebar/util/test/time-test.js b/src/sidebar/util/test/time-test.js index f24295324c3..9dc3a21af37 100644 --- a/src/sidebar/util/test/time-test.js +++ b/src/sidebar/util/test/time-test.js @@ -1,15 +1,13 @@ -import { formatDateTime } from '../time'; +import { formatSortableDateTime } from '../time'; -describe('sidebar/util/time', () => { - describe('formatDateTime', () => { - [ - new Date(Date.UTC(2023, 11, 20, 3, 5, 38)), - new Date('2020-05-04T23:02:01+05:00'), - ].forEach(date => { - it('returns right format for provided date', () => { - const expectedDateRegex = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/; - assert.match(formatDateTime(date), expectedDateRegex); - }); +describe('formatSortableDateTime', () => { + [ + new Date(Date.UTC(2023, 11, 20, 3, 5, 38)), + new Date('2020-05-04T23:02:01+05:00'), + ].forEach(date => { + it('returns right format for provided date', () => { + const expectedDateRegex = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/; + assert.match(formatSortableDateTime(date), expectedDateRegex); }); }); }); diff --git a/src/sidebar/util/time.ts b/src/sidebar/util/time.ts index 26056618df6..1d74c78b606 100644 --- a/src/sidebar/util/time.ts +++ b/src/sidebar/util/time.ts @@ -1,7 +1,14 @@ /** * Formats a date as `YYYY-MM-DD hh:mm`, using 24h and system timezone. + * + * This format has these useful characteristics: + * - Easy to read (compared to an ISO date). + * - Lexicographic and chronological order match. + * - It is detected and correctly parsed as "date" when pasted in common + * spreadsheet editors, making it useful when exporting dates for CSV + * documents. */ -export function formatDateTime(date: Date): string { +export function formatSortableDateTime(date: Date): string { const year = date.getFullYear(); const month = `${date.getMonth() + 1}`.padStart(2, '0'); const day = `${date.getDate()}`.padStart(2, '0');