Skip to content

Commit f2ad0ca

Browse files
committed
Fixes codec to only encode date format
1 parent b47ee43 commit f2ad0ca

File tree

10 files changed

+147
-157
lines changed

10 files changed

+147
-157
lines changed

packages/common/src/constants/datetime.ts

+4
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,7 @@ export const ISO_DATE_OR_DATE_TIME_LIKE_PATTERN = new RegExp(
3535
'$',
3636
].join('')
3737
);
38+
39+
export const ISO_DATE_OR_DATE_TIME_NO_OFFSET_PATTERN = new RegExp(
40+
['^', ISO_DATE_LIKE_SUBPATTERN, `(T${ISO_TIME_LIKE_SUBPATTERN})?`, '$'].join('')
41+
);

packages/common/src/fixtures/date/date.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
<bind nodeset="/data/dates/fruits_date" type="date" required="false()"
5555
relevant=" /data/dates/date_of_birth != &quot;&quot;"/>
5656
<bind nodeset="/data/dates/vegetables_date" type="date" required="false()"
57-
relevant=" /data/dates/date_of_birth != &quot;&quot;" readonly=" /data/dates/fruits_date &gt; today()"/>
57+
relevant=" /data/dates/date_of_birth != &quot;&quot;" readonly="/data/dates/date_of_birth &lt;= today()"/>
5858
<bind nodeset="/data/meta/instanceID" type="string" readonly="true()" jr:preload="uid"/>
5959
</model>
6060
</h:head>

packages/common/src/fixtures/notes/2-all-possible-notes.xml

+8-3
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
<read_only_int />
2020
<read_only_int_value>3</read_only_int_value>
2121
<note_calc_decimal_from_int />
22-
<geopoint-note>38.253094215699576 21.756382658677467 0 150</geopoint-note>
22+
<date_note>2025-12-21T23:30:05</date_note>
23+
<geopoint_note>38.253094215699576 21.756382658677467 0 150</geopoint_note>
2324
</group>
2425
<meta>
2526
<instanceID />
@@ -38,8 +39,9 @@
3839
<bind nodeset="/data/group/read_only_int_value" type="int" readonly="true()" />
3940
<bind nodeset="/data/group/note_calc_decimal_from_int" type="decimal"
4041
calculate="/data/group/read_only_int_value + 1.5" readonly="true()" />
42+
<bind nodeset="/data/group/date_note" type="date" readonly="true()" />
43+
<bind nodeset="/data/group/geopoint_note" type="geopoint" readonly="true()" />
4144
<bind nodeset="/data/meta/instanceID" type="string" readonly="true()" jr:preload="uid" />
42-
<bind nodeset="/data/group/geopoint-note" type="geopoint" readonly="true()" />
4345
</model>
4446
</h:head>
4547
<h:body>
@@ -78,7 +80,10 @@
7880
<input ref="/data/group/note_calc_decimal_from_int">
7981
<label>A note with decimal type calculated from int</label>
8082
</input>
81-
<input ref="/data/group/geopoint-note">
83+
<input ref="/data/group/date_note">
84+
<label>A note with date type</label>
85+
</input>
86+
<input ref="/data/group/geopoint_note">
8287
<label>A note with geopoint type</label>
8388
</input>
8489
</group>

packages/scenario/test/bind-types.test.ts

+25-38
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ describe('Data (<bind type>) type support', () => {
3939
t('int-value', '123'),
4040
t('decimal-value', '45.67'),
4141
t('geopoint-value', '38.25146813817506 21.758421137528785 0 0'),
42-
t('date-value', '1999-11-23'),
42+
t('date-value', '1999-11-23T23:30:05'),
4343
)
4444
),
4545
bind('/root/string-value').type('string').relevant(modelNodeRelevanceExpression),
@@ -229,7 +229,7 @@ describe('Data (<bind type>) type support', () => {
229229
});
230230

231231
it('has a ZonedDateTime | null static type', () => {
232-
expectTypeOf(answer.value).toEqualTypeOf<Temporal.ZonedDateTime | null>();
232+
expectTypeOf(answer.value).toEqualTypeOf<Temporal.PlainDate | null>();
233233
});
234234

