-
Notifications
You must be signed in to change notification settings - Fork 72
[LG-5532] feat(time-input): Segment state utils #3385
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: LG-5532/segments-display-values
Are you sure you want to change the base?
Changes from all commits
87fa353
bf0c1a9
fdcbd44
cc26d00
438f8ea
819fbdf
cfcca09
36e9fd9
97e751b
01c6487
3730806
f3d3102
26fec0b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export { isSameUTCDayAndTime } from './isSameUTCDayAndTime'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| import { isSameUTCDayAndTime } from './isSameUTCDayAndTime'; | ||
|
|
||
| describe('packages/time-input/utils/isSameUTCDayAndTime', () => { | ||
| test('returns true if the two dates are the same day and time in UTC', () => { | ||
| const date1 = new Date('2025-01-01T12:00:00Z'); | ||
| const date2 = new Date('2025-01-01T12:00:00Z'); | ||
| expect(isSameUTCDayAndTime(date1, date2)).toBe(true); | ||
| }); | ||
|
|
||
| test('returns false if the two dates are not the same day in UTC', () => { | ||
| const date1 = new Date('2025-01-01T12:00:00Z'); | ||
| const date2 = new Date('2025-01-02T12:00:00Z'); | ||
| expect(isSameUTCDayAndTime(date1, date2)).toBe(false); | ||
| }); | ||
|
|
||
| test('returns false if the two dates are not the same time in UTC', () => { | ||
| const date1 = new Date('2025-01-01T12:00:00Z'); | ||
| const date2 = new Date('2025-01-01T12:00:01Z'); | ||
| expect(isSameUTCDayAndTime(date1, date2)).toBe(false); | ||
| }); | ||
|
|
||
| test('returns false if the two dates are not the same date and time in UTC', () => { | ||
| const date1 = new Date('2025-02-01T12:00:00Z'); | ||
| const date2 = new Date('2025-01-01T12:00:01Z'); | ||
| expect(isSameUTCDayAndTime(date1, date2)).toBe(false); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| import { isValidDate } from '../isValidDate'; | ||
| import { DateType } from '../types'; | ||
|
|
||
| /** | ||
| * Checks if two dates are the same day and time in UTC. | ||
| * | ||
| * @param day1 - The first date to check | ||
| * @param day2 - The second date to check | ||
| * @returns Whether the two dates are the same day and time in UTC | ||
| */ | ||
| export const isSameUTCDayAndTime = ( | ||
| day1?: DateType, | ||
| day2?: DateType, | ||
| ): boolean => { | ||
| if (!isValidDate(day1) || !isValidDate(day2)) return false; | ||
|
|
||
| return ( | ||
| day1.getUTCDate() === day2.getUTCDate() && | ||
| day1.getUTCMonth() === day2.getUTCMonth() && | ||
| day1.getUTCFullYear() === day2.getUTCFullYear() && | ||
| day1.getUTCHours() === day2.getUTCHours() && | ||
| day1.getUTCMinutes() === day2.getUTCMinutes() && | ||
| day1.getUTCSeconds() === day2.getUTCSeconds() | ||
| ); | ||
| }; | ||
| 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 = ({ | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems like this is doing the same thing as
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you're correct. I initially looked at this, and I thought it was doing something else. You have this comment in
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The function signature of Needing to prefix
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Your implementation supports IANA strings, so we could extend
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I prefer not to pass the offset number and would rather use a string. I agree that it looks kinda wrong. I can update this to either: With the IANA string and date, I can find the offset using OR With the IANA string and date, use the Regardless of the approach, we still need to depend on date-fns. I think that 2 would be easier because we don't need to calculate the offset manually. |
||
| 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); | ||
| }; | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ignore this hook, it's being replaced in the next PR |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| import range from 'lodash/range'; | ||
|
|
||
| import { consoleOnce } from '@leafygreen-ui/lib'; | ||
|
|
||
| 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); | ||
| }, | ||
| ); | ||
| }); | ||
|
|
||
| describe('Invalid hour', () => { | ||
| test('less than 1 returns the hour', () => { | ||
| const consoleWarnSpy = jest | ||
| .spyOn(consoleOnce, 'warn') | ||
| .mockImplementation(() => {}); | ||
| expect(convert12hTo24h(0, 'AM')).toEqual(0); | ||
| expect(consoleWarnSpy).toHaveBeenCalledWith( | ||
| 'convert12hTo24h > Invalid hour: 0', | ||
| ); | ||
| consoleWarnSpy.mockRestore(); | ||
| }); | ||
|
|
||
| test('greater than 12 returns the hour', () => { | ||
| const consoleWarnSpy = jest | ||
| .spyOn(consoleOnce, 'warn') | ||
| .mockImplementation(() => {}); | ||
| expect(convert12hTo24h(13, 'AM')).toEqual(13); | ||
| expect(consoleWarnSpy).toHaveBeenCalledWith( | ||
| 'convert12hTo24h > Invalid hour: 13', | ||
| ); | ||
| consoleWarnSpy.mockRestore(); | ||
| }); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| import { consoleOnce } from '@leafygreen-ui/lib'; | ||
|
|
||
| import { DayPeriod } from '../../shared.types'; | ||
|
|
||
| /** | ||
| * Converts a 12 hour format hour to a 24 hour format hour | ||
| * | ||
| * @param hour - The hour to convert | ||
| * @param dayPeriod - The day period to use for the conversion (AM or PM) | ||
| * @returns The converted hour or the original hour if it is invalid | ||
| * | ||
| * @example | ||
| * ```js | ||
| * convert12hTo24h(12, 'AM'); // 0 | ||
| * convert12hTo24h(12, 'PM'); // 12 | ||
| * convert12hTo24h(1, 'AM'); // 1 | ||
| * convert12hTo24h(1, 'PM'); // 13 | ||
| * convert12hTo24h(0, 'AM'); // 0 | ||
| * convert12hTo24h(13, 'AM'); // 13 | ||
| * ``` | ||
| */ | ||
| export const convert12hTo24h = (hour: number, dayPeriod: DayPeriod): number => { | ||
| if (hour < 1 || hour > 12) { | ||
| consoleOnce.warn(`convert12hTo24h > Invalid hour: ${hour}`); | ||
| return hour; | ||
| } | ||
|
|
||
| if (hour === 12) { | ||
| // 12AM -> 0:00 | ||
| // 12PM -> 12:00 | ||
| return dayPeriod === DayPeriod.AM ? 0 : 12; | ||
| } | ||
|
|
||
| // if dayPeriod is PM, return hour + 12 | ||
| if (dayPeriod === DayPeriod.PM) { | ||
| return hour + 12; | ||
| } | ||
|
|
||
| return hour; | ||
| }; |
| 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 all segments are filled', () => { | ||
| 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 !== ''); | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can probably use
isEqualfromdate-fnshttps://date-fns.org/v4.1.0/docs/isEqual
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if we need to explicitly check UTC, can we rename this
isEqualUTCand move to date-utils?