javascript

exercises

exercises.js
/**
 * 15.2 Date Formatting & Calculations - Exercises
 *
 * Practice formatting dates and performing date calculations.
 */

/**
 * Exercise 1: Custom Date Formatter
 *
 * Create a flexible date formatter that supports various format tokens.
 *
 * Tokens:
 * - YYYY: 4-digit year
 * - YY: 2-digit year
 * - MMMM: Full month name
 * - MMM: Abbreviated month name
 * - MM: 2-digit month
 * - M: 1-2 digit month
 * - DD: 2-digit day
 * - D: 1-2 digit day
 * - dddd: Full weekday name
 * - ddd: Abbreviated weekday name
 * - HH: 2-digit hour (24-hour)
 * - hh: 2-digit hour (12-hour)
 * - mm: 2-digit minutes
 * - ss: 2-digit seconds
 * - A: AM/PM
 * - a: am/pm
 *
 * @param {Date} date - The date to format
 * @param {string} format - The format string
 * @returns {string} - Formatted date
 */
function formatDate(date, format) {
  // Your code here
}

// console.log(formatDate(new Date("2024-12-25T14:30:00"), "YYYY-MM-DD"));
// "2024-12-25"

// console.log(formatDate(new Date("2024-12-25T14:30:00"), "dddd, MMMM D, YYYY"));
// "Wednesday, December 25, 2024"

// console.log(formatDate(new Date("2024-12-25T14:30:00"), "MM/DD/YY"));
// "12/25/24"

// console.log(formatDate(new Date("2024-12-25T14:30:00"), "hh:mm A"));
// "02:30 PM"

// console.log(formatDate(new Date("2024-12-25T14:30:00"), "ddd, MMM D at h:mm a"));
// "Wed, Dec 25 at 2:30 pm"

/**
 * Exercise 2: Date Duration Calculator
 *
 * Create a function that calculates the exact duration between two dates.
 *
 * @param {Date} startDate - Start date
 * @param {Date} endDate - End date
 * @returns {object} - { years, months, days, hours, minutes, seconds, totalDays }
 */
function calculateDuration(startDate, endDate) {
  // Your code here
}

// console.log(calculateDuration(
//   new Date("2020-03-15T10:30:00"),
//   new Date("2024-06-20T14:45:30")
// ));
// {
//   years: 4,
//   months: 3,
//   days: 5,
//   hours: 4,
//   minutes: 15,
//   seconds: 30,
//   totalDays: 1558
// }

/**
 * Exercise 3: Date Arithmetic Functions
 *
 * Create a set of functions for date arithmetic.
 */
function add(date, amount, unit) {
  // unit: 'years', 'months', 'weeks', 'days', 'hours', 'minutes', 'seconds'
  // Your code here
}

function subtract(date, amount, unit) {
  // Your code here
}

function startOf(date, unit) {
  // unit: 'year', 'month', 'week', 'day', 'hour', 'minute'
  // Your code here
}

function endOf(date, unit) {
  // Your code here
}

// const date = new Date("2024-06-15T10:30:45");
// console.log(add(date, 1, 'months').toISOString());     // 2024-07-15T...
// console.log(subtract(date, 2, 'weeks').toISOString()); // 2024-06-01T...
// console.log(startOf(date, 'month').toISOString());     // 2024-06-01T00:00:00
// console.log(endOf(date, 'day').toISOString());         // 2024-06-15T23:59:59.999

/**
 * Exercise 4: Relative Time Formatter
 *
 * Create a comprehensive relative time formatter.
 *
 * @param {Date} date - The date to format
 * @param {Date} baseDate - The reference date (default: now)
 * @param {object} options - Options { style: 'long' | 'short' | 'narrow' }
 * @returns {string} - Relative time string
 */
function relativeTime(date, baseDate = new Date(), options = {}) {
  // Your code here
}

// console.log(relativeTime(new Date(Date.now() - 30000)));
// "30 seconds ago" or "just now"

// console.log(relativeTime(new Date(Date.now() - 3600000)));
// "1 hour ago"

