From c5cb196aa15521fdd760f345f9f0ecb68cdd627e Mon Sep 17 00:00:00 2001 From: Tom Carpenter Date: Thu, 12 Mar 2026 07:40:50 +0000 Subject: [PATCH] Use whole months for "Last 12 Months" on Energy Date Picker (#30091) * Make "now-12m" use month boundaries Currently the "Last 12 Months" date function calculates precisely 12 months from the current day. So today is March 10, then it would retrieve April 11 to March 10. However logically the name implies whole months - i.e. "now" would be "March", so the last 12 months ought to be Apr 1 to March 31. * Let energy date picker detect 12 Month range With now-12m changed to mean whole months rather than partial months, we can make the energy date picker nicely detect and render any month range, and also fix the now button so that it can pick a "now-12m" range. * Use Short Month for Displayed Range To avoid making the displayed range too large to fit on small screens. * Move subMonths into calcDate fn not input Perform both startOfMonth and subMonths as a custom function in calcDate rather than performing subMonths on the input to avoid any wierd timezone issues. Co-authored-by: Petar Petrov --------- Co-authored-by: Petar Petrov --- src/common/datetime/calc_date_range.ts | 9 +- src/common/datetime/format_date.ts | 15 +++ .../components/hui-energy-period-selector.ts | 125 ++++++++++++------ 3 files changed, 110 insertions(+), 39 deletions(-) diff --git a/src/common/datetime/calc_date_range.ts b/src/common/datetime/calc_date_range.ts index 1d7580cae6..c87a1b8976 100644 --- a/src/common/datetime/calc_date_range.ts +++ b/src/common/datetime/calc_date_range.ts @@ -93,8 +93,13 @@ export const calcDateRange = ( ]; case "now-12m": return [ - calcDate(today, subMonths, hass.locale, hass.config, 12), - calcDate(today, subMonths, hass.locale, hass.config, 0), + calcDate( + today, + (date) => subMonths(startOfMonth(date), 11), + hass.locale, + hass.config + ), + calcDate(today, endOfMonth, hass.locale, hass.config), ]; case "now-1h": return [ diff --git a/src/common/datetime/format_date.ts b/src/common/datetime/format_date.ts index aeed82b33b..7f8e331ad8 100644 --- a/src/common/datetime/format_date.ts +++ b/src/common/datetime/format_date.ts @@ -166,6 +166,21 @@ const formatDateMonthMem = memoizeOne( }) ); +// Aug +export const formatDateMonthShort = ( + dateObj: Date, + locale: FrontendLocaleData, + config: HassConfig +) => formatDateMonthShortMem(locale, config.time_zone).format(dateObj); + +const formatDateMonthShortMem = memoizeOne( + (locale: FrontendLocaleData, serverTimeZone: string) => + new Intl.DateTimeFormat(locale.language, { + month: "short", + timeZone: resolveTimeZone(locale.time_zone, serverTimeZone), + }) +); + // 2021 export const formatDateYear = ( dateObj: Date, diff --git a/src/panels/lovelace/components/hui-energy-period-selector.ts b/src/panels/lovelace/components/hui-energy-period-selector.ts index 477c847917..d4547560c4 100644 --- a/src/panels/lovelace/components/hui-energy-period-selector.ts +++ b/src/panels/lovelace/components/hui-energy-period-selector.ts @@ -8,17 +8,21 @@ import { mdiHomeClock, } from "@mdi/js"; import { + differenceInCalendarMonths, differenceInCalendarYears, differenceInDays, differenceInMonths, endOfDay, + endOfMonth, endOfToday, endOfWeek, isFirstDayOfMonth, isLastDayOfMonth, startOfDay, + startOfMonth, startOfWeek, subDays, + subMonths, } from "date-fns"; import type { UnsubscribeFunc } from "home-assistant-js-websocket"; import type { PropertyValues } from "lit"; @@ -39,6 +43,7 @@ import { calcDateRange } from "../../../common/datetime/calc_date_range"; import { firstWeekdayIndex } from "../../../common/datetime/first_weekday"; import { formatDateMonth, + formatDateMonthShort, formatDateVeryShort, formatDateYear, } from "../../../common/datetime/format_date"; @@ -286,27 +291,39 @@ export class HuiEnergyPeriodSelector extends SubscribeMixin(LitElement) { this.hass.locale, this.hass.config )}` - : html`${simpleRange === "month" - ? html`${formatDateMonth( + : html`${simpleRange === "12month" || + simpleRange === "months" || + simpleRange === "quarter" + ? html`${formatDateMonthShort( this._startDate, this.hass.locale, this.hass.config + )}–${formatDateMonthShort( + this._endDate || new Date(), + this.hass.locale, + this.hass.config )}` - : simpleRange === "day" - ? html`${formatDateVeryShort( - this._startDate, - this.hass.locale, - this.hass.config - )}` - : html`${formatDateVeryShort( - this._startDate, - this.hass.locale, - this.hass.config - )}–${formatDateVeryShort( - this._endDate || new Date(), - this.hass.locale, - this.hass.config - )}`}`} + : html`${simpleRange === "month" + ? html`${formatDateMonth( + this._startDate, + this.hass.locale, + this.hass.config + )}` + : simpleRange === "day" + ? html`${formatDateVeryShort( + this._startDate, + this.hass.locale, + this.hass.config + )}` + : html`${formatDateVeryShort( + this._startDate, + this.hass.locale, + this.hass.config + )}–${formatDateVeryShort( + this._endDate || new Date(), + this.hass.locale, + this.hass.config + )}`}`}`} ${showSubtitleYear ? html`
@@ -386,6 +403,7 @@ export class HuiEnergyPeriodSelector extends SubscribeMixin(LitElement) { if (differenceInDays(endDate!, startDate!) === 0) { return "day"; } + // Check if range is one or more whole months if ( (calcDateProperty( startDate, @@ -404,6 +422,7 @@ export class HuiEnergyPeriodSelector extends SubscribeMixin(LitElement) { config ) as number) === 0 ) { + // Single month return "month"; } if ( @@ -416,29 +435,37 @@ export class HuiEnergyPeriodSelector extends SubscribeMixin(LitElement) { ) as number) === 2 && startDate.getMonth() % 3 === 0 ) { + // Quarter year return "quarter"; } + if ( + calcDateDifferenceProperty( + endDate, + startDate, + differenceInMonths, + locale, + config + ) === 11 + ) { + if ( + calcDateDifferenceProperty( + endDate, + startDate, + differenceInCalendarYears, + locale, + config + ) === 0 + ) { + // Exact year + return "year"; + } + // Last 12 months + return "12month"; + } + // Otherwise some number of whole months + return "months"; } - if ( - calcDateProperty(startDate, isFirstDayOfMonth, locale, config) && - calcDateProperty(endDate, isLastDayOfMonth, locale, config) && - calcDateDifferenceProperty( - endDate, - startDate, - differenceInCalendarYears, - locale, - config - ) === 0 && - calcDateDifferenceProperty( - endDate, - startDate, - differenceInMonths, - locale, - config - ) === 11 - ) { - return "year"; - } + // Unnamed date range return "other"; } ); @@ -508,6 +535,30 @@ export class HuiEnergyPeriodSelector extends SubscribeMixin(LitElement) { ); } else if (range === "year") { [this._startDate, this._endDate] = calcDateRange(this.hass, "this_year"); + } else if (range === "12month") { + [this._startDate, this._endDate] = calcDateRange(this.hass, "now-12m"); + } else if (range === "months") { + // Custom month range + const difference = calcDateDifferenceProperty( + this._endDate!, + this._startDate, + differenceInCalendarMonths, + this.hass.locale, + this.hass.config + ) as number; + this._startDate = calcDate( + calcDate(today, startOfMonth, this.hass.locale, this.hass.config), + subMonths, + this.hass.locale, + this.hass.config, + difference + ); + this._endDate = calcDate( + today, + endOfMonth, + this.hass.locale, + this.hass.config + ); } else { const weekStartsOn = firstWeekdayIndex(this.hass.locale); const weekStart = calcDate(