@contentful/field-editor-date 2.0.7 → 2.0.8-canary.0

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.
@@ -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 dateObj = props.value?.toObject();
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
- const momentDay = day ? (0, _moment.default)(day) : undefined;
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 _moment = /*#__PURE__*/ _interop_require_default(require("moment"));
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,28 @@ 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
- 'kk:mm',
60
+ 'H:mm',
69
61
  'h a',
70
- 'h A',
71
- 'h',
72
- 'hh',
73
- 'HH'
62
+ 'h'
74
63
  ];
64
+ const REF_DATE = new Date(2000, 0, 1);
75
65
  function parseRawInput(raw) {
76
- let time = null;
77
- for(let i = 0; i < validInputFormats.length; i++){
78
- const date = (0, _moment.default)(raw, validInputFormats[i]);
79
- if (date.isValid()) {
80
- time = date;
81
- break;
82
- }
66
+ for (const fmt of validInputFormats){
67
+ const parsed = (0, _datefns.parse)(raw, fmt, REF_DATE);
68
+ if ((0, _datefns.isValid)(parsed)) return parsed;
83
69
  }
84
- return time;
70
+ return null;
85
71
  }
86
- const getDefaultTime = ()=>{
87
- return (0, _moment.default)(`12:00 AM`, 'hh:mm A');
88
- };
89
- const formatToString = (uses12hClock, value)=>{
90
- return uses12hClock ? value.format('hh:mm A') : value.format('HH:mm');
91
- };
72
+ const getDefaultTime = ()=>(0, _datefns.parse)('12:00 AM', 'hh:mm a', REF_DATE);
73
+ const formatToString = (uses12hClock, value)=>(0, _datefns.format)(value, uses12hClock ? 'hh:mm a' : 'HH:mm');
92
74
  const TimepickerInput = ({ disabled, uses12hClock, time = '12:00', ampm = 'AM', onChange })=>{
93
75
  const [selectedTime, setSelectedTime] = (0, _react.useState)(()=>{
94
76
  return formatToString(uses12hClock, getDefaultTime());
95
77
  });
96
78
  (0, _react.useEffect)(()=>{
97
- setSelectedTime(formatToString(uses12hClock, (0, _moment.default)(`${time} ${ampm}`, 'hh:mm A')));
79
+ setSelectedTime(formatToString(uses12hClock, (0, _datefns.parse)(`${time} ${ampm}`, 'hh:mm a', REF_DATE)));
98
80
  }, [
99
81
  time,
100
82
  ampm,
@@ -112,8 +94,8 @@ const TimepickerInput = ({ disabled, uses12hClock, time = '12:00', ampm = 'AM',
112
94
  const value = parsedTime ?? getDefaultTime();
113
95
  setSelectedTime(formatToString(uses12hClock, value));
114
96
  onChange({
115
- time: value.format('hh:mm'),
116
- ampm: value.format('A')
97
+ time: (0, _datefns.format)(value, 'hh:mm'),
98
+ ampm: (0, _datefns.format)(value, 'a').toUpperCase()
117
99
  });
118
100
  };
119
101
  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: (0, _moment.default)('2018-02-02'),
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: (0, _moment.default)('2015-01-14'),
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: (0, _moment.default)('2015-01-14'),
37
+ date: new Date('2015-01-14'),
44
38
  time: '17:00',
45
39
  ampm: 'PM',
46
40
  utcOffset: '-05:00'
@@ -52,5 +46,135 @@ 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
+ });
124
+ describe('userInputFromDatetime', ()=>{
125
+ it('parses a full ISO datetime string with timezone', ()=>{
126
+ const result = (0, _date.userInputFromDatetime)({
127
+ value: '2018-02-02T17:00+03:00',
128
+ uses12hClock: false
129
+ });
130
+ expect(result.time).toBe('17:00');
131
+ expect(result.ampm).toBe('PM');
132
+ expect(result.utcOffset).toBe('+03:00');
133
+ });
134
+ it('parses a full ISO datetime string with 12h clock', ()=>{
135
+ const result = (0, _date.userInputFromDatetime)({
136
+ value: '2018-02-02T05:00+03:00',
137
+ uses12hClock: true
138
+ });
139
+ expect(result.time).toBe('05:00');
140
+ expect(result.ampm).toBe('AM');
141
+ expect(result.utcOffset).toBe('+03:00');
142
+ });
143
+ it('returns defaults when value is null', ()=>{
144
+ const result = (0, _date.userInputFromDatetime)({
145
+ value: null,
146
+ uses12hClock: false
147
+ });
148
+ expect(result.date).toBeUndefined();
149
+ expect(result.ampm).toBe((0, _date.getDefaultAMPM)());
150
+ expect(result.utcOffset).toBe((0, _date.getDefaultUtcOffset)());
151
+ });
152
+ it('returns defaults when value is undefined', ()=>{
153
+ const result = (0, _date.userInputFromDatetime)({
154
+ value: undefined,
155
+ uses12hClock: false
156
+ });
157
+ expect(result.date).toBeUndefined();
158
+ expect(result.ampm).toBe((0, _date.getDefaultAMPM)());
159
+ });
160
+ it('returns defaults when value is empty string', ()=>{
161
+ const result = (0, _date.userInputFromDatetime)({
162
+ value: '',
163
+ uses12hClock: false
164
+ });
165
+ expect(result.date).toBeUndefined();
166
+ });
167
+ it('parses a date-only string', ()=>{
168
+ const result = (0, _date.userInputFromDatetime)({
169
+ value: '2022-09-16',
170
+ uses12hClock: false
171
+ });
172
+ expect(result.utcOffset).toBe('+00:00');
173
+ });
174
+ });
175
+ describe('getDefaultAMPM', ()=>{
176
+ it('returns AM', ()=>{
177
+ expect((0, _date.getDefaultAMPM)()).toBe('AM');
178
+ });
55
179
  });
56
180
  });
@@ -22,32 +22,36 @@ _export(exports, {
22
22
  return userInputFromDatetime;
23
23
  }
24
24
  });