// console.log(relativeTime(new Date(Date.now() + 86400000)));
// "in 1 day" or "tomorrow"

// console.log(relativeTime(new Date("2024-01-01"), new Date("2024-06-15")));
// "5 months ago"

/**
 * Exercise 5: Business Day Calculator
 *
 * Create functions for working with business days.
 * Allow for custom holidays.
 */
function isBusinessDay(date, holidays = []) {
  // Your code here
}

function addBusinessDays(date, days, holidays = []) {
  // Your code here
}

function getBusinessDaysBetween(startDate, endDate, holidays = []) {
  // Your code here
}

function getNextBusinessDay(date, holidays = []) {
  // Your code here
}

// const holidays = [
//   new Date("2024-12-25"), // Christmas
//   new Date("2024-01-01"), // New Year
// ];

// console.log(isBusinessDay(new Date("2024-12-25"), holidays)); // false
// console.log(addBusinessDays(new Date("2024-12-23"), 3, holidays).toDateString());
// Should skip Christmas and weekends

/**
 * Exercise 6: Date Range Functions
 *
 * Create functions for working with date ranges.
 */
function eachDayOfInterval(start, end) {
  // Return array of all dates between start and end
  // Your code here
}

function eachWeekOfInterval(start, end) {
  // Return array of week start dates
  // Your code here
}

function eachMonthOfInterval(start, end) {
  // Return array of month start dates
  // Your code here
}

function isWithinInterval(date, { start, end }) {
  // Your code here
}

function areIntervalsOverlapping(intervalA, intervalB) {
  // Your code here
}

// console.log(eachDayOfInterval(
//   new Date("2024-01-01"),
//   new Date("2024-01-05")
// ));
// Array of 5 dates

// console.log(areIntervalsOverlapping(
//   { start: new Date("2024-01-01"), end: new Date("2024-01-15") },
//   { start: new Date("2024-01-10"), end: new Date("2024-01-20") }
// ));
// true

/**
 * Exercise 7: Calendar Generator
 *
 * Create a function that generates calendar data for a month.
 *
 * @param {number} year - The year
 * @param {number} month - The month (1-12)
 * @returns {object} - Calendar data
 */
function generateCalendar(year, month) {
  // Your code here
  // Return: {
  //   year, month, monthName,
  //   daysInMonth, firstDayOfWeek (0-6),
  //   weeks: [
  //     [{ date, isCurrentMonth, isToday, isWeekend }, ...]
  //   ]
  // }
}

// console.log(generateCalendar(2024, 12));
// {
//   year: 2024,
//   month: 12,
//   monthName: "December",
//   daysInMonth: 31,
//   firstDayOfWeek: 0,
//   weeks: [...]
// }

/**
 * Exercise 8: Time Zone Converter
 *
 * Create functions for working with time zones.
 */
function getTimeInTimezone(date, timezone) {
  // Return formatted time in specified timezone
  // Your code here
}

function convertTimezone(date, fromTimezone, toTimezone) {
  // Convert date from one timezone to another
  // Your code here
}

function getTimezoneOffset(timezone, date = new Date()) {
  // Get offset in hours for a timezone
  // Your code here
}

// console.log(getTimeInTimezone(new Date(), "America/New_York"));
// "10:30 AM EST"

// console.log(getTimeInTimezone(new Date(), "Asia/Tokyo"));
// "12:30 AM JST"

/**
 * Exercise 9: Date Parser
 *
 * Create a function that parses various date string formats.
 *
 * @param {string} dateString - The date string to parse
 * @param {string} format - Optional format hint
 * @returns {Date|null} - Parsed date or null if invalid
 */
function parseDate(dateString, format = null) {
  // Your code here
  // Should handle:
  // - ISO format: "2024-12-25T10:30:00Z"
  // - US format: "12/25/2024"
  // - European format: "25/12/2024"
  // - Written: "December 25, 2024"
  // - Short: "Dec 25, 2024"
  // - Custom format if provided
}

// console.log(parseDate("2024-12-25"));
// console.log(parseDate("12/25/2024"));
// console.log(parseDate("25/12/2024", "DD/MM/YYYY"));
// console.log(parseDate("December 25, 2024"));

