Skip to content

Commit eddda7f

Browse files
authored
Merge pull request #60 from skbkontur/replace-moment-with-date-fns
Replace moment with date fns
2 parents 0aa1bde + 1f51908 commit eddda7f

File tree

11 files changed

+162
-40
lines changed

11 files changed

+162
-40
lines changed

DbViewer.Tests/FrontTests/BusinessObjectsChangeValuesTest.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,23 +84,29 @@ public void TestChangeDocumentDates()
8484
var filledDateRow = businessObjectEditingPage.RootAccordion.FindField("DocumentDate");
8585
filledDateRow.FieldValue.WaitTextContains("2014-12-11");
8686
filledDateRow.Edit.Click();
87+
88+
var expectedOffset = documentName == "CqlDocument" ? TimeSpan.Zero : DateTimeOffset.Now.Offset;
89+
var expectedOffsetStr = $"+{expectedOffset:hh':'mm}";
8790
filledDateRow.FieldValue.Date.ClearAndInputText("13.12.2014");
88-
filledDateRow.FieldValue.Time.ClearAndInputText("10:18");
91+
filledDateRow.FieldValue.Time.ClearAndInputText("10:18:13.567");
92+
filledDateRow.FieldValue.TimeOffsetLabel.WaitText(expectedOffsetStr);
8993
filledDateRow.Save.Click();
90-
filledDateRow.FieldValue.WaitTextContains("2014-12-13");
94+
var expectedStr = $"2014-12-13T10:18:13.567{expectedOffsetStr}";
95+
filledDateRow.FieldValue.WaitTextContains(expectedStr);
9196

9297
browser.WebDriver.Navigate().Refresh();
9398
filledDateRow = businessObjectEditingPage.RootAccordion.FindField("DocumentDate");
94-
filledDateRow.FieldValue.WaitTextContains("2014-12-13");
99+
filledDateRow.FieldValue.WaitTextContains(expectedStr);
95100

96-
GetDocument(documentName, documentId).DocumentDate.Should().Be(new DateTimeOffset(2014, 12, 13, 10, 18, 00, 00, TimeSpan.Zero));
101+
GetDocument(documentName, documentId).DocumentDate.Should().Be(new DateTimeOffset(2014, 12, 13, 10, 18, 13, 567, expectedOffset));
97102

98103
businessObjectEditingPage.RootAccordion.FindAccordionToggle("DocumentContent").ToggleButton.Click();
99104
var unfilledDateRow = businessObjectEditingPage.RootAccordion.FindField("DocumentContent_DeliveryDate");
100105
unfilledDateRow.FieldValue.WaitText("null");
101106
unfilledDateRow.Edit.Click();
102107
unfilledDateRow.FieldValue.Date.ClearAndInputText("14.12.2014");
103108
unfilledDateRow.FieldValue.Time.ClearAndInputText("20:19");
109+
unfilledDateRow.FieldValue.TimeZoneSelect.SelectValueByText("UTC");
104110
unfilledDateRow.Save.Click();
105111
unfilledDateRow.FieldValue.WaitText("2014-12-14T20:19:00Z");
106112

DbViewer.Tests/FrontTests/Pages/AccordionFieldValue.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ public AccordionFieldValue(ISearchContainer container, ISelector selector)
2828

2929
public DatePicker Date { get; set; }
3030
public Input Time { get; set; }
31-
public Label TimeZone { get; set; }
31+
public Select TimeZoneSelect { get; set; }
32+
public Label TimeOffsetLabel { get; set; }
3233
}
3334
}

DbViewer/Helpers/ObjectParser.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,15 @@ public static class ObjectParser
4141
var format = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.FFFFFFFK";
4242
if (long.TryParse(value, out var ticks))
4343
return new DateTimeOffset(ticks, TimeSpan.Zero);
44-
return DateTimeOffset.ParseExact(value, format, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal);
44+
return DateTimeOffset.ParseExact(value, format, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal);
4545
}
4646

4747
if (type == typeof(DateTime))
4848
{
4949
var format = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.FFFFFFFK";
5050
if (long.TryParse(value, out var ticks))
5151
return new DateTime(ticks, DateTimeKind.Utc);
52-
return DateTime.ParseExact(value, format, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal);
52+
return DateTime.ParseExact(value, format, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind);
5353
}
5454