25
- const _moment = /*#__PURE__*/ _interop_require_default(require("moment"));
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 startOfToday(format) {
33
- return (0, _moment.default)().set({
34
- hours: 0,
35
- minutes: 0
36
- }).format(format);
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 fieldValueToMoment(datetimeString) {
34
+ function fieldValueToDate(datetimeString) {
39
35
  if (!datetimeString) {
40
36
  return null;
41
37
  }
42
- const datetime = (0, _moment.default)(datetimeString);
43
- if (ZONE_RX.test(datetimeString)) {
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
- return _moment.default.utc(timeInput + '!' + input.ampm, 'HH:mm!A');
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 time = timeFromUserInput(input);
59
- const date = _moment.default.parseZone(input.utcOffset, 'Z').set(input.date.toObject()).set({
60
- hours: time.hours(),
61
- minutes: time.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 (date.isValid()) {
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
- let format;
87
+ if (!date.valid) {
88
+ return {
89
+ valid: null,
90
+ invalid: false
91
+ };
92
+ }
82
93
  if (usesTimezone) {
83
- format = 'YYYY-MM-DDTHH:mmZ';
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
- format = 'YYYY-MM-DDTHH:mm';
99
+ return {
100
+ valid: (0, _datefns.format)(date.valid, "yyyy-MM-dd'T'HH:mm"),
101
+ invalid: false
102
+ };
86
103
  } else {
87
- format = 'YYYY-MM-DD';
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 startOfToday('Z');
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 = fieldValueToMoment(value);
102
- if (datetime) {
103
- const timeFormat = uses12hClock ? 'hh:mm' : 'HH:mm';
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: datetime.format(timeFormat),
107
- ampm: datetime.format('A'),
108
- utcOffset: datetime.format('Z')
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 dateObj = props.value?.toObject();
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
- const momentDay = day ? moment(day) : undefined;
29
- props.onChange(momentDay);
26
+ props.onChange(day ?? undefined);
30
27
  },
31
28
  inputProps: {
32
29
  isDisabled: props.disabled,
@@ -1,44 +1,31 @@
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 moment from 'moment';
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
- 'kk:mm',
9
+ 'H:mm',
13
10
  'h a',
14
- 'h A',
15
- 'h',
16
- 'hh',
17
- 'HH'
11
+ 'h'
18
12
  ];
13
+ const REF_DATE = new Date(2000, 0, 1);
19
14
  function parseRawInput(raw) {
20
- let time = null;
21
- for(let i = 0; i < validInputFormats.length; i++){
22
- const date = moment(raw, validInputFormats[i]);
23
- if (date.isValid()) {
24
- time = date;
25
- break;
26
- }
15
+ for (const fmt of validInputFormats){
16
+ const parsed = parse(raw, fmt, REF_DATE);
17
+ if (isValid(parsed)) return parsed;
27
18
  }
28
- return time;
19
+ return null;
29
20
  }
30
- const getDefaultTime = ()=>{
31
- return moment(`12:00 AM`, 'hh:mm A');
32
- };
33
- const formatToString = (uses12hClock, value)=>{
34
- return uses12hClock ? value.format('hh:mm A') : value.format('HH:mm');
35
- };
21
+ const getDefaultTime = ()=>parse('12:00 AM', 'hh:mm a', REF_DATE);
22
+ const formatToString = (uses12hClock, value)=>format(value, uses12hClock ? 'hh:mm a' : 'HH:mm');
36
23
  export const TimepickerInput = ({ disabled, uses12hClock, time = '12:00', ampm = 'AM', onChange })=>{
37
24
  const [selectedTime, setSelectedTime] = useState(()=>{
38
25
  return formatToString(uses12hClock, getDefaultTime());
39
26
  });
40
27
  useEffect(()=>{
41
- setSelectedTime(formatToString(uses12hClock, moment(`${time} ${ampm}`, 'hh:mm A')));
28
+ setSelectedTime(formatToString(uses12hClock, parse(`${time} ${ampm}`, 'hh:mm a', REF_DATE)));
42
29
  }, [
43
30
  time,
44
31
  ampm,
@@ -56,8 +43,8 @@ export const TimepickerInput = ({ disabled, uses12hClock, time = '12:00', ampm =
56
43
  const value = parsedTime ?? getDefaultTime();
57
44
  setSelectedTime(formatToString(uses12hClock, value));
58
45
  onChange({
59
- time: value.format('hh:mm'),
60
- ampm: value.format('A')
46
+ time: format(value, 'hh:mm'),
47
+ ampm: format(value, 'a').toUpperCase()
61
48
  });
62
49
  };
63
50
  return /*#__PURE__*/ React.createElement(Flex, {
@@ -1,11 +1,10 @@
1
- import moment from 'moment';
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: moment('2018-02-02'),
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: moment('2015-01-14'),
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: moment('2015-01-14'),
33
+ date: new Date('2015-01-14'),
35
34
  time: '17:00',
36
35
  ampm: 'PM',
37
36
  utcOffset: '-05:00'
@@ -43,5 +42,135 @@ 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
+ });
120
+ describe('userInputFromDatetime', ()=>{
121
+ it('parses a full ISO datetime string with timezone', ()=>{
122
+ const result = userInputFromDatetime({
123
+ value: '2018-02-02T17:00+03:00',
124
+ uses12hClock: false
125
+ });
126
+ expect(result.time).toBe('17:00');
127
+ expect(result.ampm).toBe('PM');
128
+ expect(result.utcOffset).toBe('+03:00');
129
+ });
130
+ it('parses a full ISO datetime string with 12h clock', ()=>{
131
+ const result = userInputFromDatetime({
132
+ value: '2018-02-02T05:00+03:00',
133
+ uses12hClock: true
134
+ });
135
+ expect(result.time).toBe('05:00');
136
+ expect(result.ampm).toBe('AM');
137
+ expect(result.utcOffset).toBe('+03:00');
138
+ });
139
+ it('returns defaults when value is null', ()=>{
140
+ const result = userInputFromDatetime({
141
+ value: null,
142
+ uses12hClock: false
143
+ });
144
+ expect(result.date).toBeUndefined();
145
+ expect(result.ampm).toBe(getDefaultAMPM());
146
+ expect(result.utcOffset).toBe(getDefaultUtcOffset());
147
+ });
148
+ it('returns defaults when value is undefined', ()=>{
149
+ const result = userInputFromDatetime({
150
+ value: undefined,
151
+ uses12hClock: false
152
+ });
153
+ expect(result.date).toBeUndefined();
154
+ expect(result.ampm).toBe(getDefaultAMPM());
155
+ });
156
+ it('returns defaults when value is empty string', ()=>{
157
+ const result = userInputFromDatetime({
158
+ value: '',
159
+ uses12hClock: false
160
+ });
161
+ expect(result.date).toBeUndefined();
162
+ });
163
+ it('parses a date-only string', ()=>{
164
+ const result = userInputFromDatetime({
165
+ value: '2022-09-16',
166
+ uses12hClock: false
167
+ });
168
+ expect(result.utcOffset).toBe('+00:00');
169
+ });
170
+ });
171
+ describe('getDefaultAMPM', ()=>{
172
+ it('returns AM', ()=>{
173
+ expect(getDefaultAMPM()).toBe('AM');
174
+ });
46
175
  });
47
176
  });
@@ -1,24 +1,33 @@
1
- import moment from 'moment';
1
+ import { format, getHours, getMinutes, isValid, parse, parseISO, set } from 'date-fns';
2
2
  const ZONE_RX = /(Z|[+-]\d{2}[:+]?\d{2})$/;
3
- function startOfToday(format) {
4
- return moment().set({
5
- hours: 0,
6
- minutes: 0
7
- }).format(format);
3
+ function startOfTodayOffset() {
4
+ return format(new Date(), 'xxx');
8
5
  }
9
- function fieldValueToMoment(datetimeString) {
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 datetime = moment(datetimeString);
14
- if (ZONE_RX.test(datetimeString)) {
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
- return moment.utc(timeInput + '!' + input.ampm, 'HH:mm!A');
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 time = timeFromUserInput(input);
30
- const date = moment.parseZone(input.utcOffset, 'Z').set(input.date.toObject()).set({
31
- hours: time.hours(),
32
- minutes: time.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 (date.isValid()) {
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
- let format;
63
+ if (!date.valid) {
64
+ return {
65
+ valid: null,
66
+ invalid: false
67
+ };
68
+ }
53
69
  if (usesTimezone) {
54
- format = 'YYYY-MM-DDTHH:mmZ';
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
- format = 'YYYY-MM-DDTHH:mm';
75
+ return {
76
+ valid: format(date.valid, "yyyy-MM-dd'T'HH:mm"),
77
+ invalid: false
78
+ };
57
79
  } else {
58
- format = 'YYYY-MM-DD';
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 startOfToday('Z');
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 = fieldValueToMoment(value);
73
- if (datetime) {
74
- const timeFormat = uses12hClock ? 'hh:mm' : 'HH:mm';
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: datetime.format(timeFormat),
78
- ampm: datetime.format('A'),
79
- utcOffset: datetime.format('Z')
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?: moment.Moment;
5
- onChange: (val: moment.Moment | undefined) => void;
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;
@@ -1,8 +1,7 @@
1
- import moment from 'moment';
2
1
  export type DateTimeFormat = 'dateonly' | 'time' | 'timeZ';
3
2
  export type TimeFormat = '12' | '24';
4
3
  export type TimeResult = {
5
- date?: moment.Moment;
4
+ date?: Date;
6
5
  time?: string;
7
6
  ampm: string;
8
7
  utcOffset: string;
@@ -13,7 +13,10 @@ export declare function buildFieldValue({ data, usesTime, usesTimezone, }: {
13
13
  invalid: boolean;
14
14
  valid?: undefined;
15
15
  } | {
16
- valid: string | null;
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.7",
3
+ "version": "2.0.8-canary.0+d0604e80",
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
- "moment": "^2.20.0"
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": "49c6f5bd0ee3a9e1a8a8a6573698380282ca8d1d"
60
+ "gitHead": "d0604e80ea65d12805975ad34c70c5fd499457c5"
61
61
  }