/**
 * Exercise 10: Date/Time Utilities Object
 *
 * Create a comprehensive date utilities object.
 */
const DateUtils = {
  // Comparison
  isSame(date1, date2, unit) {
    // unit: 'year', 'month', 'day', 'hour', 'minute'
    // Your code here
  },

  isBefore(date1, date2) {
    // Your code here
  },

  isAfter(date1, date2) {
    // Your code here
  },

  // Checks
  isToday(date) {
    // Your code here
  },

  isTomorrow(date) {
    // Your code here
  },

  isYesterday(date) {
    // Your code here
  },

  isWeekend(date) {
    // Your code here
  },

  isLeapYear(year) {
    // Your code here
  },

  // Getters
  getDaysInMonth(year, month) {
    // Your code here
  },

  getQuarter(date) {
    // Your code here
  },

  getWeekNumber(date) {
    // Your code here
  },

  // Formatting
  format(date, formatStr) {
    // Your code here
  },

  formatDistance(date1, date2) {
    // Your code here
  },
};

// console.log(DateUtils.isSame(new Date("2024-06-15"), new Date("2024-06-20"), 'month'));
// true

// console.log(DateUtils.isToday(new Date()));
// true

// console.log(DateUtils.getQuarter(new Date("2024-08-15")));
// 3

// console.log(DateUtils.formatDistance(
//   new Date("2024-01-01"),
//   new Date("2024-06-15")
// ));
// "5 months"

// ============================================
// SOLUTIONS
// ============================================

