UNPKG

@tiledesk/tiledesk-server

Version:
173 lines (145 loc) 6.3 kB
const moment = require('moment-timezone'); class DatesUtil { /** * Helper function to parse a date with optional time * Supports formats: DD/MM/YYYY, DD/MM/YYYY HH:mm, DD/MM/YYYY HH:mm:ss, DD/MM/YYYY, HH:mm:ss * * @param {string} dateString - Date string with or without time * @param {string} timezone - Timezone * @returns {moment.Moment} Parsed moment object */ parseDate(dateString, timezone) { if (!dateString || typeof dateString !== 'string') { throw new Error(`The date must be a string: ${dateString}`); } if (!timezone || typeof timezone !== 'string') { throw new Error(`The timezone must be a string: ${timezone}`); } // Validate timezone: check if it's a valid IANA timezone if (!moment.tz.zone(timezone)) { throw new Error(`Invalid timezone: ${timezone}. Please use a valid IANA timezone (e.g., "Europe/Rome", "America/New_York")`); } const trimmedDate = dateString.trim(); // List of possible formats to try const formats = [ 'DD/MM/YYYY HH:mm:ss', 'DD/MM/YYYY, HH:mm:ss', 'DD/MM/YYYY HH:mm', 'DD/MM/YYYY, HH:mm', 'DD/MM/YYYY', 'YYYY/MM/DD HH:mm:ss', 'YYYY/MM/DD, HH:mm:ss', 'YYYY/MM/DD HH:mm', 'YYYY/MM/DD, HH:mm', 'YYYY/MM/DD' ]; // Try every format until one valid is found for (const format of formats) { // Parse the date in the specified format (without timezone) //const parsed = moment(trimmedDate, format, true); // strict mode const parsed = moment.tz(trimmedDate, format, timezone); if (parsed && parsed.isValid && parsed.isValid()) { // Apply the timezone to the parsed date const dateWithTimezone = parsed.tz(timezone); // Verify that the timezone was applied correctly if (!dateWithTimezone || !dateWithTimezone.isValid || !dateWithTimezone.isValid()) { throw new Error(`Unable to apply timezone ${timezone} to date: ${dateString}`); } return dateWithTimezone; } } throw new Error(`Unable to parse the date: ${dateString} with timezone: ${timezone}`); } /** * Checks if a date string contains time information * * @param {string} dateString - Date string * @returns {boolean} True if contains time information */ hasTimeComponent(dateString) { // Checks if contains time information (HH:mm or HH:mm:ss) return /[0-9]{1,2}:[0-9]{2}(:[0-9]{2})?/.test(dateString); } /** * Converts local dates to UTC for database queries * * @param {string|null|undefined} startDate - Start date in the format DD/MM/YYYY or DD/MM/YYYY HH:mm:ss (e.g. "20/01/2026" or "20/01/2026 14:30:00"). Optional. * @param {string|null|undefined} endDate - End date in the format DD/MM/YYYY or DD/MM/YYYY HH:mm:ss (e.g. "20/01/2026" or "20/01/2026 23:59:59"). Optional. * @param {string} timezone - User timezone (e.g. "Europe/Rome", "America/New_York", "Asia/Tokyo") * @returns {Object} Object with startDateUTC and/or endDateUTC in ISO string format for MongoDB */ convertLocalDatesToUTC(startDate, endDate, timezone) { if (!timezone || typeof timezone !== 'string') { throw new Error(`The timezone must be a string: ${timezone}`); } // Validate timezone: check if it's a valid IANA timezone if (!moment.tz.zone(timezone)) { throw new Error(`Invalid timezone: ${timezone}. Please use a valid IANA timezone (e.g., "Europe/Rome", "America/New_York")`); } const result = { startDateUTC: null, endDateUTC: null, startDate: null, endDate: null }; // Parse startDate if provided if (startDate) { const startMoment = this.parseDate(startDate, timezone); // If startDate does not contain time, use the start of the day // Otherwise use the specified time const startParsed = this.hasTimeComponent(startDate) ? startMoment : startMoment.startOf('day'); result.startDateUTC = startParsed.utc().toISOString(); result.startDate = startParsed.utc().toDate(); } // Parse endDate if provided if (endDate) { const endMoment = this.parseDate(endDate, timezone); // If endDate does not contain time, use the start of the next day // Otherwise use the specified time const endParsed = this.hasTimeComponent(endDate) ? endMoment : endMoment.clone().add(1, 'day').startOf('day'); result.endDateUTC = endParsed.utc().toISOString(); result.endDate = endParsed.utc().toDate(); } // At least one date must be provided if (!startDate && !endDate) { throw new Error('At least one of startDate or endDate must be provided'); } return result; } /** * Creates a MongoDB query object with the converted dates in UTC * * @param {string|null|undefined} startDate - Start date in the format DD/MM/YYYY or DD/MM/YYYY HH:mm:ss. Optional (from this date onwards). * @param {string|null|undefined} endDate - End date in the format DD/MM/YYYY or DD/MM/YYYY HH:mm:ss. Optional (until this date). * @param {string} timezone - User timezone * @param {string} fieldName - Field name in the database (default: "createdAt") * @returns {Object} MongoDB query object with $gte (if startDate provided), $lt/$lte (if endDate provided), or both */ createDateRangeQuery(startDate, endDate, timezone, fieldName = 'createdAt') { const { startDate: start, endDate: end } = this.convertLocalDatesToUTC(startDate, endDate, timezone); const query = {}; const dateQuery = {}; // Add start date condition (from this date onwards) if (start) { dateQuery.$gte = start; } // Add end date condition (until this date) if (end) { // If endDate contains time, use $lte to include up to that time // Otherwise use $lt to exclude the start of the next day const endOperator = endDate && this.hasTimeComponent(endDate) ? '$lte' : '$lt'; dateQuery[endOperator] = end; } // Only add the date query if at least one condition was set if (Object.keys(dateQuery).length > 0) { query[fieldName] = dateQuery; } return query; } } const datesUtil = new DatesUtil(); module.exports = datesUtil;