@contentful/field-editor-date 2.0.7 → 2.0.8-canary.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/DatepickerInput.js +2 -10
- package/dist/cjs/TimepickerInput.js +15 -30
- package/dist/cjs/utils/data.spec.js +336 -9
- package/dist/cjs/utils/date.js +68 -38
- package/dist/esm/DatepickerInput.js +2 -5
- package/dist/esm/TimepickerInput.js +15 -25
- package/dist/esm/utils/data.spec.js +337 -5
- package/dist/esm/utils/date.js +68 -33
- package/dist/types/DatepickerInput.d.ts +2 -3
- package/dist/types/types.d.ts +1 -2
- package/dist/types/utils/date.d.ts +4 -1
- package/package.json +3 -3
|
@@ -11,12 +11,6 @@ Object.defineProperty(exports, "DatepickerInput", {
|
|
|
11
11
|
const _react = /*#__PURE__*/ _interop_require_wildcard(require("react"));
|
|
12
12
|
const _f36components = require("@contentful/f36-components");
|
|
13
13
|
const _css = require("@emotion/css");
|
|
14
|
-
const _moment = /*#__PURE__*/ _interop_require_default(require("moment"));
|
|
15
|
-
function _interop_require_default(obj) {
|
|
16
|
-
return obj && obj.__esModule ? obj : {
|
|
17
|
-
default: obj
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
14
|
function _getRequireWildcardCache(nodeInterop) {
|
|
21
15
|
if (typeof WeakMap !== "function") return null;
|
|
22
16
|
var cacheBabelInterop = new WeakMap();
|
|
@@ -75,14 +69,12 @@ const DatepickerInput = (props)=>{
|
|
|
75
69
|
toDate
|
|
76
70
|
];
|
|
77
71
|
}, []);
|
|
78
|
-
const
|
|
79
|
-
const selectedDate = dateObj ? new Date(dateObj.years, dateObj.months, dateObj.date) : undefined;
|
|
72
|
+
const selectedDate = props.value;
|
|
80
73
|
return /*#__PURE__*/ _react.default.createElement(_f36components.Datepicker, {
|
|
81
74
|
className: styles.root,
|
|
82
75
|
selected: selectedDate,
|
|
83
76
|
onSelect: (day)=>{
|
|
84
|
-
|
|
85
|
-
props.onChange(momentDay);
|
|
77
|
+
props.onChange(day ?? undefined);
|
|
86
78
|
},
|
|
87
79
|
inputProps: {
|
|
88
80
|
isDisabled: props.disabled,
|
|
@@ -11,12 +11,7 @@ Object.defineProperty(exports, "TimepickerInput", {
|
|
|
11
11
|
const _react = /*#__PURE__*/ _interop_require_wildcard(require("react"));
|
|
12
12
|
const _f36components = require("@contentful/f36-components");
|
|
13
13
|
const _css = require("@emotion/css");
|
|
14
|
-
const
|
|
15
|
-
function _interop_require_default(obj) {
|
|
16
|
-
return obj && obj.__esModule ? obj : {
|
|
17
|
-
default: obj
|
|
18
|
-
};
|
|
19
|
-
}
|
|
14
|
+
const _datefns = require("date-fns");
|
|
20
15
|
function _getRequireWildcardCache(nodeInterop) {
|
|
21
16
|
if (typeof WeakMap !== "function") return null;
|
|
22
17
|
var cacheBabelInterop = new WeakMap();
|
|
@@ -60,41 +55,31 @@ function _interop_require_wildcard(obj, nodeInterop) {
|
|
|
60
55
|
}
|
|
61
56
|
const validInputFormats = [
|
|
62
57
|
'hh:mm a',
|
|
63
|
-
'hh:mm A',
|
|
64
58
|
'h:mm a',
|
|
65
|
-
'h:mm A',
|
|
66
59
|
'hh:mm',
|
|
67
|
-
'k:mm',
|
|
68
60
|
'kk:mm',
|
|
61
|
+
'k:mm',
|
|
69
62
|
'h a',
|
|
70
|
-
'
|
|
71
|
-
'
|
|
72
|
-
'
|
|
73
|
-
'HH'
|
|
63
|
+
'HH',
|
|
64
|
+
'H:mm',
|
|
65
|
+
'h'
|
|
74
66
|
];
|
|
67
|
+
const REF_DATE = new Date(2000, 0, 1);
|
|
75
68
|
function parseRawInput(raw) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (date.isValid()) {
|
|
80
|
-
time = date;
|
|
81
|
-
break;
|
|
82
|
-
}
|
|
69
|
+
for (const fmt of validInputFormats){
|
|
70
|
+
const parsed = (0, _datefns.parse)(raw, fmt, REF_DATE);
|
|
71
|
+
if ((0, _datefns.isValid)(parsed)) return parsed;
|
|
83
72
|
}
|
|
84
|
-
return
|
|
73
|
+
return null;
|
|
85
74
|
}
|
|
86
|
-
const getDefaultTime = ()=>
|
|
87
|
-
|
|
88
|
-
};
|
|
89
|
-
const formatToString = (uses12hClock, value)=>{
|
|
90
|
-
return uses12hClock ? value.format('hh:mm A') : value.format('HH:mm');
|
|
91
|
-
};
|
|
75
|
+
const getDefaultTime = ()=>(0, _datefns.parse)('12:00 AM', 'hh:mm a', REF_DATE);
|
|
76
|
+
const formatToString = (uses12hClock, value)=>(0, _datefns.format)(value, uses12hClock ? 'hh:mm a' : 'HH:mm');
|
|
92
77
|
const TimepickerInput = ({ disabled, uses12hClock, time = '12:00', ampm = 'AM', onChange })=>{
|
|
93
78
|
const [selectedTime, setSelectedTime] = (0, _react.useState)(()=>{
|
|
94
79
|
return formatToString(uses12hClock, getDefaultTime());
|
|
95
80
|
});
|
|
96
81
|
(0, _react.useEffect)(()=>{
|
|
97
|
-
setSelectedTime(formatToString(uses12hClock, (0,
|
|
82
|
+
setSelectedTime(formatToString(uses12hClock, (0, _datefns.parse)(`${time} ${ampm}`, 'hh:mm a', REF_DATE)));
|
|
98
83
|
}, [
|
|
99
84
|
time,
|
|
100
85
|
ampm,
|
|
@@ -112,8 +97,8 @@ const TimepickerInput = ({ disabled, uses12hClock, time = '12:00', ampm = 'AM',
|
|
|
112
97
|
const value = parsedTime ?? getDefaultTime();
|
|
113
98
|
setSelectedTime(formatToString(uses12hClock, value));
|
|
114
99
|
onChange({
|
|
115
|
-
time:
|
|
116
|
-
ampm:
|
|
100
|
+
time: (0, _datefns.format)(value, 'hh:mm'),
|
|
101
|
+
ampm: (0, _datefns.format)(value, 'a').toUpperCase()
|
|
117
102
|
});
|
|
118
103
|
};
|
|
119
104
|
return /*#__PURE__*/ _react.default.createElement(_f36components.Flex, {
|
|
@@ -2,19 +2,13 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", {
|
|
3
3
|
value: true
|
|
4
4
|
});
|
|
5
|
-
const _moment = /*#__PURE__*/ _interop_require_default(require("moment"));
|
|
6
5
|
const _date = require("./date");
|
|
7
|
-
function _interop_require_default(obj) {
|
|
8
|
-
return obj && obj.__esModule ? obj : {
|
|
9
|
-
default: obj
|
|
10
|
-
};
|
|
11
|
-
}
|
|
12
6
|
describe('date utils', ()=>{
|
|
13
7
|
describe('buildFieldValue', ()=>{
|
|
14
8
|
it('should work properly', ()=>{
|
|
15
9
|
expect((0, _date.buildFieldValue)({
|
|
16
10
|
data: {
|
|
17
|
-
date:
|
|
11
|
+
date: new Date('2018-02-02'),
|
|
18
12
|
time: '05:00',
|
|
19
13
|
ampm: 'PM',
|
|
20
14
|
utcOffset: '+03:00'
|
|
@@ -27,7 +21,7 @@ describe('date utils', ()=>{
|
|
|
27
21
|
});
|
|
28
22
|
expect((0, _date.buildFieldValue)({
|
|
29
23
|
data: {
|
|
30
|
-
date:
|
|
24
|
+
date: new Date('2015-01-14'),
|
|
31
25
|
time: '05:00',
|
|
32
26
|
ampm: 'AM',
|
|
33
27
|
utcOffset: '-05:00'
|
|
@@ -40,7 +34,7 @@ describe('date utils', ()=>{
|
|
|
40
34
|
});
|
|
41
35
|
expect((0, _date.buildFieldValue)({
|
|
42
36
|
data: {
|
|
43
|
-
date:
|
|
37
|
+
date: new Date('2015-01-14'),
|
|
44
38
|
time: '17:00',
|
|
45
39
|
ampm: 'PM',
|
|
46
40
|
utcOffset: '-05:00'
|
|
@@ -52,5 +46,338 @@ describe('date utils', ()=>{
|
|
|
52
46
|
valid: '2015-01-14T17:00-05:00'
|
|
53
47
|
});
|
|
54
48
|
});
|
|
49
|
+
it('returns date-only format when usesTime and usesTimezone are false', ()=>{
|
|
50
|
+
expect((0, _date.buildFieldValue)({
|
|
51
|
+
data: {
|
|
52
|
+
date: new Date('2020-06-15'),
|
|
53
|
+
time: '10:30',
|
|
54
|
+
ampm: 'AM',
|
|
55
|
+
utcOffset: '+00:00'
|
|
56
|
+
},
|
|
57
|
+
usesTimezone: false,
|
|
58
|
+
usesTime: false
|
|
59
|
+
})).toEqual({
|
|
60
|
+
invalid: false,
|
|
61
|
+
valid: '2020-06-15'
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
it('returns datetime without timezone when usesTime=true, usesTimezone=false', ()=>{
|
|
65
|
+
expect((0, _date.buildFieldValue)({
|
|
66
|
+
data: {
|
|
67
|
+
date: new Date('2020-06-15'),
|
|
68
|
+
time: '14:30',
|
|
69
|
+
ampm: 'PM',
|
|
70
|
+
utcOffset: '+00:00'
|
|
71
|
+
},
|
|
72
|
+
usesTimezone: false,
|
|
73
|
+
usesTime: true
|
|
74
|
+
})).toEqual({
|
|
75
|
+
invalid: false,
|
|
76
|
+
valid: '2020-06-15T14:30'
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
it('returns valid: null when no date is provided', ()=>{
|
|
80
|
+
expect((0, _date.buildFieldValue)({
|
|
81
|
+
data: {
|
|
82
|
+
time: '10:00',
|
|
83
|
+
ampm: 'AM',
|
|
84
|
+
utcOffset: '+00:00'
|
|
85
|
+
},
|
|
86
|
+
usesTimezone: true,
|
|
87
|
+
usesTime: true
|
|
88
|
+
})).toEqual({
|
|
89
|
+
invalid: false,
|
|
90
|
+
valid: null
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
it('handles midnight (12:00 AM) correctly', ()=>{
|
|
94
|
+
expect((0, _date.buildFieldValue)({
|
|
95
|
+
data: {
|
|
96
|
+
date: new Date('2021-03-01'),
|
|
97
|
+
time: '12:00',
|
|
98
|
+
ampm: 'AM',
|
|
99
|
+
utcOffset: '+00:00'
|
|
100
|
+
},
|
|
101
|
+
usesTimezone: true,
|
|
102
|
+
usesTime: true
|
|
103
|
+
})).toEqual({
|
|
104
|
+
invalid: false,
|
|
105
|
+
valid: '2021-03-01T00:00+00:00'
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
it('handles noon (12:00 PM) correctly', ()=>{
|
|
109
|
+
expect((0, _date.buildFieldValue)({
|
|
110
|
+
data: {
|
|
111
|
+
date: new Date('2021-03-01'),
|
|
112
|
+
time: '12:00',
|
|
113
|
+
ampm: 'PM',
|
|
114
|
+
utcOffset: '+00:00'
|
|
115
|
+
},
|
|
116
|
+
usesTimezone: true,
|
|
117
|
+
usesTime: true
|
|
118
|
+
})).toEqual({
|
|
119
|
+
invalid: false,
|
|
120
|
+
valid: '2021-03-01T12:00+00:00'
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
it('preserves half-hour offset +05:30 (India)', ()=>{
|
|
124
|
+
expect((0, _date.buildFieldValue)({
|
|
125
|
+
data: {
|
|
126
|
+
date: new Date('2023-08-15'),
|
|
127
|
+
time: '10:00',
|
|
128
|
+
ampm: 'AM',
|
|
129
|
+
utcOffset: '+05:30'
|
|
130
|
+
},
|
|
131
|
+
usesTimezone: true,
|
|
132
|
+
usesTime: true
|
|
133
|
+
})).toEqual({
|
|
134
|
+
invalid: false,
|
|
135
|
+
valid: '2023-08-15T10:00+05:30'
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
it('preserves negative half-hour offset -09:30', ()=>{
|
|
139
|
+
expect((0, _date.buildFieldValue)({
|
|
140
|
+
data: {
|
|
141
|
+
date: new Date('2023-08-15'),
|
|
142
|
+
time: '03:30',
|
|
143
|
+
ampm: 'AM',
|
|
144
|
+
utcOffset: '-09:30'
|
|
145
|
+
},
|
|
146
|
+
usesTimezone: true,
|
|
147
|
+
usesTime: true
|
|
148
|
+
})).toEqual({
|
|
149
|
+
invalid: false,
|
|
150
|
+
valid: '2023-08-15T03:30-09:30'
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
it('preserves quarter-hour offset +05:45 (Nepal)', ()=>{
|
|
154
|
+
expect((0, _date.buildFieldValue)({
|
|
155
|
+
data: {
|
|
156
|
+
date: new Date('2023-08-15'),
|
|
157
|
+
time: '05:45',
|
|
158
|
+
ampm: 'AM',
|
|
159
|
+
utcOffset: '+05:45'
|
|
160
|
+
},
|
|
161
|
+
usesTimezone: true,
|
|
162
|
+
usesTime: true
|
|
163
|
+
})).toEqual({
|
|
164
|
+
invalid: false,
|
|
165
|
+
valid: '2023-08-15T05:45+05:45'
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
it('preserves UTC +00:00 offset', ()=>{
|
|
169
|
+
expect((0, _date.buildFieldValue)({
|
|
170
|
+
data: {
|
|
171
|
+
date: new Date('2023-01-01'),
|
|
172
|
+
time: '00:00',
|
|
173
|
+
ampm: 'AM',
|
|
174
|
+
utcOffset: '+00:00'
|
|
175
|
+
},
|
|
176
|
+
usesTimezone: true,
|
|
177
|
+
usesTime: true
|
|
178
|
+
})).toEqual({
|
|
179
|
+
invalid: false,
|
|
180
|
+
valid: '2023-01-01T00:00+00:00'
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
it('preserves far-west offset -12:00', ()=>{
|
|
184
|
+
expect((0, _date.buildFieldValue)({
|
|
185
|
+
data: {
|
|
186
|
+
date: new Date('2023-01-01'),
|
|
187
|
+
time: '23:59',
|
|
188
|
+
ampm: 'PM',
|
|
189
|
+
utcOffset: '-12:00'
|
|
190
|
+
},
|
|
191
|
+
usesTimezone: true,
|
|
192
|
+
usesTime: true
|
|
193
|
+
})).toEqual({
|
|
194
|
+
invalid: false,
|
|
195
|
+
valid: '2023-01-01T23:59-12:00'
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
it('preserves far-east offset +14:00', ()=>{
|
|
199
|
+
expect((0, _date.buildFieldValue)({
|
|
200
|
+
data: {
|
|
201
|
+
date: new Date('2023-01-01'),
|
|
202
|
+
time: '01:00',
|
|
203
|
+
ampm: 'AM',
|
|
204
|
+
utcOffset: '+14:00'
|
|
205
|
+
},
|
|
206
|
+
usesTimezone: true,
|
|
207
|
+
usesTime: true
|
|
208
|
+
})).toEqual({
|
|
209
|
+
invalid: false,
|
|
210
|
+
valid: '2023-01-01T01:00+14:00'
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
it('11:59 PM → 23:59', ()=>{
|
|
214
|
+
expect((0, _date.buildFieldValue)({
|
|
215
|
+
data: {
|
|
216
|
+
date: new Date('2023-06-01'),
|
|
217
|
+
time: '11:59',
|
|
218
|
+
ampm: 'PM',
|
|
219
|
+
utcOffset: '+00:00'
|
|
220
|
+
},
|
|
221
|
+
usesTimezone: true,
|
|
222
|
+
usesTime: true
|
|
223
|
+
})).toEqual({
|
|
224
|
+
invalid: false,
|
|
225
|
+
valid: '2023-06-01T23:59+00:00'
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
it('01:00 AM → 01:00 (no shift)', ()=>{
|
|
229
|
+
expect((0, _date.buildFieldValue)({
|
|
230
|
+
data: {
|
|
231
|
+
date: new Date('2023-06-01'),
|
|
232
|
+
time: '01:00',
|
|
233
|
+
ampm: 'AM',
|
|
234
|
+
utcOffset: '+00:00'
|
|
235
|
+
},
|
|
236
|
+
usesTimezone: true,
|
|
237
|
+
usesTime: true
|
|
238
|
+
})).toEqual({
|
|
239
|
+
invalid: false,
|
|
240
|
+
valid: '2023-06-01T01:00+00:00'
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
describe('userInputFromDatetime', ()=>{
|
|
245
|
+
it('parses a full ISO datetime string with timezone', ()=>{
|
|
246
|
+
const result = (0, _date.userInputFromDatetime)({
|
|
247
|
+
value: '2018-02-02T17:00+03:00',
|
|
248
|
+
uses12hClock: false
|
|
249
|
+
});
|
|
250
|
+
expect(result.time).toBe('17:00');
|
|
251
|
+
expect(result.ampm).toBe('PM');
|
|
252
|
+
expect(result.utcOffset).toBe('+03:00');
|
|
253
|
+
});
|
|
254
|
+
it('parses a full ISO datetime string with 12h clock', ()=>{
|
|
255
|
+
const result = (0, _date.userInputFromDatetime)({
|
|
256
|
+
value: '2018-02-02T05:00+03:00',
|
|
257
|
+
uses12hClock: true
|
|
258
|
+
});
|
|
259
|
+
expect(result.time).toBe('05:00');
|
|
260
|
+
expect(result.ampm).toBe('AM');
|
|
261
|
+
expect(result.utcOffset).toBe('+03:00');
|
|
262
|
+
});
|
|
263
|
+
it('returns defaults when value is null', ()=>{
|
|
264
|
+
const result = (0, _date.userInputFromDatetime)({
|
|
265
|
+
value: null,
|
|
266
|
+
uses12hClock: false
|
|
267
|
+
});
|
|
268
|
+
expect(result.date).toBeUndefined();
|
|
269
|
+
expect(result.ampm).toBe((0, _date.getDefaultAMPM)());
|
|
270
|
+
expect(result.utcOffset).toBe((0, _date.getDefaultUtcOffset)());
|
|
271
|
+
});
|
|
272
|
+
it('returns defaults when value is undefined', ()=>{
|
|
273
|
+
const result = (0, _date.userInputFromDatetime)({
|
|
274
|
+
value: undefined,
|
|
275
|
+
uses12hClock: false
|
|
276
|
+
});
|
|
277
|
+
expect(result.date).toBeUndefined();
|
|
278
|
+
expect(result.ampm).toBe((0, _date.getDefaultAMPM)());
|
|
279
|
+
});
|
|
280
|
+
it('returns defaults when value is empty string', ()=>{
|
|
281
|
+
const result = (0, _date.userInputFromDatetime)({
|
|
282
|
+
value: '',
|
|
283
|
+
uses12hClock: false
|
|
284
|
+
});
|
|
285
|
+
expect(result.date).toBeUndefined();
|
|
286
|
+
});
|
|
287
|
+
it('parses a date-only string', ()=>{
|
|
288
|
+
const result = (0, _date.userInputFromDatetime)({
|
|
289
|
+
value: '2022-09-16',
|
|
290
|
+
uses12hClock: false
|
|
291
|
+
});
|
|
292
|
+
expect(result.utcOffset).toBe('+00:00');
|
|
293
|
+
});
|
|
294
|
+
it('preserves raw time and does not shift by system timezone (positive offset)', ()=>{
|
|
295
|
+
const result = (0, _date.userInputFromDatetime)({
|
|
296
|
+
value: '2020-03-15T14:00+05:30',
|
|
297
|
+
uses12hClock: false
|
|
298
|
+
});
|
|
299
|
+
expect(result.time).toBe('14:00');
|
|
300
|
+
expect(result.ampm).toBe('PM');
|
|
301
|
+
expect(result.utcOffset).toBe('+05:30');
|
|
302
|
+
});
|
|
303
|
+
it('preserves raw time and does not shift by system timezone (negative offset)', ()=>{
|
|
304
|
+
const result = (0, _date.userInputFromDatetime)({
|
|
305
|
+
value: '2020-03-15T08:30-05:30',
|
|
306
|
+
uses12hClock: false
|
|
307
|
+
});
|
|
308
|
+
expect(result.time).toBe('08:30');
|
|
309
|
+
expect(result.ampm).toBe('AM');
|
|
310
|
+
expect(result.utcOffset).toBe('-05:30');
|
|
311
|
+
});
|
|
312
|
+
it('handles UTC "Z" suffix — offset is +00:00, time is not shifted', ()=>{
|
|
313
|
+
const result = (0, _date.userInputFromDatetime)({
|
|
314
|
+
value: '2021-06-01T12:00Z',
|
|
315
|
+
uses12hClock: false
|
|
316
|
+
});
|
|
317
|
+
expect(result.time).toBe('12:00');
|
|
318
|
+
expect(result.ampm).toBe('PM');
|
|
319
|
+
expect(result.utcOffset).toBe('Z');
|
|
320
|
+
});
|
|
321
|
+
it('handles midnight UTC correctly', ()=>{
|
|
322
|
+
const result = (0, _date.userInputFromDatetime)({
|
|
323
|
+
value: '2021-06-01T00:00Z',
|
|
324
|
+
uses12hClock: false
|
|
325
|
+
});
|
|
326
|
+
expect(result.time).toBe('00:00');
|
|
327
|
+
expect(result.ampm).toBe('AM');
|
|
328
|
+
});
|
|
329
|
+
it('converts PM hour to 12h display (17:00 → 05:00 PM)', ()=>{
|
|
330
|
+
const result = (0, _date.userInputFromDatetime)({
|
|
331
|
+
value: '2018-02-02T17:00+03:00',
|
|
332
|
+
uses12hClock: true
|
|
333
|
+
});
|
|
334
|
+
expect(result.time).toBe('05:00');
|
|
335
|
+
expect(result.ampm).toBe('PM');
|
|
336
|
+
});
|
|
337
|
+
it('converts noon to 12h display (12:00 → 12:00 PM)', ()=>{
|
|
338
|
+
const result = (0, _date.userInputFromDatetime)({
|
|
339
|
+
value: '2021-03-01T12:00+00:00',
|
|
340
|
+
uses12hClock: true
|
|
341
|
+
});
|
|
342
|
+
expect(result.time).toBe('12:00');
|
|
343
|
+
expect(result.ampm).toBe('PM');
|
|
344
|
+
});
|
|
345
|
+
it('converts midnight to 12h display (00:00 → 12:00 AM)', ()=>{
|
|
346
|
+
const result = (0, _date.userInputFromDatetime)({
|
|
347
|
+
value: '2021-03-01T00:00+00:00',
|
|
348
|
+
uses12hClock: true
|
|
349
|
+
});
|
|
350
|
+
expect(result.time).toBe('12:00');
|
|
351
|
+
expect(result.ampm).toBe('AM');
|
|
352
|
+
});
|
|
353
|
+
it('handles quarter-hour offset +05:45 (Nepal)', ()=>{
|
|
354
|
+
const result = (0, _date.userInputFromDatetime)({
|
|
355
|
+
value: '2023-07-20T09:45+05:45',
|
|
356
|
+
uses12hClock: false
|
|
357
|
+
});
|
|
358
|
+
expect(result.time).toBe('09:45');
|
|
359
|
+
expect(result.utcOffset).toBe('+05:45');
|
|
360
|
+
});
|
|
361
|
+
it('handles -12:00 (far-west) offset without date shift', ()=>{
|
|
362
|
+
const result = (0, _date.userInputFromDatetime)({
|
|
363
|
+
value: '2023-01-01T23:59-12:00',
|
|
364
|
+
uses12hClock: false
|
|
365
|
+
});
|
|
366
|
+
expect(result.time).toBe('23:59');
|
|
367
|
+
expect(result.utcOffset).toBe('-12:00');
|
|
368
|
+
});
|
|
369
|
+
it('handles +14:00 (far-east) offset without date shift', ()=>{
|
|
370
|
+
const result = (0, _date.userInputFromDatetime)({
|
|
371
|
+
value: '2023-01-01T01:00+14:00',
|
|
372
|
+
uses12hClock: false
|
|
373
|
+
});
|
|
374
|
+
expect(result.time).toBe('01:00');
|
|
375
|
+
expect(result.utcOffset).toBe('+14:00');
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
describe('getDefaultAMPM', ()=>{
|
|
379
|
+
it('returns AM', ()=>{
|
|
380
|
+
expect((0, _date.getDefaultAMPM)()).toBe('AM');
|
|
381
|
+
});
|
|
55
382
|
});
|
|
56
383
|
});
|
package/dist/cjs/utils/date.js
CHANGED
|
@@ -22,32 +22,36 @@ _export(exports, {
|
|
|
22
22
|
return userInputFromDatetime;
|
|
23
23
|
}
|
|
24
24
|
});
|
|
25
|
-
const
|
|
26
|
-
function _interop_require_default(obj) {
|
|
27
|
-
return obj && obj.__esModule ? obj : {
|
|
28
|
-
default: obj
|
|
29
|
-
};
|
|
30
|
-
}
|
|
25
|
+
const _datefns = require("date-fns");
|
|
31
26
|
const ZONE_RX = /(Z|[+-]\d{2}[:+]?\d{2})$/;
|
|
32
|
-
function
|
|
33
|
-
return (0,
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
27
|
+
function startOfTodayOffset() {
|
|
28
|
+
return (0, _datefns.format)(new Date(), 'xxx');
|
|
29
|
+
}
|
|
30
|
+
function parseUtcOffset(datetimeString) {
|
|
31
|
+
const match = datetimeString.match(ZONE_RX);
|
|
32
|
+
return match ? match[1] : '+00:00';
|
|
37
33
|
}
|
|
38
|
-
function
|
|
34
|
+
function fieldValueToDate(datetimeString) {
|
|
39
35
|
if (!datetimeString) {
|
|
40
36
|
return null;
|
|
41
37
|
}
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
datetime.utcOffset(datetimeString);
|
|
45
|
-
}
|
|
46
|
-
return datetime;
|
|
38
|
+
const date = (0, _datefns.parseISO)(datetimeString);
|
|
39
|
+
return (0, _datefns.isValid)(date) ? date : null;
|
|
47
40
|
}
|
|
48
41
|
function timeFromUserInput(input) {
|
|
49
42
|
const timeInput = input.time || '00:00';
|
|
50
|
-
|
|
43
|
+
const parsed = (0, _datefns.parse)(timeInput, 'HH:mm', new Date(0));
|
|
44
|
+
let hours = (0, _datefns.getHours)(parsed);
|
|
45
|
+
const minutes = (0, _datefns.getMinutes)(parsed);
|
|
46
|
+
if (input.ampm === 'PM' && hours < 12) {
|
|
47
|
+
hours += 12;
|
|
48
|
+
} else if (input.ampm === 'AM' && hours === 12) {
|
|
49
|
+
hours = 0;
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
hours,
|
|
53
|
+
minutes
|
|
54
|
+
};
|
|
51
55
|
}
|
|
52
56
|
function datetimeFromUserInput(input) {
|
|
53
57
|
if (!input.date) {
|
|
@@ -55,12 +59,14 @@ function datetimeFromUserInput(input) {
|
|
|
55
59
|
valid: null
|
|
56
60
|
};
|
|
57
61
|
}
|
|
58
|
-
const
|
|
59
|
-
const date =
|
|
60
|
-
hours
|
|
61
|
-
minutes
|
|
62
|
+
const { hours, minutes } = timeFromUserInput(input);
|
|
63
|
+
const date = (0, _datefns.set)(input.date, {
|
|
64
|
+
hours,
|
|
65
|
+
minutes,
|
|
66
|
+
seconds: 0,
|
|
67
|
+
milliseconds: 0
|
|
62
68
|
});
|
|
63
|
-
if (
|
|
69
|
+
if ((0, _datefns.isValid)(date)) {
|
|
64
70
|
return {
|
|
65
71
|
valid: date
|
|
66
72
|
};
|
|
@@ -78,34 +84,58 @@ function buildFieldValue({ data, usesTime, usesTimezone }) {
|
|
|
78
84
|
invalid: true
|
|
79
85
|
};
|
|
80
86
|
}
|
|
81
|
-
|
|
87
|
+
if (!date.valid) {
|
|
88
|
+
return {
|
|
89
|
+
valid: null,
|
|
90
|
+
invalid: false
|
|
91
|
+
};
|
|
92
|
+
}
|
|
82
93
|
if (usesTimezone) {
|
|
83
|
-
|
|
94
|
+
return {
|
|
95
|
+
valid: (0, _datefns.format)(date.valid, "yyyy-MM-dd'T'HH:mm") + data.utcOffset,
|
|
96
|
+
invalid: false
|
|
97
|
+
};
|
|
84
98
|
} else if (usesTime) {
|
|
85
|
-
|
|
99
|
+
return {
|
|
100
|
+
valid: (0, _datefns.format)(date.valid, "yyyy-MM-dd'T'HH:mm"),
|
|
101
|
+
invalid: false
|
|
102
|
+
};
|
|
86
103
|
} else {
|
|
87
|
-
|
|
104
|
+
return {
|
|
105
|
+
valid: (0, _datefns.format)(date.valid, 'yyyy-MM-dd'),
|
|
106
|
+
invalid: false
|
|
107
|
+
};
|
|
88
108
|
}
|
|
89
|
-
return {
|
|
90
|
-
valid: date?.valid ? date.valid.format(format) : null,
|
|
91
|
-
invalid: false
|
|
92
|
-
};
|
|
93
109
|
}
|
|
94
110
|
function getDefaultAMPM() {
|
|
95
111
|
return 'AM';
|
|
96
112
|
}
|
|
97
113
|
function getDefaultUtcOffset() {
|
|
98
|
-
return
|
|
114
|
+
return startOfTodayOffset();
|
|
115
|
+
}
|
|
116
|
+
function extractTimeFromIso(value) {
|
|
117
|
+
const match = value.match(/T(\d{2}:\d{2})/);
|
|
118
|
+
return match ? match[1] : undefined;
|
|
99
119
|
}
|
|
100
120
|
function userInputFromDatetime({ value, uses12hClock }) {
|
|
101
|
-
const datetime =
|
|
102
|
-
if (datetime) {
|
|
103
|
-
const
|
|
121
|
+
const datetime = fieldValueToDate(value);
|
|
122
|
+
if (datetime && value) {
|
|
123
|
+
const rawTime = extractTimeFromIso(value);
|
|
124
|
+
const timeDate = rawTime ? (0, _datefns.parse)(rawTime, 'HH:mm', new Date(0)) : datetime;
|
|
125
|
+
const hours = (0, _datefns.getHours)(timeDate);
|
|
126
|
+
const ampm = hours < 12 ? 'AM' : 'PM';
|
|
127
|
+
let time;
|
|
128
|
+
if (uses12hClock) {
|
|
129
|
+
const h12 = hours % 12 || 12;
|
|
130
|
+
time = `${String(h12).padStart(2, '0')}:${String((0, _datefns.getMinutes)(timeDate)).padStart(2, '0')}`;
|
|
131
|
+
} else {
|
|
132
|
+
time = rawTime ?? (0, _datefns.format)(datetime, 'HH:mm');
|
|
133
|
+
}
|
|
104
134
|
return {
|
|
105
135
|
date: datetime,
|
|
106
|
-
time
|
|
107
|
-
ampm
|
|
108
|
-
utcOffset:
|
|
136
|
+
time,
|
|
137
|
+
ampm,
|
|
138
|
+
utcOffset: parseUtcOffset(value)
|
|
109
139
|
};
|
|
110
140
|
} else {
|
|
111
141
|
return {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import React, { useMemo } from 'react';
|
|
2
2
|
import { Datepicker } from '@contentful/f36-components';
|
|
3
3
|
import { css } from '@emotion/css';
|
|
4
|
-
import moment from 'moment';
|
|
5
4
|
const YEARS_INTO_FUTURE = 100;
|
|
6
5
|
const MIN_YEAR = '1000';
|
|
7
6
|
const styles = {
|
|
@@ -19,14 +18,12 @@ export const DatepickerInput = (props)=>{
|
|
|
19
18
|
toDate
|
|
20
19
|
];
|
|
21
20
|
}, []);
|
|
22
|
-
const
|
|
23
|
-
const selectedDate = dateObj ? new Date(dateObj.years, dateObj.months, dateObj.date) : undefined;
|
|
21
|
+
const selectedDate = props.value;
|
|
24
22
|
return /*#__PURE__*/ React.createElement(Datepicker, {
|
|
25
23
|
className: styles.root,
|
|
26
24
|
selected: selectedDate,
|
|
27
25
|
onSelect: (day)=>{
|
|
28
|
-
|
|
29
|
-
props.onChange(momentDay);
|
|
26
|
+
props.onChange(day ?? undefined);
|
|
30
27
|
},
|
|
31
28
|
inputProps: {
|
|
32
29
|
isDisabled: props.disabled,
|
|
@@ -1,44 +1,34 @@
|
|
|
1
1
|
import React, { useState, useCallback, useEffect } from 'react';
|
|
2
2
|
import { TextInput, Flex } from '@contentful/f36-components';
|
|
3
3
|
import { css } from '@emotion/css';
|
|
4
|
-
import
|
|
4
|
+
import { parse, format, isValid } from 'date-fns';
|
|
5
5
|
const validInputFormats = [
|
|
6
6
|
'hh:mm a',
|
|
7
|
-
'hh:mm A',
|
|
8
7
|
'h:mm a',
|
|
9
|
-
'h:mm A',
|
|
10
8
|
'hh:mm',
|
|
11
|
-
'k:mm',
|
|
12
9
|
'kk:mm',
|
|
10
|
+
'k:mm',
|
|
13
11
|
'h a',
|
|
14
|
-
'
|
|
15
|
-
'
|
|
16
|
-
'
|
|
17
|
-
'HH'
|
|
12
|
+
'HH',
|
|
13
|
+
'H:mm',
|
|
14
|
+
'h'
|
|
18
15
|
];
|
|
16
|
+
const REF_DATE = new Date(2000, 0, 1);
|
|
19
17
|
function parseRawInput(raw) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (date.isValid()) {
|
|
24
|
-
time = date;
|
|
25
|
-
break;
|
|
26
|
-
}
|
|
18
|
+
for (const fmt of validInputFormats){
|
|
19
|
+
const parsed = parse(raw, fmt, REF_DATE);
|
|
20
|
+
if (isValid(parsed)) return parsed;
|
|
27
21
|
}
|
|
28
|
-
return
|
|
22
|
+
return null;
|
|
29
23
|
}
|
|
30
|
-
const getDefaultTime = ()=>
|
|
31
|
-
|
|
32
|
-
};
|
|
33
|
-
const formatToString = (uses12hClock, value)=>{
|
|
34
|
-
return uses12hClock ? value.format('hh:mm A') : value.format('HH:mm');
|
|
35
|
-
};
|
|
24
|
+
const getDefaultTime = ()=>parse('12:00 AM', 'hh:mm a', REF_DATE);
|
|
25
|
+
const formatToString = (uses12hClock, value)=>format(value, uses12hClock ? 'hh:mm a' : 'HH:mm');
|
|
36
26
|
export const TimepickerInput = ({ disabled, uses12hClock, time = '12:00', ampm = 'AM', onChange })=>{
|
|
37
27
|
const [selectedTime, setSelectedTime] = useState(()=>{
|
|
38
28
|
return formatToString(uses12hClock, getDefaultTime());
|
|
39
29
|
});
|
|
40
30
|
useEffect(()=>{
|
|
41
|
-
setSelectedTime(formatToString(uses12hClock,
|
|
31
|
+
setSelectedTime(formatToString(uses12hClock, parse(`${time} ${ampm}`, 'hh:mm a', REF_DATE)));
|
|
42
32
|
}, [
|
|
43
33
|
time,
|
|
44
34
|
ampm,
|
|
@@ -56,8 +46,8 @@ export const TimepickerInput = ({ disabled, uses12hClock, time = '12:00', ampm =
|
|
|
56
46
|
const value = parsedTime ?? getDefaultTime();
|
|
57
47
|
setSelectedTime(formatToString(uses12hClock, value));
|
|
58
48
|
onChange({
|
|
59
|
-
time:
|
|
60
|
-
ampm:
|
|
49
|
+
time: format(value, 'hh:mm'),
|
|
50
|
+
ampm: format(value, 'a').toUpperCase()
|
|
61
51
|
});
|
|
62
52
|
};
|
|
63
53
|
return /*#__PURE__*/ React.createElement(Flex, {
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { buildFieldValue } from './date';
|
|
1
|
+
import { buildFieldValue, userInputFromDatetime, getDefaultAMPM, getDefaultUtcOffset } from './date';
|
|
3
2
|
describe('date utils', ()=>{
|
|
4
3
|
describe('buildFieldValue', ()=>{
|
|
5
4
|
it('should work properly', ()=>{
|
|
6
5
|
expect(buildFieldValue({
|
|
7
6
|
data: {
|
|
8
|
-
date:
|
|
7
|
+
date: new Date('2018-02-02'),
|
|
9
8
|
time: '05:00',
|
|
10
9
|
ampm: 'PM',
|
|
11
10
|
utcOffset: '+03:00'
|
|
@@ -18,7 +17,7 @@ describe('date utils', ()=>{
|
|
|
18
17
|
});
|
|
19
18
|
expect(buildFieldValue({
|
|
20
19
|
data: {
|
|
21
|
-
date:
|
|
20
|
+
date: new Date('2015-01-14'),
|
|
22
21
|
time: '05:00',
|
|
23
22
|
ampm: 'AM',
|
|
24
23
|
utcOffset: '-05:00'
|
|
@@ -31,7 +30,7 @@ describe('date utils', ()=>{
|
|
|
31
30
|
});
|
|
32
31
|
expect(buildFieldValue({
|
|
33
32
|
data: {
|
|
34
|
-
date:
|
|
33
|
+
date: new Date('2015-01-14'),
|
|
35
34
|
time: '17:00',
|
|
36
35
|
ampm: 'PM',
|
|
37
36
|
utcOffset: '-05:00'
|
|
@@ -43,5 +42,338 @@ describe('date utils', ()=>{
|
|
|
43
42
|
valid: '2015-01-14T17:00-05:00'
|
|
44
43
|
});
|
|
45
44
|
});
|
|
45
|
+
it('returns date-only format when usesTime and usesTimezone are false', ()=>{
|
|
46
|
+
expect(buildFieldValue({
|
|
47
|
+
data: {
|
|
48
|
+
date: new Date('2020-06-15'),
|
|
49
|
+
time: '10:30',
|
|
50
|
+
ampm: 'AM',
|
|
51
|
+
utcOffset: '+00:00'
|
|
52
|
+
},
|
|
53
|
+
usesTimezone: false,
|
|
54
|
+
usesTime: false
|
|
55
|
+
})).toEqual({
|
|
56
|
+
invalid: false,
|
|
57
|
+
valid: '2020-06-15'
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
it('returns datetime without timezone when usesTime=true, usesTimezone=false', ()=>{
|
|
61
|
+
expect(buildFieldValue({
|
|
62
|
+
data: {
|
|
63
|
+
date: new Date('2020-06-15'),
|
|
64
|
+
time: '14:30',
|
|
65
|
+
ampm: 'PM',
|
|
66
|
+
utcOffset: '+00:00'
|
|
67
|
+
},
|
|
68
|
+
usesTimezone: false,
|
|
69
|
+
usesTime: true
|
|
70
|
+
})).toEqual({
|
|
71
|
+
invalid: false,
|
|
72
|
+
valid: '2020-06-15T14:30'
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
it('returns valid: null when no date is provided', ()=>{
|
|
76
|
+
expect(buildFieldValue({
|
|
77
|
+
data: {
|
|
78
|
+
time: '10:00',
|
|
79
|
+
ampm: 'AM',
|
|
80
|
+
utcOffset: '+00:00'
|
|
81
|
+
},
|
|
82
|
+
usesTimezone: true,
|
|
83
|
+
usesTime: true
|
|
84
|
+
})).toEqual({
|
|
85
|
+
invalid: false,
|
|
86
|
+
valid: null
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
it('handles midnight (12:00 AM) correctly', ()=>{
|
|
90
|
+
expect(buildFieldValue({
|
|
91
|
+
data: {
|
|
92
|
+
date: new Date('2021-03-01'),
|
|
93
|
+
time: '12:00',
|
|
94
|
+
ampm: 'AM',
|
|
95
|
+
utcOffset: '+00:00'
|
|
96
|
+
},
|
|
97
|
+
usesTimezone: true,
|
|
98
|
+
usesTime: true
|
|
99
|
+
})).toEqual({
|
|
100
|
+
invalid: false,
|
|
101
|
+
valid: '2021-03-01T00:00+00:00'
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
it('handles noon (12:00 PM) correctly', ()=>{
|
|
105
|
+
expect(buildFieldValue({
|
|
106
|
+
data: {
|
|
107
|
+
date: new Date('2021-03-01'),
|
|
108
|
+
time: '12:00',
|
|
109
|
+
ampm: 'PM',
|
|
110
|
+
utcOffset: '+00:00'
|
|
111
|
+
},
|
|
112
|
+
usesTimezone: true,
|
|
113
|
+
usesTime: true
|
|
114
|
+
})).toEqual({
|
|
115
|
+
invalid: false,
|
|
116
|
+
valid: '2021-03-01T12:00+00:00'
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
it('preserves half-hour offset +05:30 (India)', ()=>{
|
|
120
|
+
expect(buildFieldValue({
|
|
121
|
+
data: {
|
|
122
|
+
date: new Date('2023-08-15'),
|
|
123
|
+
time: '10:00',
|
|
124
|
+
ampm: 'AM',
|
|
125
|
+
utcOffset: '+05:30'
|
|
126
|
+
},
|
|
127
|
+
usesTimezone: true,
|
|
128
|
+
usesTime: true
|
|
129
|
+
})).toEqual({
|
|
130
|
+
invalid: false,
|
|
131
|
+
valid: '2023-08-15T10:00+05:30'
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
it('preserves negative half-hour offset -09:30', ()=>{
|
|
135
|
+
expect(buildFieldValue({
|
|
136
|
+
data: {
|
|
137
|
+
date: new Date('2023-08-15'),
|
|
138
|
+
time: '03:30',
|
|
139
|
+
ampm: 'AM',
|
|
140
|
+
utcOffset: '-09:30'
|
|
141
|
+
},
|
|
142
|
+
usesTimezone: true,
|
|
143
|
+
usesTime: true
|
|
144
|
+
})).toEqual({
|
|
145
|
+
invalid: false,
|
|
146
|
+
valid: '2023-08-15T03:30-09:30'
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
it('preserves quarter-hour offset +05:45 (Nepal)', ()=>{
|
|
150
|
+
expect(buildFieldValue({
|
|
151
|
+
data: {
|
|
152
|
+
date: new Date('2023-08-15'),
|
|
153
|
+
time: '05:45',
|
|
154
|
+
ampm: 'AM',
|
|
155
|
+
utcOffset: '+05:45'
|
|
156
|
+
},
|
|
157
|
+
usesTimezone: true,
|
|
158
|
+
usesTime: true
|
|
159
|
+
})).toEqual({
|
|
160
|
+
invalid: false,
|
|
161
|
+
valid: '2023-08-15T05:45+05:45'
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
it('preserves UTC +00:00 offset', ()=>{
|
|
165
|
+
expect(buildFieldValue({
|
|
166
|
+
data: {
|
|
167
|
+
date: new Date('2023-01-01'),
|
|
168
|
+
time: '00:00',
|
|
169
|
+
ampm: 'AM',
|
|
170
|
+
utcOffset: '+00:00'
|
|
171
|
+
},
|
|
172
|
+
usesTimezone: true,
|
|
173
|
+
usesTime: true
|
|
174
|
+
})).toEqual({
|
|
175
|
+
invalid: false,
|
|
176
|
+
valid: '2023-01-01T00:00+00:00'
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
it('preserves far-west offset -12:00', ()=>{
|
|
180
|
+
expect(buildFieldValue({
|
|
181
|
+
data: {
|
|
182
|
+
date: new Date('2023-01-01'),
|
|
183
|
+
time: '23:59',
|
|
184
|
+
ampm: 'PM',
|
|
185
|
+
utcOffset: '-12:00'
|
|
186
|
+
},
|
|
187
|
+
usesTimezone: true,
|
|
188
|
+
usesTime: true
|
|
189
|
+
})).toEqual({
|
|
190
|
+
invalid: false,
|
|
191
|
+
valid: '2023-01-01T23:59-12:00'
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
it('preserves far-east offset +14:00', ()=>{
|
|
195
|
+
expect(buildFieldValue({
|
|
196
|
+
data: {
|
|
197
|
+
date: new Date('2023-01-01'),
|
|
198
|
+
time: '01:00',
|
|
199
|
+
ampm: 'AM',
|
|
200
|
+
utcOffset: '+14:00'
|
|
201
|
+
},
|
|
202
|
+
usesTimezone: true,
|
|
203
|
+
usesTime: true
|
|
204
|
+
})).toEqual({
|
|
205
|
+
invalid: false,
|
|
206
|
+
valid: '2023-01-01T01:00+14:00'
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
it('11:59 PM → 23:59', ()=>{
|
|
210
|
+
expect(buildFieldValue({
|
|
211
|
+
data: {
|
|
212
|
+
date: new Date('2023-06-01'),
|
|
213
|
+
time: '11:59',
|
|
214
|
+
ampm: 'PM',
|
|
215
|
+
utcOffset: '+00:00'
|
|
216
|
+
},
|
|
217
|
+
usesTimezone: true,
|
|
218
|
+
usesTime: true
|
|
219
|
+
})).toEqual({
|
|
220
|
+
invalid: false,
|
|
221
|
+
valid: '2023-06-01T23:59+00:00'
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
it('01:00 AM → 01:00 (no shift)', ()=>{
|
|
225
|
+
expect(buildFieldValue({
|
|
226
|
+
data: {
|
|
227
|
+
date: new Date('2023-06-01'),
|
|
228
|
+
time: '01:00',
|
|
229
|
+
ampm: 'AM',
|
|
230
|
+
utcOffset: '+00:00'
|
|
231
|
+
},
|
|
232
|
+
usesTimezone: true,
|
|
233
|
+
usesTime: true
|
|
234
|
+
})).toEqual({
|
|
235
|
+
invalid: false,
|
|
236
|
+
valid: '2023-06-01T01:00+00:00'
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
describe('userInputFromDatetime', ()=>{
|
|
241
|
+
it('parses a full ISO datetime string with timezone', ()=>{
|
|
242
|
+
const result = userInputFromDatetime({
|
|
243
|
+
value: '2018-02-02T17:00+03:00',
|
|
244
|
+
uses12hClock: false
|
|
245
|
+
});
|
|
246
|
+
expect(result.time).toBe('17:00');
|
|
247
|
+
expect(result.ampm).toBe('PM');
|
|
248
|
+
expect(result.utcOffset).toBe('+03:00');
|
|
249
|
+
});
|
|
250
|
+
it('parses a full ISO datetime string with 12h clock', ()=>{
|
|
251
|
+
const result = userInputFromDatetime({
|
|
252
|
+
value: '2018-02-02T05:00+03:00',
|
|
253
|
+
uses12hClock: true
|
|
254
|
+
});
|
|
255
|
+
expect(result.time).toBe('05:00');
|
|
256
|
+
expect(result.ampm).toBe('AM');
|
|
257
|
+
expect(result.utcOffset).toBe('+03:00');
|
|
258
|
+
});
|
|
259
|
+
it('returns defaults when value is null', ()=>{
|
|
260
|
+
const result = userInputFromDatetime({
|
|
261
|
+
value: null,
|
|
262
|
+
uses12hClock: false
|
|
263
|
+
});
|
|
264
|
+
expect(result.date).toBeUndefined();
|
|
265
|
+
expect(result.ampm).toBe(getDefaultAMPM());
|
|
266
|
+
expect(result.utcOffset).toBe(getDefaultUtcOffset());
|
|
267
|
+
});
|
|
268
|
+
it('returns defaults when value is undefined', ()=>{
|
|
269
|
+
const result = userInputFromDatetime({
|
|
270
|
+
value: undefined,
|
|
271
|
+
uses12hClock: false
|
|
272
|
+
});
|
|
273
|
+
expect(result.date).toBeUndefined();
|
|
274
|
+
expect(result.ampm).toBe(getDefaultAMPM());
|
|
275
|
+
});
|
|
276
|
+
it('returns defaults when value is empty string', ()=>{
|
|
277
|
+
const result = userInputFromDatetime({
|
|
278
|
+
value: '',
|
|
279
|
+
uses12hClock: false
|
|
280
|
+
});
|
|
281
|
+
expect(result.date).toBeUndefined();
|
|
282
|
+
});
|
|
283
|
+
it('parses a date-only string', ()=>{
|
|
284
|
+
const result = userInputFromDatetime({
|
|
285
|
+
value: '2022-09-16',
|
|
286
|
+
uses12hClock: false
|
|
287
|
+
});
|
|
288
|
+
expect(result.utcOffset).toBe('+00:00');
|
|
289
|
+
});
|
|
290
|
+
it('preserves raw time and does not shift by system timezone (positive offset)', ()=>{
|
|
291
|
+
const result = userInputFromDatetime({
|
|
292
|
+
value: '2020-03-15T14:00+05:30',
|
|
293
|
+
uses12hClock: false
|
|
294
|
+
});
|
|
295
|
+
expect(result.time).toBe('14:00');
|
|
296
|
+
expect(result.ampm).toBe('PM');
|
|
297
|
+
expect(result.utcOffset).toBe('+05:30');
|
|
298
|
+
});
|
|
299
|
+
it('preserves raw time and does not shift by system timezone (negative offset)', ()=>{
|
|
300
|
+
const result = userInputFromDatetime({
|
|
301
|
+
value: '2020-03-15T08:30-05:30',
|
|
302
|
+
uses12hClock: false
|
|
303
|
+
});
|
|
304
|
+
expect(result.time).toBe('08:30');
|
|
305
|
+
expect(result.ampm).toBe('AM');
|
|
306
|
+
expect(result.utcOffset).toBe('-05:30');
|
|
307
|
+
});
|
|
308
|
+
it('handles UTC "Z" suffix — offset is +00:00, time is not shifted', ()=>{
|
|
309
|
+
const result = userInputFromDatetime({
|
|
310
|
+
value: '2021-06-01T12:00Z',
|
|
311
|
+
uses12hClock: false
|
|
312
|
+
});
|
|
313
|
+
expect(result.time).toBe('12:00');
|
|
314
|
+
expect(result.ampm).toBe('PM');
|
|
315
|
+
expect(result.utcOffset).toBe('Z');
|
|
316
|
+
});
|
|
317
|
+
it('handles midnight UTC correctly', ()=>{
|
|
318
|
+
const result = userInputFromDatetime({
|
|
319
|
+
value: '2021-06-01T00:00Z',
|
|
320
|
+
uses12hClock: false
|
|
321
|
+
});
|
|
322
|
+
expect(result.time).toBe('00:00');
|
|
323
|
+
expect(result.ampm).toBe('AM');
|
|
324
|
+
});
|
|
325
|
+
it('converts PM hour to 12h display (17:00 → 05:00 PM)', ()=>{
|
|
326
|
+
const result = userInputFromDatetime({
|
|
327
|
+
value: '2018-02-02T17:00+03:00',
|
|
328
|
+
uses12hClock: true
|
|
329
|
+
});
|
|
330
|
+
expect(result.time).toBe('05:00');
|
|
331
|
+
expect(result.ampm).toBe('PM');
|
|
332
|
+
});
|
|
333
|
+
it('converts noon to 12h display (12:00 → 12:00 PM)', ()=>{
|
|
334
|
+
const result = userInputFromDatetime({
|
|
335
|
+
value: '2021-03-01T12:00+00:00',
|
|
336
|
+
uses12hClock: true
|
|
337
|
+
});
|
|
338
|
+
expect(result.time).toBe('12:00');
|
|
339
|
+
expect(result.ampm).toBe('PM');
|
|
340
|
+
});
|
|
341
|
+
it('converts midnight to 12h display (00:00 → 12:00 AM)', ()=>{
|
|
342
|
+
const result = userInputFromDatetime({
|
|
343
|
+
value: '2021-03-01T00:00+00:00',
|
|
344
|
+
uses12hClock: true
|
|
345
|
+
});
|
|
346
|
+
expect(result.time).toBe('12:00');
|
|
347
|
+
expect(result.ampm).toBe('AM');
|
|
348
|
+
});
|
|
349
|
+
it('handles quarter-hour offset +05:45 (Nepal)', ()=>{
|
|
350
|
+
const result = userInputFromDatetime({
|
|
351
|
+
value: '2023-07-20T09:45+05:45',
|
|
352
|
+
uses12hClock: false
|
|
353
|
+
});
|
|
354
|
+
expect(result.time).toBe('09:45');
|
|
355
|
+
expect(result.utcOffset).toBe('+05:45');
|
|
356
|
+
});
|
|
357
|
+
it('handles -12:00 (far-west) offset without date shift', ()=>{
|
|
358
|
+
const result = userInputFromDatetime({
|
|
359
|
+
value: '2023-01-01T23:59-12:00',
|
|
360
|
+
uses12hClock: false
|
|
361
|
+
});
|
|
362
|
+
expect(result.time).toBe('23:59');
|
|
363
|
+
expect(result.utcOffset).toBe('-12:00');
|
|
364
|
+
});
|
|
365
|
+
it('handles +14:00 (far-east) offset without date shift', ()=>{
|
|
366
|
+
const result = userInputFromDatetime({
|
|
367
|
+
value: '2023-01-01T01:00+14:00',
|
|
368
|
+
uses12hClock: false
|
|
369
|
+
});
|
|
370
|
+
expect(result.time).toBe('01:00');
|
|
371
|
+
expect(result.utcOffset).toBe('+14:00');
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
describe('getDefaultAMPM', ()=>{
|
|
375
|
+
it('returns AM', ()=>{
|
|
376
|
+
expect(getDefaultAMPM()).toBe('AM');
|
|
377
|
+
});
|
|
46
378
|
});
|
|
47
379
|
});
|
package/dist/esm/utils/date.js
CHANGED
|
@@ -1,24 +1,33 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { format, getHours, getMinutes, isValid, parse, parseISO, set } from 'date-fns';
|
|
2
2
|
const ZONE_RX = /(Z|[+-]\d{2}[:+]?\d{2})$/;
|
|
3
|
-
function
|
|
4
|
-
return
|
|
5
|
-
hours: 0,
|
|
6
|
-
minutes: 0
|
|
7
|
-
}).format(format);
|
|
3
|
+
function startOfTodayOffset() {
|
|
4
|
+
return format(new Date(), 'xxx');
|
|
8
5
|
}
|
|
9
|
-
function
|
|
6
|
+
function parseUtcOffset(datetimeString) {
|
|
7
|
+
const match = datetimeString.match(ZONE_RX);
|
|
8
|
+
return match ? match[1] : '+00:00';
|
|
9
|
+
}
|
|
10
|
+
function fieldValueToDate(datetimeString) {
|
|
10
11
|
if (!datetimeString) {
|
|
11
12
|
return null;
|
|
12
13
|
}
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
datetime.utcOffset(datetimeString);
|
|
16
|
-
}
|
|
17
|
-
return datetime;
|
|
14
|
+
const date = parseISO(datetimeString);
|
|
15
|
+
return isValid(date) ? date : null;
|
|
18
16
|
}
|
|
19
17
|
function timeFromUserInput(input) {
|
|
20
18
|
const timeInput = input.time || '00:00';
|
|
21
|
-
|
|
19
|
+
const parsed = parse(timeInput, 'HH:mm', new Date(0));
|
|
20
|
+
let hours = getHours(parsed);
|
|
21
|
+
const minutes = getMinutes(parsed);
|
|
22
|
+
if (input.ampm === 'PM' && hours < 12) {
|
|
23
|
+
hours += 12;
|
|
24
|
+
} else if (input.ampm === 'AM' && hours === 12) {
|
|
25
|
+
hours = 0;
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
hours,
|
|
29
|
+
minutes
|
|
30
|
+
};
|
|
22
31
|
}
|
|
23
32
|
function datetimeFromUserInput(input) {
|
|
24
33
|
if (!input.date) {
|
|
@@ -26,12 +35,14 @@ function datetimeFromUserInput(input) {
|
|
|
26
35
|
valid: null
|
|
27
36
|
};
|
|
28
37
|
}
|
|
29
|
-
const
|
|
30
|
-
const date =
|
|
31
|
-
hours
|
|
32
|
-
minutes
|
|
38
|
+
const { hours, minutes } = timeFromUserInput(input);
|
|
39
|
+
const date = set(input.date, {
|
|
40
|
+
hours,
|
|
41
|
+
minutes,
|
|
42
|
+
seconds: 0,
|
|
43
|
+
milliseconds: 0
|
|
33
44
|
});
|
|
34
|
-
if (
|
|
45
|
+
if (isValid(date)) {
|
|
35
46
|
return {
|
|
36
47
|
valid: date
|
|
37
48
|
};
|
|
@@ -49,34 +60,58 @@ export function buildFieldValue({ data, usesTime, usesTimezone }) {
|
|
|
49
60
|
invalid: true
|
|
50
61
|
};
|
|
51
62
|
}
|
|
52
|
-
|
|
63
|
+
if (!date.valid) {
|
|
64
|
+
return {
|
|
65
|
+
valid: null,
|
|
66
|
+
invalid: false
|
|
67
|
+
};
|
|
68
|
+
}
|
|
53
69
|
if (usesTimezone) {
|
|
54
|
-
|
|
70
|
+
return {
|
|
71
|
+
valid: format(date.valid, "yyyy-MM-dd'T'HH:mm") + data.utcOffset,
|
|
72
|
+
invalid: false
|
|
73
|
+
};
|
|
55
74
|
} else if (usesTime) {
|
|
56
|
-
|
|
75
|
+
return {
|
|
76
|
+
valid: format(date.valid, "yyyy-MM-dd'T'HH:mm"),
|
|
77
|
+
invalid: false
|
|
78
|
+
};
|
|
57
79
|
} else {
|
|
58
|
-
|
|
80
|
+
return {
|
|
81
|
+
valid: format(date.valid, 'yyyy-MM-dd'),
|
|
82
|
+
invalid: false
|
|
83
|
+
};
|
|
59
84
|
}
|
|
60
|
-
return {
|
|
61
|
-
valid: date?.valid ? date.valid.format(format) : null,
|
|
62
|
-
invalid: false
|
|
63
|
-
};
|
|
64
85
|
}
|
|
65
86
|
export function getDefaultAMPM() {
|
|
66
87
|
return 'AM';
|
|
67
88
|
}
|
|
68
89
|
export function getDefaultUtcOffset() {
|
|
69
|
-
return
|
|
90
|
+
return startOfTodayOffset();
|
|
91
|
+
}
|
|
92
|
+
function extractTimeFromIso(value) {
|
|
93
|
+
const match = value.match(/T(\d{2}:\d{2})/);
|
|
94
|
+
return match ? match[1] : undefined;
|
|
70
95
|
}
|
|
71
96
|
export function userInputFromDatetime({ value, uses12hClock }) {
|
|
72
|
-
const datetime =
|
|
73
|
-
if (datetime) {
|
|
74
|
-
const
|
|
97
|
+
const datetime = fieldValueToDate(value);
|
|
98
|
+
if (datetime && value) {
|
|
99
|
+
const rawTime = extractTimeFromIso(value);
|
|
100
|
+
const timeDate = rawTime ? parse(rawTime, 'HH:mm', new Date(0)) : datetime;
|
|
101
|
+
const hours = getHours(timeDate);
|
|
102
|
+
const ampm = hours < 12 ? 'AM' : 'PM';
|
|
103
|
+
let time;
|
|
104
|
+
if (uses12hClock) {
|
|
105
|
+
const h12 = hours % 12 || 12;
|
|
106
|
+
time = `${String(h12).padStart(2, '0')}:${String(getMinutes(timeDate)).padStart(2, '0')}`;
|
|
107
|
+
} else {
|
|
108
|
+
time = rawTime ?? format(datetime, 'HH:mm');
|
|
109
|
+
}
|
|
75
110
|
return {
|
|
76
111
|
date: datetime,
|
|
77
|
-
time
|
|
78
|
-
ampm
|
|
79
|
-
utcOffset:
|
|
112
|
+
time,
|
|
113
|
+
ampm,
|
|
114
|
+
utcOffset: parseUtcOffset(value)
|
|
80
115
|
};
|
|
81
116
|
} else {
|
|
82
117
|
return {
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import moment from 'moment';
|
|
3
2
|
export type DatePickerProps = {
|
|
4
|
-
value?:
|
|
5
|
-
onChange: (val:
|
|
3
|
+
value?: Date;
|
|
4
|
+
onChange: (val: Date | undefined) => void;
|
|
6
5
|
disabled?: boolean;
|
|
7
6
|
};
|
|
8
7
|
export declare const DatepickerInput: (props: DatePickerProps) => React.JSX.Element;
|
package/dist/types/types.d.ts
CHANGED
|
@@ -13,7 +13,10 @@ export declare function buildFieldValue({ data, usesTime, usesTimezone, }: {
|
|
|
13
13
|
invalid: boolean;
|
|
14
14
|
valid?: undefined;
|
|
15
15
|
} | {
|
|
16
|
-
valid:
|
|
16
|
+
valid: null;
|
|
17
|
+
invalid: boolean;
|
|
18
|
+
} | {
|
|
19
|
+
valid: string;
|
|
17
20
|
invalid: boolean;
|
|
18
21
|
};
|
|
19
22
|
export declare function getDefaultAMPM(): string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contentful/field-editor-date",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.8-canary.2+bc54b71b",
|
|
4
4
|
"main": "dist/cjs/index.js",
|
|
5
5
|
"module": "dist/esm/index.js",
|
|
6
6
|
"types": "dist/types/index.d.ts",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"@contentful/f36-tokens": "^6.1.2",
|
|
42
42
|
"@contentful/field-editor-shared": "^3.1.4",
|
|
43
43
|
"@emotion/css": "^11.13.5",
|
|
44
|
-
"
|
|
44
|
+
"date-fns": "^2.30.0"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@babel/core": "^7.7.4",
|
|
@@ -57,5 +57,5 @@
|
|
|
57
57
|
"publishConfig": {
|
|
58
58
|
"registry": "https://npm.pkg.github.com/"
|
|
59
59
|
},
|
|
60
|
-
"gitHead": "
|
|
60
|
+
"gitHead": "bc54b71bac951f367ecb112f2d869b461edb58c4"
|
|
61
61
|
}
|