@akatan/date-to-festive 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +93 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +7 -0
- package/dist/resolver.d.ts +84 -0
- package/dist/resolver.js +237 -0
- package/dist/themeRules.json +247 -0
- package/package.json +44 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 AkatanHQ
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Date To Festive
|
|
2
|
+
|
|
3
|
+
A standalone, reusable module for resolving themes based on dates. Zero dependencies, simple API.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @akatan/date-to-festive
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
### Simple Example
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { resolvePrimaryThemeForDate } from '@akatanhq/date-to-festive';
|
|
17
|
+
|
|
18
|
+
// Get today's theme
|
|
19
|
+
const theme = resolvePrimaryThemeForDate(new Date());
|
|
20
|
+
console.log(theme);
|
|
21
|
+
// { name: 'winter', category: 'seasonal', metadata: { description: '...' } }
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Get All Matching Themes
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import { resolveThemesForDate } from '@akatan/date-to-festive';
|
|
28
|
+
|
|
29
|
+
// Get all themes for Christmas
|
|
30
|
+
const themes = resolveThemesForDate(new Date('2025-12-25'));
|
|
31
|
+
// [{ name: 'christmas', category: 'seasonal', metadata: {...} }]
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### With Cultural Themes
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
const themes = resolveThemesForDate(
|
|
38
|
+
new Date('2025-10-23'),
|
|
39
|
+
{
|
|
40
|
+
enabledCultures: ['diwali'],
|
|
41
|
+
userRegion: 'india'
|
|
42
|
+
}
|
|
43
|
+
);
|
|
44
|
+
// [{ name: 'diwali', category: 'cultural', ... }, ...]
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## API
|
|
48
|
+
|
|
49
|
+
### `resolvePrimaryThemeForDate(date, options?)`
|
|
50
|
+
|
|
51
|
+
Returns the most specific theme for a given date.
|
|
52
|
+
|
|
53
|
+
**Parameters:**
|
|
54
|
+
- `date: Date` - The date to resolve
|
|
55
|
+
- `options?: ResolverOptions`
|
|
56
|
+
- `enabledCultures?: string[]` - Cultural themes to enable
|
|
57
|
+
- `userRegion?: string` - User's region
|
|
58
|
+
|
|
59
|
+
**Returns:** `ResolvedTheme | null`
|
|
60
|
+
|
|
61
|
+
### `resolveThemesForDate(date, options?)`
|
|
62
|
+
|
|
63
|
+
Returns all matching themes for a given date, sorted by specificity.
|
|
64
|
+
|
|
65
|
+
**Parameters:** Same as above
|
|
66
|
+
|
|
67
|
+
**Returns:** `ResolvedTheme[]`
|
|
68
|
+
|
|
69
|
+
## Theme Types
|
|
70
|
+
|
|
71
|
+
The module includes:
|
|
72
|
+
- **Seasonal**: winter, spring, summer, autumn, etc.
|
|
73
|
+
- **Holidays**: christmas, halloween, easter, thanksgiving, etc.
|
|
74
|
+
- **Cultural**: diwali, chinese-new-year, hanukkah, ramadan, etc. (opt-in)
|
|
75
|
+
- **Everyday**: fallback theme when nothing matches
|
|
76
|
+
|
|
77
|
+
## Features
|
|
78
|
+
|
|
79
|
+
- ✅ Zero dependencies
|
|
80
|
+
- ✅ TypeScript support
|
|
81
|
+
- ✅ Simple API - just pass a date
|
|
82
|
+
- ✅ Handles year wrap-around (e.g., New Year)
|
|
83
|
+
- ✅ Calculated holidays (Easter, Thanksgiving)
|
|
84
|
+
- ✅ Cultural theme support with opt-in
|
|
85
|
+
- ✅ Specificity sorting (shorter duration = higher priority)
|
|
86
|
+
|
|
87
|
+
## License
|
|
88
|
+
|
|
89
|
+
MIT
|
|
90
|
+
|
|
91
|
+
## Repository
|
|
92
|
+
|
|
93
|
+
https://github.com/akatanhq/date-to-festive
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme Resolver Per Day - Standalone Module
|
|
3
|
+
*
|
|
4
|
+
* A reusable module for resolving themes based on dates.
|
|
5
|
+
* Configuration is included internally - just pass a date and get themes.
|
|
6
|
+
*/
|
|
7
|
+
export type DateRule = {
|
|
8
|
+
kind: 'range';
|
|
9
|
+
from: string;
|
|
10
|
+
to: string;
|
|
11
|
+
} | {
|
|
12
|
+
kind: 'holiday-offset';
|
|
13
|
+
holiday: 'easter';
|
|
14
|
+
start: number;
|
|
15
|
+
end: number;
|
|
16
|
+
} | {
|
|
17
|
+
kind: 'nth-weekday';
|
|
18
|
+
month: number;
|
|
19
|
+
weekday: number;
|
|
20
|
+
n: number;
|
|
21
|
+
duration?: number;
|
|
22
|
+
} | {
|
|
23
|
+
kind: 'always';
|
|
24
|
+
};
|
|
25
|
+
export interface ThemeRule {
|
|
26
|
+
name: string;
|
|
27
|
+
rule: DateRule;
|
|
28
|
+
enabled?: boolean;
|
|
29
|
+
region?: string[];
|
|
30
|
+
metadata?: {
|
|
31
|
+
actualDate?: string;
|
|
32
|
+
description?: string;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export interface ThemeRulesConfig {
|
|
36
|
+
seasonal: ThemeRule[];
|
|
37
|
+
holidays: ThemeRule[];
|
|
38
|
+
cultural: ThemeRule[];
|
|
39
|
+
everyday: ThemeRule[];
|
|
40
|
+
}
|
|
41
|
+
export interface ResolverOptions {
|
|
42
|
+
enabledCultures?: string[];
|
|
43
|
+
userRegion?: string;
|
|
44
|
+
}
|
|
45
|
+
export interface ResolvedTheme {
|
|
46
|
+
name: string;
|
|
47
|
+
category: 'seasonal' | 'holidays' | 'cultural' | 'everyday';
|
|
48
|
+
rule: DateRule;
|
|
49
|
+
metadata?: {
|
|
50
|
+
actualDate?: string;
|
|
51
|
+
description?: string;
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Resolve themes for a specific date
|
|
56
|
+
*
|
|
57
|
+
* @param date - The date to resolve themes for
|
|
58
|
+
* @param options - Optional filters for cultural themes and region
|
|
59
|
+
* @returns Array of resolved themes, sorted by specificity (shortest duration first)
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```typescript
|
|
63
|
+
* const themes = resolveThemesForDate(
|
|
64
|
+
* new Date('2025-12-25'),
|
|
65
|
+
* { enabledCultures: ['diwali'], userRegion: 'india' }
|
|
66
|
+
* );
|
|
67
|
+
* // Returns: [{ name: 'christmas', category: 'seasonal', metadata: {...} }]
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export declare function resolveThemesForDate(date: Date, options?: ResolverOptions): ResolvedTheme[];
|
|
71
|
+
/**
|
|
72
|
+
* Get the primary (most specific) theme for a date
|
|
73
|
+
*
|
|
74
|
+
* @param date - The date to resolve theme for
|
|
75
|
+
* @param options - Optional filters for cultural themes and region
|
|
76
|
+
* @returns The most specific matching theme, or null if none found
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```typescript
|
|
80
|
+
* const theme = resolvePrimaryThemeForDate(new Date('2025-10-31'));
|
|
81
|
+
* // Returns: { name: 'halloween', category: 'holidays', metadata: {...} }
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export declare function resolvePrimaryThemeForDate(date: Date, options?: ResolverOptions): ResolvedTheme | null;
|
package/dist/resolver.js
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme Resolver Per Day - Standalone Module
|
|
3
|
+
*
|
|
4
|
+
* A reusable module for resolving themes based on dates.
|
|
5
|
+
* Configuration is included internally - just pass a date and get themes.
|
|
6
|
+
*/
|
|
7
|
+
import themeRulesConfigJson from './themeRules.json';
|
|
8
|
+
const themeRulesConfig = themeRulesConfigJson;
|
|
9
|
+
/**
|
|
10
|
+
* Resolve themes for a specific date
|
|
11
|
+
*
|
|
12
|
+
* @param date - The date to resolve themes for
|
|
13
|
+
* @param options - Optional filters for cultural themes and region
|
|
14
|
+
* @returns Array of resolved themes, sorted by specificity (shortest duration first)
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* const themes = resolveThemesForDate(
|
|
19
|
+
* new Date('2025-12-25'),
|
|
20
|
+
* { enabledCultures: ['diwali'], userRegion: 'india' }
|
|
21
|
+
* );
|
|
22
|
+
* // Returns: [{ name: 'christmas', category: 'seasonal', metadata: {...} }]
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export function resolveThemesForDate(date, options = {}) {
|
|
26
|
+
const { enabledCultures = [], userRegion } = options;
|
|
27
|
+
const year = date.getFullYear();
|
|
28
|
+
// Use internal config and flatten with category
|
|
29
|
+
const config = themeRulesConfig;
|
|
30
|
+
const allRules = [
|
|
31
|
+
...config.seasonal.map(rule => ({ ...rule, category: 'seasonal' })),
|
|
32
|
+
...config.holidays.map(rule => ({ ...rule, category: 'holidays' })),
|
|
33
|
+
...config.cultural.map(rule => ({ ...rule, category: 'cultural' })),
|
|
34
|
+
...config.everyday.map(rule => ({ ...rule, category: 'everyday' }))
|
|
35
|
+
];
|
|
36
|
+
// Filter available themes based on enabled cultures and region
|
|
37
|
+
const availableRules = allRules.filter(rule => {
|
|
38
|
+
// Skip everyday theme in first pass
|
|
39
|
+
if (rule.name === 'everyday') {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
// Always include non-cultural themes (enabled undefined or true)
|
|
43
|
+
if (rule.enabled === undefined || rule.enabled === true) {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
// For cultural themes, check if they're enabled
|
|
47
|
+
if (rule.enabled === false) {
|
|
48
|
+
return enabledCultures.includes(rule.name) ||
|
|
49
|
+
(rule.region && userRegion && rule.region.includes(userRegion));
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
52
|
+
});
|
|
53
|
+
// Find all matching themes
|
|
54
|
+
const matchingThemes = [];
|
|
55
|
+
for (const rule of availableRules) {
|
|
56
|
+
if (isRuleActive(rule.rule, date, year)) {
|
|
57
|
+
matchingThemes.push({
|
|
58
|
+
name: rule.name,
|
|
59
|
+
category: rule.category,
|
|
60
|
+
rule: rule.rule,
|
|
61
|
+
metadata: rule.metadata
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// Sort by duration (shortest first = most specific)
|
|
66
|
+
matchingThemes.sort((a, b) => {
|
|
67
|
+
const ruleA = allRules.find(r => r.name === a.name);
|
|
68
|
+
const ruleB = allRules.find(r => r.name === b.name);
|
|
69
|
+
const durationA = getThemeDuration(ruleA.rule, year);
|
|
70
|
+
const durationB = getThemeDuration(ruleB.rule, year);
|
|
71
|
+
return durationA - durationB;
|
|
72
|
+
});
|
|
73
|
+
// If no themes match, return everyday theme
|
|
74
|
+
if (matchingThemes.length === 0) {
|
|
75
|
+
const everydayRule = config.everyday.find(r => r.name === 'everyday');
|
|
76
|
+
if (everydayRule) {
|
|
77
|
+
matchingThemes.push({
|
|
78
|
+
name: 'everyday',
|
|
79
|
+
category: 'everyday',
|
|
80
|
+
rule: everydayRule.rule,
|
|
81
|
+
metadata: everydayRule.metadata
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return matchingThemes;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Get the primary (most specific) theme for a date
|
|
89
|
+
*
|
|
90
|
+
* @param date - The date to resolve theme for
|
|
91
|
+
* @param options - Optional filters for cultural themes and region
|
|
92
|
+
* @returns The most specific matching theme, or null if none found
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```typescript
|
|
96
|
+
* const theme = resolvePrimaryThemeForDate(new Date('2025-10-31'));
|
|
97
|
+
* // Returns: { name: 'halloween', category: 'holidays', metadata: {...} }
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
export function resolvePrimaryThemeForDate(date, options = {}) {
|
|
101
|
+
const themes = resolveThemesForDate(date, options);
|
|
102
|
+
return themes.length > 0 ? themes[0] : null;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Check if a date rule is active for a given date
|
|
106
|
+
*/
|
|
107
|
+
function isRuleActive(rule, date, year) {
|
|
108
|
+
switch (rule.kind) {
|
|
109
|
+
case 'range':
|
|
110
|
+
return isInRange(rule.from, rule.to, date);
|
|
111
|
+
case 'holiday-offset':
|
|
112
|
+
return isHolidayOffsetActive(rule.holiday, rule.start, rule.end, date, year);
|
|
113
|
+
case 'nth-weekday':
|
|
114
|
+
return isNthWeekdayActive(rule.month, rule.weekday, rule.n, rule.duration || 1, date);
|
|
115
|
+
case 'always':
|
|
116
|
+
return true;
|
|
117
|
+
default:
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Check if a date falls within a range
|
|
123
|
+
*/
|
|
124
|
+
function isInRange(from, to, date) {
|
|
125
|
+
const month = date.getMonth() + 1;
|
|
126
|
+
const day = date.getDate();
|
|
127
|
+
const currentDate = `${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`;
|
|
128
|
+
// Handle year wrap-around (e.g., Christmas: 12-26 to 01-07)
|
|
129
|
+
if (from <= to) {
|
|
130
|
+
return currentDate >= from && currentDate <= to;
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
return currentDate >= from || currentDate <= to;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Check if a date is within a holiday offset range
|
|
138
|
+
*/
|
|
139
|
+
function isHolidayOffsetActive(holiday, startOffset, endOffset, date, year) {
|
|
140
|
+
let holidayDate;
|
|
141
|
+
switch (holiday) {
|
|
142
|
+
case 'easter':
|
|
143
|
+
holidayDate = calculateEaster(year);
|
|
144
|
+
break;
|
|
145
|
+
default:
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
const startDate = new Date(holidayDate);
|
|
149
|
+
startDate.setDate(startDate.getDate() + startOffset);
|
|
150
|
+
const endDate = new Date(holidayDate);
|
|
151
|
+
endDate.setDate(endDate.getDate() + endOffset);
|
|
152
|
+
return date >= startDate && date <= endDate;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Check if a date matches the nth weekday of a month
|
|
156
|
+
*/
|
|
157
|
+
function isNthWeekdayActive(month, weekday, n, duration = 1, date) {
|
|
158
|
+
const year = date.getFullYear();
|
|
159
|
+
// Find the nth occurrence of the weekday in the month
|
|
160
|
+
let count = 0;
|
|
161
|
+
let targetDate = null;
|
|
162
|
+
for (let day = 1; day <= 31; day++) {
|
|
163
|
+
const testDate = new Date(year, month - 1, day);
|
|
164
|
+
if (testDate.getMonth() !== month - 1)
|
|
165
|
+
break;
|
|
166
|
+
if (testDate.getDay() === weekday) {
|
|
167
|
+
count++;
|
|
168
|
+
if (count === n) {
|
|
169
|
+
targetDate = testDate;
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (!targetDate)
|
|
175
|
+
return false;
|
|
176
|
+
// If duration is specified, check if date is within the duration window
|
|
177
|
+
if (duration > 1) {
|
|
178
|
+
const halfDuration = Math.floor(duration / 2);
|
|
179
|
+
const startDate = new Date(targetDate);
|
|
180
|
+
startDate.setDate(startDate.getDate() - halfDuration);
|
|
181
|
+
const endDate = new Date(targetDate);
|
|
182
|
+
endDate.setDate(endDate.getDate() + (duration - halfDuration - 1));
|
|
183
|
+
return date >= startDate && date <= endDate;
|
|
184
|
+
}
|
|
185
|
+
// Single day event - exact match
|
|
186
|
+
return date.toDateString() === targetDate.toDateString();
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Calculate Easter date using Gauss algorithm
|
|
190
|
+
*/
|
|
191
|
+
function calculateEaster(year) {
|
|
192
|
+
const a = year % 19;
|
|
193
|
+
const b = Math.floor(year / 100);
|
|
194
|
+
const c = year % 100;
|
|
195
|
+
const d = Math.floor(b / 4);
|
|
196
|
+
const e = b % 4;
|
|
197
|
+
const f = Math.floor((b + 8) / 25);
|
|
198
|
+
const g = Math.floor((b - f + 1) / 3);
|
|
199
|
+
const h = (19 * a + b - d - g + 15) % 30;
|
|
200
|
+
const i = Math.floor(c / 4);
|
|
201
|
+
const k = c % 4;
|
|
202
|
+
const l = (32 + 2 * e + 2 * i - h - k) % 7;
|
|
203
|
+
const m = Math.floor((a + 11 * h + 22 * l) / 451);
|
|
204
|
+
const month = Math.floor((h + l - 7 * m + 114) / 31);
|
|
205
|
+
const day = ((h + l - 7 * m + 114) % 31) + 1;
|
|
206
|
+
return new Date(year, month - 1, day);
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Calculate theme duration in days
|
|
210
|
+
*/
|
|
211
|
+
function getThemeDuration(rule, year) {
|
|
212
|
+
switch (rule.kind) {
|
|
213
|
+
case 'range': {
|
|
214
|
+
const [fromMonth, fromDay] = rule.from.split('-').map(Number);
|
|
215
|
+
const [toMonth, toDay] = rule.to.split('-').map(Number);
|
|
216
|
+
const startDate = new Date(year, fromMonth - 1, fromDay);
|
|
217
|
+
const endDate = new Date(year, toMonth - 1, toDay);
|
|
218
|
+
// Handle year wrap-around
|
|
219
|
+
if (endDate < startDate) {
|
|
220
|
+
endDate.setFullYear(year + 1);
|
|
221
|
+
}
|
|
222
|
+
const diffTime = endDate.getTime() - startDate.getTime();
|
|
223
|
+
return Math.ceil(diffTime / (1000 * 60 * 60 * 24)) + 1;
|
|
224
|
+
}
|
|
225
|
+
case 'holiday-offset': {
|
|
226
|
+
return Math.abs(rule.end - rule.start) + 1;
|
|
227
|
+
}
|
|
228
|
+
case 'nth-weekday': {
|
|
229
|
+
return rule.duration || 1;
|
|
230
|
+
}
|
|
231
|
+
case 'always': {
|
|
232
|
+
return Infinity;
|
|
233
|
+
}
|
|
234
|
+
default:
|
|
235
|
+
return Infinity;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
{
|
|
2
|
+
"seasonal": [
|
|
3
|
+
{
|
|
4
|
+
"name": "new-year",
|
|
5
|
+
"rule": {
|
|
6
|
+
"kind": "range",
|
|
7
|
+
"from": "12-26",
|
|
8
|
+
"to": "01-07"
|
|
9
|
+
},
|
|
10
|
+
"metadata": {
|
|
11
|
+
"actualDate": "01-01",
|
|
12
|
+
"description": "New Year celebration period"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"name": "christmas",
|
|
17
|
+
"rule": {
|
|
18
|
+
"kind": "range",
|
|
19
|
+
"from": "11-25",
|
|
20
|
+
"to": "12-25"
|
|
21
|
+
},
|
|
22
|
+
"metadata": {
|
|
23
|
+
"actualDate": "12-25",
|
|
24
|
+
"description": "Christmas season"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"name": "winter",
|
|
29
|
+
"rule": {
|
|
30
|
+
"kind": "range",
|
|
31
|
+
"from": "01-08",
|
|
32
|
+
"to": "02-28"
|
|
33
|
+
},
|
|
34
|
+
"metadata": {
|
|
35
|
+
"description": "Winter season after New Year"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"name": "spring-beginning",
|
|
40
|
+
"rule": {
|
|
41
|
+
"kind": "range",
|
|
42
|
+
"from": "03-01",
|
|
43
|
+
"to": "03-20"
|
|
44
|
+
},
|
|
45
|
+
"metadata": {
|
|
46
|
+
"description": "Early spring period"
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"name": "spring",
|
|
51
|
+
"rule": {
|
|
52
|
+
"kind": "range",
|
|
53
|
+
"from": "03-21",
|
|
54
|
+
"to": "05-31"
|
|
55
|
+
},
|
|
56
|
+
"metadata": {
|
|
57
|
+
"description": "Spring season"
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"name": "summer-beginning",
|
|
62
|
+
"rule": {
|
|
63
|
+
"kind": "range",
|
|
64
|
+
"from": "06-01",
|
|
65
|
+
"to": "06-20"
|
|
66
|
+
},
|
|
67
|
+
"metadata": {
|
|
68
|
+
"description": "Early summer period"
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"name": "summer-holiday",
|
|
73
|
+
"rule": {
|
|
74
|
+
"kind": "range",
|
|
75
|
+
"from": "06-21",
|
|
76
|
+
"to": "08-31"
|
|
77
|
+
},
|
|
78
|
+
"metadata": {
|
|
79
|
+
"description": "Summer vacation period"
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"name": "back-to-school",
|
|
84
|
+
"rule": {
|
|
85
|
+
"kind": "range",
|
|
86
|
+
"from": "08-15",
|
|
87
|
+
"to": "09-15"
|
|
88
|
+
},
|
|
89
|
+
"metadata": {
|
|
90
|
+
"description": "Back to school season"
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"name": "autumn",
|
|
95
|
+
"rule": {
|
|
96
|
+
"kind": "range",
|
|
97
|
+
"from": "09-16",
|
|
98
|
+
"to": "11-24"
|
|
99
|
+
},
|
|
100
|
+
"metadata": {
|
|
101
|
+
"description": "Autumn/Fall season"
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
],
|
|
105
|
+
"holidays": [
|
|
106
|
+
{
|
|
107
|
+
"name": "halloween",
|
|
108
|
+
"rule": {
|
|
109
|
+
"kind": "range",
|
|
110
|
+
"from": "10-20",
|
|
111
|
+
"to": "10-31"
|
|
112
|
+
},
|
|
113
|
+
"metadata": {
|
|
114
|
+
"actualDate": "10-31",
|
|
115
|
+
"description": "Halloween celebration"
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"name": "thanksgiving",
|
|
120
|
+
"rule": {
|
|
121
|
+
"kind": "nth-weekday",
|
|
122
|
+
"month": 11,
|
|
123
|
+
"weekday": 4,
|
|
124
|
+
"n": 4,
|
|
125
|
+
"duration": 7
|
|
126
|
+
},
|
|
127
|
+
"metadata": {
|
|
128
|
+
"description": "Thanksgiving (4th Thursday of November)"
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"name": "easter",
|
|
133
|
+
"rule": {
|
|
134
|
+
"kind": "holiday-offset",
|
|
135
|
+
"holiday": "easter",
|
|
136
|
+
"start": -7,
|
|
137
|
+
"end": 1
|
|
138
|
+
},
|
|
139
|
+
"metadata": {
|
|
140
|
+
"description": "Easter celebration (calculated date)"
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"name": "carnival",
|
|
145
|
+
"rule": {
|
|
146
|
+
"kind": "holiday-offset",
|
|
147
|
+
"holiday": "easter",
|
|
148
|
+
"start": -49,
|
|
149
|
+
"end": -46
|
|
150
|
+
},
|
|
151
|
+
"metadata": {
|
|
152
|
+
"description": "Carnival/Mardi Gras (before Easter)"
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
],
|
|
156
|
+
"cultural": [
|
|
157
|
+
{
|
|
158
|
+
"name": "diwali",
|
|
159
|
+
"rule": {
|
|
160
|
+
"kind": "range",
|
|
161
|
+
"from": "10-20",
|
|
162
|
+
"to": "10-25"
|
|
163
|
+
},
|
|
164
|
+
"enabled": false,
|
|
165
|
+
"region": ["india", "global"],
|
|
166
|
+
"metadata": {
|
|
167
|
+
"description": "Festival of Lights (approximate dates)"
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"name": "chinese-new-year",
|
|
172
|
+
"rule": {
|
|
173
|
+
"kind": "range",
|
|
174
|
+
"from": "01-21",
|
|
175
|
+
"to": "02-20"
|
|
176
|
+
},
|
|
177
|
+
"enabled": false,
|
|
178
|
+
"region": ["china", "global"],
|
|
179
|
+
"metadata": {
|
|
180
|
+
"description": "Chinese New Year (approximate dates)"
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
"name": "hanukkah",
|
|
185
|
+
"rule": {
|
|
186
|
+
"kind": "range",
|
|
187
|
+
"from": "11-25",
|
|
188
|
+
"to": "12-31"
|
|
189
|
+
},
|
|
190
|
+
"enabled": false,
|
|
191
|
+
"region": ["global"],
|
|
192
|
+
"metadata": {
|
|
193
|
+
"description": "Hanukkah (approximate dates)"
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
"name": "ramadan",
|
|
198
|
+
"rule": {
|
|
199
|
+
"kind": "range",
|
|
200
|
+
"from": "03-01",
|
|
201
|
+
"to": "04-30"
|
|
202
|
+
},
|
|
203
|
+
"enabled": false,
|
|
204
|
+
"region": ["global"],
|
|
205
|
+
"metadata": {
|
|
206
|
+
"description": "Ramadan (approximate dates, varies by lunar calendar)"
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
"name": "eid-celebration",
|
|
211
|
+
"rule": {
|
|
212
|
+
"kind": "range",
|
|
213
|
+
"from": "04-01",
|
|
214
|
+
"to": "05-15"
|
|
215
|
+
},
|
|
216
|
+
"enabled": false,
|
|
217
|
+
"region": ["global"],
|
|
218
|
+
"metadata": {
|
|
219
|
+
"description": "Eid celebration (approximate dates)"
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
"name": "holi",
|
|
224
|
+
"rule": {
|
|
225
|
+
"kind": "range",
|
|
226
|
+
"from": "02-25",
|
|
227
|
+
"to": "03-25"
|
|
228
|
+
},
|
|
229
|
+
"enabled": false,
|
|
230
|
+
"region": ["india", "global"],
|
|
231
|
+
"metadata": {
|
|
232
|
+
"description": "Holi - Festival of Colors (approximate dates)"
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
],
|
|
236
|
+
"everyday": [
|
|
237
|
+
{
|
|
238
|
+
"name": "everyday",
|
|
239
|
+
"rule": {
|
|
240
|
+
"kind": "always"
|
|
241
|
+
},
|
|
242
|
+
"metadata": {
|
|
243
|
+
"description": "Default fallback theme"
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
]
|
|
247
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@akatan/date-to-festive",
|
|
3
|
+
"version": "1.0.6",
|
|
4
|
+
"description": "A standalone, reusable module for resolving themes based on dates",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"module": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"dev": "tsc --watch",
|
|
16
|
+
"clean": "rimraf dist",
|
|
17
|
+
"prepublishOnly": "npm run clean && npm run build"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"theme",
|
|
21
|
+
"date",
|
|
22
|
+
"resolver",
|
|
23
|
+
"seasonal",
|
|
24
|
+
"holidays",
|
|
25
|
+
"cultural"
|
|
26
|
+
],
|
|
27
|
+
"author": "AkatanHQ",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+ssh://git@github.com/AkatanHQ/date-to-festive.git"
|
|
32
|
+
},
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://github.com/AkatanHQ/date-to-festive/issues"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://github.com/AkatanHQ/date-to-festive#readme",
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"rimraf": "^6.1.3",
|
|
39
|
+
"typescript": "^5.0.0"
|
|
40
|
+
},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=14.0.0"
|
|
43
|
+
}
|
|
44
|
+
}
|