* Add defer until datetime * fixup! Add defer until datetime * fixup! fixup! Add defer until datetime * fixup! fixup! fixup! Add defer until datetime * fixup! fixup! fixup! fixup! Add defer until datetime * fixup! fixup! fixup! fixup! fixup! Add defer until datetime * fixup! fixup! fixup! fixup! fixup! fixup! Add defer until datetime
219 lines
6.9 KiB
JavaScript
219 lines
6.9 KiB
JavaScript
const moment = require('moment-timezone');
|
|
|
|
/**
|
|
* Convert a date string from user timezone to UTC for database storage
|
|
* @param {string} dateString - Date in YYYY-MM-DD format
|
|
* @param {string} userTimezone - User's timezone (e.g., 'America/New_York')
|
|
* @param {string} timeOfDay - Time of day ('start' for 00:00:00, 'end' for 23:59:59)
|
|
* @returns {Date} UTC Date object
|
|
*/
|
|
function dateStringToUTC(dateString, userTimezone, timeOfDay = 'start') {
|
|
if (!dateString) return null;
|
|
|
|
// Create moment in user's timezone at start or end of day
|
|
const momentInUserTz = moment.tz(dateString, userTimezone);
|
|
|
|
if (timeOfDay === 'end') {
|
|
momentInUserTz.endOf('day');
|
|
} else {
|
|
momentInUserTz.startOf('day');
|
|
}
|
|
|
|
// Convert to UTC Date object
|
|
return momentInUserTz.utc().toDate();
|
|
}
|
|
|
|
/**
|
|
* Convert a UTC date to user timezone date string
|
|
* @param {Date} utcDate - UTC Date object
|
|
* @param {string} userTimezone - User's timezone
|
|
* @returns {string} Date string in YYYY-MM-DD format
|
|
*/
|
|
function utcToUserDateString(utcDate, userTimezone) {
|
|
if (!utcDate) return null;
|
|
|
|
return moment.utc(utcDate).tz(userTimezone).format('YYYY-MM-DD');
|
|
}
|
|
|
|
/**
|
|
* Get current date in user's timezone as YYYY-MM-DD string
|
|
* @param {string} userTimezone - User's timezone
|
|
* @returns {string} Today's date in user timezone
|
|
*/
|
|
function getCurrentDateInTimezone(userTimezone) {
|
|
return moment.tz(userTimezone).format('YYYY-MM-DD');
|
|
}
|
|
|
|
/**
|
|
* Get start and end of day in UTC for a given date in user timezone
|
|
* @param {string} dateString - Date in YYYY-MM-DD format
|
|
* @param {string} userTimezone - User's timezone
|
|
* @returns {Object} { start: Date, end: Date } - UTC Date objects
|
|
*/
|
|
function getDayBoundsInUTC(dateString, userTimezone) {
|
|
if (!dateString) return null;
|
|
|
|
const dayStart = dateStringToUTC(dateString, userTimezone, 'start');
|
|
const dayEnd = dateStringToUTC(dateString, userTimezone, 'end');
|
|
|
|
return { start: dayStart, end: dayEnd };
|
|
}
|
|
|
|
/**
|
|
* Get today's bounds in UTC for a user's timezone
|
|
* @param {string} userTimezone - User's timezone
|
|
* @returns {Object} { start: Date, end: Date } - UTC Date objects
|
|
*/
|
|
function getTodayBoundsInUTC(userTimezone) {
|
|
const todayInUserTz = getCurrentDateInTimezone(userTimezone);
|
|
return getDayBoundsInUTC(todayInUserTz, userTimezone);
|
|
}
|
|
|
|
/**
|
|
* Get date range for "upcoming" tasks (next N days) in user timezone
|
|
* Includes today through N days ahead
|
|
* @param {string} userTimezone - User's timezone
|
|
* @param {number} days - Number of days to look ahead (default: 7)
|
|
* @returns {Object} { start: Date, end: Date } - UTC Date objects
|
|
*/
|
|
function getUpcomingRangeInUTC(userTimezone, days = 7) {
|
|
const now = moment.tz(userTimezone).startOf('day');
|
|
const endDate = now.clone().add(days, 'days').endOf('day');
|
|
|
|
return {
|
|
start: now.utc().toDate(),
|
|
end: endDate.utc().toDate(),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Check if a UTC date falls on "today" in user's timezone
|
|
* @param {Date} utcDate - UTC Date to check
|
|
* @param {string} userTimezone - User's timezone
|
|
* @returns {boolean} True if the date is today in user's timezone
|
|
*/
|
|
function isToday(utcDate, userTimezone) {
|
|
if (!utcDate) return false;
|
|
|
|
const todayInUserTz = getCurrentDateInTimezone(userTimezone);
|
|
const dateInUserTz = utcToUserDateString(utcDate, userTimezone);
|
|
|
|
return todayInUserTz === dateInUserTz;
|
|
}
|
|
|
|
/**
|
|
* Check if a UTC date is overdue (before today) in user's timezone
|
|
* @param {Date} utcDate - UTC Date to check
|
|
* @param {string} userTimezone - User's timezone
|
|
* @returns {boolean} True if the date is before today in user's timezone
|
|
*/
|
|
function isOverdue(utcDate, userTimezone) {
|
|
if (!utcDate) return false;
|
|
|
|
const todayInUserTz = getCurrentDateInTimezone(userTimezone);
|
|
const dateInUserTz = utcToUserDateString(utcDate, userTimezone);
|
|
|
|
return dateInUserTz < todayInUserTz;
|
|
}
|
|
|
|
/**
|
|
* Convert due_date from request body to UTC Date for database storage
|
|
* @param {string} dueDateString - Due date from frontend (YYYY-MM-DD)
|
|
* @param {string} userTimezone - User's timezone
|
|
* @returns {Date|null} UTC Date object or null
|
|
*/
|
|
function processDueDateForStorage(dueDateString, userTimezone) {
|
|
if (!dueDateString || dueDateString.trim() === '') {
|
|
return null;
|
|
}
|
|
|
|
// Convert user's date to UTC at end of day in their timezone
|
|
// This ensures the task remains "due" throughout their entire day
|
|
return dateStringToUTC(dueDateString, userTimezone, 'end');
|
|
}
|
|
|
|
/**
|
|
* Convert UTC due_date from database to user timezone string for frontend
|
|
* @param {Date} utcDueDate - Due date from database (UTC)
|
|
* @param {string} userTimezone - User's timezone
|
|
* @returns {string|null} Date string (YYYY-MM-DD) or null
|
|
*/
|
|
function processDueDateForResponse(utcDueDate, userTimezone) {
|
|
if (!utcDueDate) return null;
|
|
|
|
return utcToUserDateString(utcDueDate, userTimezone);
|
|
}
|
|
|
|
/**
|
|
* Convert defer_until datetime from request body to UTC Date for database storage
|
|
* @param {string} deferUntilString - Defer until datetime from frontend (ISO 8601 format)
|
|
* @param {string} userTimezone - User's timezone
|
|
* @returns {Date|null} UTC Date object or null
|
|
*/
|
|
function processDeferUntilForStorage(deferUntilString, userTimezone) {
|
|
if (!deferUntilString || deferUntilString.trim() === '') {
|
|
return null;
|
|
}
|
|
|
|
// Parse the datetime string in the user's timezone
|
|
const momentInUserTz = moment.tz(deferUntilString, userTimezone);
|
|
|
|
// Convert to UTC Date object
|
|
return momentInUserTz.utc().toDate();
|
|
}
|
|
|
|
/**
|
|
* Convert UTC defer_until from database to user timezone ISO string for frontend
|
|
* @param {Date} utcDeferUntil - Defer until from database (UTC)
|
|
* @param {string} userTimezone - User's timezone
|
|
* @returns {string|null} ISO datetime string or null
|
|
*/
|
|
function processDeferUntilForResponse(utcDeferUntil, userTimezone) {
|
|
if (!utcDeferUntil) return null;
|
|
|
|
return moment.utc(utcDeferUntil).tz(userTimezone).toISOString();
|
|
}
|
|
|
|
/**
|
|
* Validate timezone string
|
|
* @param {string} timezone - Timezone to validate
|
|
* @returns {boolean} True if timezone is valid
|
|
*/
|
|
function isValidTimezone(timezone) {
|
|
if (!timezone || typeof timezone !== 'string') {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
// moment.tz.zone() returns null for invalid timezones
|
|
return moment.tz.zone(timezone) !== null;
|
|
} catch (error) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get safe timezone (fallback to UTC if invalid)
|
|
* @param {string} timezone - Timezone to validate
|
|
* @returns {string} Valid timezone or 'UTC'
|
|
*/
|
|
function getSafeTimezone(timezone) {
|
|
return isValidTimezone(timezone) ? timezone : 'UTC';
|
|
}
|
|
|
|
module.exports = {
|
|
dateStringToUTC,
|
|
utcToUserDateString,
|
|
getCurrentDateInTimezone,
|
|
getDayBoundsInUTC,
|
|
getTodayBoundsInUTC,
|
|
getUpcomingRangeInUTC,
|
|
isToday,
|
|
isOverdue,
|
|
processDueDateForStorage,
|
|
processDueDateForResponse,
|
|
processDeferUntilForStorage,
|
|
processDeferUntilForResponse,
|
|
isValidTimezone,
|
|
getSafeTimezone,
|
|
};
|