22 changed files with 780 additions and 31 deletions
@ -0,0 +1,22 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2023 The Thingsboard Authors |
||||
|
|
||||
|
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
you may not use this file except in compliance with the License. |
||||
|
You may obtain a copy of the License at |
||||
|
|
||||
|
http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
|
||||
|
Unless required by applicable law or agreed to in writing, software |
||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
See the License for the specific language governing permissions and |
||||
|
limitations under the License. |
||||
|
|
||||
|
--> |
||||
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" style="width: 100%;"> |
||||
|
<mat-select [formControl]="cssUnitFormControl"> |
||||
|
<mat-option *ngFor="let cssUnit of cssUnitsList" [value]="cssUnit">{{ cssUnit }}</mat-option> |
||||
|
</mat-select> |
||||
|
</mat-form-field> |
||||
@ -0,0 +1,82 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2023 The Thingsboard Authors
|
||||
|
///
|
||||
|
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
/// you may not use this file except in compliance with the License.
|
||||
|
/// You may obtain a copy of the License at
|
||||
|
///
|
||||
|
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
///
|
||||
|
/// Unless required by applicable law or agreed to in writing, software
|
||||
|
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
|
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
/// See the License for the specific language governing permissions and
|
||||
|
/// limitations under the License.
|
||||
|
///
|
||||
|
|
||||
|
import { Component, forwardRef, Input, OnInit } from '@angular/core'; |
||||
|
import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormControl } from '@angular/forms'; |
||||
|
import { cssUnit, cssUnits } from '@home/components/widget/config/widget-settings.models'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-css-unit-select', |
||||
|
templateUrl: './css-unit-select.component.html', |
||||
|
styleUrls: [], |
||||
|
providers: [ |
||||
|
{ |
||||
|
provide: NG_VALUE_ACCESSOR, |
||||
|
useExisting: forwardRef(() => CssUnitSelectComponent), |
||||
|
multi: true |
||||
|
} |
||||
|
] |
||||
|
}) |
||||
|
export class CssUnitSelectComponent implements OnInit, ControlValueAccessor { |
||||
|
|
||||
|
@Input() |
||||
|
disabled: boolean; |
||||
|
|
||||
|
cssUnitsList = cssUnits; |
||||
|
|
||||
|
cssUnitFormControl: UntypedFormControl; |
||||
|
|
||||
|
modelValue: cssUnit; |
||||
|
|
||||
|
private propagateChange = null; |
||||
|
|
||||
|
constructor() {} |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
this.cssUnitFormControl = new UntypedFormControl(); |
||||
|
this.cssUnitFormControl.valueChanges.subscribe((value: cssUnit) => { |
||||
|
this.updateModel(value); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
registerOnChange(fn: any): void { |
||||
|
this.propagateChange = fn; |
||||
|
} |
||||
|
|
||||
|
registerOnTouched(fn: any): void { |
||||
|
} |
||||
|
|
||||
|
setDisabledState(isDisabled: boolean): void { |
||||
|
this.disabled = isDisabled; |
||||
|
if (this.disabled) { |
||||
|
this.cssUnitFormControl.disable(); |
||||
|
} else { |
||||
|
this.cssUnitFormControl.enable(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
writeValue(value: cssUnit): void { |
||||
|
this.modelValue = value; |
||||
|
this.cssUnitFormControl.patchValue(this.modelValue, {emitEvent: false}); |
||||
|
} |
||||
|
|
||||
|
updateModel(value: cssUnit): void { |
||||
|
if (this.modelValue !== value) { |
||||
|
this.modelValue = value; |
||||
|
this.propagateChange(this.modelValue); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2023 The Thingsboard Authors |
||||
|
|
||||
|
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
you may not use this file except in compliance with the License. |
||||
|
You may obtain a copy of the License at |
||||
|
|
||||
|
http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
|
||||
|
Unless required by applicable law or agreed to in writing, software |
||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
See the License for the specific language governing permissions and |
||||
|
limitations under the License. |
||||
|
|
||||
|
--> |
||||
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" style="width: 100%;"> |
||||
|
<mat-select [formControl]="dateFormatFormControl" [compareWith]="dateFormatsCompare" > |
||||
|
<mat-option *ngFor="let dateFormat of dateFormatList" [value]="dateFormat">{{ dateFormatDisplayValue(dateFormat) }}</mat-option> |
||||
|
</mat-select> |
||||
|
<button #customFormatButton |
||||
|
*ngIf="dateFormatFormControl.value?.custom" matSuffix mat-icon-button (click)="openDateFormatSettingsPopup($event, customFormatButton)"> |
||||
|
<tb-icon>edit</tb-icon> |
||||
|
</button> |
||||
|
</mat-form-field> |
||||
@ -0,0 +1,148 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2023 The Thingsboard Authors
|
||||
|
///
|
||||
|
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
/// you may not use this file except in compliance with the License.
|
||||
|
/// You may obtain a copy of the License at
|
||||
|
///
|
||||
|
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
///
|
||||
|
/// Unless required by applicable law or agreed to in writing, software
|
||||
|
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
|
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
/// See the License for the specific language governing permissions and
|
||||
|
/// limitations under the License.
|
||||
|
///
|
||||
|
|
||||
|
import { Component, forwardRef, Input, OnInit, Renderer2, ViewChild, ViewContainerRef } from '@angular/core'; |
||||
|
import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormControl } from '@angular/forms'; |
||||
|
import { |
||||
|
compareDateFormats, |
||||
|
dateFormats, |
||||
|
DateFormatSettings |
||||
|
} from '@home/components/widget/config/widget-settings.models'; |
||||
|
import { TranslateService } from '@ngx-translate/core'; |
||||
|
import { DatePipe } from '@angular/common'; |
||||
|
import { MatButton } from '@angular/material/button'; |
||||
|
import { TbPopoverService } from '@shared/components/popover.service'; |
||||
|
import { deepClone } from '@core/utils'; |
||||
|
import { |
||||
|
DateFormatSettingsPanelComponent |
||||
|
} from '@home/components/widget/lib/settings/common/date-format-settings-panel.component'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-date-format-select', |
||||
|
templateUrl: './date-format-select.component.html', |
||||
|
styleUrls: [], |
||||
|
providers: [ |
||||
|
{ |
||||
|
provide: NG_VALUE_ACCESSOR, |
||||
|
useExisting: forwardRef(() => DateFormatSelectComponent), |
||||
|
multi: true |
||||
|
} |
||||
|
] |
||||
|
}) |
||||
|
export class DateFormatSelectComponent implements OnInit, ControlValueAccessor { |
||||
|
|
||||
|
@ViewChild('customFormatButton', {static: false}) |
||||
|
customFormatButton: MatButton; |
||||
|
|
||||
|
@Input() |
||||
|
disabled: boolean; |
||||
|
|
||||
|
dateFormatList = dateFormats; |
||||
|
|
||||
|
dateFormatsCompare = compareDateFormats; |
||||
|
|
||||
|
dateFormatFormControl: UntypedFormControl; |
||||
|
|
||||
|
modelValue: DateFormatSettings; |
||||
|
|
||||
|
private propagateChange = null; |
||||
|
|
||||
|
private formatCache: {[format: string]: string} = {}; |
||||
|
|
||||
|
constructor(private translate: TranslateService, |
||||
|
private date: DatePipe, |
||||
|
private popoverService: TbPopoverService, |
||||
|
private renderer: Renderer2, |
||||
|
private viewContainerRef: ViewContainerRef) {} |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
this.dateFormatFormControl = new UntypedFormControl(); |
||||
|
this.dateFormatFormControl.valueChanges.subscribe((value: DateFormatSettings) => { |
||||
|
this.updateModel(value); |
||||
|
if (value?.custom) { |
||||
|
setTimeout(() => { |
||||
|
this.openDateFormatSettingsPopup(null, this.customFormatButton); |
||||
|
}, 0); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
registerOnChange(fn: any): void { |
||||
|
this.propagateChange = fn; |
||||
|
} |
||||
|
|
||||
|
registerOnTouched(fn: any): void { |
||||
|
} |
||||
|
|
||||
|
setDisabledState(isDisabled: boolean): void { |
||||
|
this.disabled = isDisabled; |
||||
|
if (this.disabled) { |
||||
|
this.dateFormatFormControl.disable(); |
||||
|
} else { |
||||
|
this.dateFormatFormControl.enable(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
writeValue(value: DateFormatSettings): void { |
||||
|
this.modelValue = value; |
||||
|
this.dateFormatFormControl.patchValue(this.modelValue, {emitEvent: false}); |
||||
|
} |
||||
|
|
||||
|
updateModel(value: DateFormatSettings): void { |
||||
|
if (!compareDateFormats(this.modelValue, value)) { |
||||
|
this.modelValue = value; |
||||
|
this.propagateChange(this.modelValue); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
dateFormatDisplayValue(value: DateFormatSettings): string { |
||||
|
if (value.custom) { |
||||
|
return this.translate.instant('date.custom-date'); |
||||
|
} else if (value.lastUpdateAgo) { |
||||
|
return this.translate.instant('date.last-update-n-ago'); |
||||
|
} else { |
||||
|
if (!this.formatCache[value.format]) { |
||||
|
this.formatCache[value.format] = this.date.transform(Date.now(), value.format); |
||||
|
} |
||||
|
return this.formatCache[value.format]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
openDateFormatSettingsPopup($event: Event, matButton: MatButton) { |
||||
|
if ($event) { |
||||
|
$event.stopPropagation(); |
||||
|
} |
||||
|
const trigger = matButton._elementRef.nativeElement; |
||||
|
if (this.popoverService.hasPopover(trigger)) { |
||||
|
this.popoverService.hidePopover(trigger); |
||||
|
} else { |
||||
|
const ctx: any = { |
||||
|
dateFormat: deepClone(this.modelValue) |
||||
|
}; |
||||
|
const dateFormatSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer, |
||||
|
this.viewContainerRef, DateFormatSettingsPanelComponent, 'top', true, null, |
||||
|
ctx, |
||||
|
{}, |
||||
|
{}, {}, true); |
||||
|
dateFormatSettingsPanelPopover.tbComponentRef.instance.popover = dateFormatSettingsPanelPopover; |
||||
|
dateFormatSettingsPanelPopover.tbComponentRef.instance.dateFormatApplied.subscribe((dateFormat) => { |
||||
|
dateFormatSettingsPanelPopover.hide(); |
||||
|
this.modelValue = dateFormat; |
||||
|
this.propagateChange(this.modelValue); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,47 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2023 The Thingsboard Authors |
||||
|
|
||||
|
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
you may not use this file except in compliance with the License. |
||||
|
You may obtain a copy of the License at |
||||
|
|
||||
|
http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
|
||||
|
Unless required by applicable law or agreed to in writing, software |
||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
See the License for the specific language governing permissions and |
||||
|
limitations under the License. |
||||
|
|
||||
|
--> |
||||
|
<div class="tb-date-format-settings-panel"> |
||||
|
<div class="tb-date-format-settings-title" translate>date.custom-date</div> |
||||
|
<div class="tb-form-row no-border no-padding"> |
||||
|
<div class="fixed-title-width" translate>date.format</div> |
||||
|
<mat-form-field class="tb-date-format-input" required fxFlex appearance="outline" subscriptSizing="dynamic"> |
||||
|
<input matInput [formControl]="dateFormatFormControl" placeholder="{{ 'widget-config.set' | translate }}"> |
||||
|
<div matSuffix tb-help-popup="date/date-format" [tb-help-popup-style]="{width: '800px'}"></div> |
||||
|
</mat-form-field> |
||||
|
</div> |
||||
|
<mat-divider></mat-divider> |
||||
|
<div class="tb-form-row no-border no-padding date-format-preview"> |
||||
|
<div class="fixed-title-width" translate>date.preview</div> |
||||
|
<div class="preview-text" fxFlex>{{ previewText }}</div> |
||||
|
</div> |
||||
|
<div class="tb-date-format-settings-panel-buttons"> |
||||
|
<button mat-button |
||||
|
color="primary" |
||||
|
type="button" |
||||
|
(click)="cancel()"> |
||||
|
{{ 'action.cancel' | translate }} |
||||
|
</button> |
||||
|
<button mat-raised-button |
||||
|
color="primary" |
||||
|
type="button" |
||||
|
(click)="applyDateFormat()" |
||||
|
[disabled]="dateFormatFormControl.invalid || !dateFormatFormControl.dirty"> |
||||
|
{{ 'action.apply' | translate }} |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
@ -0,0 +1,67 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2023 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
|
||||
|
@import '../../../../../../../../scss/constants'; |
||||
|
|
||||
|
.tb-date-format-settings-panel { |
||||
|
width: 500px; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
gap: 16px; |
||||
|
@media #{$mat-xs} { |
||||
|
width: 90vw; |
||||
|
} |
||||
|
.tb-date-format-settings-title { |
||||
|
font-size: 16px; |
||||
|
font-weight: 500; |
||||
|
line-height: 24px; |
||||
|
letter-spacing: 0.25px; |
||||
|
color: rgba(0, 0, 0, 0.87); |
||||
|
} |
||||
|
.tb-form-row { |
||||
|
.fixed-title-width { |
||||
|
min-width: 120px; |
||||
|
} |
||||
|
&.date-format-preview { |
||||
|
align-items: flex-start; |
||||
|
.preview-text { |
||||
|
font-size: 14px; |
||||
|
font-style: normal; |
||||
|
font-weight: 400; |
||||
|
line-height: 20px; |
||||
|
letter-spacing: 0.2px; |
||||
|
color: rgba(0, 0, 0, 0.38); |
||||
|
} |
||||
|
} |
||||
|
.mat-mdc-form-field.tb-date-format-input { |
||||
|
.mat-mdc-text-field-wrapper.mdc-text-field--outlined { |
||||
|
.mat-mdc-form-field-icon-suffix { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
line-height: normal; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
.tb-date-format-settings-panel-buttons { |
||||
|
height: 60px; |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
gap: 16px; |
||||
|
justify-content: flex-end; |
||||
|
align-items: flex-end; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,70 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2023 The Thingsboard Authors
|
||||
|
///
|
||||
|
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
/// you may not use this file except in compliance with the License.
|
||||
|
/// You may obtain a copy of the License at
|
||||
|
///
|
||||
|
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
///
|
||||
|
/// Unless required by applicable law or agreed to in writing, software
|
||||
|
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
|
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
/// See the License for the specific language governing permissions and
|
||||
|
/// limitations under the License.
|
||||
|
///
|
||||
|
|
||||
|
import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core'; |
||||
|
import { PageComponent } from '@shared/components/page.component'; |
||||
|
import { DateFormatSettings } from '@home/components/widget/config/widget-settings.models'; |
||||
|
import { TbPopoverComponent } from '@shared/components/popover.component'; |
||||
|
import { UntypedFormControl, Validators } from '@angular/forms'; |
||||
|
import { Store } from '@ngrx/store'; |
||||
|
import { AppState } from '@core/core.state'; |
||||
|
import { DatePipe } from '@angular/common'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-date-format-settings-panel', |
||||
|
templateUrl: './date-format-settings-panel.component.html', |
||||
|
providers: [], |
||||
|
styleUrls: ['./date-format-settings-panel.component.scss'], |
||||
|
encapsulation: ViewEncapsulation.None |
||||
|
}) |
||||
|
export class DateFormatSettingsPanelComponent extends PageComponent implements OnInit { |
||||
|
|
||||
|
@Input() |
||||
|
dateFormat: DateFormatSettings; |
||||
|
|
||||
|
@Input() |
||||
|
popover: TbPopoverComponent<DateFormatSettingsPanelComponent>; |
||||
|
|
||||
|
@Output() |
||||
|
dateFormatApplied = new EventEmitter<DateFormatSettings>(); |
||||
|
|
||||
|
dateFormatFormControl: UntypedFormControl; |
||||
|
|
||||
|
previewText = ''; |
||||
|
|
||||
|
constructor(private date: DatePipe, |
||||
|
protected store: Store<AppState>) { |
||||
|
super(store); |
||||
|
} |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
this.dateFormatFormControl = new UntypedFormControl(this.dateFormat.format, [Validators.required]); |
||||
|
this.dateFormatFormControl.valueChanges.subscribe((value: string) => { |
||||
|
this.previewText = this.date.transform(Date.now(), value); |
||||
|
}); |
||||
|
this.previewText = this.date.transform(Date.now(), this.dateFormat.format); |
||||
|
} |
||||
|
|
||||
|
cancel() { |
||||
|
this.popover?.hide(); |
||||
|
} |
||||
|
|
||||
|
applyDateFormat() { |
||||
|
this.dateFormat.format = this.dateFormatFormControl.value; |
||||
|
this.dateFormatApplied.emit(this.dateFormat); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,88 @@ |
|||||
|
#### Pre-defined format options |
||||
|
|
||||
|
| Option | Equivalent to | Examples (given in `en-US` locale) | |
||||
|
|------------|---------------------------------|-----------------------------------------------| |
||||
|
| short | M/d/yy, h:mm a | 6/15/15, 9:03 AM | |
||||
|
| medium | MMM d, y, h:mm:ss a | Jun 15, 2015, 9:03:01 AM | |
||||
|
| long | MMMM d, y, h:mm:ss a z | June 15, 2015 at 9:03:01 AM GMT+1 | |
||||
|
| full | EEEE, MMMM d, y, h:mm:ss a zzzz | Monday, June 15, 2015 at 9:03:01 AM GMT+01:00 | |
||||
|
| shortDate | M/d/yy | 6/15/15 | |
||||
|
| mediumDate | MMM d, y | Jun 15, 2015 | |
||||
|
| longDate | MMMM d, y | June 15, 2015 | |
||||
|
| fullDate | EEEE, MMMM d, y | Monday, June 15, 2015 | |
||||
|
| shortTime | h:mm a | 9:03 AM | |
||||
|
| mediumTime | h:mm:ss a | 9:03:01 AM | |
||||
|
| longTime | h:mm:ss a z | 9:03:01 AM GMT+1 | |
||||
|
| fullTime | h:mm:ss a zzzz | 9:03:01 AM GMT+01:00 | |
||||
|
|
||||
|
#### Custom format options |
||||
|
|
||||
|
You can construct a format string using symbols to specify the components |
||||
|
of a date-time value, as described in the following table. |
||||
|
Format details depend on the locale. |
||||
|
Fields marked with (*) are only available in the extra data set for the given locale. |
||||
|
|
||||
|
| Field type | Format | Description | Example Value | |
||||
|
|---------------------|-------------|--------------------------------------------------------------|------------------------------------------------------------| |
||||
|
| Era | G, GG & GGG | Abbreviated | AD | |
||||
|
| | GGGG | Wide | Anno Domini | |
||||
|
| | GGGGG | Narrow | A | |
||||
|
| Year | y | Numeric: minimum digits | 2, 20, 201, 2017, 20173 | |
||||
|
| | yy | Numeric: 2 digits + zero padded | 02, 20, 01, 17, 73 | |
||||
|
| | yyy | Numeric: 3 digits + zero padded | 002, 020, 201, 2017, 20173 | |
||||
|
| | yyyy | Numeric: 4 digits or more + zero padded | 0002, 0020, 0201, 2017, 20173 | |
||||
|
| Week-numbering year | Y | Numeric: minimum digits | 2, 20, 201, 2017, 20173 | |
||||
|
| | YY | Numeric: 2 digits + zero padded | 02, 20, 01, 17, 73 | |
||||
|
| | YYY | Numeric: 3 digits + zero padded | 002, 020, 201, 2017, 20173 | |
||||
|
| | YYYY | Numeric: 4 digits or more + zero padded | 0002, 0020, 0201, 2017, 20173 | |
||||
|
| Month | M | Numeric: 1 digit | 9, 12 | |
||||
|
| | MM | Numeric: 2 digits + zero padded | 09, 12 | |
||||
|
| | MMM | Abbreviated | Sep | |
||||
|
| | MMMM | Wide | September | |
||||
|
| | MMMMM | Narrow | S | |
||||
|
| Month standalone | L | Numeric: 1 digit | 9, 12 | |
||||
|
| | LL | Numeric: 2 digits + zero padded | 09, 12 | |
||||
|
| | LLL | Abbreviated | Sep | |
||||
|
| | LLLL | Wide | September | |
||||
|
| | LLLLL | Narrow | S | |
||||
|
| Week of year | w | Numeric: minimum digits | 1... 53 | |
||||
|
| | ww | Numeric: 2 digits + zero padded | 01... 53 | |
||||
|
| Week of month | W | Numeric: 1 digit | 1... 5 | |
||||
|
| Day of month | d | Numeric: minimum digits | 1 | |
||||
|
| | dd | Numeric: 2 digits + zero padded | 01 | |
||||
|
| Week day | E, EE & EEE | Abbreviated | Tue | |
||||
|
| | EEEE | Wide | Tuesday | |
||||
|
| | EEEEE | Narrow | T | |
||||
|
| | EEEEEE | Short | Tu | |
||||
|
| Week day standalone | c, cc | Numeric: 1 digit | 2 | |
||||
|
| | ccc | Abbreviated | Tue | |
||||
|
| | cccc | Wide | Tuesday | |
||||
|
| | ccccc | Narrow | T | |
||||
|
| | cccccc | Short | Tu | |
||||
|
| Period | a, aa & aaa | Abbreviated | am/pm or AM/PM | |
||||
|
| | aaaa | Wide (fallback to `a` when missing) | ante meridiem/post meridiem | |
||||
|
| | aaaaa | Narrow | a/p | |
||||
|
| Period* | B, BB & BBB | Abbreviated | mid. | |
||||
|
| | BBBB | Wide | am, pm, midnight, noon, morning, afternoon, evening, night | |
||||
|
| | BBBBB | Narrow | md | |
||||
|
| Period standalone* | b, bb & bbb | Abbreviated | mid. | |
||||
|
| | bbbb | Wide | am, pm, midnight, noon, morning, afternoon, evening, night | |
||||
|
| | bbbbb | Narrow | md | |
||||
|
| Hour 1-12 | h | Numeric: minimum digits | 1, 12 | |
||||
|
| | hh | Numeric: 2 digits + zero padded | 01, 12 | |
||||
|
| Hour 0-23 | H | Numeric: minimum digits | 0, 23 | |
||||
|
| | HH | Numeric: 2 digits + zero padded | 00, 23 | |
||||
|
| Minute | m | Numeric: minimum digits | 8, 59 | |
||||
|
| | mm | Numeric: 2 digits + zero padded | 08, 59 | |
||||
|
| Second | s | Numeric: minimum digits | 0... 59 | |
||||
|
| | ss | Numeric: 2 digits + zero padded | 00... 59 | |
||||
|
| Fractional seconds | S | Numeric: 1 digit | 0... 9 | |
||||
|
| | SS | Numeric: 2 digits + zero padded | 00... 99 | |
||||
|
| | SSS | Numeric: 3 digits + zero padded (= milliseconds) | 000... 999 | |
||||
|
| Zone | z, zz & zzz | Short specific non location format (fallback to O) | GMT-8 | |
||||
|
| | zzzz | Long specific non location format (fallback to OOOO) | GMT-08:00 | |
||||
|
| | Z, ZZ & ZZZ | ISO8601 basic format | -0800 | |
||||
|
| | ZZZZ | Long localized GMT format | GMT-8:00 | |
||||
|
| | ZZZZZ | ISO8601 extended format + Z indicator for offset 0 (= XXXXX) | -08:00 | |
||||
|
| | O, OO & OOO | Short localized GMT format | GMT-8 | |
||||
|
| | OOOO | Long localized GMT format | GMT-08:00 | |
||||
Loading…
Reference in new issue