Browse Source

feature/utc (#561)

* Code for UTC implementation

* Modified text on 'Today' button as it 'Date-Only' fields have this button and Date-Only buttons only work with UTC

Ensure that Date Only field only works in UTC and no conversion is made

* fixed code error for UTC conversion of just date-only fields

* Displaying date-times on frontend as Local

* Removed UTC text from translation JSON files

* Code review comment changes

* Simplified logic for 'Now' button when using the two toggles for time mode

* code review changes (pairing with Sebastian)

Co-authored-by: segalj <jason.segal@reedbusiness.com>
pull/564/head
jasonsegal23 5 years ago
committed by GitHub
parent
commit
749854110c
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      backend/i18n/frontend_en.json
  2. 2
      backend/i18n/frontend_nl.json
  3. 2
      backend/src/Squidex/Areas/Api/Controllers/UI/MyUIOptions.cs
  4. 4
      backend/src/Squidex/appsettings.json
  5. 2
      frontend/app/features/content/shared/content-status.component.ts
  6. 26
      frontend/app/framework/angular/forms/editors/date-time-editor.component.html
  7. 15
      frontend/app/framework/angular/forms/editors/date-time-editor.component.scss
  8. 83
      frontend/app/framework/angular/forms/editors/date-time-editor.component.ts
  9. 6
      frontend/app/framework/utils/date-time.ts
  10. 6
      frontend/app/shared/state/contents.forms.spec.ts
  11. 4
      frontend/app/shared/state/contents.forms.visitors.ts

2
backend/i18n/frontend_en.json

@ -210,7 +210,7 @@
"common.created": "Created",
"common.date": "Date",
"common.dateTimeEditor.now": "Now",
"common.dateTimeEditor.nowTooltip": "Use Now (UTC)",
"common.dateTimeEditor.nowTooltip": "Use Now",
"common.dateTimeEditor.today": "Today",
"common.dateTimeEditor.todayTooltip": "Use Today (UTC)",
"common.delete": "Delete",

2
backend/i18n/frontend_nl.json

@ -210,7 +210,7 @@
"common.created": "Gemaakt",
"common.date": "Datum",
"common.dateTimeEditor.now": "Nu",
"common.dateTimeEditor.nowTooltip": "Nu gebruiken (UTC)",
"common.dateTimeEditor.nowTooltip": "Nu gebruiken",
"common.dateTimeEditor.today": "Vandaag",
"common.dateTimeEditor.todayTooltip": "Gebruik vandaag (UTC)",
"common.delete": "Verwijderen",

2
backend/src/Squidex/Areas/Api/Controllers/UI/MyUIOptions.cs

@ -29,6 +29,8 @@ namespace Squidex.Areas.Api.Controllers.UI
public bool HideDateButtons { get; set; }
public bool HideDateTimeModeButton { get; set; }
public bool DisableScheduledChanges { get; set; }
public bool RedirectToLogin { get; set; }

4
backend/src/Squidex/appsettings.json

@ -118,6 +118,10 @@
*/
"hideDateButtons": false,
/*
* Hide the Local/UTC button
*/
"hideDateTimeModeButton": false,
/*
* True to disable scheduled content status changed, for example when you have your own scheduling system.
*/

2
frontend/app/features/content/shared/content-status.component.ts

@ -43,7 +43,7 @@ export class ContentStatusComponent {
public get tooltipText() {
if (this.scheduled) {
return `Will be set to '${this.scheduled.status}' at ${this.scheduled.dueTime.toStringFormatUTC('PPpp')}`;
return `Will be set to '${this.scheduled.status}' at ${this.scheduled.dueTime.toStringFormat('PPpp')}`;
} else {
return this.status;
}

26
frontend/app/framework/angular/forms/editors/date-time-editor.component.html

@ -1,25 +1,37 @@
<div>
<div (sqxResizeCondition)="setCompact($event)" [sqxResizeMinWidth]="500" [sqxResizeMaxWidth]="0">
<div class="form-inline">
<div class="form-group mr-1">
<div *ngIf="!isCompact && isDateTimeMode && shouldShowDateTimeModeButton">
<button type="button" class="btn btn-text-secondary btn-sm btn-time-mode" (click)="setLocalMode(false)"
*ngIf="isLocalMode">
Local
</button>
<button type="button" class="btn btn-text-secondary btn-sm btn-time-mode" (click)="setLocalMode(true)"
*ngIf="!isLocalMode">
UTC
</button>
</div>
<div class="input-group">
<input type="text" class="form-control form-date" [formControl]="dateControl" placeholder="{{ 'common.date' | sqxTranslate }}" [class.form-date-only]="!showTime" (blur)="callTouched()" maxlength="10" #dateInput>
<input type="text" class="form-control form-date" [formControl]="dateControl" placeholder="{{ 'common.date' | sqxTranslate }}" [class.form-date-only]="!isDateTimeMode" [class.form-date-time-only]="isDateTimeMode && shouldShowDateTimeModeButton"
(blur)="callTouched()" maxlength="10" #dateInput>
<input type="text" class="form-control form-time" [formControl]="timeControl" placeholder="{{ 'common.time' | sqxTranslate }}" (blur)="callTouched()" *ngIf="showTime">
<input type="text" class="form-control form-time" [formControl]="timeControl" placeholder="{{ 'common.time' | sqxTranslate }}" (blur)="callTouched()" *ngIf="isDateTimeMode">
</div>
<button type="button" class="btn btn-text-secondary btn-sm btn-clear" [class.hidden]="!hasValue" [disabled]="snapshot.isDisabled" (click)="reset()" *ngIf="!hideClear">
<button type="button" class="btn btn-text-secondary btn-sm btn-clear" [class.hidden]="!hasValue"
[disabled]="snapshot.isDisabled" (click)="reset()" *ngIf="!hideClear">
<i class="icon-close"></i>
</button>
</div>
<div class="form-group" *ngIf="showTime && shouldShowDateButtons">
<div class="form-group" *ngIf="isDateTimeMode && shouldShowDateButtons">
<button type="button" class="btn btn-text-secondary" [disabled]="snapshot.isDisabled" (click)="writeNow()" title="i18n:common.dateTimeEditor.nowTooltip">
{{ 'common.dateTimeEditor.now' | sqxTranslate }}
</button>
</div>
<div class="form-group" *ngIf="!showTime && shouldShowDateButtons">
<div class="form-group" *ngIf="!isDateTimeMode && shouldShowDateButtons">
<button type="button" class="btn btn-text-secondary" [disabled]="snapshot.isDisabled" (click)="writeNow()" title="i18n:common.dateTimeEditor.todayTooltip">
{{ 'common.dateTimeEditor.today' | sqxTranslate }}
</button>
</div>
</div>
</div>
</div>

15
frontend/app/framework/angular/forms/editors/date-time-editor.component.scss

@ -33,16 +33,27 @@
width: 8.5rem;
}
.form-date-time-only {
padding-left: 3.3rem;
width: 11rem;
}
.form-time {
width: 7rem;
}
}
.btn-clear {
& {
.btn {
&-clear {
@include absolute(auto, 4px, 3px, auto);
}
&-time-mode {
@include absolute(1px, auto, -1px, auto);
z-index: 1000;
}
&:focus {
box-shadow: none;
}

83
frontend/app/framework/angular/forms/editors/date-time-editor.component.ts

@ -32,6 +32,7 @@ export class DateTimeEditorComponent extends StatefulControlComponent<{}, string
private picker: any;
private dateTime: DateTime | null;
private hideDateButtonsSettings: boolean;
private hideDateTimeModeButtonSetting: boolean;
private suppressEvents = false;
@Input()
@ -46,17 +47,29 @@ export class DateTimeEditorComponent extends StatefulControlComponent<{}, string
@Input()
public hideDateButtons: boolean;
@Input()
public hideDateTimeModeButton: boolean;
@Input()
public isCompact: boolean;
@ViewChild('dateInput', { static: false })
public dateInput: ElementRef<HTMLInputElement>;
public timeControl = new FormControl();
public dateControl = new FormControl();
public isLocalMode = true;
public get shouldShowDateButtons() {
return !this.hideDateButtonsSettings && !this.hideDateButtons;
}
public get showTime() {
public get shouldShowDateTimeModeButton() {
return !this.hideDateTimeModeButtonSetting && !this.hideDateTimeModeButton;
}
public get isDateTimeMode() {
return this.mode === 'DateTime';
}
@ -68,23 +81,28 @@ export class DateTimeEditorComponent extends StatefulControlComponent<{}, string
super(changeDetector, {});
this.hideDateButtonsSettings = !!uiOptions.get('hideDateButtons');
this.hideDateTimeModeButtonSetting = !!uiOptions.get('hideDateTimeModeButton');
}
public ngOnInit() {
this.own(
this.timeControl.valueChanges.subscribe(() => {
this.dateTime = this.getValue();
this.callChangeFormatted();
}));
this.own(
this.dateControl.valueChanges.subscribe(() => {
this.dateTime = this.getValue();
this.callChangeFormatted();
}));
}
public writeValue(obj: any) {
try {
this.dateTime = DateTime.parseISO(obj);
this.dateTime = DateTime.parseISO(obj, false);
} catch (ex) {
this.dateTime = null;
}
@ -109,7 +127,7 @@ export class DateTimeEditorComponent extends StatefulControlComponent<{}, string
}
public ngAfterViewInit() {
this.picker = new Pikaday({ field: this.dateInput.nativeElement, format: 'YYYY-MM-DD',
this.picker = new Pikaday({field: this.dateInput.nativeElement, format: 'YYYY-MM-DD',
onSelect: () => {
if (this.suppressEvents) {
return;
@ -125,7 +143,7 @@ export class DateTimeEditorComponent extends StatefulControlComponent<{}, string
}
public writeNow() {
this.writeValue(DateTime.now().toISOString());
this.dateTime = DateTime.now();
this.updateControls();
this.callChangeFormatted();
@ -138,58 +156,55 @@ export class DateTimeEditorComponent extends StatefulControlComponent<{}, string
this.dateTime = null;
this.updateControls();
this.callChange(null);
this.callChangeFormatted();
this.callTouched();
return false;
}
private callChangeFormatted() {
this.callChange(this.getValue());
this.callChange(this.dateTime?.toISOString());
}
private getValue(): string | null {
private getValue(): DateTime | null {
if (!this.dateControl.value) {
return null;
}
let result: string | null = null;
if (this.showTime && this.timeControl.value) {
if (this.isDateTimeMode && this.timeControl.value) {
const combined = `${this.dateControl.value}T${this.timeControl.value}`;
const parsed = DateTime.tryParseISO(combined, true);
if (parsed) {
result = parsed.toISOString();
}
}
if (!result) {
const parsed = DateTime.tryParseISO(this.dateControl.value, true);
if (parsed) {
result = parsed.toISOString();
}
return DateTime.tryParseISO(combined, !this.isLocalMode);
}
return result;
return DateTime.tryParseISO(this.dateControl.value);
}
private updateControls() {
this.suppressEvents = true;
if (this.dateTime && this.mode === 'DateTime') {
this.timeControl.setValue(this.dateTime.toStringFormatUTC('HH:mm:ss'), NO_EMIT);
if (this.dateTime && this.isDateTimeMode) {
if (this.isLocalMode) {
this.timeControl.setValue(this.dateTime.toStringFormat('HH:mm:ss'), NO_EMIT);
} else {
this.timeControl.setValue(this.dateTime.toStringFormatUTC('HH:mm:ss'), NO_EMIT);
}
} else {
this.timeControl.setValue(null, NO_EMIT);
}
if (this.dateTime && this.picker) {
const dateString = this.dateTime.toStringFormatUTC('yyyy-MM-dd');
let dateString: string;
if (this.isDateTimeMode && this.isLocalMode) {
dateString = this.dateTime.toStringFormat('yyyy-MM-dd');
this.picker.setDate(DateHelper.getLocalDate(this.dateTime.raw), true);
} else {
dateString = this.dateTime.toStringFormatUTC('yyyy-MM-dd');
this.picker.setDate(DateHelper.getUTCDate(this.dateTime.raw), true);
this.picker.setDate(DateHelper.getUTCDate(this.dateTime.raw), true);
}
this.dateControl.setValue(dateString, NO_EMIT);
} else {
@ -198,4 +213,14 @@ export class DateTimeEditorComponent extends StatefulControlComponent<{}, string
this.suppressEvents = false;
}
public setLocalMode(isLocalMode: boolean) {
this.isLocalMode = isLocalMode;
this.updateControls();
}
public setCompact(isCompact: boolean) {
this.next(s => ({ ...s, isCompact }));
}
}

6
frontend/app/framework/utils/date-time.ts

@ -5,7 +5,7 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { addDays, addHours, addMilliseconds, addMinutes, addMonths, addSeconds, addYears, format, formatDistanceToNow, parse, parseISO, startOfDay, startOfMonth, startOfTomorrow, startOfWeek, startOfYesterday } from 'date-fns';
import { addDays, addHours, addMilliseconds, addMinutes, addMonths, addSeconds, addYears, format, formatDistanceToNow, formatISO, parse, parseISO, startOfDay, startOfMonth, startOfTomorrow, startOfWeek, startOfYesterday } from 'date-fns';
import { DateHelper } from './date-helper';
const DATE_FORMAT = 'yyyy-MM-dd';
@ -177,6 +177,10 @@ export class DateTime {
return format(this.value, DATE_FORMAT);
}
public toISODateTime(): string {
return formatISO(this.value);
}
public toStringFormat(pattern: string): string {
return format(this.value, pattern);
}

6
frontend/app/shared/state/contents.forms.spec.ts

@ -250,19 +250,19 @@ describe('DateTimeField', () => {
it('should format old format to date', () => {
const dateField = createField({ properties: createProperties('DateTime', { editor: 'Date' }) });
expect(FieldFormatter.format(dateField, '2017-12-12')).toBe('2017-12-12');
expect(FieldFormatter.format(dateField, '2017-12-12')).toBe('12/12/2017');
});
it('should format to date', () => {
const dateField = createField({ properties: createProperties('DateTime', { editor: 'Date' }) });
expect(FieldFormatter.format(dateField, '2017-12-12T16:00:00Z')).toBe('2017-12-12');
expect(FieldFormatter.format(dateField, '2017-12-12T16:00:00Z')).toBe('12/12/2017');
});
it('should format to date time', () => {
const field2 = createField({ properties: createProperties('DateTime', { editor: 'DateTime' }) });
expect(FieldFormatter.format(field2, '2017-12-12T16:00:00Z')).toBe('2017-12-12 16:00:00');
expect(FieldFormatter.format(field2, '2017-12-12T16:00:00Z')).toBe('12/12/2017, 4:00:00 PM');
});
it('should return default for DateFieldProperties', () => {

4
frontend/app/shared/state/contents.forms.visitors.ts

@ -130,9 +130,9 @@ export class FieldFormatter implements FieldPropertiesVisitor<FieldValue> {
const parsed = DateTime.parseISO(this.value);
if (properties.editor === 'Date') {
return parsed.toStringFormatUTC('yyyy-MM-dd');
return parsed.toStringFormat('P');
} else {
return parsed.toStringFormatUTC('yyyy-MM-dd HH:mm:ss');
return parsed.toStringFormat('Ppp');
}
} catch (ex) {
return this.value;

Loading…
Cancel
Save