[READ-ONLY] One Calendar is a privacy-first calendar web app built with Next.js. It has modern security features, including e2ee, password-protected sharing, and self-destructing share links 馃搮 calendar.xyehr.cn
nextjs
0

Configure Feed

Select the types of activity you want to include in your feed.

One-Calendar / lib / time-analytics.ts
3.6 kB 141 lines
1export interface TimeCategory { 2 id: string 3 name: string 4 color: string 5 keywords: string[] 6} 7 8export interface TimeAnalytics { 9 totalEvents: number 10 totalHours: number 11 categorizedHours: Record<string, number> 12 mostProductiveDay: string 13 mostProductiveHour: number 14 averageEventDuration: number 15 activeDays: number 16 busiestCategoryId: string 17 longestEvent: { 18 title: string 19 duration: number 20 } 21} 22 23export function analyzeTimeUsage( 24 events: any[], 25 categories: TimeCategory[] = [], 26): TimeAnalytics { 27 const result: TimeAnalytics = { 28 totalEvents: events.length, 29 totalHours: 0, 30 categorizedHours: {}, 31 mostProductiveDay: '', 32 mostProductiveHour: 0, 33 averageEventDuration: 0, 34 activeDays: 0, 35 busiestCategoryId: 'uncategorized', 36 longestEvent: { 37 title: '', 38 duration: 0, 39 }, 40 } 41 42 categories.forEach((category) => { 43 result.categorizedHours[category.id] = 0 44 }) 45 result.categorizedHours['uncategorized'] = 0 46 47 const eventsByDay: Record<string, number> = {} 48 const eventsByHour: Record<number, number> = {} 49 for (let i = 0; i < 24; i++) { 50 eventsByHour[i] = 0 51 } 52 53 events.forEach((event) => { 54 const startDate = new Date(event.startDate) 55 const endDate = new Date(event.endDate) 56 const durationMs = endDate.getTime() - startDate.getTime() 57 const normalizedDurationMs = event.isAllDay 58 ? Math.max(1, Math.ceil(durationMs / (1000 * 60 * 60 * 24))) * 59 24 * 60 60 * 61 60 * 62 1000 63 : durationMs 64 const durationHours = normalizedDurationMs / (1000 * 60 * 60) 65 66 result.totalHours += durationHours 67 68 if (durationHours > result.longestEvent.duration) { 69 result.longestEvent = { 70 title: event.title, 71 duration: durationHours, 72 } 73 } 74 75 const dayKey = startDate.toISOString().split('T')[0] 76 eventsByDay[dayKey] = (eventsByDay[dayKey] || 0) + durationHours 77 78 const hour = startDate.getHours() 79 eventsByHour[hour] = (eventsByHour[hour] || 0) + 1 80 81 if ( 82 event.calendarId && 83 categories.some((cat) => cat.id === event.calendarId) 84 ) { 85 result.categorizedHours[event.calendarId] += durationHours 86 return 87 } 88 89 let categorized = false 90 for (const category of categories) { 91 const matchesKeyword = category.keywords.some( 92 (keyword) => 93 event.title.toLowerCase().includes(keyword.toLowerCase()) || 94 (event.description && 95 event.description.toLowerCase().includes(keyword.toLowerCase())), 96 ) 97 98 if (matchesKeyword) { 99 result.categorizedHours[category.id] += durationHours 100 categorized = true 101 break 102 } 103 } 104 105 if (!categorized) { 106 result.categorizedHours['uncategorized'] += durationHours 107 } 108 }) 109 110 let maxHours = 0 111 for (const [day, hours] of Object.entries(eventsByDay)) { 112 if (hours > maxHours) { 113 maxHours = hours 114 result.mostProductiveDay = day 115 } 116 } 117 118 let maxEvents = 0 119 for (const [hour, count] of Object.entries(eventsByHour)) { 120 if (count > maxEvents) { 121 maxEvents = count 122 result.mostProductiveHour = Number.parseInt(hour) 123 } 124 } 125 126 result.activeDays = Object.keys(eventsByDay).length 127 result.averageEventDuration = 128 result.totalEvents > 0 ? result.totalHours / result.totalEvents : 0 129 130 let busiestCategoryId = 'uncategorized' 131 let busiestHours = -1 132 for (const [categoryId, hours] of Object.entries(result.categorizedHours)) { 133 if (hours > busiestHours) { 134 busiestHours = hours 135 busiestCategoryId = categoryId 136 } 137 } 138 result.busiestCategoryId = busiestCategoryId 139 140 return result 141}