@tmlmobilidade/dates 20260121.2332.6 → 20260128.2248.28

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.
@@ -0,0 +1,81 @@
1
+ import { Dates } from './dates.js';
2
+ import { IsoWeekday } from '@tmlmobilidade/types';
3
+ import { TimezoneIdentified } from './types.js';
4
+ export declare const DEFAULT_CAL_TZ = "Europe/Lisbon";
5
+ export type CalendarKey = `${number}-${number}-${number}`;
6
+ export interface CalendarDay {
7
+ calendarKey: CalendarKey;
8
+ date: Dates;
9
+ dayOfMonth: number;
10
+ dayOfWeek: number;
11
+ isCurrentMonth: boolean;
12
+ isToday: boolean;
13
+ isWeekend: boolean;
14
+ }
15
+ export interface MonthGrid {
16
+ days: CalendarDay[];
17
+ month: number;
18
+ monthName: string;
19
+ weeks: CalendarDay[][];
20
+ year: number;
21
+ }
22
+ /**
23
+ * Canonical calendar day key (civil date) in a timezone.
24
+ * This is the ONE key you should use for calendar UI, selections, holidays, event mapping, etc.
25
+ */
26
+ export declare function calendarKey(d: Dates, tz?: TimezoneIdentified): CalendarKey;
27
+ /**
28
+ * CalendarKey -> Dates at noon Lisbon (stable for civil-day iteration)
29
+ */
30
+ export declare function datesFromCalendarKey(key: CalendarKey): Dates;
31
+ /**
32
+ * Extracts the month and year from a given calendar key string.
33
+ *
34
+ * @param key - The calendar key in the format 'YYYY-MM' or similar.
35
+ * @returns An object containing the numeric `month` and `year` extracted from the key.
36
+ */
37
+ export declare function monthYearFromKey(key: CalendarKey): {
38
+ month: number;
39
+ year: number;
40
+ };
41
+ /**
42
+ * Parse a string/Date/Dates into a calendar key.
43
+ * - "YYYY-MM-DD" is parsed at 12:00 in tz (avoids DST edge cases + avoids operational cutoff semantics)
44
+ * - ISO strings keep their offset and are then represented in tz
45
+ */
46
+ export declare function parseCalendarKey(input: Date | Dates | string, tz?: TimezoneIdentified): CalendarKey;
47
+ /**
48
+ * Convert CalendarKey "YYYY-MM-DD" to "YYYYMMDD" (useful for comparing with period.dates that are stored as yyyyMMdd,
49
+ * but semantically represent civil days).
50
+ */
51
+ export declare function keyToYYYYMMDD(key: CalendarKey): string;
52
+ /**
53
+ * Convert "YYYYMMDD" to CalendarKey "YYYY-MM-DD".
54
+ */
55
+ export declare function yyyymmddToKey(yyyymmdd: string): CalendarKey;
56
+ /**
57
+ * ISO weekday (1..7 Mon..Sun) for a calendar key or Dates (civil day).
58
+ */
59
+ export declare function calendarWeekday(input: CalendarKey | Dates, tz?: TimezoneIdentified): IsoWeekday;
60
+ export declare function isWeekend(input: CalendarKey | Dates, tz?: TimezoneIdentified): boolean;
61
+ /**
62
+ * Iterate calendar day keys from start to end inclusive (civil days).
63
+ */
64
+ export declare function eachCalendarDay(start: CalendarKey, end: CalendarKey, tz?: TimezoneIdentified): CalendarKey[];
65
+ /**
66
+ * Inclusive range check for civil calendar days.
67
+ */
68
+ export declare function isKeyInRange(key: CalendarKey, start: CalendarKey, end?: CalendarKey): boolean;
69
+ /**
70
+ * Generates a calendar grid for a specific month, including overflow days from previous and next months.
71
+ * IMPORTANT: This grid is based on civil days, not operational days.
72
+ */
73
+ export declare function generateMonthGrid(year: number, month: number, fixedWeeks?: boolean, tz?: TimezoneIdentified): MonthGrid;
74
+ export declare function getNextMonth(year: number, month: number): {
75
+ month: number;
76
+ year: number;
77
+ };
78
+ export declare function getPreviousMonth(year: number, month: number): {
79
+ month: number;
80
+ year: number;
81
+ };
@@ -0,0 +1,169 @@
1
+ /* * */
2
+ import { Dates } from './dates.js';
3
+ /* * */
4
+ export const DEFAULT_CAL_TZ = 'Europe/Lisbon';
5
+ /* * */
6
+ /* Calendar day helpers (civil days, not operational days) */
7
+ /* * */
8
+ /**
9
+ * Canonical calendar day key (civil date) in a timezone.
10
+ * This is the ONE key you should use for calendar UI, selections, holidays, event mapping, etc.
11
+ */
12
+ export function calendarKey(d, tz = DEFAULT_CAL_TZ) {
13
+ return d.setZone(tz, 'offset_only').toFormat('yyyy-MM-dd');
14
+ }
15
+ /**
16
+ * CalendarKey -> Dates at noon Lisbon (stable for civil-day iteration)
17
+ */
18
+ export function datesFromCalendarKey(key) {
19
+ return Dates.fromFormat(`${key} 12:00`, 'yyyy-MM-dd HH:mm', DEFAULT_CAL_TZ);
20
+ }
21
+ /**
22
+ * Extracts the month and year from a given calendar key string.
23
+ *
24
+ * @param key - The calendar key in the format 'YYYY-MM' or similar.
25
+ * @returns An object containing the numeric `month` and `year` extracted from the key.
26
+ */
27
+ export function monthYearFromKey(key) {
28
+ return { month: Number(key.slice(5, 7)), year: Number(key.slice(0, 4)) };
29
+ }
30
+ /**
31
+ * Parse a string/Date/Dates into a calendar key.
32
+ * - "YYYY-MM-DD" is parsed at 12:00 in tz (avoids DST edge cases + avoids operational cutoff semantics)
33
+ * - ISO strings keep their offset and are then represented in tz
34
+ */
35
+ export function parseCalendarKey(input, tz = DEFAULT_CAL_TZ) {
36
+ if (input instanceof Dates)
37
+ return calendarKey(input, tz);
38
+ if (input instanceof Date) {
39
+ return calendarKey(Dates.fromJSDate(input).setZone(tz, 'offset_only'), tz);
40
+ }
41
+ if (/^\d{4}-\d{2}-\d{2}$/.test(input)) {
42
+ return calendarKey(Dates.fromFormat(`${input} 12:00`, 'yyyy-MM-dd HH:mm', tz), tz);
43
+ }
44
+ return calendarKey(Dates.fromISO(input).setZone(tz, 'offset_only'), tz);
45
+ }
46
+ /**
47
+ * Convert CalendarKey "YYYY-MM-DD" to "YYYYMMDD" (useful for comparing with period.dates that are stored as yyyyMMdd,
48
+ * but semantically represent civil days).
49
+ */
50
+ export function keyToYYYYMMDD(key) {
51
+ return key.replace(/-/g, '');
52
+ }
53
+ /**
54
+ * Convert "YYYYMMDD" to CalendarKey "YYYY-MM-DD".
55
+ */
56
+ export function yyyymmddToKey(yyyymmdd) {
57
+ return `${yyyymmdd.slice(0, 4)}-${yyyymmdd.slice(4, 6)}-${yyyymmdd.slice(6, 8)}`;
58
+ }
59
+ /**
60
+ * ISO weekday (1..7 Mon..Sun) for a calendar key or Dates (civil day).
61
+ */
62
+ export function calendarWeekday(input, tz = DEFAULT_CAL_TZ) {
63
+ const key = input instanceof Dates ? calendarKey(input, tz) : input;
64
+ const d = Dates.fromFormat(`${key} 12:00`, 'yyyy-MM-dd HH:mm', tz);
65
+ return Number(d.toFormat('E'));
66
+ }
67
+ export function isWeekend(input, tz = DEFAULT_CAL_TZ) {
68
+ const wd = calendarWeekday(input, tz);
69
+ return wd === 6 || wd === 7;
70
+ }
71
+ /**
72
+ * Iterate calendar day keys from start to end inclusive (civil days).
73
+ */
74
+ export function eachCalendarDay(start, end, tz = DEFAULT_CAL_TZ) {
75
+ const out = [];
76
+ let current = Dates.fromFormat(`${start} 12:00`, 'yyyy-MM-dd HH:mm', tz);
77
+ const last = Dates.fromFormat(`${end} 12:00`, 'yyyy-MM-dd HH:mm', tz);
78
+ while (current.unix_timestamp <= last.unix_timestamp) {
79
+ out.push(calendarKey(current, tz));
80
+ current = current.plus({ days: 1 });
81
+ }
82
+ return out;
83
+ }
84
+ /**
85
+ * Inclusive range check for civil calendar days.
86
+ */
87
+ export function isKeyInRange(key, start, end) {
88
+ if (!end)
89
+ return key === start;
90
+ return key >= start && key <= end;
91
+ }
92
+ /* * */
93
+ /* Month grid generation (Monday-first, civil calendar) */
94
+ /* * */
95
+ /**
96
+ * Generates a calendar grid for a specific month, including overflow days from previous and next months.
97
+ * IMPORTANT: This grid is based on civil days, not operational days.
98
+ */
99
+ export function generateMonthGrid(year, month, fixedWeeks = false, tz = DEFAULT_CAL_TZ) {
100
+ // Create the first day of the target month at noon in tz to avoid DST edge cases
101
+ const firstDayOfMonth = Dates.fromFormat(`${year}-${String(month).padStart(2, '0')}-01 12:00`, 'yyyy-MM-dd HH:mm', tz);
102
+ const lastDayOfMonth = firstDayOfMonth.endOf('month');
103
+ // Today (civil)
104
+ const todayKey = calendarKey(Dates.now(tz), tz);
105
+ // ISO weekday for first day (1=Mon..7=Sun)
106
+ const firstDayOfWeekNumber = Number(firstDayOfMonth.toFormat('c'));
107
+ const daysFromPrevMonth = firstDayOfWeekNumber - 1; // Monday-first
108
+ // Trailing days
109
+ const lastDayOfWeekNumber = Number(lastDayOfMonth.toFormat('c')); // 1..7
110
+ const naturalDaysFromNextMonth = (7 - lastDayOfWeekNumber) % 7; // Sunday -> 0
111
+ const days = [];
112
+ const pushDay = (date, isCurrentMonth) => {
113
+ const key = calendarKey(date, tz);
114
+ const dow = Number(date.toFormat('c')); // 1..7
115
+ days.push({
116
+ calendarKey: key,
117
+ date,
118
+ dayOfMonth: Number(date.toFormat('d')),
119
+ dayOfWeek: dow,
120
+ isCurrentMonth,
121
+ isToday: key === todayKey,
122
+ isWeekend: dow === 6 || dow === 7,
123
+ });
124
+ };
125
+ // Previous month overflow
126
+ if (daysFromPrevMonth > 0) {
127
+ const prevMonthLastDay = firstDayOfMonth.minus({ days: 1 });
128
+ const prevMonthStart = prevMonthLastDay.minus({ days: daysFromPrevMonth - 1 });
129
+ for (let i = 0; i < daysFromPrevMonth; i++) {
130
+ pushDay(prevMonthStart.plus({ days: i }), false);
131
+ }
132
+ }
133
+ // Current month
134
+ const daysInMonth = Number(lastDayOfMonth.toFormat('d'));
135
+ for (let d = 1; d <= daysInMonth; d++) {
136
+ pushDay(firstDayOfMonth.plus({ days: d - 1 }), true);
137
+ }
138
+ // Next month overflow
139
+ const daysFromNextMonth = fixedWeeks ? 42 - days.length : naturalDaysFromNextMonth;
140
+ for (let i = 1; i <= daysFromNextMonth; i++) {
141
+ pushDay(lastDayOfMonth.plus({ days: i }), false);
142
+ }
143
+ // Weeks (rows)
144
+ const weeks = [];
145
+ for (let i = 0; i < days.length; i += 7) {
146
+ weeks.push(days.slice(i, i + 7));
147
+ }
148
+ const monthName = firstDayOfMonth.toFormat('MMMM');
149
+ return {
150
+ days,
151
+ month,
152
+ monthName: monthName.charAt(0).toUpperCase() + monthName.slice(1),
153
+ weeks,
154
+ year,
155
+ };
156
+ }
157
+ /* * */
158
+ /* Navigation helpers */
159
+ /* * */
160
+ export function getNextMonth(year, month) {
161
+ if (month === 12)
162
+ return { month: 1, year: year + 1 };
163
+ return { month: month + 1, year };
164
+ }
165
+ export function getPreviousMonth(year, month) {
166
+ if (month === 1)
167
+ return { month: 12, year: year - 1 };
168
+ return { month: month - 1, year };
169
+ }
package/dist/dates.d.ts CHANGED
@@ -155,9 +155,12 @@ export declare class Dates {
155
155
  /**
156
156
  * Returns the date as a string in the specified format.
157
157
  * @param format The format string (see Luxon tokens documentation)
158
+ * @param opts Optional formatting options (e.g., { locale: 'pt' })
158
159
  * @returns The date as a string in the specified format
159
160
  */
160
- toFormat(format: string): string;
161
+ toFormat(format: string, opts?: {
162
+ locale?: string;
163
+ }): string;
161
164
  /**
162
165
  * Returns the date as a string in the specified format.
163
166
  * @param format The format string (see Luxon tokens documentation)
package/dist/dates.js CHANGED
@@ -327,13 +327,14 @@ export class Dates {
327
327
  /**
328
328
  * Returns the date as a string in the specified format.
329
329
  * @param format The format string (see Luxon tokens documentation)
330
+ * @param opts Optional formatting options (e.g., { locale: 'pt' })
330
331
  * @returns The date as a string in the specified format
331
332
  */
332
- toFormat(format) {
333
+ toFormat(format, opts) {
333
334
  if (!this.iso)
334
335
  throw new Error('ISO date is not set.');
335
336
  const dateTime = DateTime.fromISO(this.iso, { setZone: true });
336
- return dateTime.toFormat(format);
337
+ return dateTime.setLocale(opts?.locale || 'pt').toFormat(format);
337
338
  }
338
339
  /**
339
340
  * Returns the date as a string in the specified format.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,6 @@
1
+ export * from './calendar.js';
1
2
  export * from './dates.js';
2
3
  export * from './format.js';
4
+ export * from './period-utils.js';
3
5
  export * from './types.js';
4
6
  export * from './utils.js';
package/dist/index.js CHANGED
@@ -1,4 +1,6 @@
1
+ export * from './calendar.js';
1
2
  export * from './dates.js';
2
3
  export * from './format.js';
4
+ export * from './period-utils.js';
3
5
  export * from './types.js';
4
6
  export * from './utils.js';
@@ -0,0 +1,41 @@
1
+ import { OperationalDate } from '@tmlmobilidade/types';
2
+ import { CalendarKey } from './calendar.js';
3
+ /**
4
+ * Converts a date range into an array of CalendarKey strings (YYYY-MM-DD).
5
+ * Returned in sorted order (ascending).
6
+ *
7
+ * @param start - Start day (inclusive) as CalendarKey
8
+ * @param end - End day (inclusive) as CalendarKey
9
+ */
10
+ export declare function convertRangeToKeysArray(start: CalendarKey, end: CalendarKey): CalendarKey[];
11
+ /**
12
+ * Merges two date arrays and returns a sorted, unique array.
13
+ * Duplicates are automatically removed.
14
+ *
15
+ * @param existing - Existing array of operational_date strings
16
+ * @param newDates - New array of operational_date strings to merge
17
+ * @returns Sorted array of unique operational_date strings
18
+ *
19
+ */
20
+ export declare function mergeDateArrays(existing: OperationalDate[], newDates: OperationalDate[]): OperationalDate[];
21
+ /**
22
+ * Removes specified dates from an array.
23
+ * Returns a sorted array with the dates removed.
24
+ *
25
+ * @param array - Original array of operational_date strings
26
+ * @param datesToRemove - Array of operational_date strings to remove
27
+ * @returns Sorted array with specified dates removed
28
+ *
29
+ */
30
+ export declare function removeDatesFromArray(array: OperationalDate[], datesToRemove: OperationalDate[]): OperationalDate[];
31
+ /**
32
+ * Finds common dates between two arrays (intersection).
33
+ * Returns a sorted array of dates that exist in both arrays.
34
+ *
35
+ * @param array1 - First array of operational_date strings
36
+ * @param array2 - Second array of operational_date strings
37
+ * @returns Sorted array of dates found in both arrays
38
+ *
39
+ */
40
+ export declare function findCommonDates(array1: OperationalDate[], array2: OperationalDate[]): OperationalDate[];
41
+ export declare function convertKeysToOperationalDates(keys: CalendarKey[]): OperationalDate[];
@@ -0,0 +1,64 @@
1
+ /* * */
2
+ import { calendarKey, datesFromCalendarKey, keyToYYYYMMDD } from './calendar.js';
3
+ /* * */
4
+ /**
5
+ * Converts a date range into an array of CalendarKey strings (YYYY-MM-DD).
6
+ * Returned in sorted order (ascending).
7
+ *
8
+ * @param start - Start day (inclusive) as CalendarKey
9
+ * @param end - End day (inclusive) as CalendarKey
10
+ */
11
+ export function convertRangeToKeysArray(start, end) {
12
+ const keys = [];
13
+ const from = start < end ? start : end;
14
+ const to = start < end ? end : start;
15
+ let current = datesFromCalendarKey(from);
16
+ const endDate = datesFromCalendarKey(to);
17
+ while (current.unix_timestamp <= endDate.unix_timestamp) {
18
+ keys.push(calendarKey(current));
19
+ current = current.plus({ days: 1 });
20
+ }
21
+ return keys;
22
+ }
23
+ /**
24
+ * Merges two date arrays and returns a sorted, unique array.
25
+ * Duplicates are automatically removed.
26
+ *
27
+ * @param existing - Existing array of operational_date strings
28
+ * @param newDates - New array of operational_date strings to merge
29
+ * @returns Sorted array of unique operational_date strings
30
+ *
31
+ */
32
+ export function mergeDateArrays(existing, newDates) {
33
+ const uniqueDates = new Set([...existing, ...newDates]);
34
+ return Array.from(uniqueDates).sort();
35
+ }
36
+ /**
37
+ * Removes specified dates from an array.
38
+ * Returns a sorted array with the dates removed.
39
+ *
40
+ * @param array - Original array of operational_date strings
41
+ * @param datesToRemove - Array of operational_date strings to remove
42
+ * @returns Sorted array with specified dates removed
43
+ *
44
+ */
45
+ export function removeDatesFromArray(array, datesToRemove) {
46
+ const removeSet = new Set(datesToRemove);
47
+ return array.filter(date => !removeSet.has(date)).sort();
48
+ }
49
+ /**
50
+ * Finds common dates between two arrays (intersection).
51
+ * Returns a sorted array of dates that exist in both arrays.
52
+ *
53
+ * @param array1 - First array of operational_date strings
54
+ * @param array2 - Second array of operational_date strings
55
+ * @returns Sorted array of dates found in both arrays
56
+ *
57
+ */
58
+ export function findCommonDates(array1, array2) {
59
+ const set1 = new Set(array1);
60
+ return array2.filter(date => set1.has(date)).sort();
61
+ }
62
+ export function convertKeysToOperationalDates(keys) {
63
+ return keys.map(k => keyToYYYYMMDD(k));
64
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tmlmobilidade/dates",
3
- "version": "20260121.2332.6",
3
+ "version": "20260128.2248.28",
4
4
  "author": {
5
5
  "email": "iso@tmlmobilidade.pt",
6
6
  "name": "TML-ISO"
@@ -42,7 +42,7 @@
42
42
  "devDependencies": {
43
43
  "@tmlmobilidade/tsconfig": "*",
44
44
  "@tmlmobilidade/types": "*",
45
- "@types/node": "25.0.3",
45
+ "@types/node": "25.1.0",
46
46
  "resolve-tspaths": "0.8.23",
47
47
  "tsc-watch": "7.2.0",
48
48
  "typescript": "5.9.3"