5555
throw new InvalidOperationException($"Unsupported property type: {type.FullName}");

db-viewer-ui/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,12 @@
4848
"@skbkontur/react-stack-layout": "^1.0.3",
4949
"@skbkontur/react-ui-validations": "^1.8.3",
5050
"copy-to-clipboard": "^3.3.1",
51+
"date-fns": "^2.29.1",
5152
"decimal.js": "^10.2.1",
5253
"lodash": "^4.17.20",
53-
"moment": "^2.29.1",
5454
"qs": "^6.9.6",
55-
"whatwg-fetch": "^3.5.0",
56-
"tslib": "^2.3.0"
55+
"tslib": "^2.3.0",
56+
"whatwg-fetch": "^3.5.0"
5757
},
5858
"devDependencies": {
5959
"@babel/core": "^7.14.8",

db-viewer-ui/src/Components/DateTimeRangePicker/DatePicker.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { DatePicker as DefaultDatePicker } from "@skbkontur/react-ui";
2-
import moment from "moment";
32
import React from "react";
43

54
import { RussianDateFormat } from "../../Domain/DataTypes/DateTimeRange";
@@ -90,10 +89,10 @@ export class DatePicker extends React.Component<DatePickerProps, DatePickerState
9089
private readonly convertStringToDate = (newStringifiedDate: RussianDateFormat): Date => {
9190
const { timeZone } = this.props;
9291
const date = DateUtils.convertStringToDate(newStringifiedDate);
93-
const ISODate = moment(date).format("YYYY-MM-DD");
92+
const ISODate = DateUtils.convertDateToString(date, null, "yyyy-MM-dd");
9493
const time = this.props.defaultTime || defaultTime;
9594
const timeZoneOffset = TimeUtils.getTimeZoneOffsetOrDefault(timeZone);
96-
return moment(`${ISODate}T${time}${TimeUtils.timeZoneOffsetToString(timeZoneOffset)}`).toDate();
95+
return new Date(`${ISODate}T${time}${TimeUtils.timeZoneOffsetToString(timeZoneOffset)}`);
9796
};
9897

9998
private readonly convertDateToStringWithTimezone = (date: Nullable<Date | string>, timeZone?: number) => {

db-viewer-ui/src/Components/DateTimeRangePicker/DateTimePicker.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import moment from "moment";
21
import React from "react";
32

43
import { Time, TimeZone } from "../../Domain/DataTypes/Time";
@@ -35,8 +34,8 @@ export function DateTimePicker({
3534
}
3635

3736
const timeZoneOffset = TimeUtils.getTimeZoneOffsetOrDefault(timeZone);
38-
const date = DateUtils.convertDateToString(value, timeZoneOffset, "YYYY-MM-DD");
39-
const newDateTime = moment(`${date}T${newTime}${TimeUtils.timeZoneOffsetToString(timeZoneOffset)}`).toDate();
37+
const date = DateUtils.convertDateToString(value, timeZoneOffset, "yyyy-MM-dd");
38+
const newDateTime = new Date(`${date}T${newTime}${TimeUtils.timeZoneOffsetToString(timeZoneOffset)}`);
4039
onChange(newDateTime);
4140
};
4241

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { Select } from "@skbkontur/react-ui";
2+
import React from "react";
3+
4+
import { Time } from "../../Domain/DataTypes/Time";
5+
import { DateUtils } from "../../Domain/Utils/DateUtils";
6+
7+
import { DatePicker } from "./DatePicker";
8+
import { jsStyles } from "./DateTimePicker.styles";
9+
import { TimePicker } from "./TimePicker";
10+
11+
interface DateTimePickerWithTimeZone {
12+
error?: boolean;
13+
defaultTime: Time;
14+
value: Nullable<string>;
15+
onChange: (value: Nullable<string>) => void;
16+
disabled?: boolean;
17+
timeZoneEditable?: boolean;
18+
}
19+
20+
export function DateTimePickerWithTimeZone({
21+
error,
22+
defaultTime,
23+
value,
24+
onChange,
25+
disabled,
26+
timeZoneEditable,
27+
}: DateTimePickerWithTimeZone): JSX.Element {
28+
const [time, setTime] = React.useState<Nullable<string>>(null);
29+
const [offset, setOffset] = React.useState<Nullable<string>>(null);
30+
const [date, setDate] = React.useState<Nullable<Date>>(null);
31+
React.useEffect(() => loadState(value), [value]);
32+
React.useEffect(() => handleDateTimeChange(date, time, offset), [date, time, offset]);
33+
34+
const handleDateTimeChange = (
35+
newDate: Nullable<Date>,
36+
newTime: Nullable<Time>,
37+
newOffset: Nullable<string>
38+
): void => {
39+
if (!newDate) {
40+
onChange(null);
41+
return;
42+
}
43+
const date = DateUtils.convertDateToString(newDate, null, "yyyy-MM-dd");
44+
const newDateTime = `${date}T${newTime ?? defaultTime}${newOffset ?? ""}`;
45+
onChange(newDateTime);
46+
};
47+
48+
const loadState = (dateStr: Nullable<string>): void => {
49+
if (!dateStr) {
50+
return;
51+
}
52+
const timeOffset = getTimeZoneString(dateStr);
53+
setOffset(timeOffset);
54+
const dateTimeWithoutTimezone = timeOffset ? dateStr.slice(0, -timeOffset.length) : dateStr;
55+
setDate(new Date(dateTimeWithoutTimezone));
56+
setTime(DateUtils.convertDateToString(dateTimeWithoutTimezone, null, "HH:mm:ss.SSS"));
57+
};
58+
59+
const getTimeZoneString = (date: string): Nullable<string> => {
60+
const timezoneRegex = /.*T.*(Z|[+-].*)/i;
61+
const matches = date.match(timezoneRegex);
62+
if (!matches || matches.length < 2) {
63+
return null;
64+
}
65+
return matches[1];
66+
};
67+
68+
return (
69+
<span>
70+
<span className={jsStyles.dateRangeItem()}>
71+
<DatePicker
72+
data-tid="Date"
73+
value={date}
74+
defaultTime={time || defaultTime}
75+
onChange={newDate => setDate(newDate)}
76+
error={error}
77+
disabled={disabled}
78+
width={110}
79+
/>
80+
</span>
81+
<span className={jsStyles.dateRangeItem()}>
82+
<TimePicker
83+
data-tid="Time"
84+
value={time === defaultTime ? null : time}
85+
error={error}
86+
defaultTime={defaultTime}
87+
disabled={disabled || !date}
88+
onChange={(_, newTime) => setTime(newTime)}
89+
useSeconds
90+
/>
91+
</span>
92+
<span className={jsStyles.dateRangeItem()}>
93+
{timeZoneEditable ? (
94+
<Select<string>
95+
data-tid="TimeZoneSelect"
96+
defaultValue="UTC"
97+
value={offset ? "UTC" : "local"}
98+
items={["UTC", "local"]}
99+
onValueChange={v => setOffset(v === "local" ? null : "Z")}
100+
/>
101+
) : (
102+
<span data-tid="TimeOffsetLabel">{offset}</span>
103+
)}
104+
</span>
105+
</span>
106+
);
107+
}

db-viewer-ui/src/Components/DateTimeRangePicker/TimePicker.tsx

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ interface TimePickerProps {
1111
disabled?: boolean;
1212
onChange: (e: React.SyntheticEvent<any>, value: Time) => void;
1313
warning?: boolean;
14+
useSeconds?: boolean;
1415
}
1516

1617
interface TimePickerState {
@@ -45,9 +46,10 @@ export class TimePicker extends React.Component<TimePickerProps, TimePickerState
4546
private handleBlur = (e: React.SyntheticEvent<any>) => {
4647
const { defaultTime } = this.props;
4748
const { value } = this.state;
48-
if (DateUtils.isCorrectTime(value)) {
49-
this.props.onChange(e, value);
50-
if (defaultTime === value) {
49+
const trimmed = value.endsWith(".") || value.endsWith(":") ? value.slice(0, -1) : value;
50+
if (DateUtils.isCorrectTime(trimmed)) {
51+
this.props.onChange(e, trimmed);
52+
if (defaultTime === trimmed) {
5153
this.setState({ value: emptyValue });
5254
}
5355
} else {
@@ -64,19 +66,20 @@ export class TimePicker extends React.Component<TimePickerProps, TimePickerState
6466
};
6567

6668
public render(): JSX.Element {
69+
const { disabled, warning, error, useSeconds, defaultTime } = this.props;
6770
return (
6871
<Input
6972
data-tid="Input"
70-
disabled={this.props.disabled}
71-
mask="99:99"
73+
disabled={disabled}
74+
mask={useSeconds ? "99:99:99.999" : "99:99"}
7275
value={this.state.value}
73-
width={58}
74-
error={this.props.error}
75-
placeholder={this.props.disabled ? undefined : this.props.defaultTime}
76+
width={useSeconds ? 96 : 58}
77+
error={error}
78+
placeholder={disabled ? undefined : defaultTime}
7679
onValueChange={this.handleChange}
7780
onBlur={this.handleBlur}
7881
onFocus={this.handleFocus}
79-
warning={this.props.warning}
82+
warning={warning}
8083
/>
8184
);
8285
}

db-viewer-ui/src/Components/ObjectViewer/ObjectItemRender.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import React from "react";
55
import { PropertyMetaInformation } from "../../Domain/Api/DataTypes/PropertyMetaInformation";
66
import { ICustomRenderer } from "../../Domain/Objects/CustomRenderer";
77
import { FileUtils } from "../../Domain/Utils/FileUtils";
8-
import { DateTimePicker } from "../DateTimeRangePicker/DateTimePicker";
8+
import { DateTimePickerWithTimeZone } from "../DateTimeRangePicker/DateTimePickerWithTimeZone";
99
import { StyledSelect } from "../ObjectFilter/OperatorSelect";
1010

1111
function getByPath(target: Nullable<{}>, path: string[]): any {
@@ -113,10 +113,11 @@ export function renderForEdit(
113113
case "DateTime":
114114
case "DateTimeOffset":
115115
return (
116-
<DateTimePicker
117-
value={value != null ? new Date(String(value)) : null}
116+
<DateTimePickerWithTimeZone
117+
value={value != null ? String(value) : null}
118118
onChange={onChange}
119-
defaultTime={""}
119+
defaultTime={"00:00:00"}
120+
timeZoneEditable={type === "DateTime"}
120121
/>
121122
);
122123
case "Float":
Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,29 @@
1-
import moment from "moment";
1+
import { addMinutes, format, parse } from "date-fns";
22

33
import { RussianDateFormat } from "../DataTypes/DateTimeRange";
44

55
export class DateUtils {
6-
private static readonly datePickerFormat: RussianDateFormat = "DD.MM.YYYY";
6+
private static readonly datePickerFormat: RussianDateFormat = "dd.MM.yyyy";
77

88
public static isCorrectTime(time: string): boolean {
99
return Boolean(time.match(/^([01]?[0-9]|2[0-3]):[0-5][0-9]/));
1010
}
1111

1212
public static convertDateToString(
1313
date: Date | string,
14-
timeZone: number,
15-
format: string = this.datePickerFormat
14+
timeZone: number | null,
15+
dateFormat: string = this.datePickerFormat
1616
): string {
17-
return moment.utc(date).utcOffset(timeZone).format(format);
17+
const dateDate = timeZone == null ? new Date(date) : this.toTimeZone(new Date(date), timeZone);
18+
return format(dateDate, dateFormat);
1819
}
1920

2021
public static convertStringToDate(date: RussianDateFormat): Date {
21-
return moment(date, this.datePickerFormat).toDate();
22+
return parse(date, this.datePickerFormat, new Date());
23+
}
24+
25+
public static toTimeZone(date: Date | string, timeZoneOffsetInMinutes: number): Date {
26+
const dateDate = new Date(date);
27+
return addMinutes(dateDate, dateDate.getTimezoneOffset() + timeZoneOffsetInMinutes);
2228
}
2329
}

0 commit comments

Comments
 (0)