Skip to content

Commit b0614b5

Browse files
kevinkim-ogpm0nggh
andauthored
PLU-394: FormSG MyInfo Child Birth date in dd/MM/yyyy format (#832)
### TL;DR Added support for parsing and formatting MyInfo Child birth dates in FormSG date fields. ### What changed? Added handling for MyInfo Child birth dates which come in dd/mm/yyyy format within the FormSG date field converter. Note: we maintain a single converter for FormSG date fields while accommodating the MyInfo Child date format, to minimise confusion for users. ### How to test? 1. Run the unit tests to verify the new date parsing functionality 2. Verify that the MyInfo Child birth dates can be transformed to other formats correctly 3. Ensure existing date formats continue to work as expected ### Why make this change? MyInfo Child sends birth dates in a different format (`dd/mm/yyyy`) compared to other FormSG date fields, which sends dates as (`dd MMM yyyy`). This change ensures compatibility with MyInfo Child data while maintaining a single, consistent converter for all FormSG date fields to minimise user confusion. --------- Co-authored-by: Ong Guan Hong Malcolm <[email protected]>
1 parent 10d891d commit b0614b5

File tree

5 files changed

+160
-69
lines changed

5 files changed

+160
-69
lines changed

packages/backend/src/apps/formatter/__tests__/date-time.common.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,5 +64,10 @@ describe('common date-time formatter functions', () => {
6464
expect(dateTime.toUnixInteger()).toEqual(1727452800)
6565
},
6666
)
67+
68+
it('supports parsing MyInfo Child date field', () => {
69+
const dateTime = parseDateTime('dd/LL/yyyy', '25/03/2024')
70+
expect(dateTime.toUnixInteger()).toEqual(1711296000)
71+
})
6772
})
6873
})

