1
+ import { DateRange } from "../types/types.js" ;
2
+
3
+ /**
4
+ * Calculates date range based on a time frame string
5
+ * @param timeFrame Various time frame formats like "last-week", "past-7d", "previous-calendar-week", "ytd", etc.
6
+ * @returns Object with startDate and endDate in ISO 8601 format
7
+ */
8
+ export function calculateDateRange ( timeFrame : string ) : DateRange {
9
+ // If timeFrame is a simple keyword that toISO8601 can handle directly
10
+ if ( [ 'today' , 'yesterday' , 'last-week' , 'last-month' , 'start-of-year' ] . includes ( timeFrame . toLowerCase ( ) ) ) {
11
+ return {
12
+ startDate : toISO8601 ( timeFrame ) ,
13
+ endDate : toISO8601 ( 'now' )
14
+ } ;
15
+ }
16
+
17
+ const now = new Date ( ) ;
18
+ let startDate = new Date ( now ) ;
19
+ let endDate = new Date ( now ) ;
20
+
21
+ const normalizedTimeFrame = timeFrame . toLowerCase ( ) ;
22
+
23
+ // Handle special cases first
24
+ if ( normalizedTimeFrame === 'previous-calendar-week' ) {
25
+ const today = now . getDay ( ) ; // 0 is Sunday, 6 is Saturday
26
+
27
+ // Calculate previous week's Sunday
28
+ startDate = new Date ( now ) ;
29
+ startDate . setDate ( now . getDate ( ) - today - 7 ) ;
30
+ startDate . setHours ( 0 , 0 , 0 , 0 ) ;
31
+
32
+ // Calculate previous week's Saturday
33
+ endDate = new Date ( startDate ) ;
34
+ endDate . setDate ( startDate . getDate ( ) + 6 ) ;
35
+ endDate . setHours ( 23 , 59 , 59 , 999 ) ;
36
+ }
37
+ // Handle trailing periods (last 7 days, etc.)
38
+ else if ( normalizedTimeFrame === 'last-7-days' || normalizedTimeFrame === 'last-week' ) {
39
+ startDate . setDate ( now . getDate ( ) - 7 ) ;
40
+ }
41
+ // Handle past-Nd format (days)
42
+ else if ( normalizedTimeFrame . startsWith ( 'past-' ) && normalizedTimeFrame . endsWith ( 'd' ) ) {
43
+ const days = parseInt ( normalizedTimeFrame . substring ( 5 , normalizedTimeFrame . length - 1 ) ) ;
44
+ if ( ! isNaN ( days ) ) {
45
+ startDate . setDate ( now . getDate ( ) - days ) ;
46
+ }
47
+ }
48
+ // Handle past-Nw format (weeks)
49
+ else if ( normalizedTimeFrame . startsWith ( 'past-' ) && normalizedTimeFrame . endsWith ( 'w' ) ) {
50
+ const weeks = parseInt ( normalizedTimeFrame . substring ( 5 , normalizedTimeFrame . length - 1 ) ) ;
51
+ if ( ! isNaN ( weeks ) ) {
52
+ startDate . setDate ( now . getDate ( ) - ( weeks * 7 ) ) ;
53
+ }
54
+ }
55
+ // Handle past-Nm format (months)
56
+ else if ( normalizedTimeFrame . startsWith ( 'past-' ) && normalizedTimeFrame . endsWith ( 'm' ) ) {
57
+ const months = parseInt ( normalizedTimeFrame . substring ( 5 , normalizedTimeFrame . length - 1 ) ) ;
58
+ if ( ! isNaN ( months ) ) {
59
+ startDate . setMonth ( now . getMonth ( ) - months ) ;
60
+ }
61
+ }
62
+ // Handle past-Ny format (years)
63
+ else if ( normalizedTimeFrame . startsWith ( 'past-' ) && normalizedTimeFrame . endsWith ( 'y' ) ) {
64
+ const years = parseInt ( normalizedTimeFrame . substring ( 5 , normalizedTimeFrame . length - 1 ) ) ;
65
+ if ( ! isNaN ( years ) ) {
66
+ startDate . setFullYear ( now . getFullYear ( ) - years ) ;
67
+ }
68
+ }
69
+ // Handle ytd (year to date)
70
+ else if ( normalizedTimeFrame === 'ytd' ) {
71
+ startDate = new Date ( now . getFullYear ( ) , 0 , 1 ) ; // January 1st of current year
72
+ }
73
+ // Handle qtd (quarter to date)
74
+ else if ( normalizedTimeFrame === 'qtd' ) {
75
+ const quarter = Math . floor ( now . getMonth ( ) / 3 ) ;
76
+ startDate = new Date ( now . getFullYear ( ) , quarter * 3 , 1 ) ;
77
+ }
78
+ // Handle mtd (month to date)
79
+ else if ( normalizedTimeFrame === 'mtd' ) {
80
+ startDate = new Date ( now . getFullYear ( ) , now . getMonth ( ) , 1 ) ;
81
+ }
82
+ // Handle wtd (week to date - starting from Sunday)
83
+ else if ( normalizedTimeFrame === 'wtd' ) {
84
+ const day = now . getDay ( ) ; // 0 = Sunday, 6 = Saturday
85
+ startDate . setDate ( now . getDate ( ) - day ) ;
86
+ }
87
+ // Default to 7 days if format not recognized
88
+ else {
89
+ startDate . setDate ( now . getDate ( ) - 7 ) ;
90
+ console . warn ( `Unrecognized timeFrame format: ${ timeFrame } . Defaulting to past 7 days.` ) ;
91
+ }
92
+
93
+ return {
94
+ startDate : startDate . toISOString ( ) ,
95
+ endDate : endDate . toISOString ( )
96
+ } ;
97
+ }
98
+
99
+ /**
100
+ * Natural language processing for time periods
101
+ * This function handles natural language queries and converts them to standardized time frame strings
102
+ * @param query Natural language query like "what was the price last week" or "show me prices from last month"
103
+ * @returns Standardized time frame string
104
+ */
105
+ export function parseNaturalLanguageTimeFrame ( query : string ) : string {
106
+ const normalizedQuery = query . toLowerCase ( ) ;
107
+
108
+ if ( normalizedQuery . includes ( 'last week' ) || normalizedQuery . includes ( 'previous week' ) ) {
109
+ return 'last-week' ;
110
+ }
111
+ else if ( normalizedQuery . includes ( 'last month' ) || normalizedQuery . includes ( 'previous month' ) ) {
112
+ return 'last-month' ;
113
+ }
114
+ else if ( normalizedQuery . includes ( 'yesterday' ) ) {
115
+ return 'yesterday' ;
116
+ }
117
+ else if ( normalizedQuery . includes ( 'last year' ) || normalizedQuery . includes ( 'previous year' ) ) {
118
+ return 'past-1y' ;
119
+ }
120
+ else if ( normalizedQuery . includes ( 'this year' ) || normalizedQuery . includes ( 'year to date' ) || normalizedQuery . includes ( 'ytd' ) ) {
121
+ return 'ytd' ;
122
+ }
123
+ else if ( normalizedQuery . includes ( 'this month' ) || normalizedQuery . includes ( 'month to date' ) || normalizedQuery . includes ( 'mtd' ) ) {
124
+ return 'mtd' ;
125
+ }
126
+ else if ( normalizedQuery . includes ( 'this quarter' ) || normalizedQuery . includes ( 'quarter to date' ) || normalizedQuery . includes ( 'qtd' ) ) {
127
+ return 'qtd' ;
128
+ }
129
+ else if ( normalizedQuery . includes ( 'calendar week' ) ) {
130
+ return 'previous-calendar-week' ;
131
+ }
132
+
133
+ // Default to last 7 days if no specific time frame mentioned
134
+ return 'last-7-days' ;
135
+ }
136
+
137
+ export function toISO8601 ( dateStr : string ) : string {
138
+ // Handle keywords first
139
+ const normalizedStr = dateStr . toLowerCase ( ) ;
140
+
141
+ const now = new Date ( ) ;
142
+
143
+ switch ( normalizedStr ) {
144
+ case 'today' : {
145
+ const date = new Date ( now ) ;
146
+ date . setHours ( 0 , 0 , 0 , 0 ) ;
147
+ return date . toISOString ( ) ;
148
+ }
149
+ case 'yesterday' : {
150
+ const date = new Date ( now ) ;
151
+ date . setDate ( date . getDate ( ) - 1 ) ;
152
+ date . setHours ( 0 , 0 , 0 , 0 ) ;
153
+ return date . toISOString ( ) ;
154
+ }
155
+ case 'last-week' : {
156
+ const date = new Date ( now ) ;
157
+ date . setDate ( date . getDate ( ) - 7 ) ;
158
+ date . setHours ( 0 , 0 , 0 , 0 ) ;
159
+ return date . toISOString ( ) ;
160
+ }
161
+ case 'last-month' : {
162
+ const date = new Date ( now ) ;
163
+ date . setMonth ( date . getMonth ( ) - 1 ) ;
164
+ date . setHours ( 0 , 0 , 0 , 0 ) ;
165
+ return date . toISOString ( ) ;
166
+ }
167
+ case 'start-of-year' : {
168
+ const date = new Date ( now ) ;
169
+ date . setMonth ( 0 , 1 ) ;
170
+ date . setHours ( 0 , 0 , 0 , 0 ) ;
171
+ return date . toISOString ( ) ;
172
+ }
173
+ case 'now' : {
174
+ return now . toISOString ( ) ;
175
+ }
176
+ }
177
+
178
+ // If already in ISO format, return as is
179
+ if ( dateStr . match ( / ^ \d { 4 } - \d { 2 } - \d { 2 } T \d { 2 } : \d { 2 } : \d { 2 } (?: \. \d { 3 } ) ? Z $ / ) ) {
180
+ return dateStr ;
181
+ }
182
+
183
+ // For any other date string, parse it normally
184
+ try {
185
+ const date = new Date ( dateStr ) ;
186
+ return date . toISOString ( ) ;
187
+ } catch ( error ) {
188
+ console . error ( 'Error parsing date:' , error ) ;
189
+ // Return current time if parsing fails
190
+ return now . toISOString ( ) ;
191
+ }
192
+ }
0 commit comments