Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

moment-timezone compatability for DateInput #297

Merged
merged 2 commits into from Dec 2, 2016
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
53 changes: 43 additions & 10 deletions packages/datetime/src/dateInput.tsx
Expand Up @@ -132,12 +132,12 @@ export class DateInput extends AbstractComponent<IDateInputProps, IDateInputStat
public constructor(props?: IDateInputProps, context?: any) {
super(props, context);

const defaultValue = this.props.defaultValue ? moment(this.props.defaultValue) : moment(null);
const defaultValue = this.props.defaultValue ? this.fromDateToMoment(this.props.defaultValue) : moment(null);

this.state = {
isInputFocused: false,
isOpen: false,
value: this.props.value !== undefined ? moment(this.props.value) : defaultValue,
value: this.props.value !== undefined ? this.fromDateToMoment(this.props.value) : defaultValue,
valueString: null,
};
}
Expand All @@ -152,7 +152,7 @@ export class DateInput extends AbstractComponent<IDateInputProps, IDateInputStat
canClearSelection={this.props.canClearSelection}
defaultValue={null}
onChange={this.handleDateChange}
value={this.validAndInRange(this.state.value) ? this.state.value.toDate() : null}
value={this.validAndInRange(this.state.value) ? this.fromMomentToDate(this.state.value) : null}
/>
);

Expand Down Expand Up @@ -200,7 +200,7 @@ export class DateInput extends AbstractComponent<IDateInputProps, IDateInputStat

public componentWillReceiveProps(nextProps: IDateInputProps) {
if (nextProps.value !== this.props.value) {
this.setState({ value: moment(nextProps.value) });
this.setState({ value: this.fromDateToMoment(nextProps.value) });
}

super.componentWillReceiveProps(nextProps);
Expand Down Expand Up @@ -237,15 +237,16 @@ export class DateInput extends AbstractComponent<IDateInputProps, IDateInputStat
}

private handleDateChange = (date: Date, hasUserManuallySelectedDate: boolean) => {
const momentDate = this.fromDateToMoment(date);
const hasMonthChanged = date !== null && !this.isNull(this.state.value) && this.state.value.isValid() &&
date.getMonth() !== this.state.value.toDate().getMonth();
momentDate.month() !== this.state.value.month();
const isOpen = !(this.props.closeOnSelection && hasUserManuallySelectedDate && !hasMonthChanged);
if (this.props.value === undefined) {
this.setState({ isInputFocused: false, isOpen, value: moment(date) });
this.setState({ isInputFocused: false, isOpen, value: momentDate });
} else {
this.setState({ isInputFocused: false, isOpen });
}
Utils.safeInvoke(this.props.onChange, date);
Utils.safeInvoke(this.props.onChange, this.fromMomentToDate(momentDate));
}

private handleIconClick = (e: React.SyntheticEvent<HTMLElement>) => {
Expand Down Expand Up @@ -288,7 +289,7 @@ export class DateInput extends AbstractComponent<IDateInputProps, IDateInputStat
} else {
this.setState({ valueString });
}
Utils.safeInvoke(this.props.onChange, value.toDate());
Utils.safeInvoke(this.props.onChange, this.fromMomentToDate(value));
} else {
this.setState({ valueString });
}
Expand All @@ -308,9 +309,9 @@ export class DateInput extends AbstractComponent<IDateInputProps, IDateInputStat
if (!value.isValid()) {
Utils.safeInvoke(this.props.onError, new Date(undefined));
} else if (!this.dateIsInRange(value)) {
Utils.safeInvoke(this.props.onError, value.toDate());
Utils.safeInvoke(this.props.onError, this.fromMomentToDate(value));
} else {
Utils.safeInvoke(this.props.onChange, value.toDate());
Utils.safeInvoke(this.props.onChange, this.fromMomentToDate(value));
}
} else {
this.setState({ isInputFocused: false });
Expand All @@ -320,4 +321,36 @@ export class DateInput extends AbstractComponent<IDateInputProps, IDateInputStat
private setInputRef = (el: HTMLElement) => {
this.inputRef = el;
}

/**
* Translate a moment into a Date object, adjusting the moment timezone into the local one.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where does the adjustment happen? i'm assuming it's inside moment but it's not obvious why this works.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Date objects are always in the local timezone. Moment operates in the specified timezone after moment.tz.setDefault has been called (or the local timezone if none has been configured). Therefore directly copying the individual components of the dates corrects for the timezone discrepancy.

* This is a no-op unless moment-timezone's setDefault has been called.
*/
private fromMomentToDate = (momentDate: moment.Moment) => {
return new Date(
momentDate.year(),
momentDate.month(),
momentDate.date(),
momentDate.hours(),
momentDate.minutes(),
momentDate.seconds(),
momentDate.milliseconds(),
);
}

/**
* Translate a Date object into a moment, adjusting the local timezone into the moment one.
* This is a no-op unless moment-timezone's setDefault has been called.
*/
private fromDateToMoment = (date: Date) => {
return moment([
date.getFullYear(),
date.getMonth(),
date.getDate(),
date.getHours(),
date.getMinutes(),
date.getSeconds(),
date.getMilliseconds(),
]);
}
}