@@ -420,17 +420,33 @@ function extractUniqueModels<T>(
420420 return uniq ( entries . map ( getModel ) . filter ( ( m ) : m is string => m != null && m !== '<synthetic>' ) ) ;
421421}
422422
423+ /**
424+ * Shared method for formatting dates with proper timezone handling
425+ * @param dateStr - Input date string
426+ * @param twoLine - Whether to format as two lines (true) or single line (false)
427+ * @returns Formatted date string
428+ */
429+ function formatDateInternal ( dateStr : string , twoLine : boolean ) : string {
430+ const date = new Date ( dateStr ) ;
431+
432+ // Detect if the string includes UTC indicator (Z) or timezone offset (±HH:MM)
433+ const hasTimezone = / Z | [ + - ] \d { 2 } : \d { 2 } / . test ( dateStr ) ;
434+
435+ // Use UTC getters if timezone is specified, otherwise use local getters
436+ const year = hasTimezone ? date . getUTCFullYear ( ) : date . getFullYear ( ) ;
437+ const month = String ( hasTimezone ? date . getUTCMonth ( ) + 1 : date . getMonth ( ) + 1 ) . padStart ( 2 , '0' ) ;
438+ const day = String ( hasTimezone ? date . getUTCDate ( ) : date . getDate ( ) ) . padStart ( 2 , '0' ) ;
439+
440+ return twoLine ? `${ year } \n${ month } -${ day } ` : `${ year } -${ month } -${ day } ` ;
441+ }
442+
423443/**
424444 * Formats a date string to YYYY-MM-DD format
425445 * @param dateStr - Input date string
426446 * @returns Formatted date string in YYYY-MM-DD format
427447 */
428448export function formatDate ( dateStr : string ) : string {
429- const date = new Date ( dateStr ) ;
430- const year = date . getFullYear ( ) ;
431- const month = String ( date . getMonth ( ) + 1 ) . padStart ( 2 , '0' ) ;
432- const day = String ( date . getDate ( ) ) . padStart ( 2 , '0' ) ;
433- return `${ year } -${ month } -${ day } ` ;
449+ return formatDateInternal ( dateStr , false ) ;
434450}
435451
436452/**
@@ -439,11 +455,7 @@ export function formatDate(dateStr: string): string {
439455 * @returns Formatted date string with newline separator (YYYY\nMM-DD)
440456 */
441457export function formatDateCompact ( dateStr : string ) : string {
442- const date = new Date ( dateStr ) ;
443- const year = date . getFullYear ( ) ;
444- const month = String ( date . getMonth ( ) + 1 ) . padStart ( 2 , '0' ) ;
445- const day = String ( date . getDate ( ) ) . padStart ( 2 , '0' ) ;
446- return `${ year } \n${ month } -${ day } ` ;
458+ return formatDateInternal ( dateStr , true ) ;
447459}
448460
449461/**
@@ -1142,16 +1154,30 @@ export async function loadSessionBlockData(
11421154
11431155if ( import . meta. vitest != null ) {
11441156 describe ( 'formatDate' , ( ) => {
1145- it ( 'formats UTC timestamp to local date' , ( ) => {
1146- // Test with UTC timestamps - results depend on local timezone
1157+ it ( 'formats UTC timestamps using UTC date' , ( ) => {
1158+ // UTC timestamps should always use UTC date regardless of local timezone
11471159 expect ( formatDate ( '2024-01-01T00:00:00Z' ) ) . toBe ( '2024-01-01' ) ;
11481160 expect ( formatDate ( '2024-12-31T23:59:59Z' ) ) . toBe ( '2024-12-31' ) ;
1161+ expect ( formatDate ( '2024-01-01T12:00:00.000Z' ) ) . toBe ( '2024-01-01' ) ;
11491162 } ) ;
11501163
1151- it ( 'handles various date formats' , ( ) => {
1152- expect ( formatDate ( '2024-01-01' ) ) . toBe ( '2024-01-01' ) ;
1153- expect ( formatDate ( '2024-01-01T12:00:00' ) ) . toBe ( '2024-01-01' ) ;
1154- expect ( formatDate ( '2024-01-01T12:00:00.000Z' ) ) . toBe ( '2024-01-01' ) ;
1164+ it ( 'formats timezone offset strings using UTC date' , ( ) => {
1165+ // Strings with timezone offsets should use UTC date
1166+ expect ( formatDate ( '2024-01-01T00:00:00+00:00' ) ) . toBe ( '2024-01-01' ) ;
1167+ expect ( formatDate ( '2024-01-01T00:00:00-05:00' ) ) . toBe ( '2024-01-01' ) ;
1168+ expect ( formatDate ( '2024-12-31T23:59:59+08:00' ) ) . toBe ( '2024-12-31' ) ;
1169+ } ) ;
1170+
1171+ it ( 'formats local date strings using local date' , ( ) => {
1172+ // Without timezone indicator, should use local date interpretation
1173+ const localDate = new Date ( '2024-01-01T12:00:00' ) ;
1174+ const expectedYear = localDate . getFullYear ( ) ;
1175+ const expectedMonth = String ( localDate . getMonth ( ) + 1 ) . padStart ( 2 , '0' ) ;
1176+ const expectedDay = String ( localDate . getDate ( ) ) . padStart ( 2 , '0' ) ;
1177+ const expected = `${ expectedYear } -${ expectedMonth } -${ expectedDay } ` ;
1178+
1179+ expect ( formatDate ( '2024-01-01' ) ) . toBe ( expected ) ;
1180+ expect ( formatDate ( '2024-01-01T12:00:00' ) ) . toBe ( expected ) ;
11551181 } ) ;
11561182
11571183 it ( 'pads single digit months and days' , ( ) => {
@@ -1161,17 +1187,32 @@ if (import.meta.vitest != null) {
11611187 } ) ;
11621188
11631189 describe ( 'formatDateCompact' , ( ) => {
1164- it ( 'formats UTC timestamp to local date with line break' , ( ) => {
1190+ it ( 'formats UTC timestamps using UTC date with line break' , ( ) => {
1191+ // UTC timestamps should always use UTC date regardless of local timezone
11651192 expect ( formatDateCompact ( '2024-01-01T00:00:00Z' ) ) . toBe ( '2024\n01-01' ) ;
1166- } ) ;
1167-
1168- it ( 'handles various date formats' , ( ) => {
11691193 expect ( formatDateCompact ( '2024-12-31T23:59:59Z' ) ) . toBe ( '2024\n12-31' ) ;
1170- expect ( formatDateCompact ( '2024-01-01' ) ) . toBe ( '2024\n01-01' ) ;
1171- expect ( formatDateCompact ( '2024-01-01T12:00:00' ) ) . toBe ( '2024\n01-01' ) ;
11721194 expect ( formatDateCompact ( '2024-01-01T12:00:00.000Z' ) ) . toBe ( '2024\n01-01' ) ;
11731195 } ) ;
11741196
1197+ it ( 'formats timezone offset strings using UTC date with line break' , ( ) => {
1198+ // Strings with timezone offsets should use UTC date
1199+ expect ( formatDateCompact ( '2024-01-01T00:00:00+00:00' ) ) . toBe ( '2024\n01-01' ) ;
1200+ expect ( formatDateCompact ( '2024-01-01T00:00:00-05:00' ) ) . toBe ( '2024\n01-01' ) ;
1201+ expect ( formatDateCompact ( '2024-12-31T23:59:59+08:00' ) ) . toBe ( '2024\n12-31' ) ;
1202+ } ) ;
1203+
1204+ it ( 'formats local date strings using local date with line break' , ( ) => {
1205+ // Without timezone indicator, should use local date interpretation
1206+ const localDate = new Date ( '2024-01-01T12:00:00' ) ;
1207+ const expectedYear = localDate . getFullYear ( ) ;
1208+ const expectedMonth = String ( localDate . getMonth ( ) + 1 ) . padStart ( 2 , '0' ) ;
1209+ const expectedDay = String ( localDate . getDate ( ) ) . padStart ( 2 , '0' ) ;
1210+ const expected = `${ expectedYear } \n${ expectedMonth } -${ expectedDay } ` ;
1211+
1212+ expect ( formatDateCompact ( '2024-01-01' ) ) . toBe ( expected ) ;
1213+ expect ( formatDateCompact ( '2024-01-01T12:00:00' ) ) . toBe ( expected ) ;
1214+ } ) ;
1215+
11751216 it ( 'pads single digit months and days' , ( ) => {
11761217 expect ( formatDateCompact ( '2024-01-05T00:00:00Z' ) ) . toBe ( '2024\n01-05' ) ;
11771218 expect ( formatDateCompact ( '2024-10-01T00:00:00Z' ) ) . toBe ( '2024\n10-01' ) ;
0 commit comments