tududi/backend/utils/timezone-utils.js
Chris a81ca2f2b6
Fix upcoming completed issue (#404)
* Fix upcoming completed issue

* fixup! Fix upcoming completed issue

* fixup! fixup! Fix upcoming completed issue

* Fix completed icon plscement

* fixup! Fix completed icon plscement

* Add upcoming section tests
2025-10-11 00:08:13 +03:00

187 lines
5.8 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);
}
/**
* 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,
isValidTimezone,
getSafeTimezone,
};