Skip to content

Commit

Permalink
Refactor tooltip timezone methods
Browse files Browse the repository at this point in the history
  • Loading branch information
hudson-newey committed Dec 13, 2023
1 parent ada17ec commit 62ded06
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 75 deletions.
99 changes: 38 additions & 61 deletions src/app/components/shared/datetime/datetime/datetime.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,74 +39,15 @@ export class DatetimeComponent extends AbstractDatetimeComponent {
}

public update(): void {
this.formattedValue = this.luxonDateTime.toFormat(this.dateTimeFormat());

const fullDateTime = this.luxonDateTime.toFormat("yyyy-MM-dd HH:mm:ss");
const timezoneName = this.timezoneInformation();
const timezoneName = this.longTimezone();

// we do not place the timezoneName in brackets as the ngx-bootstrap tooltip
// is an "absolute" width. This means that the brackets will be split over multiple lines
// degrading the readability of the tooltip and user experience
this.tooltipValue = `${fullDateTime} ${timezoneName}`;

this.rawDateTime = this.luxonDateTime.toISO();
}

protected timezoneInformation(): string {
let timezoneName: string;
let offsetValue: string;

switch (typeof this.timezone) {
// if the timezone is undefined, the component is displaying an instant (user localized time)
// we should therefore return the users timezone name
case "undefined": {
const userDateTime = DateTime.local();

timezoneName = userDateTime.zoneName;
offsetValue = userDateTime.zone.formatOffset(
userDateTime.offset,
"short"
);
break;
}

case "string": {
const dateTime = this.luxonDateTime.setZone(this.timezone);

timezoneName = dateTime.zoneName;
offsetValue = dateTime.zone.formatOffset(dateTime.offset, "short");
break;
}

// the object type can either be a Zone or a TimezoneInformation object
case "object": {
if (this.timezone instanceof Zone) {
timezoneName = this.timezone.name;
offsetValue = this.timezone.formatOffset(
this.luxonDateTime.offset,
"short"
);
break;
} else {
// since we have exhausted all other types for the timezone variable, we can assume that its a custom
// timezone information object
const zoneObject = IANAZone.create(this.timezone.identifier);

timezoneName = zoneObject.name;
offsetValue = zoneObject.formatOffset(
this.luxonDateTime.offset,
"short"
);
}
}
}

// if we don't know the timezone name, timezoneName will be undefined, and we should just return the offset
if (!timezoneName || !this.isIanaTimezone(timezoneName)) {
return offsetValue;
}

return `${timezoneName} ${offsetValue}`;
this.formattedValue = this.luxonDateTime.toFormat(this.dateTimeFormat());
}

protected dateTimeFormat(): string {
Expand Down Expand Up @@ -138,6 +79,42 @@ export class DatetimeComponent extends AbstractDatetimeComponent {
return "yyyy-MM-dd HH:mm:ss";
}

// the longTimezone() method returns the timezone name and offset
// eg. "Australia/Perth +08:00"
// we use this method to display the full timezone name and offset in the tooltip
private longTimezone(): string {
let zoneObject: Zone;

// if the timezone is not set (either implicitly or explicitly)
// the datetime will be localized to the users local timezone
if (typeof this.timezone === "undefined") {
zoneObject = DateTime.local().zone;
} else {
zoneObject =
this.timezone instanceof Zone
? this.timezone
: IANAZone.create(this.timezone["identifier"] ?? this.timezone);
}

const timezoneName = zoneObject.name;
const offsetValue = zoneObject.formatOffset(
this.luxonDateTime.offset,
"short"
);

// if we don't know the timezone name, timezoneName will be undefined, and we should just return the offset
// this can happen because valid IANAZone objects can be created from offsets
// eg. [timezone]="utc+08:00" will create a valid IANAZone object with the timezone.name property name "utc+08:00"
// as this is not an actual timezone name (and is actually an offset), we should not emit it in the
// in place of the timezone name
if (!timezoneName || !this.isIanaTimezone(timezoneName)) {
return offsetValue;
}

return `${timezoneName} ${offsetValue}`;
}


// it is possible to provide a offset as Luxon Zone (not an IANA timezone)
// see: https://github.com/moment/luxon/blob/3125686af82d9a25c7267a1cf1eb838a3d41144f/docs/zones.md?plain=1#L55-L58
// this is an incorrect timezoneName, as it is an offset, not timezone name. We should therefore omit the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,20 @@ export class ZonedDateTimeComponent
@Input() public override timezone?: Zone | TimezoneInformation | string;
private isImplicitTimezone = false;

protected override get luxonDateTime(): DateTime {
const luxonDate: DateTime =
this.value instanceof Date ? DateTime.fromJSDate(this.value) : this.value;

if (!this.isImplicitTimezone) {
return luxonDate.setZone(this.timezone["identifier"] ?? this.timezone, {
keepLocalTime: false,
});
}

// when compared to the datetime component, we do not localize the date/time to the users timezone
return luxonDate;
}

// we use ngOnChanges so that the timezone can be reactively implicitly set
public ngOnChanges(): void {
// because we want to get the luxon date/time without removing localization date
Expand Down Expand Up @@ -59,18 +73,4 @@ export class ZonedDateTimeComponent
// we can reuse the logic for updating the component fields
super.ngOnChanges();
}

protected override get luxonDateTime(): DateTime {
const luxonDate: DateTime =
this.value instanceof Date ? DateTime.fromJSDate(this.value) : this.value;

if (!this.isImplicitTimezone) {
return luxonDate.setZone(this.timezone["identifier"] ?? this.timezone, {
keepLocalTime: false,
});
}

// when compared to the datetime component, we do not localize the date/time to the users timezone
return luxonDate;
}
}

0 comments on commit 62ded06

Please sign in to comment.