diff --git a/src/app/components/admin/datetime-templates/datetime-templates.component.html b/src/app/components/admin/datetime-templates/datetime-templates.component.html index df6f4dca2..72aefca0a 100644 --- a/src/app/components/admin/datetime-templates/datetime-templates.component.html +++ b/src/app/components/admin/datetime-templates/datetime-templates.component.html @@ -3,22 +3,17 @@

DateTime Examples

- - - + DateTime Examples

baw-datetime

- Date & Time Instants:
+ Date & Time Instants: -
<baw-datetime [value]="fakeUserDate" />
+
<baw-datetime [value]="fakeDateWithZone" />
- +
- Date Instants:
+ Date Instants: -
<baw-datetime [value]="fakeUserDate" dateOnly />
+
<baw-datetime [value]="fakeDateWithZone" dateOnly />
- +
- Time Instants:
+ Time Instants: -
<baw-datetime [value]="fakeUserDate" timeOnly />
+
<baw-datetime [value]="fakeDateWithZone" timeOnly />
- +
@@ -80,7 +75,7 @@

baw-datetime

baw-zoned-datetime (Implicit)

- Zoned Date & Time Instants:
+ Zoned Date & Time Instants:
<baw-zoned-datetime [value]="fakeDateWithZone" />
@@ -90,7 +85,7 @@

baw-zoned-datetime (Implicit)

- Zoned Date & Time Instants:
+ Zoned Date & Time Instants:
<baw-zoned-datetime [value]="fakeDateWithZone" dateOnly />
@@ -100,7 +95,7 @@

baw-zoned-datetime (Implicit)

- Zoned Date & Time Instants:
+ Zoned Date & Time Instants:
<baw-zoned-datetime [value]="fakeDateWithZone" timeOnly />
@@ -112,7 +107,7 @@

baw-zoned-datetime (Implicit)

baw-zoned-datetime (Explicit)

- Zoned Date & Time Instants:
+ Zoned Date & Time Instants:
<baw-zoned-datetime [value]="fakeDate" [timezone]="fakeTimezone" />
@@ -122,7 +117,7 @@

baw-zoned-datetime (Explicit)

- Zoned Date Instants:
+ Zoned Date Instants:
<baw-zoned-datetime [value]="fakeDate" [timezone]="fakeTimezone" dateOnly />
@@ -136,7 +131,7 @@

baw-zoned-datetime (Explicit)

- Zoned Time Instants:
+ Zoned Time Instants:
<baw-zoned-datetime [value]="fakeDate" [timezone]="fakeTimezone" timeOnly />
@@ -154,7 +149,7 @@

baw-zoned-datetime (Explicit)

baw-duration

- Durations (sexagesimal):
+ Durations (sexagesimal):
<baw-duration [value]="fakeDuration" />
@@ -164,7 +159,7 @@

baw-duration

- Duration (ISO 8601):
+ Duration (ISO 8601):
<baw-duration [value]="fakeDuration" iso8601 />
@@ -174,7 +169,7 @@

baw-duration

- Duration (Humanized):
+ Duration (Humanized):
<baw-duration [value]="fakeDuration" humanized />
@@ -188,7 +183,7 @@

baw-duration

baw-time-since