235235
it('has a date populated value', () => {
@@ -263,7 +263,7 @@ describe('Data (<bind type>) type support', () => {
263263
t('int-value', '123'),
264264
t('decimal-value', '45.67'),
265265
t('geopoint-value', '38.25146813817506 21.758421137528785 1000 25'),
266-
t('date-value', '2025-12-20T13:45:55'),
266+
t('date-value', '2025-12-20'),
267267
)
268268
),
269269
bind('/root/string-value').type('string').relevant(inputRelevanceExpression),
@@ -653,12 +653,12 @@ describe('Data (<bind type>) type support', () => {
653653
});
654654

655655
it('has a ZonedDateTime | null static type', () => {
656-
expectTypeOf(answer.value).toEqualTypeOf<Temporal.ZonedDateTime | null>();
656+
expectTypeOf(answer.value).toEqualTypeOf<Temporal.PlainDate | null>();
657657
});
658658

659659
it('has a date populated value', () => {
660-
expect(answer.value).to.deep.equal(Temporal.PlainDateTime.from('2025-12-20T13:45:55'));
661-
expect(answer.stringValue).toEqual('2025-12-20T13:45:55');
660+
expect(answer.value).to.deep.equal(Temporal.PlainDate.from('2025-12-20'));
661+
expect(answer.stringValue).toEqual('2025-12-20');
662662
});
663663

664664
it('has an null as blank value', () => {
@@ -668,43 +668,30 @@ describe('Data (<bind type>) type support', () => {
668668
expect(answer.stringValue).toBe('');
669669
});
670670

671-
it.each(['13:30:55', '2025-23-23', 'ZYX', '2025-03-07T14:30:00+invalid'])(
672-
'has null when incorrect value is passed',
673-
(expression) => {
674-
scenario.answer('/root/date-value', expression);
675-
answer = getTypedInputNodeAnswer('/root/date-value', 'date');
676-
expect(answer.value).toBeNull();
677-
expect(answer.stringValue).toBe('');
678-
}
679-
);
671+
it.each([
672+
'13:30:55',
673+
'2025-23-23',
674+
'ZYX',
675+
'2025-03-07T14:30:00+invalid',
676+
'2025-03-07T14:30:00-08:00',
677+
'2025-03-07T14:30:00Z',
678+
])('has null when incorrect value is passed', (expression) => {
679+
scenario.answer('/root/date-value', expression);
680+
answer = getTypedInputNodeAnswer('/root/date-value', 'date');
681+
expect(answer.value).toBeNull();
682+
expect(answer.stringValue).toBe('');
683+
});
680684

681685
it.each([
682686
{
683-
expression: '2025-03-07',
684-
expectedAsObject: Temporal.PlainDate.from('2025-03-07'),
685-
expectedAsText: '2025-03-07',
686-
},
687-
{
688-
expression: '2025-03-07T14:30:00-08:00',
689-
expectedAsObject: Temporal.PlainDateTime.from('2025-03-07T14:30:00-08:00'),
690-
expectedAsText: '2025-03-07T14:30:00',
691-
},
692-
{
693-
expression: '2025-03-07T14:30:00-08:00[America/Los_Angeles]',
694-
expectedAsObject: Temporal.ZonedDateTime.from(
695-
'2025-03-07T14:30:00-08:00[America/Los_Angeles]'
696-
),
697-
expectedAsText: '2025-03-07T14:30:00-08:00[America/Los_Angeles]',
698-
},
699-
{
700-
expression: '2025-03-07T14:30:00Z',
701-
expectedAsObject: Temporal.ZonedDateTime.from('2025-03-07T14:30:00[UTC]'),
702-
expectedAsText: '2025-03-07T14:30:00Z',
687+
expression: '2025-03-14',
688+
expectedAsObject: Temporal.PlainDate.from('2025-03-14'),
689+
expectedAsText: '2025-03-14',
703690
},
704691
{
705-
expression: '2025-03-07T14:30:00',
706-
expectedAsObject: Temporal.PlainDateTime.from('2025-03-07T14:30:00'),
707-
expectedAsText: '2025-03-07T14:30:00',
692+
expression: '2025-12-21T14:30:00',
693+
expectedAsObject: Temporal.PlainDate.from('2025-12-21'),
694+
expectedAsText: '2025-12-21',
708695
},
709696
])('sets value with valid date', ({ expression, expectedAsObject, expectedAsText }) => {
710697
scenario.answer('/root/date-value', expression);

packages/web-forms/src/components/controls/Input/InputDate.vue

+15-9
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<script setup lang="ts">
22
import { computed } from 'vue';
33
import Calendar from 'primevue/calendar';
4-
import { Temporal } from 'temporal-polyfill';
54
import type { DateInputNode } from '@getodk/xforms-engine';
5+
import { ISO_DATE_LIKE_PATTERN } from '@getodk/common/constants/datetime.ts';
66
77
interface InputDateProps {
88
readonly question: DateInputNode;
@@ -12,19 +12,17 @@ const props = defineProps<InputDateProps>();
1212
1313
const value = computed({
1414
get: () => {
15-
const temporalValue = props.question.currentState.value;
16-
17-
if (temporalValue == null) {
15+
if (props.question.currentState.value == null) {
1816
return null;
1917
}
2018
21-
if (temporalValue instanceof Temporal.ZonedDateTime) {
22-
return new Date(temporalValue.toInstant().epochMilliseconds);
19+
const temporalValue = props.question.currentState.value.toString();
20+
if (!ISO_DATE_LIKE_PATTERN.test(temporalValue)) {
21+
return null;
2322
}
2423
25-
// For PlainDate and PlainDateTime, use ISO string with UTC assumption
26-
const time = temporalValue instanceof Temporal.PlainDate ? 'T00:00:00Z' : 'Z';
27-
return new Date(temporalValue.toString() + time);
24+
// Convert to ISO string (yyyy-mm-dd) and append time for start of day local
25+
return new Date(temporalValue + 'T00:00:00');
2826
},
2927
set: (newDate) => {
3028
props.question.setValue(newDate);
@@ -39,6 +37,8 @@ const isDisabled = computed(() => props.question.currentState.readonly === true)
3937
</template>
4038

4139
<style lang="scss">
40+
@import 'primeflex/core/_variables.scss';
41+
4242
.p-calendar {
4343
width: 50%;
4444
}
@@ -63,4 +63,10 @@ const isDisabled = computed(() => props.question.currentState.readonly === true)
6363
.p-calendar.p-calendar-disabled .p-datepicker-trigger-icon {
6464
cursor: not-allowed;
6565
}
66+
67+
@media screen and (max-width: #{$sm}) {
68+
.p-calendar {
69+
width: 100%;
70+
}
71+
}
6672
</style>

packages/web-forms/src/components/controls/NoteControl.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ const value = computed<NoteRenderableValue>(() => {
7474
<GeopointFormattedValue :question="question" />
7575
</template>
7676

77-
<template v-if="question.valueType === 'date'">
77+
<template v-else-if="question.valueType === 'date'">
7878
{{ value.toString() }}
7979
</template>
8080

packages/xforms-engine/src/lib/codecs/Date/Datetime.ts

-82
This file was deleted.

packages/xforms-engine/src/lib/codecs/Date/DatetimeValueCodec.ts

-20
This file was deleted.

0 commit comments

Comments
 (0)