Fix ISO date formatting when the local is ar

moment has a bug, in that moment.format() doesn't generate a correct
ISO8601 string when the locale is ar.  This fix works around that.

fixes ADMIN-728

test plan:
  - log in as a student with some todos in the planner
  - set her language to Arabic (6th from the bottom)
  - open the planner
  > expect the planner to load. no network errors
  > expect the todos to be on the correct dates dates and the
  > times to be correct (if you're in chrome, you can translate the page)
  - go to the card dashboard
  > expect the todo sidebar to look right too.

Change-Id: I1d0dc3ea7c3a7e6d8265918f1f66b14bd1761206
Reviewed-on: https://gerrit.instructure.com/141000
Reviewed-by: Jon Willesen <jonw+gerrit@instructure.com>
Tested-by: Jenkins
QA-Review: Deepeeca Soundarrajan <dsoundarrajan@instructure.com>
Product-Review: Ed Schiebel <eschiebel@instructure.com>
This commit is contained in:
Ed Schiebel 2018-02-14 17:39:22 -05:00
parent a4743b0ba3
commit 265e72ffb2
9 changed files with 26 additions and 19 deletions

View File

@ -104,7 +104,7 @@ export default function ToDoItem (props) {
<Text color="secondary" size="small" weight="bold" lineHeight="fit">
{getContextShortName(props.courses, props.courseId)}
</Text>
<List variant="inline" delimeter="pipe">
<List variant="inline" delimiter="pipe" size="small">
{getInformationRow(props.dueAt, props.points)}
</List>
</div>

View File

@ -64,8 +64,8 @@ export const loadInitialItems = currentMoment => (
const firstMomentDate = currentMoment.clone().subtract(2, 'weeks');
const lastMomentDate = currentMoment.clone().add(2, 'weeks');
axios.get('/api/v1/planner/items', { params: {
start_date: firstMomentDate.format(),
end_date: lastMomentDate.format(),
start_date: firstMomentDate.toISOString(),
end_date: lastMomentDate.toISOString(),
order: 'asc'
}}).then((response) => {
const linkHeader = parseLinkHeader(response.headers.link)

View File

@ -118,7 +118,7 @@ class SearchFormComponent extends Component {
}
setSelectedFrom = (_, from, _rawValue, rawConversionFailed) => {
const startOfFrom = from ? moment(from).startOf('day').format() : '';
const startOfFrom = from ? moment(from).startOf('day').toISOString() : '';
this.setState(prevState => ({
selected: {
...prevState.selected,
@ -128,7 +128,7 @@ class SearchFormComponent extends Component {
}
setSelectedTo = (_, to, _rawValue, rawConversionFailed) => {
const endOfTo = to ? moment(to).endOf('day').format() : '';
const endOfTo = to ? moment(to).endOf('day').toISOString() : '';
this.setState(prevState => ({
selected: {
...prevState.selected,

View File

@ -292,13 +292,13 @@ describe('api actions', () => {
});
it('does set default time of 11:59 pm for planner date', () => {
const plannerItem = {date: moment('2017-06-22T10:05:54').tz("Atlantic/Azores").format()};
const plannerItem = {date: moment('2017-06-22T10:05:54').tz("Atlantic/Azores").toISOString()};
Actions.savePlannerItem(plannerItem)(() => {});
return moxiosWait((request) => {
expect(request.config.method).toBe('post');
expect(request.url).toBe('api/v1/planner_notes');
expect(JSON.parse(request.config.data).transformedToApi).toBeTruthy();
expect(moment(JSON.parse(request.config.data).date).tz("Atlantic/Azores").format()).toBe(moment('2017-06-22T23:59:59').tz("Atlantic/Azores").format());
expect(moment(JSON.parse(request.config.data).date).tz("Atlantic/Azores").toISOString()).toBe(moment('2017-06-22T23:59:59').tz("Atlantic/Azores").toISOString());
});
});

View File

@ -72,7 +72,7 @@ describe('api actions', () => {
});
return moxiosWait(request => {
expect(request.config.url).toBe('/api/v1/planner/items');
expect(request.config.params.start_date).toBe(fromMoment.format());
expect(request.config.params.start_date).toBe(fromMoment.toISOString());
});
});
@ -93,7 +93,7 @@ describe('api actions', () => {
});
return moxiosWait(request => {
expect(request.config.url).toBe('/api/v1/planner/items');
expect(request.config.params.end_date).toBe(fromMoment.format());
expect(request.config.params.end_date).toBe(fromMoment.toISOString());
expect(request.config.params.order).toBe('desc');
});
});
@ -145,7 +145,7 @@ describe('api actions', () => {
Actions.getFirstNewActivityDate(mockMoment)(mockDispatch, getBasicState);
return moxiosWait(request => {
expect(request.config.params.filter).toBe('new_activity');
expect(request.config.params.start_date).toBe(mockMoment.subtract(6, 'months').format());
expect(request.config.params.start_date).toBe(mockMoment.subtract(6, 'months').toISOString());
expect(request.config.params.order).toBe('asc');
});
});

View File

@ -71,7 +71,7 @@ export function getFirstNewActivityDate (fromMoment) {
return (dispatch, getState) => {
fromMoment = fromMoment.clone().subtract(6, 'months');
return axios.get('api/v1/planner/items', { params: {
start_date: fromMoment.format(),
start_date: fromMoment.toISOString(),
filter: 'new_activity',
order: 'asc'
}}).then(response => {
@ -137,7 +137,7 @@ function fetchParams (loadingOptions) {
return [nextPageUrl, {}];
} else {
const params = {
[timeParam]: loadingOptions.fromMoment.format()
[timeParam]: loadingOptions.fromMoment.toISOString()
};
if (loadingOptions.intoThePast) {
params.order = 'desc';

View File

@ -182,11 +182,11 @@ it('changes state when new date is typed in', () => {
const mockCallback = jest.fn();
const wrapper = mount(<UpdateItemTray {...defaultProps} onSavePlannerItem={mockCallback} noteItem={noteItem} />);
const newDate = moment('2017-10-16');
wrapper.instance().handleDateChange({}, newDate.format());
wrapper.instance().handleDateChange({}, newDate.toISOString());
wrapper.instance().handleSave();
expect(mockCallback).toHaveBeenCalledWith({
title: noteItem.title,
date: newDate.format(),
date: newDate.toISOString(),
context: {
id: null
}
@ -258,7 +258,7 @@ it('invokes save callback with updated data', () => {
wrapper.instance().handleChange('details', 'new details');
wrapper.instance().handleSave();
expect(saveMock).toHaveBeenCalledWith({
title: 'new title', date: moment('2017-05-01').format(), context: {id: '43'}, details: 'new details',
title: 'new title', date: moment('2017-05-01').toISOString(), context: {id: '43'}, details: 'new details',
});
});

View File

@ -50,7 +50,7 @@ export class UpdateItemTray extends Component {
super(props);
const updates = this.getNoteUpdates(props);
if (!updates.date) {
updates.date = props.noteItem && props.noteItem.date ? props.noteItem.date : moment.tz(props.timeZone).format();
updates.date = props.noteItem && props.noteItem.date ? props.noteItem.date : moment.tz(props.timeZone).toISOString();
}
this.state = {
updates,
@ -122,7 +122,7 @@ export class UpdateItemTray extends Component {
}
handleDateChange = (e, date) => {
const value = date ? moment(date).format() : ''; // works if date is a moment obj (instui 3) or iso string (instui 4)
const value = date ? moment(date).toISOString() : ''; // works if date is a moment obj (instui 3) or iso string (instui 4)
this.handleChange('date', value);
}

View File

@ -56,8 +56,8 @@ const getItemDetailsFromPlannable = (apiResponse, timeZone) => {
}
// Standardize 00:00:00 date to 11:59PM on the current day to make due date less confusing
let currentDay = moment(details.date);
if (currentDay.tz(timeZone).format('HH:mm:ss') === '00:00:00') {
details.date = currentDay.endOf('day').format();
if (isMidnight(currentDay, timeZone)) {
details.date = currentDay.endOf('day').toISOString();
}
return details;
};
@ -225,3 +225,10 @@ function getGroupContext(apiResponse, group) {
url: group.url
};
}
function isMidnight(currentDay, timeZone) {
// in languages like Arabic, moment.format returns strings in the native characters
// so this doesn't work
//currentDay.tz(timeZone).format('HH:mm:ss') === '00:00:00'
return currentDay.hours() === currentDay.minutes() === currentDay.seconds() === 0;
}