/*
// Solution 1
function formatDate(date, format) {
    const months = ['January', 'February', 'March', 'April', 'May', 'June',
                    'July', 'August', 'September', 'October', 'November', 'December'];
    const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
    
    const hours12 = date.getHours() % 12 || 12;
    const ampm = date.getHours() >= 12 ? 'PM' : 'AM';
    
    const tokens = {
        'YYYY': date.getFullYear(),
        'YY': String(date.getFullYear()).slice(-2),
        'MMMM': months[date.getMonth()],
        'MMM': months[date.getMonth()].slice(0, 3),
        'MM': String(date.getMonth() + 1).padStart(2, '0'),
        'M': date.getMonth() + 1,
        'DD': String(date.getDate()).padStart(2, '0'),
        'D': date.getDate(),
        'dddd': days[date.getDay()],
        'ddd': days[date.getDay()].slice(0, 3),
        'HH': String(date.getHours()).padStart(2, '0'),
        'H': date.getHours(),
        'hh': String(hours12).padStart(2, '0'),
        'h': hours12,
        'mm': String(date.getMinutes()).padStart(2, '0'),
        'm': date.getMinutes(),
        'ss': String(date.getSeconds()).padStart(2, '0'),
        's': date.getSeconds(),
        'A': ampm,
        'a': ampm.toLowerCase()
    };
    
    // Sort by length descending to match longer tokens first
    const pattern = Object.keys(tokens)
        .sort((a, b) => b.length - a.length)
        .join('|');
    
    return format.replace(new RegExp(pattern, 'g'), match => tokens[match]);
}

// Solution 2
function calculateDuration(startDate, endDate) {
    let start = new Date(startDate);
    let end = new Date(endDate);
    
    if (start > end) [start, end] = [end, start];
    
    const totalMs = end - start;
    const totalDays = Math.floor(totalMs / (1000 * 60 * 60 * 24));
    
    let years = end.getFullYear() - start.getFullYear();
    let months = end.getMonth() - start.getMonth();
    let days = end.getDate() - start.getDate();
    let hours = end.getHours() - start.getHours();
    let minutes = end.getMinutes() - start.getMinutes();
    let seconds = end.getSeconds() - start.getSeconds();
    
    if (seconds < 0) { seconds += 60; minutes--; }
    if (minutes < 0) { minutes += 60; hours--; }
    if (hours < 0) { hours += 24; days--; }
    if (days < 0) {
        const prevMonth = new Date(end.getFullYear(), end.getMonth(), 0);
        days += prevMonth.getDate();
        months--;
    }
    if (months < 0) { months += 12; years--; }
    
    return { years, months, days, hours, minutes, seconds, totalDays };
}

// Solution 3
function add(date, amount, unit) {
    const result = new Date(date);
    switch (unit) {
        case 'years': result.setFullYear(result.getFullYear() + amount); break;
        case 'months': result.setMonth(result.getMonth() + amount); break;
        case 'weeks': result.setDate(result.getDate() + amount * 7); break;
        case 'days': result.setDate(result.getDate() + amount); break;
        case 'hours': result.setHours(result.getHours() + amount); break;
        case 'minutes': result.setMinutes(result.getMinutes() + amount); break;
        case 'seconds': result.setSeconds(result.getSeconds() + amount); break;
    }
    return result;
}

function subtract(date, amount, unit) {
    return add(date, -amount, unit);
}

function startOf(date, unit) {
    const result = new Date(date);
    switch (unit) {
        case 'year':
            result.setMonth(0);
        case 'month':
            result.setDate(1);
        case 'day':
            result.setHours(0);
        case 'hour':
            result.setMinutes(0);
        case 'minute':
            result.setSeconds(0, 0);
            break;
        case 'week':
            result.setDate(result.getDate() - result.getDay());
            result.setHours(0, 0, 0, 0);
            break;
    }
    return result;
}

function endOf(date, unit) {
    const result = new Date(date);
    switch (unit) {
        case 'year':
            result.setMonth(11);
            result.setDate(31);
            result.setHours(23, 59, 59, 999);
            break;
        case 'month':
            result.setMonth(result.getMonth() + 1, 0);
            result.setHours(23, 59, 59, 999);
            break;
        case 'day':
            result.setHours(23, 59, 59, 999);
            break;
        case 'hour':
            result.setMinutes(59, 59, 999);
            break;
        case 'minute':
            result.setSeconds(59, 999);
            break;
        case 'week':
            result.setDate(result.getDate() + (6 - result.getDay()));
            result.setHours(23, 59, 59, 999);
            break;
    }
    return result;
}

// Solution 4
function relativeTime(date, baseDate = new Date(), options = {}) {
    const rtf = new Intl.RelativeTimeFormat('en', { 
        numeric: 'auto',
        style: options.style || 'long'
    });
    
    const diff = date - baseDate;
    const absDiff = Math.abs(diff);
    const sign = diff < 0 ? -1 : 1;
    
    const seconds = absDiff / 1000;
    const minutes = seconds / 60;
    const hours = minutes / 60;
    const days = hours / 24;
    const weeks = days / 7;
    const months = days / 30;
    const years = days / 365;
    
    if (seconds < 60) return rtf.format(Math.round(seconds) * sign, 'second');
    if (minutes < 60) return rtf.format(Math.round(minutes) * sign, 'minute');
    if (hours < 24) return rtf.format(Math.round(hours) * sign, 'hour');
    if (days < 7) return rtf.format(Math.round(days) * sign, 'day');
    if (weeks < 4) return rtf.format(Math.round(weeks) * sign, 'week');
    if (months < 12) return rtf.format(Math.round(months) * sign, 'month');
    return rtf.format(Math.round(years) * sign, 'year');
}

// Solution 5
function isBusinessDay(date, holidays = []) {
    const day = date.getDay();
    if (day === 0 || day === 6) return false;
    
    const dateStr = date.toDateString();
    return !holidays.some(h => h.toDateString() === dateStr);
}

function addBusinessDays(date, days, holidays = []) {
    const result = new Date(date);
    let added = 0;
    const direction = days >= 0 ? 1 : -1;
    days = Math.abs(days);
    
    while (added < days) {
        result.setDate(result.getDate() + direction);
        if (isBusinessDay(result, holidays)) {
            added++;
        }
    }
    
    return result;
}

function getBusinessDaysBetween(startDate, endDate, holidays = []) {
    let count = 0;
    const current = new Date(startDate);
    
    while (current < endDate) {
        current.setDate(current.getDate() + 1);
        if (isBusinessDay(current, holidays)) {
            count++;
        }
    }
    
    return count;
}

function getNextBusinessDay(date, holidays = []) {
    const result = new Date(date);
    do {
        result.setDate(result.getDate() + 1);
    } while (!isBusinessDay(result, holidays));
    return result;
}

// Solution 6
function eachDayOfInterval(start, end) {
    const days = [];
    const current = new Date(start);
    
    while (current <= end) {
        days.push(new Date(current));
        current.setDate(current.getDate() + 1);
    }
    
    return days;
}

function eachWeekOfInterval(start, end) {
    const weeks = [];
    const current = new Date(start);
    current.setDate(current.getDate() - current.getDay()); // Start of week
    
    while (current <= end) {
        weeks.push(new Date(current));
        current.setDate(current.getDate() + 7);
    }
    
    return weeks;
}

function eachMonthOfInterval(start, end) {
    const months = [];
    const current = new Date(start.getFullYear(), start.getMonth(), 1);
    
    while (current <= end) {
        months.push(new Date(current));
        current.setMonth(current.getMonth() + 1);
    }
    
    return months;
}

function isWithinInterval(date, { start, end }) {
    return date >= start && date <= end;
}

function areIntervalsOverlapping(intervalA, intervalB) {
    return intervalA.start <= intervalB.end && intervalB.start <= intervalA.end;
}

// Solution 7
function generateCalendar(year, month) {
    const monthNames = ['January', 'February', 'March', 'April', 'May', 'June',
                        'July', 'August', 'September', 'October', 'November', 'December'];
    
    const firstDay = new Date(year, month - 1, 1);
    const lastDay = new Date(year, month, 0);
    const today = new Date();
    today.setHours(0, 0, 0, 0);
    
    const weeks = [];
    let week = [];
    
    // Previous month days
    const prevMonthEnd = new Date(year, month - 1, 0);
    for (let i = 0; i < firstDay.getDay(); i++) {
        const day = prevMonthEnd.getDate() - firstDay.getDay() + i + 1;
        const date = new Date(year, month - 2, day);
        week.push({
            date,
            day,
            isCurrentMonth: false,
            isToday: date.getTime() === today.getTime(),
            isWeekend: date.getDay() === 0 || date.getDay() === 6
        });
    }
    
    // Current month days
    for (let day = 1; day <= lastDay.getDate(); day++) {
        const date = new Date(year, month - 1, day);
        week.push({
            date,
            day,
            isCurrentMonth: true,
            isToday: date.getTime() === today.getTime(),
            isWeekend: date.getDay() === 0 || date.getDay() === 6
        });
        
        if (week.length === 7) {
            weeks.push(week);
            week = [];
        }
    }
    
    // Next month days
    let nextDay = 1;
    while (week.length > 0 && week.length < 7) {
        const date = new Date(year, month, nextDay);
        week.push({
            date,
            day: nextDay++,
            isCurrentMonth: false,
            isToday: false,
            isWeekend: date.getDay() === 0 || date.getDay() === 6
        });
    }
    if (week.length) weeks.push(week);
    
    return {
        year,
        month,
        monthName: monthNames[month - 1],
        daysInMonth: lastDay.getDate(),
        firstDayOfWeek: firstDay.getDay(),
        weeks
    };
}

// Solution 8
function getTimeInTimezone(date, timezone) {
    return date.toLocaleTimeString('en-US', {
        timeZone: timezone,
        hour: '2-digit',
        minute: '2-digit',
        timeZoneName: 'short'
    });
}

function convertTimezone(date, fromTimezone, toTimezone) {
    // Create formatter for each timezone
    const fromFormatter = new Intl.DateTimeFormat('en-US', {
        timeZone: fromTimezone,
        year: 'numeric', month: '2-digit', day: '2-digit',
        hour: '2-digit', minute: '2-digit', second: '2-digit',
        hour12: false
    });
    
    // This is a simplified conversion - for accurate conversion,
    // you'd need to parse and reconstruct the date
    return date.toLocaleString('en-US', { timeZone: toTimezone });
}

function getTimezoneOffset(timezone, date = new Date()) {
    const utcDate = new Date(date.toLocaleString('en-US', { timeZone: 'UTC' }));
    const tzDate = new Date(date.toLocaleString('en-US', { timeZone: timezone }));
    return (tzDate - utcDate) / (1000 * 60 * 60);
}

// Solution 9
function parseDate(dateString, format = null) {
    if (!dateString) return null;
    
    // Try ISO format first
    const isoDate = new Date(dateString);
    if (!isNaN(isoDate.getTime()) && dateString.includes('-')) {
        return isoDate;
    }
    
    // US format: MM/DD/YYYY
    const usMatch = dateString.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/);
    if (usMatch && format !== 'DD/MM/YYYY') {
        return new Date(usMatch[3], usMatch[1] - 1, usMatch[2]);
    }
    
    // European format: DD/MM/YYYY
    if (format === 'DD/MM/YYYY') {
        const euMatch = dateString.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/);
        if (euMatch) {
            return new Date(euMatch[3], euMatch[2] - 1, euMatch[1]);
        }
    }
    
    // Written formats
    const writtenDate = new Date(dateString);
    if (!isNaN(writtenDate.getTime())) {
        return writtenDate;
    }
    
    return null;
}

// Solution 10
const DateUtils = {
    isSame(date1, date2, unit) {
        switch (unit) {
            case 'year': return date1.getFullYear() === date2.getFullYear();
            case 'month': return this.isSame(date1, date2, 'year') && 
                                date1.getMonth() === date2.getMonth();
            case 'day': return this.isSame(date1, date2, 'month') && 
                              date1.getDate() === date2.getDate();
            case 'hour': return this.isSame(date1, date2, 'day') && 
                               date1.getHours() === date2.getHours();
            case 'minute': return this.isSame(date1, date2, 'hour') && 
                                 date1.getMinutes() === date2.getMinutes();
            default: return date1.getTime() === date2.getTime();
        }
    },
    
    isBefore(date1, date2) {
        return date1.getTime() < date2.getTime();
    },
    
    isAfter(date1, date2) {
        return date1.getTime() > date2.getTime();
    },
    
    isToday(date) {
        return this.isSame(date, new Date(), 'day');
    },
    
    isTomorrow(date) {
        const tomorrow = new Date();
        tomorrow.setDate(tomorrow.getDate() + 1);
        return this.isSame(date, tomorrow, 'day');
    },
    
    isYesterday(date) {
        const yesterday = new Date();
        yesterday.setDate(yesterday.getDate() - 1);
        return this.isSame(date, yesterday, 'day');
    },
    
    isWeekend(date) {
        const day = date.getDay();
        return day === 0 || day === 6;
    },
    
    isLeapYear(year) {
        return (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0);
    },
    
    getDaysInMonth(year, month) {
        return new Date(year, month, 0).getDate();
    },
    
    getQuarter(date) {
        return Math.floor(date.getMonth() / 3) + 1;
    },
    
    getWeekNumber(date) {
        const d = new Date(date);
        d.setHours(0, 0, 0, 0);
        d.setDate(d.getDate() + 4 - (d.getDay() || 7));
        const yearStart = new Date(d.getFullYear(), 0, 1);
        return Math.ceil((((d - yearStart) / 86400000) + 1) / 7);
    },
    
    format(date, formatStr) {
        return formatDate(date, formatStr); // Uses solution 1
    },
    
    formatDistance(date1, date2) {
        const diff = Math.abs(date2 - date1);
        const days = Math.floor(diff / (1000 * 60 * 60 * 24));
        
        if (days < 1) return 'less than a day';
        if (days === 1) return '1 day';
        if (days < 30) return `${days} days`;
        
        const months = Math.floor(days / 30);
        if (months === 1) return '1 month';
        if (months < 12) return `${months} months`;
        
        const years = Math.floor(days / 365);
        if (years === 1) return '1 year';
        return `${years} years`;
    }
};
*/
Exercises - JavaScript Tutorial | DeepML