Skip to content

Commit

Permalink
Add support for millisecond precision
Browse files Browse the repository at this point in the history
Co-Authored-By: Roger Tuan <11964962+arcataroger@users.noreply.github.com>
  • Loading branch information
wojtekmaj and arcataroger committed Jul 26, 2023
1 parent a7377c2 commit 8e32aa1
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 8 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ Displays a complete clock.
| secondHandOppositeLength | The length of the part of a second hand on the opposite side the hand is pointing to, in %. | `10` | `20` |
| secondHandWidth | Second hand width, in pixels. | `1` | `2` |
| size | Clock size, in pixels (e.g. `200`) or as string (e.g. `"50vw"`). | `150` | <ul><li>Number: `250`</li><li>String: `"50vw"`</li></ul> |
| useMillisecondPrecision | Whether to use millisecond precision. | `false` | `true` |
| value | Clock value. Must be provided. | n/a | Date: `new Date()` |

## License
Expand Down
60 changes: 57 additions & 3 deletions src/Clock.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,12 @@ describe('Clock', () => {
const hourAngle = fullCircle / 12;
const hourMinuteAngle = hourAngle / 60;
const hourSecondAngle = hourMinuteAngle / 60;
const hourMillisecondAngle = hourSecondAngle / 1000;
const minuteAngle = fullCircle / 60;
const minuteSecondAngle = minuteAngle / 60;
const minuteMillisecondAngle = minuteSecondAngle / 1000;
const secondAngle = fullCircle / 60;
const secondMillisecondAngle = secondAngle / 1000;

function getDeg(transform: string) {
const match = transform.match(/rotate\(([0-9.]*)deg\)/);
Expand Down Expand Up @@ -168,7 +171,7 @@ describe('Clock', () => {
expect(hand).toBeInTheDocument();
});

it('is properly angled', () => {
it('is properly angled by default', () => {
const hour = 9;
const minute = 20;
const second = 47;
Expand All @@ -182,6 +185,25 @@ describe('Clock', () => {
hour * hourAngle + minute * hourMinuteAngle + second * hourSecondAngle,
);
});

it('is properly angled when given useMillisecondPrecision', () => {
const hour = 9;
const minute = 20;
const second = 47;
const millisecond = 321;
const date = new Date(2017, 0, 1, hour, minute, second, millisecond);

const { container } = render(<Clock useMillisecondPrecision value={date} />);

const hand = container.querySelector('.react-clock__hour-hand') as HTMLDivElement;

expect(getAngle(hand)).toBeCloseTo(
hour * hourAngle +
minute * hourMinuteAngle +
second * hourSecondAngle +
millisecond * hourMillisecondAngle,
);
});
});

describe('minute hand', () => {
Expand All @@ -201,7 +223,7 @@ describe('Clock', () => {
expect(hand).not.toBeInTheDocument();
});

it('is properly angled', () => {
it('is properly angled by default', () => {
const hour = 9;
const minute = 20;
const second = 47;
Expand All @@ -213,6 +235,22 @@ describe('Clock', () => {

expect(getAngle(hand)).toBeCloseTo(minute * minuteAngle + second * minuteSecondAngle);
});

it('is properly angled when given useMillisecondPrecision', () => {
const hour = 9;
const minute = 20;
const second = 47;
const millisecond = 321;
const date = new Date(2017, 0, 1, hour, minute, second, millisecond);

const { container } = render(<Clock useMillisecondPrecision value={date} />);

const hand = container.querySelector('.react-clock__minute-hand') as HTMLDivElement;

expect(getAngle(hand)).toBeCloseTo(
minute * minuteAngle + second * minuteSecondAngle + millisecond * minuteMillisecondAngle,
);
});
});

describe('second hand', () => {
Expand All @@ -232,7 +270,7 @@ describe('Clock', () => {
expect(hand).not.toBeInTheDocument();
});

it('is properly angled', () => {
it('is properly angled by default', () => {
const hour = 9;
const minute = 20;
const second = 47;
Expand All @@ -244,5 +282,21 @@ describe('Clock', () => {

expect(getAngle(hand)).toBeCloseTo(second * secondAngle);
});

it('is properly angled when given useMillisecondPrecision', () => {
const hour = 9;
const minute = 20;
const second = 47;
const millisecond = 321;
const date = new Date(2017, 0, 1, hour, minute, second, millisecond);

const { container } = render(<Clock useMillisecondPrecision value={date} />);

const hand = container.querySelector('.react-clock__second-hand') as HTMLDivElement;

expect(getAngle(hand)).toBeCloseTo(
second * secondAngle + millisecond * secondMillisecondAngle,
);
});
});
});
20 changes: 16 additions & 4 deletions src/Clock.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import clsx from 'clsx';
import { getHours, getMinutes, getSeconds } from '@wojtekmaj/date-utils';
import { getHours, getMilliseconds, getMinutes, getSeconds } from '@wojtekmaj/date-utils';

import Hand from './Hand';
import MinuteMark from './MinuteMark';
Expand Down Expand Up @@ -49,6 +49,7 @@ export type ClockProps = {
secondHandOppositeLength?: OppositeHandLength;
secondHandWidth?: HandWidth;
size?: React.CSSProperties['width'];
useMillisecondPrecision?: boolean;
value?: string | Date | null;
};

Expand All @@ -75,6 +76,7 @@ const Clock: React.FC<ClockProps> = function Clock({
secondHandOppositeLength,
secondHandWidth = 1,
size = 150,
useMillisecondPrecision,
value,
}) {
function renderMinuteMarksFn() {
Expand Down Expand Up @@ -135,7 +137,10 @@ const Clock: React.FC<ClockProps> = function Clock({

function renderHourHandFn() {
const angle = value
? getHours(value) * 30 + getMinutes(value) / 2 + getSeconds(value) / 120
? getHours(value) * 30 +
getMinutes(value) / 2 +
getSeconds(value) / 120 +
(useMillisecondPrecision ? getMilliseconds(value) / 120000 : 0)
: 0;

return (
Expand All @@ -155,7 +160,10 @@ const Clock: React.FC<ClockProps> = function Clock({
}

const angle = value
? getHours(value) * 360 + getMinutes(value) * 6 + getSeconds(value) / 10
? getHours(value) * 360 +
getMinutes(value) * 6 +
getSeconds(value) / 10 +
(useMillisecondPrecision ? getMilliseconds(value) / 10000 : 0)
: 0;

return (
Expand All @@ -174,7 +182,11 @@ const Clock: React.FC<ClockProps> = function Clock({
return null;
}

const angle = value ? getMinutes(value) * 360 + getSeconds(value) * 6 : 0;
const angle = value
? getMinutes(value) * 360 +
getSeconds(value) * 6 +
(useMillisecondPrecision ? getMilliseconds(value) * 0.006 : 0)
: 0;

return (
<Hand
Expand Down
25 changes: 24 additions & 1 deletion test/Test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useCallback, useState } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import Clock from 'react-clock/src';
import 'react-clock/src/Clock.css';
import { useSetInterval } from '@wojtekmaj/react-hooks';
Expand All @@ -23,6 +23,7 @@ export default function Test() {
const [renderMinuteMarks, setRenderMinuteMarks] = useState(true);
const [renderNumbers, setRenderNumbers] = useState(true);
const [renderSecondHand, setRenderSecondHand] = useState(true);
const [useMillisecondPrecision, setUseMillisecondPrecision] = useState(false);
const [value, setValue] = useState<LooseValue>(now);

const updateDate = useCallback(() => {
Expand All @@ -31,6 +32,25 @@ export default function Test() {

useSetInterval(updateDate, 1000);

useEffect(() => {
if (!useMillisecondPrecision) {
return;
}

let animationFrame: number;

function callback() {
updateDate();
animationFrame = requestAnimationFrame(callback);
}

animationFrame = requestAnimationFrame(callback);

return () => {
cancelAnimationFrame(animationFrame);
};
}, [updateDate, useMillisecondPrecision]);

function renderDebugInfo() {
const renderTime = (timeToRender: string | Date | null) => {
if (timeToRender instanceof Date) {
Expand All @@ -57,11 +77,13 @@ export default function Test() {
renderMinuteMarks={renderMinuteMarks}
renderNumbers={renderNumbers}
renderSecondHand={renderSecondHand}
useMillisecondPrecision={useMillisecondPrecision}
setRenderHourMarks={setRenderHourMarks}
setRenderMinuteHand={setRenderMinuteHand}
setRenderMinuteMarks={setRenderMinuteMarks}
setRenderNumbers={setRenderNumbers}
setRenderSecondHand={setRenderSecondHand}
setUseMillisecondPrecision={setUseMillisecondPrecision}
/>
</aside>
<main className="Test__container__content">
Expand All @@ -81,6 +103,7 @@ export default function Test() {
renderNumbers={renderNumbers}
renderSecondHand={renderSecondHand}
size={200}
useMillisecondPrecision={useMillisecondPrecision}
value={value}
/>
</form>
Expand Down
20 changes: 20 additions & 0 deletions test/ViewOptions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ type ViewOptionsProps = {
renderMinuteMarks: boolean;
renderNumbers: boolean;
renderSecondHand: boolean;
useMillisecondPrecision: boolean;
setRenderHourMarks: (renderHourMarks: boolean) => void;
setRenderMinuteHand: (renderMinuteHand: boolean) => void;
setRenderMinuteMarks: (renderMinuteMarks: boolean) => void;
setRenderNumbers: (renderNumbers: boolean) => void;
setRenderSecondHand: (renderSecondHand: boolean) => void;
setUseMillisecondPrecision: (useMillisecondPrecision: boolean) => void;
};

export default function ViewOptions({
Expand All @@ -19,11 +21,13 @@ export default function ViewOptions({
renderMinuteMarks,
renderNumbers,
renderSecondHand,
useMillisecondPrecision,
setRenderHourMarks,
setRenderMinuteHand,
setRenderMinuteMarks,
setRenderNumbers,
setRenderSecondHand,
setUseMillisecondPrecision,
}: ViewOptionsProps) {
function onRenderMinuteHandChange(event: React.ChangeEvent<HTMLInputElement>) {
const { checked } = event.target;
Expand Down Expand Up @@ -55,6 +59,12 @@ export default function ViewOptions({
setRenderNumbers(checked);
}

function onUseMillisecondPrecisionChange(event: React.ChangeEvent<HTMLInputElement>) {
const { checked } = event.target;

setUseMillisecondPrecision(checked);
}

return (
<fieldset>
<legend>View options</legend>
Expand Down Expand Up @@ -108,6 +118,16 @@ export default function ViewOptions({
/>
<label htmlFor="renderNumbers">Show numbers</label>
</div>

<div>
<input
checked={useMillisecondPrecision}
id="useMillisecondPrecision"
onChange={onUseMillisecondPrecisionChange}
type="checkbox"
/>
<label htmlFor="useMillisecondPrecision">Use millisecond precision</label>
</div>
</fieldset>
);
}

0 comments on commit 8e32aa1

Please sign in to comment.