- Relative Date & Time Instants:
+ Relative Date & Time Instants:
<baw-time-since [value]="fakeDate" />
diff --git a/src/app/components/admin/datetime-templates/datetime-templates.component.ts b/src/app/components/admin/datetime-templates/datetime-templates.component.ts index 94527e1e1..76695b70a 100644 --- a/src/app/components/admin/datetime-templates/datetime-templates.component.ts +++ b/src/app/components/admin/datetime-templates/datetime-templates.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from "@angular/core"; +import { Component } from "@angular/core"; import { List } from "immutable"; import { PageComponent } from "@helpers/page/pageComponent"; import { DateTime, Duration } from "luxon"; @@ -9,28 +9,21 @@ import { adminMenuItemActions } from "../dashboard/dashboard.component"; selector: "baw-admin-datetime-templates", templateUrl: "datetime-templates.component.html", }) -class DateTimeExampleComponent extends PageComponent implements OnInit { +class DateTimeExampleComponent extends PageComponent { public constructor() { super(); } protected fakeTimezone = "Australia/Perth"; - protected fakeDate; - protected fakeDuration; + protected fakeDate = DateTime.now(); + protected fakeDuration = Duration.fromObject({ hours: 1, minutes: 30 }); - public ngOnInit(): void { - this.fakeDate = DateTime.now(); - this.fakeDuration = Duration.fromObject({ hours: 1, minutes: 30 }); - } - - protected get fakeUserDate(): DateTime { - const luxonDateTime = this.fakeDate instanceof DateTime ? this.fakeDate : DateTime.fromISO(this.fakeDate); - return luxonDateTime; - } - - // used for implicit timezone tests protected get fakeDateWithZone(): DateTime { - const luxonDateTime = this.fakeDate instanceof DateTime ? this.fakeDate : DateTime.fromISO(this.fakeDate); + const luxonDateTime = + this.fakeDate instanceof DateTime + ? this.fakeDate + : DateTime.fromISO(this.fakeDate); + return luxonDateTime.setZone(this.fakeTimezone, { keepLocalTime: true }); } @@ -45,12 +38,17 @@ class DateTimeExampleComponent extends PageComponent implements OnInit { protected updateFakeDate(event: any): void { const inputValue: string = event.target.value; - const newDate = DateTime.fromISO(inputValue).setZone(this.fakeTimezone, { keepLocalTime: true }); + const newDate = DateTime.fromFormat(inputValue, "yyyy-MM-dd HH:mm:ss"); if (newDate.isValid) { this.fakeDate = newDate; } } + + // used in the date/time input + protected formatDate(dateTime: DateTime): string { + return dateTime.toFormat("yyyy-MM-dd HH:mm:ss"); + } } DateTimeExampleComponent.linkToRoute({ diff --git a/src/app/components/shared/datetime/abstract-datetime.component.spec.ts b/src/app/components/shared/datetime/abstract-datetime.component.spec.ts new file mode 100644 index 000000000..1fa7d03f5 --- /dev/null +++ b/src/app/components/shared/datetime/abstract-datetime.component.spec.ts @@ -0,0 +1,23 @@ +import { AbstractDatetimeComponent } from "./abstract-datetime.component"; + +describe("AbstractDatetimeComponent", () => { + let abstractDatetimeComponent: AbstractDatetimeComponent; + + beforeEach(() => { + abstractDatetimeComponent = new AbstractDatetimeComponentMock(); + }); + + it("should create", () => { + expect(abstractDatetimeComponent).toBeTruthy(); + }); + + it("should have a default prefix of an empty string", () => { + expect(abstractDatetimeComponent.suffix()).toEqual(""); + }); +}); + +class AbstractDatetimeComponentMock extends AbstractDatetimeComponent { + public formattedValue = () => ""; + public tooltipValue = () => ""; + public rawDateTime = () => ""; +} diff --git a/src/app/components/shared/datetime/datetime/datetime.component.spec.ts b/src/app/components/shared/datetime/datetime/datetime.component.spec.ts index 27baacccc..011f079a6 100644 --- a/src/app/components/shared/datetime/datetime/datetime.component.spec.ts +++ b/src/app/components/shared/datetime/datetime/datetime.component.spec.ts @@ -14,13 +14,13 @@ describe("DatetimeComponent", () => { }); function setup(): void { - // to ensure that tests pass for all timezones, we set the default timezone to utc - // this will ensure that all DateTime objects are created in utc instead of the test runners local timezone + // to ensure that tests pass for all timezones, we set the default timezone to Australia/Perth + // this mocks the test runners timezone to be Australia/Perth so that they will assert correctly in all timezones Settings.defaultZone = "Australia/Perth"; - // utc datetime should be localised to "Australia/Perth" + // utc datetime should be localized to "Australia/Perth" let fakeDateTime = DateTime.fromISO("2020-01-01T12:10:11.000Z"); - fakeDateTime = fakeDateTime.setZone("utc"); + fakeDateTime = fakeDateTime.toUTC(); spectator = createComponent({ detectChanges: false }); spectator.component.value = fakeDateTime; @@ -36,6 +36,9 @@ describe("DatetimeComponent", () => { expect(spectator.component).toBeInstanceOf(DatetimeComponent); }); + // the output time should be localized to the users timezone + // eg. Because this fake DateTime was created using UTC+0, it should be localized to UTC+8 + // adding 8 hours to the UTC time it("should display the full date and time in the users local timezone by default", () => { const fakeDateTime = DateTime.fromISO("2020-01-01T12:10:11.000Z"); const expectedDateTime = "2020-01-01 20:10:11"; @@ -81,17 +84,6 @@ describe("DatetimeComponent", () => { expect(spectator.element).toHaveExactTrimmedText(expectedDateTime); }); - it("should show a full utc date time in the users timezone", () => { - const fakeDateTime = DateTime.fromISO("2020-01-01T12:10:11.000Z"); - const expectedDateTime = "2020-01-01 20:10:11"; - - spectator.component.value = fakeDateTime; - - spectator.detectChanges(); - - expect(spectator.element).toHaveExactTrimmedText(expectedDateTime); - }); - it("should have a tooltip that displays the full un-localized date, time and utc offset for a JavaScript date object", () => { const expectedTooltip = "2020-01-01 20:10:11 (Australia/Perth UTC+08:00)"; const mockDate = new Date("2020-01-01T12:10:11.000Z"); @@ -112,7 +104,7 @@ describe("DatetimeComponent", () => { assertTooltip(componentElement(), expectedTooltip); }); - it("should localize an iso8601 date/time with an offset to the users local timezone", () => { + it("should localize an iso8601 date/time with an offset", () => { const expectedDateTime = "2020-01-01 19:10:11"; const mockDateTime = new Date("2020-01-01T12:10:11.000+01:00"); @@ -122,7 +114,7 @@ describe("DatetimeComponent", () => { expect(spectator.element).toHaveExactTrimmedText(expectedDateTime); }); - it("should localize a Luxon DateTime object with a timezone offset to the users local timezone", () => { + it("should localize a Luxon DateTime object with a timezone offset", () => { const expectedDateTime = "2020-01-01 19:10:11"; const mockDateTime = DateTime.fromISO("2020-01-01T12:10:11.000+01:00"); @@ -133,8 +125,8 @@ describe("DatetimeComponent", () => { }); it("should have the correct tooltip for a Luxon DateTime object with an offset, but no timezone", () => { - // since tooltips should always be localized to the users timezone, we know that the tooltip should - // include timezone information + // since tooltips should always be localized to the users timezone, we know the users timezone name + // so unlike the zoned-datetime component, the tooltip should include timezone information const expectedTooltip = "2020-01-01 19:10:11 (Australia/Perth UTC+08:00)"; const mockDateTime = new Date("2020-01-01T12:10:11.000+01:00"); diff --git a/src/app/components/shared/datetime/duration/duration.component.ts b/src/app/components/shared/datetime/duration/duration.component.ts index a70afdd20..d0d50ebda 100644 --- a/src/app/components/shared/datetime/duration/duration.component.ts +++ b/src/app/components/shared/datetime/duration/duration.component.ts @@ -19,7 +19,7 @@ export class DurationComponent extends AbstractDatetimeComponent { @Input() public iso8601: string | boolean; public formattedValue(): string { - // we use isInstantiated() to check if the humanized attribute is set (empty) + // we use isInstantiated() to check if the humanized attribute is set // and we also check that it is not set to false // by allowing boolean values, we support reactive formats (e.g. [humanized]="isHumanized") if (isInstantiated(this.humanized) && this.humanized !== false) { diff --git a/src/app/components/shared/datetime/time-since/time-since.component.spec.ts b/src/app/components/shared/datetime/time-since/time-since.component.spec.ts index 990e06094..9d060f042 100644 --- a/src/app/components/shared/datetime/time-since/time-since.component.spec.ts +++ b/src/app/components/shared/datetime/time-since/time-since.component.spec.ts @@ -47,7 +47,7 @@ describe("RelativeTimeComponent", () => { expect(spectator.element).toHaveExactTrimmedText(expectedText); }); - it("should not emit seconds or miliseconds in the formatted text", () => { + it("should not emit seconds or milliseconds in the formatted text", () => { const expectedText = "4 hours 42 minutes ago"; const fakeDuration = Duration.fromObject({ hours: 4, @@ -62,7 +62,7 @@ describe("RelativeTimeComponent", () => { expect(spectator.element).toHaveExactTrimmedText(expectedText); }); - it("should round up times to the nearest second", () => { + it("should round up times to the nearest minute", () => { const expectedText = "4 hours 42 minutes ago"; const fakeDuration = Duration.fromObject({ hours: 4, diff --git a/src/app/components/shared/datetime/zoned-datetime/zoned-datetime.component.spec.ts b/src/app/components/shared/datetime/zoned-datetime/zoned-datetime.component.spec.ts index b8e3f3323..1d814fb26 100644 --- a/src/app/components/shared/datetime/zoned-datetime/zoned-datetime.component.spec.ts +++ b/src/app/components/shared/datetime/zoned-datetime/zoned-datetime.component.spec.ts @@ -59,7 +59,9 @@ describe("ZonedDateTimeComponent", () => { const expectedTooltip = "2020-01-01 12:10:11 (Australia/Darwin UTC+09:30)"; // set the implicit timezone - mockDateTime = mockDateTime.setZone("utc"); + mockDateTime = mockDateTime.setZone("Australia/Brisbane", { + keepLocalTime: true, + }); // set the explicit timezone to UTC+08:00 spectator.component.timezone = "Australia/Darwin"; // UTC+09:30 @@ -91,6 +93,7 @@ describe("ZonedDateTimeComponent", () => { testDateTime = testDateTime.setZone(implicitTimezone, { keepLocalTime: true }); spectator.component.timezone = undefined; spectator.component.value = testDateTime; + spectator.component.ngOnChanges(); spectator.detectChanges(); @@ -106,7 +109,9 @@ describe("ZonedDateTimeComponent", () => { let implicitDateTime = DateTime.fromISO("2020-01-01T12:10:11.000Z"); // don't recalculate the time by using keepLocalTime: true - implicitDateTime = implicitDateTime.setZone(implicitTimezone, { keepLocalTime: true }); + implicitDateTime = implicitDateTime.setZone(implicitTimezone, { + keepLocalTime: true, + }); spectator.component.value = implicitDateTime; spectator.detectChanges(); @@ -130,14 +135,16 @@ describe("ZonedDateTimeComponent", () => { let mockDateTime = DateTime.fromISO("2020-01-01T12:10:11.000Z"); // set the implicit date/time timezone to UTC+09:30 - // we use keepLocalTime = true because otherwise, the time will be localised to Australia/Darwin + // we use keepLocalTime = true because otherwise, the time adjusted and localized to Australia/Darwin // eg. The time will be 12:10:11 + 09:30 = 21:40:11 without us ever localising it + // because we want to check if the component is localising it, we can disable this behavior when creating + // our fake DateTime object mockDateTime = mockDateTime.setZone("Australia/Darwin", { keepLocalTime: true, - }); // utc+09:30 + }); if (!implicitTimezone) { - spectator.component.timezone = "Australia/Darwin"; + spectator.component.timezone = "Australia/Darwin"; // utc+09:30 } spectator.component.value = mockDateTime; @@ -151,13 +158,14 @@ describe("ZonedDateTimeComponent", () => { const expectedDate = "2020-01-01 12:10:11"; // Australia/Darwin is the same timezone used in the explicit and implicit tests + // by using Settings.defaultZone, we mock the runners timezone to Australia/Darwin Settings.defaultZone = "Australia/Darwin"; spectator.detectChanges(); expect(spectator.element).toHaveExactTrimmedText(expectedDate); }); - it("should not localise the date/time to the defined timezone", () => { + it("should not localize the date/time to the defined timezone", () => { const expectedDate = "2020-01-01 12:10:11"; spectator.detectChanges(); @@ -182,7 +190,7 @@ describe("ZonedDateTimeComponent", () => { // if we are using UTC+0, we do not know the timezone, however, we know the offset // we should therefore emit the offset in the tooltip, but not the timezone - it("should have a tooltip that displays the date/time and the timezone of UTC+0", () => { + it("should use the correct tooltip when we know the offset, but not timezone", () => { const expectedTooltip = "2020-01-01 12:10:11 (UTC+00:00)"; spectator.component.timezone = "utc"; @@ -191,13 +199,15 @@ describe("ZonedDateTimeComponent", () => { assertTooltip(componentElement(), expectedTooltip); }); - it("should have a tooltip that displays the date/time and the timezone of UTC+0", () => { - const expectedTooltip = "2020-01-01 12:10:11 (Australia/Darwin UTC+09:30)"; + it("should have a tooltip that displays the date/time and the timezone", () => { + const expectedTooltip = + "2020-01-01 12:10:11 (Australia/Darwin UTC+09:30)"; assertTooltip(componentElement(), expectedTooltip); }); it("should update timezone correctly when assigned a new value", () => { - const expectedTooltip = "2020-01-01 12:10:11 (Australia/Brisbane UTC+10:00)"; + const expectedTooltip = + "2020-01-01 12:10:11 (Australia/Brisbane UTC+10:00)"; spectator.component.timezone = "Australia/Brisbane"; spectator.detectChanges(); diff --git a/src/app/components/shared/datetime/zoned-datetime/zoned-datetime.component.ts b/src/app/components/shared/datetime/zoned-datetime/zoned-datetime.component.ts index f9670dfe0..3fc9064cd 100644 --- a/src/app/components/shared/datetime/zoned-datetime/zoned-datetime.component.ts +++ b/src/app/components/shared/datetime/zoned-datetime/zoned-datetime.component.ts @@ -26,7 +26,7 @@ export class ZonedDateTimeComponent // this component is the same as the datetime component except that it allows // us to explicitly override the timezone set in the Luxon DateTime object - @Input() public override timezone: Zone | TimezoneInformation | string; + @Input() public override timezone?: Zone | TimezoneInformation | string; private isImplicitTimezone = false; // we use ngOnChanges so that the timezone can be reactively implicitly set @@ -38,6 +38,9 @@ export class ZonedDateTimeComponent // if the timezone hasn't been explicitly set in the template (e.g.