packages/backend/src/apps/formatter/__tests__/date-time.format.test.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,54 @@ describe('convert date time', () => {
7171
toFormat: 'dd/LL/yy',
7272
expectedResult: '01/04/24',
7373
},
74+
{
75+
inputFormat: 'dd/LL/yy',
76+
inputValue: '01/04/24',
77+
toFormat: 'dd/LL/yyyy',
78+
expectedResult: '01/04/2024',
79+
},
80+
{
81+
inputFormat: 'dd/LL/yyyy',
82+
inputValue: '01/04/2024',
83+
toFormat: 'dd LLLL yyyy',
84+
expectedResult: '01 April 2024',
85+
},
86+
{
87+
inputFormat: 'dd LLLL yyyy',
88+
inputValue: '01 April 2024',
89+
toFormat: 'yyyy/LL/dd',
90+
expectedResult: '2024/04/01',
91+
},
92+
{
93+
inputFormat: 'yyyy/LL/dd',
94+
inputValue: '2024/04/01',
95+
toFormat: 'hh:mm a',
96+
expectedResult: '12:00 am',
97+
},
98+
{
99+
inputFormat: 'hh:mm a',
100+
inputValue: '11:45 pm',
101+
toFormat: 'hh:mm:ss a',
102+
expectedResult: '11:45:00 pm',
103+
},
104+
{
105+
inputFormat: 'hh:mm:ss a',
106+
inputValue: '11:45:00 pm',
107+
toFormat: 'hh:mm a',
108+
expectedResult: '11:45 pm',
109+
},
110+
{
111+
inputFormat: 'dd LLL yyyy hh:mm a',
112+
inputValue: '01 Apr 2024 11:45 pm',
113+
toFormat: 'dd LLL yyyy',
114+
expectedResult: '01 Apr 2024',
115+
},
116+
{
117+
inputFormat: 'dd LLL yyyy hh:mm:ss a',
118+
inputValue: '01 Apr 2024 11:45:30 pm',
119+
toFormat: 'hh:mm:ss a',
120+
expectedResult: '11:45:30 pm',
121+
},
74122
])('can handle all supported input formats', (testParams) => {
75123
const { inputFormat, inputValue, toFormat, expectedResult } = testParams
76124
$.step.parameters = {
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { z } from 'zod'
2+
3+
import { ensureZodEnumValue } from '@/helpers/zod-utils'
4+
5+
// These are the list of common input and output date format options
6+
export const commonDateFormats = [
7+
'dd/LL/yy',
8+
'dd/LL/yyyy',
9+
'dd LLL yyyy',
10+
'dd LLLL yyyy',
11+
'yyyy/LL/dd',
12+
'hh:mm a',
13+
'hh:mm:ss a',
14+
'dd LLL yyyy hh:mm a',
15+
'dd LLL yyyy hh:mm:ss a',
16+
] as const
17+
18+
const formatStringsEnum = z.enum(commonDateFormats)
19+
20+
export const commonDateFormatOptions = [
21+
{
22+
label: 'DD/MM/YY',
23+
description: '25/03/24',
24+
value: ensureZodEnumValue(formatStringsEnum, 'dd/LL/yy'),
25+
},
26+
{
27+
label: 'DD/MM/YYYY',
28+
description: '25/03/2024',
29+
value: ensureZodEnumValue(formatStringsEnum, 'dd/LL/yyyy'),
30+
},
31+
{
32+
label: 'DD MMM YYYY',
33+
description: '25 Mar 2024',
34+
value: ensureZodEnumValue(formatStringsEnum, 'dd LLL yyyy'),
35+
},
36+
{
37+
label: 'DD MMMM YYYY',
38+
description: '25 March 2024',
39+
value: ensureZodEnumValue(formatStringsEnum, 'dd LLLL yyyy'),
40+
},
41+
{
42+
label: 'YYYY/MM/DD',
43+
description: '2024/03/25',
44+
value: ensureZodEnumValue(formatStringsEnum, 'yyyy/LL/dd'),
45+
},
46+
{
47+
label: 'HH:mm (am/pm)',
48+
description: '12:04 PM',
49+
value: ensureZodEnumValue(formatStringsEnum, 'hh:mm a'),
50+
},
51+
{
52+
label: 'HH:mm:ss (am/pm)',
53+
description: '12:04:05 pm',
54+
value: ensureZodEnumValue(formatStringsEnum, 'hh:mm:ss a'),
55+
},
56+
{
57+
label: 'DD MMM YYYY HH:mm (am/pm)',
58+
description: '25 Mar 2024 12:04 pm',
59+
value: ensureZodEnumValue(formatStringsEnum, 'dd LLL yyyy hh:mm a'),
60+
},
61+
{
62+
label: 'DD MMM YYYY HH:mm:ss (am/pm)',
63+
description: '25 Mar 2024 12:04:05 pm',
64+
value: ensureZodEnumValue(formatStringsEnum, 'dd LLL yyyy hh:mm:ss a'),
65+
},
66+
]

packages/backend/src/apps/formatter/actions/date-time/common/date-time-format.ts

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ import type { IField } from '@plumber/types'
33
import { DateTime } from 'luxon'
44
import z from 'zod'
55

6+
import {
7+
commonDateFormatOptions,
8+
commonDateFormats,
9+
} from './date-format-options'
10+
611
/**
712
* This file contains stuff to handle all the date formats we want to support:
813
* - Field and schema definitions
@@ -17,9 +22,13 @@ interface DateFormatConverter {
1722
stringify: (dateTime: DateTime) => string
1823
}
1924

20-
const supportedFormats = z.enum(['formsgSubmissionTime', 'formsgDateField'])
25+
const supportedFormats = z.enum([
26+
'formsgSubmissionTime',
27+
'formsgDateField', // dd LLL yyyy; this is kept due to backwards compatibility
28+
...commonDateFormats,
29+
])
2130

22-
const formatConverters = {
31+
const formatConverters = Object.assign({
2332
formsgSubmissionTime: {
2433
description: 'FormSG submission time',
2534
parse: (input: string): DateTime => DateTime.fromISO(input),
@@ -51,7 +60,20 @@ const formatConverters = {
5160
},
5261
stringify: (dateTime: DateTime): string => dateTime.toFormat('dd MMM yyyy'),
5362
},
54-
} satisfies Record<z.infer<typeof supportedFormats>, DateFormatConverter>
63+
...Object.fromEntries(
64+
commonDateFormats
65+
.filter((format) => format !== 'dd LLL yyyy') // Exclude repeated option due to formsgDateField
66+
.map((format) => [
67+
format,
68+
{
69+
description: format,
70+
parse: (input: string): DateTime =>
71+
DateTime.fromFormat(input, format),
72+
stringify: (dateTime: DateTime): string => dateTime.toFormat(format),
73+
},
74+
]),
75+
),
76+
}) satisfies Record<z.infer<typeof supportedFormats>, DateFormatConverter>
5577

5678
//
5779
// Field definitions and schema
@@ -73,18 +95,21 @@ export const field = {
7395
showOptionValue: false,
7496
options: [
7597
{
76-
label: 'FormSG submission time - e.g. 2024-02-25T08:15:30.250+08:00',
77-
description:
78-
'Select this if you are transforming a FormSG submission time',
98+
label: 'FormSG Submission Time',
99+
description: '2024-03-25T08:15:30.250+08:00',
79100
value: supportedFormats.enum.formsgSubmissionTime,
80101
},
81102
{
82103
// FormSG UI is a bit misleading; although the field shows dd/mm/yyyy,
83104
// date fields are sent as dd MMM yyyy over webhooks.
84-
label: 'FormSG date field - e.g. 25 Mar 2024',
85-
description: 'Select this if you are transforming a FormSG date field',
105+
label: 'FormSG Date Field - DD MMM YYYY',
106+
description: '25 Mar 2024',
86107
value: supportedFormats.enum.formsgDateField,
87108
},
109+
// Exclude repeated option due to formsgDateField
110+
...commonDateFormatOptions.filter(
111+
(option) => option.value !== 'dd LLL yyyy',
112+
),
88113
],
89114
} satisfies IField
90115

@@ -100,7 +125,7 @@ export function parseDateTime(
100125

101126
if (!result.isValid) {
102127
throw new Error(
103-
`'${valueToTransform}' is not a valid ${formatConverters[dateTimeFormat].description}`,
128+
`${valueToTransform}' is not a valid ${formatConverters[dateTimeFormat].description}`,
104129
)
105130
}
106131

packages/backend/src/apps/formatter/actions/date-time/transforms/convert-date-time/fields.ts

Lines changed: 7 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,15 @@ import type { IFieldDropdown } from '@plumber/types'
22

33
import z from 'zod'
44

5-
import { ensureZodEnumValue, ensureZodObjectKey } from '@/helpers/zod-utils'
5+
import { ensureZodObjectKey } from '@/helpers/zod-utils'
66

7-
const formatStringsEnum = z.enum([
8-
'dd/LL/yy',
9-
'dd/LL/yyyy',
10-
'dd LLL yyyy',
11-
'dd LLLL yyyy',
12-
'yyyy/LL/dd',
13-
'hh:mm a',
14-
'hh:mm:ss a',
15-
'dd LLL yyyy hh:mm a',
16-
'dd LLL yyyy hh:mm:ss a',
17-
])
7+
import {
8+
commonDateFormatOptions,
9+
commonDateFormats,
10+
} from '../../common/date-format-options'
1811

1912
export const fieldSchema = z.object({
20-
formatDateTimeToFormat: formatStringsEnum,
13+
formatDateTimeToFormat: z.enum(commonDateFormats),
2114
})
2215

2316
export const field = {
@@ -27,51 +20,5 @@ export const field = {
2720
required: true,
2821
variables: false,
2922
showOptionValue: false,
30-
options: [
31-
{
32-
label: 'DD/MM/YY',
33-
description: '02/01/24',
34-
value: ensureZodEnumValue(formatStringsEnum, 'dd/LL/yy'),
35-
},
36-
{
37-
label: 'DD/MM/YYYY',
38-
description: '02/01/2024',
39-
value: ensureZodEnumValue(formatStringsEnum, 'dd/LL/yyyy'),
40-
},
41-
{
42-
label: 'DD MMM YYYY',
43-
description: '02 Jan 2006',
44-
value: ensureZodEnumValue(formatStringsEnum, 'dd LLL yyyy'),
45-
},
46-
{
47-
label: 'DD MMMM YYYY',
48-
description: '02 January 2006',
49-
value: ensureZodEnumValue(formatStringsEnum, 'dd LLLL yyyy'),
50-
},
51-
{
52-
label: 'YYYY/MM/DD',
53-
description: '2024/01/22',
54-
value: ensureZodEnumValue(formatStringsEnum, 'yyyy/LL/dd'),
55-
},
56-
{
57-
label: 'HH:mm (am/pm)',
58-
description: '12:04 PM',
59-
value: ensureZodEnumValue(formatStringsEnum, 'hh:mm a'),
60-
},
61-
{
62-
label: 'HH:mm:ss (am/pm)',
63-
description: '12:04:05 pm',
64-
value: ensureZodEnumValue(formatStringsEnum, 'hh:mm:ss a'),
65-
},
66-
{
67-
label: 'DD MMM YYYY HH:mm (am/pm)',
68-
description: '02 Jan 2006 12:04 pm',
69-
value: ensureZodEnumValue(formatStringsEnum, 'dd LLL yyyy hh:mm a'),
70-
},
71-
{
72-
label: 'DD MMM YYYY HH:mm:ss (am/pm)',
73-
description: '02 Jan 2006 12:04:05 pm',
74-
value: ensureZodEnumValue(formatStringsEnum, 'dd LLL yyyy hh:mm:ss a'),
75-
},
76-
],
23+
options: commonDateFormatOptions,
7724
} satisfies IFieldDropdown

0 commit comments

Comments
 (0)