Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/date-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export { maxDate } from './maxDate';
export { minDate } from './minDate';
export { newTZDate } from './newTZDate';
export { newUTC } from './newUTC';
export { newUTCFromTimeZone } from './newUTCFromTimeZone';
export { setToUTCMidnight } from './setToUTCMidnight';
export { setUTCDate } from './setUTCDate';
export { setUTCMonth } from './setUTCMonth';
Expand Down
1 change: 1 addition & 0 deletions packages/date-utils/src/newUTCFromTimeZone/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { newUTCFromTimeZone } from './newUTCFromTimeZone';
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { newUTCFromTimeZone } from './newUTCFromTimeZone';

describe('packages/date-utils/newUTCFromTimeZone', () => {
describe('UTC', () => {
test('creates a new UTC date from a given time zone', () => {
const date = newUTCFromTimeZone({
year: '2026',
month: '02',
day: '20',
hour: '23',
minute: '00',
second: '00',
timeZone: 'UTC',
});

// February 20, 2026 11:00:00 PM/23:00:00 in UTC is February 20, 2026 23:00:00 UTC
expect(date).toEqual(new Date('2026-02-20T23:00:00Z'));
});
});

describe('America/New_York', () => {
test('creates a new UTC date from a given time zone', () => {
const date = newUTCFromTimeZone({
year: '2026',
month: '02',
day: '20',
hour: '23',
minute: '00',
second: '00',
timeZone: 'America/New_York',
});

// February 20, 2026 11:00:00 PM/23:00:00 in America/New_York is February 21, 2026 04:00:00 UTC (UTC-5 hours)
expect(date).toEqual(new Date('2026-02-21T04:00:00Z'));
});
});

describe('Pacific/Kiritimati', () => {
test('creates a new UTC date from a given time zone', () => {
const date = newUTCFromTimeZone({
year: '2026',
month: '02',
day: '20',
hour: '23',
minute: '00',
second: '00',
timeZone: 'Pacific/Kiritimati',
});

// February 20, 2026 11:00:00 PM/23:00:00 in Pacific/Kiritimati is February 20, 2026 09:00:00 UTC (UTC+14 hours)
expect(date).toEqual(new Date('2026-02-20T09:00:00Z'));
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { zonedTimeToUtc } from 'date-fns-tz';

/**
* Creates a new UTC date from a given time zone.
* This takes the local date created above and converts it to UTC using the `zonedTimeToUtc` helper function.
*
* @param year - The year
* @param month - The month (1-12)
* @param day - The day
* @param hour - The hour in 24 hour format
* @param minute - The minute
* @param second - The second
* @param timeZone - The time zone
* @returns The new UTC date
*
* @example
* ```js
* // February 20, 2026 11:00:00 PM/23:00:00 in America/New_York is February 21, 2026 04:00:00 UTC
* newUTCFromTimeZone({ year: '2026', month: '02', day: '20', hour: '11', minute: '00', second: '00', timeZone: 'America/New_York' });
* // returns new Date('2026-02-21T04:00:00Z')
* ```
*/
export const newUTCFromTimeZone = ({
year,
month,
day,
hour,
minute,
second,
timeZone,
}: {
year: string;
month: string;
day: string;
hour: string;
minute: string;
second: string;
timeZone: string;
}) => {
const newDate = new Date(
Number(year),
Number(month) - 1,
Number(day),
Number(hour),
Number(minute),
Number(second),
);

return zonedTimeToUtc(newDate, timeZone);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import range from 'lodash/range';

import { convert12hTo24h } from './convert12hTo24h';

describe('convert12hTo24h', () => {
describe('AM', () => {
test('12 AM converts to 0', () => {
expect(convert12hTo24h('12', 'AM')).toEqual('0');
});

test.each(range(1, 12).map(i => [i, i]))(
'%i AM converts to %i',
(input, expected) => {
expect(convert12hTo24h(`${input}`, 'AM')).toEqual(`${expected}`);
},
);
});

describe('PM', () => {
test('12 PM converts to 12', () => {
expect(convert12hTo24h('12', 'PM')).toEqual('12');
});

test.each(range(1, 12).map(i => [i, i + 12]))(
'%i PM converts to %i',
(input, expected) => {
expect(convert12hTo24h(`${input}`, 'PM')).toEqual(`${expected}`);
},
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* Converts a 12 hour format hour to a 24 hour format hour
*
* @example
* ```js
* convert12hTo24h('12', 'AM'); // '0'
* convert12hTo24h('12', 'PM'); // '12'
* convert12hTo24h('1', 'AM'); // '1'
* convert12hTo24h('1', 'PM'); // '13'
* ```
*
* @param hour - The hour to convert
* @param dayPeriod - The day period to use for the conversion (AM or PM)
* @returns The converted hour
*/
export const convert12hTo24h = (hour: string, dayPeriod: string) => {
if (hour === '') return hour;

// if dayPeriod is AM and hour is 12, return 0 since 12 AM is 00:00
if (dayPeriod === 'AM') {
if (hour === '12') {
return '0';
}

// else return hour as-is
return hour;
}

// if dayPeriod is PM and hour is 12, return 12 since 12 PM is 12:00
if (hour === '12') {
return '12';
}

// else return hour + 12
return `${parseInt(hour) + 12}`;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { doesSomeSegmentExist } from './doesSomeSegmentExist';

describe('doesSomeSegmentExist', () => {
test('returns true if at least one segment is filled', () => {
expect(doesSomeSegmentExist({ hour: '', minute: '', second: '00' })).toBe(
true,
);
});

test('returns true if at all segments are filled', () => {
Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The phrase 'at all segments' should be 'all segments' to be grammatically correct.

Suggested change
test('returns true if at all segments are filled', () => {
test('returns true if all segments are filled', () => {

Copilot uses AI. Check for mistakes.
expect(
doesSomeSegmentExist({ hour: '12', minute: '00', second: '00' }),
).toBe(true);
});

test('returns false if no segments are filled', () => {
expect(doesSomeSegmentExist({ hour: '', minute: '', second: '' })).toBe(
false,
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { TimeSegmentsState } from '../../shared.types';

/**
* Checks if some segment exists
*
* @param segments - The segments to check
* @returns Whether some segment exists
*/
export const doesSomeSegmentExist = (segments: TimeSegmentsState) => {
// check if all segments are not empty
return Object.values(segments).some(segment => segment !== '');
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { findUnitOptionByDayPeriod } from './findUnitOptionByDayPeriod';

describe('packages/time-input/utils/findUnitOptionByDayPeriod', () => {
test('returns the unit option by day period', () => {
expect(
findUnitOptionByDayPeriod('AM', [
{ displayName: 'AM', value: 'AM' },
{ displayName: 'PM', value: 'PM' },
]),
).toEqual({ displayName: 'AM', value: 'AM' });
expect(
findUnitOptionByDayPeriod('PM', [
{ displayName: 'AM', value: 'AM' },
{ displayName: 'PM', value: 'PM' },
]),
).toEqual({ displayName: 'PM', value: 'PM' });
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { UnitOption } from '../../TimeInputSelect/TimeInputSelect.types';

/**
* Finds the select unit option based on the day period.
*
* @param dayPeriod - The day period to use for the select unit.
* @param unitOptions - The valid unit options to use for the select unit.
* @returns The select unit option.
*/
export const findUnitOptionByDayPeriod = (
dayPeriod: string,
unitOptions: Array<UnitOption>,
): UnitOption => {
const selectUnitOption = unitOptions.find(
option => option.displayName === dayPeriod,
) as UnitOption;
return selectUnitOption;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { getFormattedTimeSegments } from './getFormattedTimeSegments';

describe('packages/time-input/utils/getFormattedTimeSegments', () => {
test('returns the formatted time segments if all segments are 0', () => {
const formattedTimeSegments = getFormattedTimeSegments({
hour: '0',
minute: '0',
second: '0',
});
expect(formattedTimeSegments).toEqual({
hour: '00',
minute: '00',
second: '00',
});
});

test('returns the formatted time segments', () => {
const formattedTimeSegments = getFormattedTimeSegments({
hour: '2',
minute: '3',
second: '1',
});
expect(formattedTimeSegments).toEqual({
hour: '02',
minute: '03',
second: '01',
});
});

test('does not format segments that are already formatted', () => {
const formattedTimeSegments = getFormattedTimeSegments({
hour: '02',
minute: '03',
second: '01',
});
expect(formattedTimeSegments).toEqual({
hour: '02',
minute: '03',
second: '01',
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { getValueFormatter } from '@leafygreen-ui/input-box';

import { TimeSegmentsState } from '../../shared.types';

/**
* Formats the time segments to a string with 2 digits for each segment.
*
* @param segments - The time segments to format
* @returns The formatted time segments
*
* @example
* ```js
* getFormattedTimeSegments({ hour: '2', minute: '30', second: '0' });
* // returns: { hour: '02', minute: '30', second: '00' }
* ```
*/
export const getFormattedTimeSegments = (segments: TimeSegmentsState) => {
const hour = getValueFormatter({ charsCount: 2, allowZero: true })(
segments.hour,
);
const minute = getValueFormatter({ charsCount: 2, allowZero: true })(
segments.minute,
);
const second = getValueFormatter({ charsCount: 2, allowZero: true })(
segments.second,
);
return { hour, minute, second };
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { getFormattedTimeSegmentsFromDate } from './getFormattedTimeSegmentsFromDate';

describe('packages/time-input/utils/getFormattedTimeSegmentsFromDate', () => {
test('returns the formatted time segments from a date', () => {
const formattedTimeSegments = getFormattedTimeSegmentsFromDate(
new Date('2025-01-01T01:00:00Z'),
'en-US',
'America/New_York',
);
expect(formattedTimeSegments).toEqual({
hour: '08',
minute: '00',
second: '00',
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { DateType, LocaleString } from '@leafygreen-ui/date-utils';

import { TimeSegmentsState } from '../../shared.types';
import { getFormatPartsValues } from '../getFormatPartsValues/getFormatPartsValues';
import { getFormattedTimeSegments } from '../getFormattedTimeSegments/getFormattedTimeSegments';

/**
* Gets the formatted time segments from a date
*
* @param date - The date to get the formatted time segments from
* @param locale - The locale to use
* @param timeZone - The time zone to use
* @returns The formatted time segments
*
* @example
* ```js
* getFormattedTimeSegmentsFromDate(new Date('2025-01-01T12:00:00Z'), 'en-US', 'America/New_York');
* // returns: { hour: '12', minute: '00', second: '00' }
* ```
*/
export const getFormattedTimeSegmentsFromDate = (
date: DateType,
locale: LocaleString,
timeZone: string,
): TimeSegmentsState => {
const { hour, minute, second } = getFormatPartsValues({
locale,
timeZone,
value: date,
});

return getFormattedTimeSegments({ hour, minute, second });
};
Loading
Loading