From f5e4d82efca8a817dd221c91bd8ccc9691532332 Mon Sep 17 00:00:00 2001 From: MaysWind Date: Wed, 31 Dec 2025 00:00:04 +0800 Subject: [PATCH] in insights & explore, time-based category supports calculated based on the transaction's time zone --- src/lib/datetime.ts | 137 ++++++++++++++++++++++++++++++++---------- src/stores/explore.ts | 17 ++++-- 2 files changed, 116 insertions(+), 38 deletions(-) diff --git a/src/lib/datetime.ts b/src/lib/datetime.ts index 10bd2173..38ad7a0a 100644 --- a/src/lib/datetime.ts +++ b/src/lib/datetime.ts @@ -804,41 +804,87 @@ export function getThisYearLastUnixTime(): number { return moment.unix(getThisYearFirstUnixTime()).add(1, 'years').subtract(1, 'seconds').unix(); } -export function getYearFirstUnixTimeBySpecifiedUnixTime(unixTime: number): number { - const date = moment.unix(unixTime).set({ hour: 0, minute: 0, second: 0, millisecond: 0 }); - return date.subtract(date.dayOfYear() - 1, 'days').unix(); +export function getYearFirstUnixTimeBySpecifiedUnixTime(unixTime: number, utcOffset?: number): number { + let date = moment.unix(unixTime); + + if (isNumber(utcOffset)) { + date = date.tz(getFixedTimezoneName(utcOffset)); + } + + return date.set({ hour: 0, minute: 0, second: 0, millisecond: 0 }).subtract(date.dayOfYear() - 1, 'days').unix(); } -export function getYearLastUnixTimeBySpecifiedUnixTime(unixTime: number): number { - return moment.unix(getYearFirstUnixTimeBySpecifiedUnixTime(unixTime)).add(1, 'years').subtract(1, 'seconds').unix(); +export function getYearLastUnixTimeBySpecifiedUnixTime(unixTime: number, utcOffset?: number): number { + let date = moment.unix(getYearFirstUnixTimeBySpecifiedUnixTime(unixTime, utcOffset)); + + if (isNumber(utcOffset)) { + date = date.tz(getFixedTimezoneName(utcOffset)); + } + + return date.add(1, 'years').subtract(1, 'seconds').unix(); } -export function getQuarterFirstUnixTimeBySpecifiedUnixTime(unixTime: number): number { - const date = moment.unix(unixTime).set({ hour: 0, minute: 0, second: 0, millisecond: 0 }); +export function getQuarterFirstUnixTimeBySpecifiedUnixTime(unixTime: number, utcOffset?: number): number { + let date = moment.unix(unixTime); + + if (isNumber(utcOffset)) { + date = date.tz(getFixedTimezoneName(utcOffset)); + } + + date = date.set({ hour: 0, minute: 0, second: 0, millisecond: 0 }); const month = date.month(); const quarterStartMonth = Math.floor(month / 3) * 3; return date.set({ month: quarterStartMonth, date: 1 }).unix(); } -export function getQuarterLastUnixTimeBySpecifiedUnixTime(unixTime: number): number { - return moment.unix(getQuarterFirstUnixTimeBySpecifiedUnixTime(unixTime)).add(3, 'months').subtract(1, 'seconds').unix(); +export function getQuarterLastUnixTimeBySpecifiedUnixTime(unixTime: number, utcOffset?: number): number { + let date = moment.unix(getQuarterFirstUnixTimeBySpecifiedUnixTime(unixTime, utcOffset)); + + if (isNumber(utcOffset)) { + date = date.tz(getFixedTimezoneName(utcOffset)); + } + + return date.add(3, 'months').subtract(1, 'seconds').unix(); } -export function getMonthFirstUnixTimeBySpecifiedUnixTime(unixTime: number): number { - const date = moment.unix(unixTime).set({ hour: 0, minute: 0, second: 0, millisecond: 0 }); - return date.subtract(date.date() - 1, 'days').unix(); +export function getMonthFirstUnixTimeBySpecifiedUnixTime(unixTime: number, utcOffset?: number): number { + let date = moment.unix(unixTime); + + if (isNumber(utcOffset)) { + date = date.tz(getFixedTimezoneName(utcOffset)); + } + + return date.set({ hour: 0, minute: 0, second: 0, millisecond: 0 }).subtract(date.date() - 1, 'days').unix(); } -export function getMonthLastUnixTimeBySpecifiedUnixTime(unixTime: number): number { - return moment.unix(getMonthFirstUnixTimeBySpecifiedUnixTime(unixTime)).add(1, 'months').subtract(1, 'seconds').unix(); +export function getMonthLastUnixTimeBySpecifiedUnixTime(unixTime: number, utcOffset?: number): number { + let date = moment.unix(getMonthFirstUnixTimeBySpecifiedUnixTime(unixTime, utcOffset)); + + if (isNumber(utcOffset)) { + date = date.tz(getFixedTimezoneName(utcOffset)); + } + + return date.add(1, 'months').subtract(1, 'seconds').unix(); } -export function getDayFirstUnixTimeBySpecifiedUnixTime(unixTime: number): number { - return moment.unix(unixTime).set({ hour: 0, minute: 0, second: 0, millisecond: 0 }).unix(); +export function getDayFirstUnixTimeBySpecifiedUnixTime(unixTime: number, utcOffset?: number): number { + let date = moment.unix(unixTime); + + if (isNumber(utcOffset)) { + date = date.tz(getFixedTimezoneName(utcOffset)); + } + + return date.set({ hour: 0, minute: 0, second: 0, millisecond: 0 }).unix(); } -export function getDayLastUnixTimeBySpecifiedUnixTime(unixTime: number): number { - return moment.unix(unixTime).set({ hour: 0, minute: 0, second: 0, millisecond: 0 }).add(1, 'days').subtract(1, 'seconds').unix(); +export function getDayLastUnixTimeBySpecifiedUnixTime(unixTime: number, utcOffset?: number): number { + let date = moment.unix(unixTime); + + if (isNumber(utcOffset)) { + date = date.tz(getFixedTimezoneName(utcOffset)); + } + + return date.set({ hour: 0, minute: 0, second: 0, millisecond: 0 }).add(1, 'days').subtract(1, 'seconds').unix(); } export function getYearFirstUnixTime(year: number): number { @@ -1486,8 +1532,12 @@ export function isDateRangeMatchOneMonth(minTime: number, maxTime: number): bool return isDateRangeMatchFullMonths(minTime, maxTime); } -export function getFiscalYearFromUnixTime(unixTime: number, fiscalYearStartValue: number): number { - const date = moment.unix(unixTime); +export function getFiscalYearFromUnixTime(unixTime: number, fiscalYearStartValue: number, utcOffset?: number): number { + let date = moment.unix(unixTime); + + if (isNumber(utcOffset)) { + date = date.tz(getFixedTimezoneName(utcOffset)); + } // For January 1 fiscal year start, fiscal year matches calendar year if (fiscalYearStartValue === FiscalYearStart.JanuaryFirstDay.value) { @@ -1517,12 +1567,22 @@ export function getFiscalYearFromUnixTime(unixTime: number, fiscalYearStartValue return year + 1; } -export function getFiscalYearStartUnixTime(unixTime: number, fiscalYearStartValue: number): number { - const date = moment.unix(unixTime); +export function getFiscalYearStartUnixTime(unixTime: number, fiscalYearStartValue: number, utcOffset?: number): number { + let date = moment.unix(unixTime); + + if (isNumber(utcOffset)) { + date = date.tz(getFixedTimezoneName(utcOffset)); + } // For January 1 fiscal year start, fiscal year start time is always January 1 in the input calendar year if (fiscalYearStartValue === FiscalYearStart.JanuaryFirstDay.value) { - return moment().year(date.year()).month(0).date(1).hour(0).minute(0).second(0).millisecond(0).unix(); + let finalDate = moment(); + + if (isNumber(utcOffset)) { + finalDate = finalDate.tz(getFixedTimezoneName(utcOffset)); + } + + return finalDate.year(date.year()).month(0).date(1).hour(0).minute(0).second(0).millisecond(0).unix(); } let fiscalYearStart = FiscalYearStart.valueOf(fiscalYearStartValue); @@ -1545,7 +1605,13 @@ export function getFiscalYearStartUnixTime(unixTime: number, fiscalYearStartValu startYear = year; } - return moment().set({ + let finalDate = moment(); + + if (isNumber(utcOffset)) { + finalDate = finalDate.tz(getFixedTimezoneName(utcOffset)); + } + + return finalDate.set({ year: startYear, month: fiscalYearStart.month - 1, // 0-index date: fiscalYearStart.day, @@ -1556,21 +1622,26 @@ export function getFiscalYearStartUnixTime(unixTime: number, fiscalYearStartValu }).unix(); } -export function getFiscalYearEndUnixTime(unixTime: number, fiscalYearStart: number): number { - const fiscalYearStartTime = moment.unix(getFiscalYearStartUnixTime(unixTime, fiscalYearStart)); - return fiscalYearStartTime.add(1, 'years').subtract(1, 'seconds').unix(); +export function getFiscalYearEndUnixTime(unixTime: number, fiscalYearStart: number, utcOffset?: number): number { + let date = moment.unix(getFiscalYearStartUnixTime(unixTime, fiscalYearStart)); + + if (isNumber(utcOffset)) { + date = date.tz(getFixedTimezoneName(utcOffset)); + } + + return date.add(1, 'years').subtract(1, 'seconds').unix(); } -export function getCurrentFiscalYear(fiscalYearStart: number): number { +export function getCurrentFiscalYear(fiscalYearStart: number, utcOffset?: number): number { const date = moment(); - return getFiscalYearFromUnixTime(date.unix(), fiscalYearStart); + return getFiscalYearFromUnixTime(date.unix(), fiscalYearStart, utcOffset); } -export function getFiscalYearTimeRangeFromUnixTime(unixTime: number, fiscalYearStart: number): FiscalYearUnixTime { - const start = getFiscalYearStartUnixTime(unixTime, fiscalYearStart); - const end = getFiscalYearEndUnixTime(unixTime, fiscalYearStart); +export function getFiscalYearTimeRangeFromUnixTime(unixTime: number, fiscalYearStart: number, utcOffset?: number): FiscalYearUnixTime { + const start = getFiscalYearStartUnixTime(unixTime, fiscalYearStart, utcOffset); + const end = getFiscalYearEndUnixTime(unixTime, fiscalYearStart, utcOffset); return { - year: getFiscalYearFromUnixTime(unixTime, fiscalYearStart), + year: getFiscalYearFromUnixTime(unixTime, fiscalYearStart, utcOffset), minUnixTime: start, maxUnixTime: end, }; diff --git a/src/stores/explore.ts b/src/stores/explore.ts index 12eb1505..4ffda279 100644 --- a/src/stores/explore.ts +++ b/src/stores/explore.ts @@ -11,6 +11,7 @@ import { useExchangeRatesStore } from './exchangeRates.ts'; import { itemAndIndex, values } from '@/core/base.ts'; import { AmountFilterType } from '@/core/numeral.ts'; import { DateRangeScene, DateRange } from '@/core/datetime.ts'; +import { TimezoneTypeForStatistics } from '@/core/timezone.ts'; import { AccountCategory } from '@/core/account.ts'; import { TransactionType } from '@/core/transaction.ts'; import { @@ -122,6 +123,12 @@ export const useExploresStore = defineStore('explores', () => { const exchangeRatesStore = useExchangeRatesStore(); function getDataCategoryInfo(dimension: TransactionExploreDataDimension, queryName: string, queryIndex: number, transaction: TransactionInsightDataItem): CategoriedInfo { + let transactionTimeUtfOffset: number | undefined = undefined; + + if (settingsStore.appSettings.timezoneUsedForInsightsExplorePage === TimezoneTypeForStatistics.TransactionTimezone.type) { + transactionTimeUtfOffset = transaction.utcOffset; + } + if (dimension === TransactionExploreDataDimension.None) { const valueMetric = TransactionExploreValueMetric.valueOf(transactionExploreFilter.value.valueMetric); return { @@ -157,7 +164,7 @@ export const useExploresStore = defineStore('explores', () => { categoryIdType: TransactionExploreDimensionType.Other }; } else if (dimension === TransactionExploreDataDimension.DateTimeByYearMonthDay) { - const unixTime = getDayFirstUnixTimeBySpecifiedUnixTime(transaction.time).toString(10); + const unixTime = getDayFirstUnixTimeBySpecifiedUnixTime(transaction.time, transactionTimeUtfOffset).toString(10); return { categoryName: unixTime, @@ -165,7 +172,7 @@ export const useExploresStore = defineStore('explores', () => { categoryIdType: TransactionExploreDimensionType.Other }; } else if (dimension === TransactionExploreDataDimension.DateTimeByYearMonth) { - const unixTime = getMonthFirstUnixTimeBySpecifiedUnixTime(transaction.time).toString(10); + const unixTime = getMonthFirstUnixTimeBySpecifiedUnixTime(transaction.time, transactionTimeUtfOffset).toString(10); return { categoryName: unixTime, @@ -173,7 +180,7 @@ export const useExploresStore = defineStore('explores', () => { categoryIdType: TransactionExploreDimensionType.Other }; } else if (dimension === TransactionExploreDataDimension.DateTimeByYearQuarter) { - const unixTime = getQuarterFirstUnixTimeBySpecifiedUnixTime(transaction.time).toString(10); + const unixTime = getQuarterFirstUnixTimeBySpecifiedUnixTime(transaction.time, transactionTimeUtfOffset).toString(10); return { categoryName: unixTime, @@ -181,7 +188,7 @@ export const useExploresStore = defineStore('explores', () => { categoryIdType: TransactionExploreDimensionType.Other }; } else if (dimension === TransactionExploreDataDimension.DateTimeByYear) { - const unixTime = getYearFirstUnixTimeBySpecifiedUnixTime(transaction.time).toString(10); + const unixTime = getYearFirstUnixTimeBySpecifiedUnixTime(transaction.time, transactionTimeUtfOffset).toString(10); return { categoryName: unixTime, @@ -189,7 +196,7 @@ export const useExploresStore = defineStore('explores', () => { categoryIdType: TransactionExploreDimensionType.Other }; } else if (dimension === TransactionExploreDataDimension.DateTimeByFiscalYear) { - const unixTime = getFiscalYearStartUnixTime(transaction.time, userStore.currentUserFiscalYearStart).toString(10); + const unixTime = getFiscalYearStartUnixTime(transaction.time, userStore.currentUserFiscalYearStart, transactionTimeUtfOffset).toString(10); return { categoryName: unixTime,