24x7
v1.0.0
Published
A production-ready date/time library for JavaScript with first-class intervals, business-aware date math, timezone support, and deterministic time control
Maintainers
Readme
24x7
A production-ready date/time library for JavaScript with unique features for modern applications.
Why 24x7 Exists
24x7 was created to provide a modern, production-ready date/time library with unique features:
- First-class intervals - Work with time intervals as first-class objects with end-exclusive behavior
- First-class date ranges - Work with date ranges as first-class objects, not just calculations
- Business-aware date math - Handle business days, holidays, and custom weekends with ease
- Deterministic time control - Freeze time for predictable testing without global side effects
- Timezone support - Full timezone support with UTC default and IANA timezone support
- Global configuration - Set defaults for formats, weekends, holidays, and more
- Flexible parsing - Support for custom date formats with automatic ISO detection
Installation
npm install 24x7Quick Start
import _tx from '24x7';
// Create a date
const date = _tx('2025-01-15');
// Chain operations (all immutable)
const nextWeek = date.addDays(7).startOfDay();
// Format
console.log(nextWeek.format('YYYY-MM-DD')); // '2025-01-22'Configuration
24x7 supports comprehensive global configuration with a structured, hierarchical system. Configuration follows a priority system:
- Instance overrides (highest priority)
- Global config (set via
_tx.config()) - DEFAULT_CONFIG (lowest priority)
Basic Usage
import _tx from '24x7';
// Get current configuration
const config = _tx.config();
// Set configuration options
_tx.config({
immutable: true,
parsing: {
inputFormats: ['DD/MM/YYYY', 'YYYY-MM-DD'],
strict: false,
safe: true
},
formatting: {
outputFormat: 'YYYY-MM-DD'
},
calendar: {
weekends: [0, 6],
holidays: ['2025-01-15']
}
});
// Reset to defaults
_tx.resetConfig();Configuration Structure
Immutability
_tx.config({
immutable: true, // Instances are immutable by default
mutableFallback: false // Allow mutation only when explicitly enabled
});Usage:
// Default: immutable (returns new instance)
const date1 = _tx('2025-01-15');
const date2 = date1.addDays(5); // date1 unchanged
// Explicitly enable mutation
const date3 = _tx('2025-01-15').immutable(false);
date3.addDays(5); // date3 is mutated in placeParsing Configuration
_tx.config({
parsing: {
inputFormats: ['DD/MM/YYYY', 'YYYY-MM-DD'], // Priority order
strict: false, // Reject invalid dates
safe: true, // Do not throw on invalid input
overflow: 'reject', // 'reject' | 'clamp'
yearPivot: 50, // Two-digit year pivot (50 = 1950-2049)
monthNames: null, // Custom month names
allowNative: true, // Fallback to native Date parsing
trimInput: true // Trim input strings
}
});Examples:
// Use inputFormats for automatic parsing
_tx.config({
parsing: {
inputFormats: ['DD/MM/YYYY', 'MM/DD/YYYY']
}
});
x('23/01/2025'); // Parsed as DD/MM/YYYY
x('01/23/2025'); // Parsed as MM/DD/YYYY
// Strict mode rejects invalid dates
_tx.config({
parsing: {
strict: true
}
});
// Safe mode returns current date instead of throwing
_tx.config({
parsing: {
safe: true
}
});
x('invalid'); // Returns current date instead of throwingFormatting Configuration
_tx.config({
formatting: {
outputFormat: 'YYYY-MM-DD',
tokens: {}, // Custom format tokens
ordinal: n => `${n}th`, // Custom ordinal function
pad: {
year: 4,
month: 2,
day: 2,
hour: 2,
minute: 2,
second: 2
},
rules: {
sameDay: 'HH:mm', // Format for same day
sameYear: 'MMM D', // Format for same year
default: 'YYYY-MM-DD' // Default format
}
}
});Examples:
// Set default output format
_tx.config({
formatting: {
outputFormat: 'MMMM D, YYYY'
}
});
x('2025-01-15').format(); // "January 15, 2025"
// Custom tokens
_tx.config({
formatting: {
tokens: {
'Q': (date) => Math.floor(date.month() / 3) + 1
}
}
});
x('2025-01-15').format('YYYY-Q'); // "2025-1"Calendar / Business Logic
_tx.config({
calendar: {
weekends: [0, 6], // Sunday, Saturday
weekStart: 1, // Monday (0=Sunday, 1=Monday, etc.)
holidays: ['2025-01-15', '2025-12-25'], // ISO date strings
businessHours: {
start: '09:00',
end: '17:00'
},
includeHolidaysInRanges: false
}
});Examples:
// Set global weekends and holidays
_tx.config({
calendar: {
weekends: [0, 6],
holidays: ['2025-01-15', '2025-12-25']
}
});
// Business day methods use config defaults
x('2025-01-10').addBusinessDays(5); // Uses config weekends/holidays
// Override per call
x('2025-01-10').addBusinessDays(5, {
weekends: [5, 6], // Override config
holidays: ['2025-01-20'] // Override config
});Date Math Configuration
_tx.config({
math: {
clampMode: 'end', // 'end' | 'strict' | 'overflow'
diffRounding: 'floor' // 'floor' | 'ceil' | 'round'
}
});Time / Clock Control
_tx.config({
time: {
timezone: 'UTC', // 'UTC' | 'local' | IANA timezone (e.g., 'America/New_York')
precision: 'ms', // 'ms' | 's'
freeze: false, // false | Date | ISO string
dst: 'preserve', // 'preserve' | 'shift' | 'error'
startOfDay: '00:00'
}
});Examples:
// Set default timezone to UTC (default)
_tx.config({
time: {
timezone: 'UTC'
}
});
// Set default timezone to local
_tx.config({
time: {
timezone: 'local'
}
});
// Set default timezone to IANA timezone
_tx.config({
time: {
timezone: 'America/New_York'
}
});
// Freeze time
_tx.config({
time: {
freeze: '2025-01-15' // Freeze to specific date
}
});
// Or use the convenience method
_tx.freeze('2025-01-15');Debugging Configuration
_tx.config({
debug: {
warnings: false,
onWarn: (message, context) => {
console.warn(message, context);
}
}
});Performance Configuration
_tx.config({
performance: {
cacheFormats: true, // Cache compiled format patterns
fastParse: false // Use faster but less strict parsing
}
});Policy / Guards
_tx.config({
policy: {
maxFuture: '2y', // Maximum future date (e.g., '2y', '1M')
maxPast: '10y', // Maximum past date
forbidWeekends: false, // Reject weekend dates
forbidHolidays: false // Reject holiday dates
}
});Intervals / Ranges Configuration
_tx.config({
intervals: {
inclusive: false, // End-exclusive by default
allowZeroDuration: false,
splitRemainder: true, // Keep leftover when splitting
defaultSplitUnit: 'minute',
overlap: {
inclusive: false // End === start is NOT overlap
},
businessOnly: false // Ignore non-business time by default
}
});Examples:
// End-exclusive intervals (default)
_tx.config({
intervals: {
inclusive: false
}
});
const interval = _tx('2025-01-01').interval('2025-01-10');
interval.contains('2025-01-10'); // false (end-exclusive)
// End-inclusive intervals
_tx.config({
intervals: {
inclusive: true
}
});
const inclusiveInterval =_tx('2025-01-01').interval('2025-01-10');
inclusiveInterval.contains('2025-01-10'); // true (end-inclusive)
// Allow zero-duration intervals
_tx.config({
intervals: {
allowZeroDuration: true
}
});
const zeroInterval =_tx('2025-01-01').interval('2025-01-01'); // AllowedInstance-Level Configuration
You can override configuration per instance:
// Create instance with custom config
const date =_tx('2025-01-15', {
immutable: false,
calendar: {
weekends: [5, 6] // Friday-Saturday weekend
}
});
// Or modify immutability after creation
const mutableDate =_tx('2025-01-15').immutable(false);
mutableDate.addDays(5); // Mutates in placeConfiguration Resolution
Configuration is resolved with the following priority:
- Instance overrides - Passed when creating DateTime or via
.immutable() - Global config - Set via
_tx.config() - DEFAULT_CONFIG - Built-in defaults
// Global config
_tx.config({
calendar: {
weekends: [0, 6]
}
});
// Instance override
const date =_tx('2025-01-15', {
calendar: {
weekends: [5, 6] // This instance uses Friday-Saturday
}
});
// Method-level override (highest priority)
date.addBusinessDays(5, {
weekends: [1, 2] // This call uses Monday-Tuesday
});Core Features
Immutable API (Default)
By default, all operations return new instances. The original date is never modified:
const date1 =_tx('2025-01-15');
const date2 = date1.addDays(5);
console.log(date1.format('YYYY-MM-DD')); // '2025-01-15' (unchanged)
console.log(date2.format('YYYY-MM-DD')); // '2025-01-20'Mutable Mode (Explicit)
You can enable mutation for specific instances:
// Enable mutation explicitly
const date =_tx('2025-01-15').immutable(false);
date.addDays(5); // Mutates date in place
// Check immutability
date.isImmutable(); // false
// Create mutable instance from config
const mutableDate =_tx('2025-01-15', { immutable: false });
mutableDate.addDays(5); // Mutates in placeNote: Immutability is the default and recommended mode. Use mutable mode only when explicitly needed for performance or specific use cases.
Chainable Methods
Methods can be chained for fluent APIs:
const result =_tx('2025-01-15')
.addMonths(1)
.startOfMonth()
.addDays(2)
.format('YYYY-MM-DD');Unique Features
1️⃣ Intervals (First-class)
Work with time intervals as first-class objects with end-exclusive behavior by default:
Creating Intervals
import _tx from '24x7';
// Create an interval (end-exclusive by default)
const interval =_tx('2025-01-01').interval('2025-01-10');
// Static method (alternative)
const staticInterval = _tx.interval('2025-01-01', '2025-01-10');
// End-exclusive: [start, end) - end is NOT included
const exclusive =_tx('2025-01-01').interval('2025-01-10');
exclusive.contains('2025-01-01'); // true
exclusive.contains('2025-01-10'); // false (end is exclusive)Interval Configuration
_tx.config({
intervals: {
inclusive: false, // End-exclusive by default
allowZeroDuration: false,
splitRemainder: true, // Keep leftover when splitting
defaultSplitUnit: 'minute',
overlap: {
inclusive: false // End === start is NOT overlap
},
businessOnly: false // Ignore non-business time by default
}
});Checking if Date or Interval is Contained
import _tx from '24x7';
const interval =_tx('2025-01-01').interval('2025-01-10');
// Check if date is in interval
interval.contains('2025-01-05'); // true
interval.contains('2025-01-10'); // false (end-exclusive)
// Check if another interval is contained
const large =_tx('2025-01-01').interval('2025-01-31');
const small =_tx('2025-01-10').interval('2025-01-20');
large.contains(small); // trueOverlap Detection
import _tx from '24x7';
const interval1 =_tx('2025-01-01').interval('2025-01-10');
const interval2 =_tx('2025-01-05').interval('2025-01-15');
const interval3 =_tx('2025-01-20').interval('2025-01-25');
interval1.overlaps(interval2); // true
interval1.overlaps(interval3); // false
// With inclusive overlap option
interval1.overlaps(interval2, { inclusive: true });Intersection
import _tx from '24x7';
const interval1 =_tx('2025-01-01').interval('2025-01-10');
const interval2 =_tx('2025-01-05').interval('2025-01-15');
const intersection = interval1.intersect(interval2);
if (intersection) {
console.log(intersection.start().format('YYYY-MM-DD')); // '2025-01-05'
console.log(intersection.end().format('YYYY-MM-DD')); // '2025-01-10'
}Clamping
import _tx from '24x7';
const container =_tx('2025-01-01').interval('2025-01-31');
const toClamp =_tx('2024-12-25').interval('2025-02-05');
const clamped = container.clamp(toClamp);
// Clamped to fit within container: 2025-01-01 to 2025-01-31Splitting Intervals
import _tx from '24x7';
const interval =_tx('2025-01-01T00:00:00').interval('2025-01-01T02:30:00');
// Split by unit
const hourIntervals = interval.splitBy('hour');
// Returns array of 1-hour intervals
// Split by duration (milliseconds)
const customIntervals = interval.splitBy(30 * 60 * 1000); // 30 minutesIterating Over Intervals
import _tx from '24x7';
const interval =_tx('2025-01-01').interval('2025-01-05');
// Iterate by day
for (const day of interval.iterate('day')) {
console.log(day.format('YYYY-MM-DD'));
}
// Output:
// 2025-01-01
// 2025-01-02
// 2025-01-03
// 2025-01-04
// (2025-01-05 is excluded - end-exclusive)Duration Calculations
import _tx from '24x7';
const interval =_tx('2025-01-01T10:00:00').interval('2025-01-01T14:30:00');
interval.duration(); // Duration in milliseconds (default)
interval.duration('s'); // Duration in seconds
interval.duration('minute'); // Duration in minutes
interval.duration('hour'); // Duration in hours
interval.duration('day'); // Duration in daysMerging Intervals
import _tx from '24x7';
const interval1 =_tx('2025-01-01').interval('2025-01-10');
const interval2 =_tx('2025-01-05').interval('2025-01-15');
const interval3 =_tx('2025-01-20').interval('2025-01-25');
const merged = _tx.merge([interval1, interval2, interval3]);
// Returns interval from earliest start to latest end
// 2025-01-01 to 2025-01-25Real-World Use Cases
import _tx from '24x7';
// Use case 1: Time slot booking
function isTimeSlotAvailable(slot, bookedSlots) {
return !bookedSlots.some(booked => slot.overlaps(booked));
}
// Use case 2: Calculate working hours in interval
function getWorkingHours(interval, businessHours) {
// Filter to business hours only
return interval.splitBy('hour').filter(hour => {
const hourStart = hour.start().hour();
return hourStart >= 9 && hourStart < 17;
});
}
// Use case 3: Find gaps between intervals
function findGaps(intervals) {
// Sort by start time
const sorted = [...intervals].sort((a, b) =>
a.start().getTime() - b.start().getTime()
);
const gaps = [];
for (let i = 0; i < sorted.length - 1; i++) {
const gap = sorted[i].end().interval(sorted[i + 1].start());
if (gap.duration() > 0) {
gaps.push(gap);
}
}
return gaps;
}2️⃣ Date Ranges (First-class)
Work with date ranges as first-class objects:
Creating Ranges
import _tx from '24x7';
// Create a range from a date to another date
const range =_tx('2025-01-01').range('2025-01-10');
// Static method (alternative)
const staticRange = _tx.range('2025-01-01', '2025-01-10');
// Range with different input types
const range2 =_tx('2025-01-01').range(new Date('2025-01-10'));
const range3 =_tx(new Date('2025-01-01')).range('2025-01-10');
// Single day range
const singleDay =_tx('2025-01-15').range('2025-01-15');Checking if Date is in Range
import _tx from '24x7';
const range =_tx('2025-01-01').range('2025-01-10');
// Check with DateTime instance
range.contains(_tx('2025-01-05')); // true
range.contains(_tx('2025-01-15')); // false
// Check with string
range.contains('2025-01-05'); // true
range.contains('2025-01-01'); // true (inclusive start)
range.contains('2025-01-10'); // true (inclusive end)
// Check with Date object
range.contains(new Date('2025-01-05')); // true
// Real-world example: Check if today is in a promotion period
const promotionStart =_tx('2025-01-01');
const promotionEnd =_tx('2025-01-31');
const promotionRange = promotionStart.range(promotionEnd);
const isPromotionActive = promotionRange.contains(_tx()); // true if today is in rangeGetting Range Information
import _tx from '24x7';
const range =_tx('2025-01-01').range('2025-01-10');
// Get start and end dates
const start = range.start(); // DateTime instance
const end = range.end(); // DateTime instance
console.log(start.format('YYYY-MM-DD')); // '2025-01-01'
console.log(end.format('YYYY-MM-DD')); // '2025-01-10'
// Get number of days (inclusive)
range.days(); // 10 (includes both start and end)
// Calculate duration in other units
const days = range.days();
const hours = days * 24;
const minutes = hours * 60;Iterating Over Range
import _tx from '24x7';
const range =_tx('2025-01-01').range('2025-01-05');
// Iterate with for...of
for (const day of range) {
console.log(day.format('YYYY-MM-DD'));
}
// Output:
// 2025-01-01
// 2025-01-02
// 2025-01-03
// 2025-01-04
// 2025-01-05
// Convert to array
const daysArray = range.toArray();
console.log(daysArray.length); // 5
// Use array methods
const weekdays = range.toArray().filter(day => {
const dayOfWeek = day.day();
return dayOfWeek !== 0 && dayOfWeek !== 6; // Exclude weekends
});
// Map to formatted strings
const formattedDays = range.toArray().map(day => day.format('MMM D'));
console.log(formattedDays); // ['Jan 1', 'Jan 2', 'Jan 3', 'Jan 4', 'Jan 5']
// Real-world example: Get all business days in a range
const businessDays = range.toArray().filter(day => {
return day.isBusinessDay({ weekends: [0, 6] });
});Range Overlap Detection
import _tx from '24x7';
const range1 =_tx('2025-01-01').range('2025-01-10');
const range2 =_tx('2025-01-05').range('2025-01-15');
const range3 =_tx('2025-01-20').range('2025-01-25');
// Check if ranges overlap
range1.overlaps(range2); // true (overlaps from Jan 5-10)
range1.overlaps(range3); // false (no overlap)
// Real-world example: Check if booking periods conflict
const booking1 =_tx('2025-01-10').range('2025-01-15');
const booking2 =_tx('2025-01-12').range('2025-01-18');
const hasConflict = booking1.overlaps(booking2); // true
// Find overlapping period
if (booking1.overlaps(booking2)) {
const overlapStart = booking1.start().isAfter(booking2.start())
? booking1.start()
: booking2.start();
const overlapEnd = booking1.end().isBefore(booking2.end())
? booking1.end()
: booking2.end();
const overlapRange = overlapStart.range(overlapEnd);
console.log(`Overlap: ${overlapRange.days()} days`);
}Real-World Use Cases
import _tx from '24x7';
// Use case 1: Event date range validation
function isEventDateValid(eventStart, eventEnd, today = _tx()) {
const eventRange = eventStart.range(eventEnd);
return eventRange.contains(today) || eventRange.start().isAfter(today);
}
// Use case 2: Calculate working days in a project timeline
function getWorkingDays(startDate, endDate, holidays = []) {
const range =_tx(startDate).range(endDate);
return range.toArray().filter(day => {
return day.isBusinessDay({ weekends: [0, 6], holidays });
}).length;
}
// Use case 3: Check if two schedules conflict
function schedulesConflict(schedule1Start, schedule1End, schedule2Start, schedule2End) {
const schedule1 =_tx(schedule1Start).range(schedule1End);
const schedule2 =_tx(schedule2Start).range(schedule2End);
return schedule1.overlaps(schedule2);
}3️⃣ Business-Aware Date Math
Handle business days with custom weekends and holidays:
Adding Business Days
import _tx from '24x7';
// Basic usage: Add 5 business days (skips weekends)
const result =_tx('2025-01-10') // Friday
.addBusinessDays(5, {
weekends: [0, 6] // Sunday and Saturday
});
// Result: 2025-01-17 (Friday) - skips weekend
// With holidays
const result2 =_tx('2025-01-10')
.addBusinessDays(5, {
weekends: [0, 6],
holidays: ['2025-01-15', '2025-01-20'] // Wednesday and Monday
});
// Skips weekends AND holidays
// Custom weekends (e.g., Friday-Saturday weekend)
const result3 =_tx('2025-01-08') // Wednesday
.addBusinessDays(3, {
weekends: [5, 6] // Friday and Saturday
});
// Real-world example: Calculate delivery date (5 business days)
const orderDate =_tx('2025-01-10');
const holidays = ['2025-01-15', '2025-01-20']; // Company holidays
const deliveryDate = orderDate.addBusinessDays(5, {
weekends: [0, 6],
holidays
});
console.log(`Order placed: ${orderDate.format('MMM D')}`);
console.log(`Expected delivery: ${deliveryDate.format('MMM D')}`);Subtracting Business Days
import _tx from '24x7';
// Subtract business days
const past =_tx('2025-01-20')
.subtractBusinessDays(3, {
weekends: [0, 6]
});
// Real-world example: Calculate deadline (10 business days ago)
const deadline =_tx('2025-01-31')
.subtractBusinessDays(10, {
weekends: [0, 6],
holidays: ['2025-01-15']
});
console.log(`Deadline: ${deadline.format('MMM D')}`);Checking Business Days
import _tx from '24x7';
// Check if a date is a business day
x('2025-01-15').isBusinessDay({
weekends: [0, 6],
holidays: ['2025-01-15']
}); // false (it's a holiday)
x('2025-01-16').isBusinessDay({
weekends: [0, 6],
holidays: ['2025-01-15']
}); // true (Thursday, not a holiday)
x('2025-01-18').isBusinessDay({
weekends: [0, 6]
}); // false (Saturday)
// Real-world example: Validate business day
function canProcessOrder(date, holidays = []) {
return _tx(date).isBusinessDay({
weekends: [0, 6],
holidays
});
}
if (canProcessOrder('2025-01-15', ['2025-01-15'])) {
console.log('Order can be processed today');
} else {
console.log('Order will be processed next business day');
}Counting Business Days Between Dates
import _tx from '24x7';
// Count business days between two dates
const count =_tx('2025-01-01')
.businessDaysUntil('2025-01-31', {
weekends: [0, 6],
holidays: ['2025-01-15']
});
// Returns 21 (excluding weekends and holidays)
// Forward count (positive)
x('2025-01-10').businessDaysUntil('2025-01-20', {
weekends: [0, 6]
}); // 7 business days
// Backward count (negative)
x('2025-01-20').businessDaysUntil('2025-01-10', {
weekends: [0, 6]
}); // -7 business days
// Real-world example: Calculate SLA remaining days
function getSLARemainingDays(startDate, endDate, holidays = []) {
const today = _tx();
const remaining = today.businessDaysUntil(endDate, {
weekends: [0, 6],
holidays
});
return Math.max(0, remaining); // Don't return negative
}
const startDate =_tx('2025-01-01');
const endDate =_tx('2025-01-31');
const remaining = getSLARemainingDays(startDate, endDate, ['2025-01-15']);
console.log(`${remaining} business days remaining`);Custom Weekend Configurations
import _tx from '24x7';
// Middle Eastern weekend (Friday-Saturday)
const result =_tx('2025-01-08') // Wednesday
.addBusinessDays(3, {
weekends: [5, 6] // Friday and Saturday
});
// No weekends (7-day work week)
const result2 =_tx('2025-01-10')
.addBusinessDays(5, {
weekends: [] // No weekends
});
// Single day weekend
const result3 =_tx('2025-01-10')
.addBusinessDays(5, {
weekends: [0] // Only Sunday
});Holiday Management
import _tx from '24x7';
// Holidays as strings (ISO format preferred)
const holidays = [
'2025-01-01', // New Year
'2025-01-15', // Custom holiday
'2025-12-25' // Christmas
];
// Holidays as Date objects
const holidayDates = [
new Date('2025-01-01'),
new Date('2025-01-15'),
new Date('2025-12-25')
];
// Holidays as DateTime instances
const holidayDateTimes = [
_tx('2025-01-01'),
_tx('2025-01-15'),
_tx('2025-12-25')
];
// All work the same
const result = _tx('2025-01-10')
.addBusinessDays(5, {
weekends: [0, 6],
holidays: holidays // or holidayDates or holidayDateTimes
});
// Real-world example: Company holiday calendar
class HolidayCalendar {
constructor() {
this.holidays = [];
}
addHoliday(date) {
this.holidays.push(_tx(date).format('YYYY-MM-DD'));
return this;
}
getBusinessDays(startDate, endDate) {
return _tx(startDate).businessDaysUntil(endDate, {
weekends: [0, 6],
holidays: this.holidays
});
}
}
const calendar = new HolidayCalendar();
calendar
.addHoliday('2025-01-01')
.addHoliday('2025-07-04')
.addHoliday('2025-12-25');
const businessDays = calendar.getBusinessDays('2025-01-01', '2025-01-31');
console.log(`${businessDays} business days in January`);Real-World Use Cases
import _tx from '24x7';
// Use case 1: Calculate invoice due date (Net 30 business days)
function getInvoiceDueDate(invoiceDate, holidays = []) {
return _tx(invoiceDate).addBusinessDays(30, {
weekends: [0, 6],
holidays
});
}
// Use case 2: Project timeline with business days only
function calculateProjectEndDate(startDate, businessDaysNeeded, holidays = []) {
return _tx(startDate).addBusinessDays(businessDaysNeeded, {
weekends: [0, 6],
holidays
});
}
// Use case 3: SLA monitoring
function checkSLACompliance(createdDate, slaBusinessDays, holidays = []) {
const today = _tx();
const deadline =_tx(createdDate).addBusinessDays(slaBusinessDays, {
weekends: [0, 6],
holidays
});
return {
deadline: deadline.format('YYYY-MM-DD'),
isOverdue: today.isAfter(deadline),
daysRemaining: today.businessDaysUntil(deadline, {
weekends: [0, 6],
holidays
})
};
}
// Use case 4: Work schedule validation
function isValidWorkSchedule(startDate, endDate, maxConsecutiveDays, holidays = []) {
const range =_tx(startDate).range(endDate);
const businessDays = range.toArray().filter(day =>
day.isBusinessDay({ weekends: [0, 6], holidays })
);
// Check for consecutive days exceeding limit
let consecutive = 1;
let maxConsecutive = 1;
for (let i = 1; i < businessDays.length; i++) {
const daysDiff = businessDays[i].getTime() - businessDays[i-1].getTime();
if (daysDiff === 24 * 60 * 60 * 1000) { // Exactly 1 day
consecutive++;
maxConsecutive = Math.max(maxConsecutive, consecutive);
} else {
consecutive = 1;
}
}
return maxConsecutive <= maxConsecutiveDays;
}4️⃣ Deterministic Time Control (Testing-Friendly)
Freeze time for predictable tests:
Basic Freeze/Unfreeze
import _tx from '24x7';
// Freeze time to a specific date
_tx.freeze('2025-01-15');
// Now _tx() always returns the frozen time
const now = _tx(); // Always '2025-01-15'
const later = _tx().addDays(1); // Always '2025-01-16'
// Unfreeze to return to normal behavior
_tx.unfreeze();
// Check if time is frozen
_tx.isFrozen(); // falseFreezing with Different Input Types
import _tx from '24x7';
// Freeze with string
_tx.freeze('2025-01-15');
// Freeze with Date object
_tx.freeze(new Date('2025-01-15'));
// Freeze with timestamp
_tx.freeze(1705334400000);
// Freeze to current time (useful for snapshot testing)
_tx.freeze(); // Freezes to current momentTesting Examples
import _tx from '24x7';
// Example 1: Testing date-dependent logic
describe('Order Processing', () => {
beforeEach(() => {
// Freeze time to a known date
_tx.freeze('2025-01-15T10:00:00');
});
afterEach(() => {
// Always unfreeze in teardown
_tx.unfreeze();
});
it('should calculate delivery date correctly', () => {
const orderDate = _tx(); // Always '2025-01-15'
const deliveryDate = orderDate.addBusinessDays(5, {
weekends: [0, 6]
});
expect(deliveryDate.format('YYYY-MM-DD')).toBe('2025-01-22');
});
it('should handle same-day orders', () => {
const orderDate = _tx();
const isSameDay = orderDate.format('YYYY-MM-DD') === _tx().format('YYYY-MM-DD');
expect(isSameDay).toBe(true);
});
});
// Example 2: Testing time-sensitive features
describe('Promotion System', () => {
beforeEach(() => {
_tx.freeze('2025-01-10');
});
afterEach(() => {
_tx.unfreeze();
});
it('should check if promotion is active', () => {
const promotionStart =_tx('2025-01-01');
const promotionEnd =_tx('2025-01-31');
const range = promotionStart.range(promotionEnd);
expect(range.contains(_tx())).toBe(true); // Today is in range
});
it('should detect expired promotions', () => {
_tx.freeze('2025-02-01'); // After promotion ends
const promotionStart =_tx('2025-01-01');
const promotionEnd =_tx('2025-01-31');
const range = promotionStart.range(promotionEnd);
expect(range.contains(_tx())).toBe(false); // Today is not in range
});
});
// Example 3: Testing with multiple freeze points
describe('Time Progression', () => {
afterEach(() => {
_tx.unfreeze();
});
it('should simulate time progression', () => {
// Start at day 1
_tx.freeze('2025-01-01');
const day1 = _tx();
expect(day1.format('YYYY-MM-DD')).toBe('2025-01-01');
// Move to day 2
_tx.freeze('2025-01-02');
const day2 = _tx();
expect(day2.format('YYYY-MM-DD')).toBe('2025-01-02');
// Move to day 3
_tx.freeze('2025-01-03');
const day3 = _tx();
expect(day3.format('YYYY-MM-DD')).toBe('2025-01-03');
});
});Integration with Test Frameworks
import _tx from '24x7';
// Jest example
describe('My Feature', () => {
beforeAll(() => {
_tx.freeze('2025-01-15');
});
afterAll(() => {
_tx.unfreeze();
});
test('feature works with frozen time', () => {
// Your test code
});
});
// Mocha example
describe('My Feature', function() {
beforeEach(function() {
_tx.freeze('2025-01-15');
});
afterEach(function() {
_tx.unfreeze();
});
it('should work with frozen time', function() {
// Your test code
});
});
// Vitest example
import { beforeEach, afterEach, test } from 'vitest';
beforeEach(() => {
_tx.freeze('2025-01-15');
});
afterEach(() => {
_tx.unfreeze();
});
test('feature works', () => {
// Your test code
});Real-World Testing Scenarios
import _tx from '24x7';
// Scenario 1: Testing subscription renewal
describe('Subscription Service', () => {
beforeEach(() => {
_tx.freeze('2025-01-15');
});
afterEach(() => {
_tx.unfreeze();
});
it('should calculate next billing date', () => {
const subscriptionDate =_tx('2025-01-01');
const nextBilling = subscriptionDate.addMonths(1);
expect(nextBilling.format('YYYY-MM-DD')).toBe('2025-02-01');
});
it('should detect expired subscriptions', () => {
const expiryDate =_tx('2025-01-10');
const isExpired = _tx().isAfter(expiryDate);
expect(isExpired).toBe(true); // Today is after expiry
});
});
// Scenario 2: Testing business day calculations
describe('Business Day Calculator', () => {
beforeEach(() => {
_tx.freeze('2025-01-15'); // Wednesday
});
afterEach(() => {
_tx.unfreeze();
});
it('should skip weekends', () => {
const start =_tx('2025-01-15'); // Wednesday
const result = start.addBusinessDays(2, { weekends: [0, 6] });
// Should be Friday (skips weekend if it falls on one)
expect(result.format('YYYY-MM-DD')).toBe('2025-01-17');
});
it('should skip holidays', () => {
const start =_tx('2025-01-14'); // Tuesday
const result = start.addBusinessDays(1, {
weekends: [0, 6],
holidays: ['2025-01-15'] // Wednesday is a holiday
});
// Should skip to Thursday
expect(result.format('YYYY-MM-DD')).toBe('2025-01-16');
});
});
// Scenario 3: Testing date range operations
describe('Date Range Operations', () => {
beforeEach(() => {
_tx.freeze('2025-01-15');
});
afterEach(() => {
_tx.unfreeze();
});
it('should check if current date is in range', () => {
const range =_tx('2025-01-01').range('2025-01-31');
expect(range.contains(_tx())).toBe(true);
});
it('should handle edge cases', () => {
const range =_tx('2025-01-15').range('2025-01-15'); // Single day
expect(range.contains(_tx())).toBe(true);
expect(range.days()).toBe(1);
});
});Important Notes:
- Freezing time is global - affects all
_tx()calls - Always call
_tx.unfreeze()in test teardown (afterEach/afterAll) - Freezing affects only
_tx()without arguments - explicit dates are unaffected - Safe to use in parallel test runners (each test should manage its own freeze state)
Examples
Factory Function
Create dates from various inputs:
import _tx from '24x7';
// Current time
const now = _tx();
console.log(now.format('YYYY-MM-DD HH:mm:ss')); // Current date/time
// From ISO string
const date1 =_tx('2025-01-15');
const date2 =_tx('2025-01-15T14:30:00Z');
const date3 =_tx('2025-01-15T14:30:00.000Z');
// From Date object
const nativeDate = new Date('2025-01-15');
const date4 =_tx(nativeDate);
// From timestamp (milliseconds)
const date5 =_tx(1705334400000); // Unix timestamp
// From timestamp (seconds)
const date6 =_tx(1705334400 * 1000);
// Undefined uses current time
const date7 =_tx(undefined); // Same as _tx()Date Manipulation
Add or subtract time units:
import _tx from '24x7';
const date =_tx('2025-01-15T10:30:00');
// Add milliseconds
date.add(1000); // Add 1 second
// Add time units
date.addYears(1); // '2026-01-15T10:30:00'
date.addMonths(2); // '2025-03-15T10:30:00'
date.addDays(7); // '2025-01-22T10:30:00'
date.addHours(5); // '2025-01-15T15:30:00'
date.addMinutes(30); // '2025-01-15T11:00:00'
date.addSeconds(45); // '2025-01-15T10:30:45'
// Subtract time units
date.subtract(1000); // Subtract 1 second
date.addDays(-7); // Subtract 7 days (same as subtractDays)
date.subtract(7 * 24 * 60 * 60 * 1000); // Subtract 7 days in milliseconds
// Chain operations
const result =_tx('2025-01-15')
.addMonths(1)
.addDays(5)
.addHours(2)
.format('YYYY-MM-DD HH:mm'); // '2025-02-20 02:00'Date Comparison
Compare dates in various ways:
import _tx from '24x7';
const date1 =_tx('2025-01-15');
const date2 =_tx('2025-01-20');
const date3 =_tx('2025-01-15');
// Before/After checks
date1.isBefore(date2); // true
date1.isAfter(date2); // false
date1.isSame(date3); // true
// Compare with different input types
date1.isBefore('2025-01-20'); // true (string)
date1.isBefore(new Date('2025-01-20')); // true (Date object)
date1.isBefore(1705334400000); // true (timestamp)
// Between check (inclusive)
const start =_tx('2025-01-10');
const end =_tx('2025-01-20');
const middle =_tx('2025-01-15');
middle.isBetween(start, end); // true
start.isBetween(start, end); // true (inclusive)
end.isBetween(start, end); // true (inclusive)
x('2025-01-25').isBetween(start, end); // false
// Real-world example: Check if date is in current month
const today = _tx();
const monthStart = today.startOfMonth();
const monthEnd = today.endOfMonth();
const isInCurrentMonth = today.isBetween(monthStart, monthEnd); // Always trueDate Information
Extract date components:
import _tx from '24x7';
const date =_tx('2025-01-15T14:30:45.123');
// Get date parts
date.year(); // 2025
date.month(); // 0 (January, 0-indexed)
date.date(); // 15 (day of month, 1-indexed)
date.day(); // 3 (Wednesday, 0=Sunday)
date.hour(); // 14
date.minute(); // 30
date.second(); // 45
date.millisecond(); // 123
// Use in calculations
const isWeekend = date.day() === 0 || date.day() === 6;
const isJanuary = date.month() === 0;
const isLeapYear = date.year() % 4 === 0 && (date.year() % 100 !== 0 || date.year() % 400 === 0);
// Format with extracted values
const customFormat = `${date.year()}-${String(date.month() + 1).padStart(2, '0')}-${String(date.date()).padStart(2, '0')}`;Start/End of Periods
Get boundaries of time periods:
import _tx from '24x7';
const date =_tx('2025-01-15T14:30:45');
// Day boundaries
date.startOfDay(); // '2025-01-15T00:00:00.000'
date.endOfDay(); // '2025-01-15T23:59:59.999'
// Month boundaries
date.startOfMonth(); // '2025-01-01T00:00:00.000'
date.endOfMonth(); // '2025-01-31T23:59:59.999'
// Year boundaries
date.startOfYear(); // '2025-01-01T00:00:00.000'
date.endOfYear(); // '2025-12-31T23:59:59.999'
// Common use case: Get all dates in current month
const monthStart = _tx().startOfMonth();
const monthEnd = _tx().endOfMonth();
const daysInMonth = monthEnd.date(); // 28-31 depending on month
// Get first Monday of month
const firstDay =_tx('2025-01-15').startOfMonth();
const firstMonday = firstDay.addDays((8 - firstDay.day()) % 7 || 7);Conversion
Convert to different formats:
import _tx from '24x7';
const date =_tx('2025-01-15T14:30:45.123');
// To native Date object (cloned for safety)
const nativeDate = date.toDate();
console.log(nativeDate instanceof Date); // true
// To timestamp
const timestamp = date.valueOf(); // or date.getTime()
console.log(timestamp); // 1705334445123
// To ISO string
const isoString = date.toISOString();
console.log(isoString); // '2025-01-15T14:30:45.123Z'
// Implicit conversion (uses valueOf)
const num = +date; // Same as date.valueOf()
const str = String(date); // Uses toString()
// Use with native Date methods
const utcString = date.toDate().toUTCString();Timezone
Work with different timezones (default is UTC):
import _tx from '24x7';
// Default timezone is UTC
const date =_tx('2025-01-15T10:00:00Z');
console.log(date.tz()); // 'UTC'
console.log(date.isUTC()); // true
// Convert to local timezone
const local = date.toLocal();
console.log(local.tz()); // 'local'
console.log(local.isLocal()); // true
// Convert to specific IANA timezone (chaining works!)
const kolkata = date.tz('Asia/Kolkata');
console.log(kolkata.tz()); // 'Asia/Kolkata'
console.log(kolkata.format('YYYY-MM-DD HH:mm:ss')); // Shows time in Kolkata timezone
console.log(kolkata.ianaTimezone()); // 'Asia/Kolkata'
// Convert to another timezone
const nyDate = date.toTimezone('America/New_York');
console.log(nyDate.tz()); // 'America/New_York'
// Convert back to UTC
const backToUTC = nyDate.toUTC();
console.log(backToUTC.isUTC()); // true
// Get timezone offset
console.log(date.timezoneOffset()); // 0 (minutes)
console.log(date.timezoneOffsetString()); // '+00:00'
console.log(kolkata.timezoneOffsetString()); // '+05:30'
// Set global default timezone
_tx.config({
time: {
timezone: 'America/Los_Angeles'
}
});
const pacificDate =_tx('2025-01-15T10:00:00Z');
console.log(pacificDate.tz()); // 'America/Los_Angeles'Supported Timezones
- UTC - Coordinated Universal Time (default)
- local - System's local timezone
- IANA timezones - Any valid IANA timezone identifier (e.g.,
'America/New_York','Asia/Kolkata','Europe/London')
Timezone Methods
date.tz() // Get current timezone
date.tz(timezone) // Set timezone (returns DateTime for chaining)
date.toTimezone(timezone) // Convert to timezone (alternative method)
date.toUTC() // Convert to UTC
date.toLocal() // Convert to local timezone
date.timezoneOffset() // Get offset in minutes
date.timezoneOffsetString() // Get offset as string (e.g., '+05:30')
date.isUTC() // Check if UTC
date.isLocal() // Check if local
date.ianaTimezone() // Get IANA timezone name or nullTimezone-Aware Date Components
All date component methods (year(), month(), date(), hour(), minute(), second(), etc.) automatically respect the timezone:
const utc =_tx('2025-01-15T10:00:00Z');
console.log(utc.hour()); // 10 (UTC)
const kolkata = utc.tz('Asia/Kolkata');
console.log(kolkata.hour()); // 15 (IST, UTC+5:30)
// Formatting also respects timezone
console.log(utc.format('YYYY-MM-DD HH:mm:ss')); // '2025-01-15 10:00:00'
console.log(kolkata.format('YYYY-MM-DD HH:mm:ss')); // '2025-01-15 15:30:00'Real-World Timezone Examples
import _tx from '24x7';
// Example 1: Convert meeting time across timezones
const meetingUTC =_tx('2025-01-15T14:00:00Z');
const meetingNY = meetingUTC.tz('America/New_York');
const meetingLA = meetingUTC.tz('America/Los_Angeles');
const meetingLondon = meetingUTC.tz('Europe/London');
console.log('Meeting times:');
console.log('UTC:', meetingUTC.format('YYYY-MM-DD HH:mm'));
console.log('New York:', meetingNY.format('YYYY-MM-DD HH:mm'));
console.log('Los Angeles:', meetingLA.format('YYYY-MM-DD HH:mm'));
console.log('London:', meetingLondon.format('YYYY-MM-DD HH:mm'));
// Example 2: Calculate time difference between timezones
const now = _tx();
const nyTime = now.tz('America/New_York');
const offset = nyTime.timezoneOffset() - now.timezoneOffset();
console.log(`Time difference: ${offset} minutes`);
// Example 3: Display user's local time
function displayUserTime(userTimezone) {
const now = _tx().tz(userTimezone);
return now.format('YYYY-MM-DD HH:mm:ss');
}
console.log(displayUserTime('Asia/Kolkata')); // User's local time in Kolkata
console.log(displayUserTime('America/New_York')); // User's local time in NYFormat Patterns
Format dates with custom patterns:
import _tx from '24x7';
const date =_tx('2025-01-15T14:30:45');
// Common formats
date.format('YYYY-MM-DD'); // '2025-01-15'
date.format('MM/DD/YYYY'); // '01/15/2025'
date.format('DD-MM-YYYY'); // '15-01-2025'
date.format('YYYY-MM-DD HH:mm:ss'); // '2025-01-15 14:30:45'
// Human-readable formats
date.format('MMMM D, YYYY'); // 'January 15, 2025'
date.format('MMM D, YYYY'); // 'Jan 15, 2025'
date.format('dddd, MMMM D, YYYY'); // 'Wednesday, January 15, 2025'
date.format('ddd, MMM D'); // 'Wed, Jan 15'
// Time formats
date.format('HH:mm'); // '14:30'
date.format('HH:mm:ss'); // '14:30:45'
date.format('hh:mm A'); // '02:30 PM'
date.format('hh:mm a'); // '02:30 pm'
// Combined formats
date.format('YYYY-MM-DD HH:mm'); // '2025-01-15 14:30'
date.format('MMM D, YYYY [at] hh:mm A'); // 'Jan 15, 2025 at 02:30 PM'
// File-safe format
date.format('YYYY-MM-DD_HH-mm-ss'); // '2025-01-15_14-30-45'API Reference
Factory Function
_t_tx() // Current time (or frozen time)
_tx('2025-01-15') // Parse string (ISO format)
_tx('23/01/2025', 'DD/MM/YYYY') // Parse with format
_tx('2025-01-15', { immutable: false }) // With config object
_tx(new Date()) // From Date object
_tx(1234567890) // From timestampConfiguration Methods
_tx.config() // Get current configuration
_tx.config(options) // Set configuration (deep merge)
_tx.resetConfig() // Reset to DEFAULT_CONFIGImmutability Control
date.immutable(true) // Enable immutability (default)
date.immutable(false) // Disable immutability (mutable mode)
date.isImmutable() // Check if instance is immutableDate Manipulation
date.add(ms) // Add milliseconds (returns new instance if immutable)
date.subtract(ms) // Subtract milliseconds (returns new instance if immutable)
date.addYears(n) // Add years
date.addMonths(n) // Add months
date.addDays(n) // Add days
date.addHours(n) // Add hours
date.addMinutes(n) // Add minutes
date.addSeconds(n) // Add secondsInterval Methods
date.interval(end) // Create interval from date to end
_tx.interval(start, end) // Static method to create interval
interval.contains(date | interval) // Check if contains date or interval
interval.overlaps(other, options) // Check if overlaps with another interval
interval.intersect(other) // Get intersection interval or null
interval.clamp(other) // Clamp interval to fit within this
interval.splitBy(unit | duration) // Split into array of intervals
interval.iterate(unit) // Iterator over interval
interval.duration(unit?) // Get duration in specified unit
_tx.merge(intervals[]) // Merge multiple intervalsDate Comparison
date.isBefore(other) // Check if before
date.isAfter(other) // Check if after
date.isSame(other) // Check if equal
date.isBetween(start, end) // Check if betweenDate Information
date.year() // Get year
date.month() // Get month (0-11)
date.date() // Get day of month (1-31)
date.day() // Get day of week (0-6)
date.hour() // Get hour (0-23)
date.minute() // Get minute (0-59)
date.second() // Get second (0-59)
date.millisecond() // Get millisecond (0-999)Start/End of Periods
date.startOfDay() // Start of day (00:00:00.000)
date.endOfDay() // End of day (23:59:59.999)
date.startOfMonth() // Start of month
date.endOfMonth() // End of month
date.startOfYear() // Start of year
date.endOfYear() // End of yearConversion
date.toDate() // Get native Date object
date.valueOf() // Get timestamp
date.getTime() // Get timestamp
date.toISOString() // Get ISO string
date.format(pattern) // Format with pattern (requires format plugin)
// Timezone methods
date.tz() // Get current timezone (or set with date.tz('Asia/Kolkata'))
date.tz(timezone) // Set timezone and return DateTime (for chaining)
date.timezone() // Alias for tz() (backward compatibility)
date.toTimezone(tz) // Convert to timezone
date.toUTC() // Convert to UTC
date.toLocal() // Convert to local
date.timezoneOffset() // Get offset in minutes
date.timezoneOffsetString() // Get offset as string (e.g., '+05:30')
date.isUTC() // Check if UTC
date.isLocal() // Check if local
date.ianaTimezone() // Get IANA timezone name or nullFormat Patterns
date.format('YYYY-MM-DD') // '2025-01-15'
date.format('MMM D, YYYY') // 'Jan 15, 2025'
date.format('YYYY-MM-DD HH:mm') // '2025-01-15 14:30'
date.format('dddd, MMMM D, YYYY') // 'Wednesday, January 15, 2025'Pattern tokens:
YYYY- 4-digit yearYY- 2-digit yearMMMM- Full month nameMMM- Short month nameMM- 2-digit monthM- Month numberDD- 2-digit dayD- Day numberdddd- Full day nameddd- Short day nameHH- 24-hour format (2-digit)hh- 12-hour format (2-digit)mm- Minutes (2-digit)ss- Seconds (2-digit)A- AM/PMa- am/pm
Plugin System
Plugins extend the DateTime prototype. All plugins are included by default, but you can create custom plugins:
Basic Plugin Example
import _tx, { DateTime } from '24x7';
// Simplest form: Named function becomes the method
function myMethod() {
// Your custom method
return this;
}
_tx.extend(myMethod);
// Now you can use it
_tx().myMethod();
// Alternative 1: Explicit method name
_tx.extend('myMethod2', function() {
return this;
});
// Alternative 2: Install function (for multiple methods)
_tx.extend((DateTime) => {
DateTime.prototype.method1 = function() { return this; };
DateTime.prototype.method2 = function() { return this; };
});
// Alternative 3: Object with install method (also supported)
const myPlugin = {
install(DateTime) {
DateTime.prototype.myMethod3 = function() {
return this;
};
}
};
_tx.extend(myPlugin);Advanced Plugin Examples
import _tx, { DateTime } from '24x7';
// Example 1: Add timezone offset methods (install function for multiple methods)
_tx.extend((DateTime) => {
DateTime.prototype.getTimezoneOffset = function() {
return this.toDate().getTimezoneOffset();
};
DateTime.prototype.toUTC = function() {
const date = this.toDate();
const utcDate = new Date(date.getTime() + (date.getTimezoneOffset() * 60000));
return new DateTime(utcDate);
};
});
_tx.extend(timezonePlugin);
// Usage
const date =_tx('2025-01-15T14:30:00');
console.log(date.getTimezoneOffset()); // -480 (PST)
const utc = date.toUTC();
// Example 2: Add age calculation (direct method)
function age(referenceDate = _tx()) {
const ref = referenceDate instanceof DateTime
? referenceDate
: new DateTime(new Date(referenceDate));
let years = ref.year() - this.year();
const monthDiff = ref.month() - this.month();
const dayDiff = ref.date() - this.date();
if (monthDiff < 0 || (monthDiff === 0 && dayDiff < 0)) {
years--;
}
return years;
}
_tx.extend(age);
// Usage
const birthDate =_tx('1990-05-15');
const age = birthDate.age(); // Age as of today
const ageOnDate = birthDate.age(_tx('2025-05-15')); // Age on specific date
// Example 3: Add quarter methods (install function for multiple methods)
_tx.extend((DateTime) => {
DateTime.prototype.quarter = function() {
return Math.floor(this.month() / 3) + 1;
};
DateTime.prototype.startOfQuarter = function() {
const quarter = this.quarter();
const month = (quarter - 1) * 3;
const newDate = new Date(this._date);
newDate.setMonth(month, 1);
newDate.setHours(0, 0, 0, 0);
return new DateTime(newDate);
};
DateTime.prototype.endOfQuarter = function() {
const quarter = this.quarter();
const month = quarter * 3 - 1;
const newDate = new Date(this._date);
newDate.setMonth(month + 1, 0);
newDate.setHours(23, 59, 59, 999);
return new DateTime(newDate);
};
});
// Usage
const date =_tx('2025-02-15');
console.log(date.quarter()); // 1 (Q1)
const quarterStart = date.startOfQuarter(); // 2025-01-01
const quarterEnd = date.endOfQuarter(); // 2025-03-31
// Example 4: Add relative time formatting (direct method)
function fromNow() {
const now = _tx();
const diffMs = now.getTime() - this.getTime();
const diffSeconds = Math.floor(diffMs / 1000);
const diffMinutes = Math.floor(diffSeconds / 60);
const diffHours = Math.floor(diffMinutes / 60);
const diffDays = Math.floor(diffHours / 24);
if (Math.abs(diffSeconds) < 60) {
return diffSeconds >= 0 ? 'just now' : 'in a few seconds';
} else if (Math.abs(diffMinutes) < 60) {
const mins = Math.abs(diffMinutes);
return diffMinutes >= 0 ? `${mins} minute${mins > 1 ? 's' : ''} ago` : `in ${mins} minute${mins > 1 ? 's' : ''}`;
} else if (Math.abs(diffHours) < 24) {
const hrs = Math.abs(diffHours);
return diffHours >= 0 ? `${hrs} hour${hrs > 1 ? 's' : ''} ago` : `in ${hrs} hour${hrs > 1 ? 's' : ''}`;
} else if (Math.abs(diffDays) < 7) {
const days = Math.abs(diffDays);
return diffDays >= 0 ? `${days} day${days > 1 ? 's' : ''} ago` : `in ${days} day${days > 1 ? 's' : ''}`;
} else {
return this.format('MMM D, YYYY');
}
}
_tx.extend(fromNow);
// Usage
const past = _tx().subtractHours(2);
console.log(past.fromNow()); // "2 hours ago"
const future = _tx().addDays(3);
console.log(future.fromNow()); // "in 3 days"Creating Reusable Plugins
// plugins/custom.js - Single method
export default function myMethod() {
return this;
}
// plugins/custom.js - Multiple methods (install function)
export default (DateTime) => {
DateTime.prototype.method1 = function() { return this; };
DateTime.prototype.method2 = function() { return this; };
};
// In your application
import _tx from '24x7';
import customPlugin from './plugins/custom.js';
_tx.extend(customPlugin);Plugin Best Practices
import _tx, { DateTime } from '24x7';
// Single method - use named function
function chainableMethod() {
// Do something
return this;
}
_tx.extend(chainableMethod);
// Multiple methods - use install function
_tx.extend((DateTime) => {
// 1. Always return this for chainability (when appropriate)
DateTime.prototype.chainableMethod2 = function() {
return this;
};
// 2. Create new DateTime instances for immutable operations
DateTime.prototype.immutableOperation = function() {
const newDate = new Date(this._date);
// Modify newDate
return new DateTime(newDate);
};
// 3. Validate inputs
DateTime.prototype.safeMethod = function(input) {
if (typeof input !== 'number') {
throw new TypeError('Expected a number');
}
// Your logic
return this;
};
// 4. Use descriptive method names
DateTime.prototype.calculateBusinessHours = function() {
// Better than calculateBH()
return this;
};
});Error Handling
24x7 throws explicit errors for invalid operations:
Parsing Errors
import _tx from '24x7';
// Invalid date string
try {
_tx('invalid-date');
} catch (error) {
console.error(error); // TypeError: Cannot parse date from: invalid-date
console.error(error.message); // "Cannot parse date from: invalid-date"
}
// Invalid date object
try {
_tx(new Date('invalid'));
} catch (error) {
console.error(error); // TypeError: Invalid Date object provided
}
// Invalid input type
try {
_tx({ not: 'a date' });
} catch (error) {
console.error(error); // TypeError: Cannot parse date from type: object
}Range Errors
import _tx from '24x7';
// Start date after end date
try {
_tx('2025-01-10').range('2025-01-05'); // start > end
} catch (error) {
console.error(error); // RangeError: Start date must be before or equal to end date
}
// Valid range (no error)
const validRange =_tx('2025-01-05').range('2025-01-10'); // OK
const singleDayRange =_tx('2025-01-05').range('2025-01-05'); // OK (same day)Business Day Errors
import _tx from '24x7';
// Invalid weekends array
try {
_tx('2025-01-10').addBusinessDays(5, {
weekends: 'not an array' // Should be array
});
} catch (error) {
console.error(error); // TypeError: weekends must be an array
}
// Invalid weekend values
try {
_tx('2025-01-10').addBusinessDays(5, {
weekends: [0, 7] // 7 is invalid (0-6 only)
});
} catch (error) {
console.error(error); // TypeError: weekends must be an array of numbers 0-6
}
// Invalid days parameter
try {
_tx('2025-01-10').addBusinessDays('five'); // Should be number
} catch (error) {
console.error(error); // TypeError: addBusinessDays() expects a number
}Comparison Errors
import _tx from '24x7';
// Invalid comparison value
try {
_tx('2025-01-10').isBefore('not-a-date');
} catch (error) {
console.error(error); // TypeError: Cannot compare with invalid date: not-a-date
}
// Valid comparisons
x('2025-01-10').isBefore('2025-01-15'); // true (no error)
x('2025-01-10').isBefore(new Date('2025-01-15')); // true (no error)Format Errors
import _tx from '24x7';
// Invalid pattern type
try {
_tx('2025-01-15').format(123); // Should be string
} catch (error) {
console.error(error); // TypeError: format() expects a string pattern
}
// Valid format
x('2025-01-15').format('YYYY-MM-DD'); // '2025-01-15' (no error)Manipulation Errors
import _tx from '24x7';
// Invalid add/subtract values
try {
_tx('2025-01-15').add('not a number');
} catch (error) {
console.error(error); // TypeError: add() expects a number (milliseconds)
}
try {
_tx('2025-01-15').addDays('five');
} catch (error) {
console.error(error); // TypeError: addDays() expects a number
}
// Valid operations
x('2025-01-15').add(1000); // OK
x('2025-01-15').addDays(5); // OKError Handling Best Practices
import _tx from '24x7';
// Pattern 1: Try-catch for user input
function parseUserDate(input) {
try {
return _tx(input);
} catch (error) {
console.error('Invalid date input:', input);
return null; // or return a default date
}
}
// Pattern 2: Validate before operations
function safeAddBusinessDays(date, days, options) {
if (typeof days !== 'number') {
throw new TypeError('Days must be a number');
}
try {
return _tx(date).addBusinessDays(days, options);
} catch (error) {
console.error('Error adding business days:', error);
throw error; // Re-throw or handle as needed
}
}
// Pattern 3: Graceful degradation
function getDateOrNow(input) {
try {
return _tx(input);
} catch {
return _tx(); // Fallback to current time
}
}
// Pattern 4: Type checking
function isValidDateInput(input) {
try {
_tx(input);
return true;
} catch {
return false;
}
}
// Usage
if (isValidDateInput(userInput)) {
const date =_tx(userInput);
// Process date
} else {
// Show error to user
console.error('Please enter a valid date');
}Browser and Node.js Support
- ES2020+ required
- Node.js: 14+ (or any environment with ES modules)
- Browsers: Modern browsers with ES module support
Design Principles
- Immutability: All operations return new instances
- Explicit APIs: No magic, no surprises
- Predictable behavior: DST changes don't silently corrupt data
- Small core: Extensible via plugins
- No dependencies: Self-contained
- Tree-shakable: Import only what you need
License
See LICENSE file for details.
Contributing
This is a production-ready library. Contributions should maintain:
- Immutability guarantees
- Explicit error handling
- Comprehensive documentation
- Test coverage
Non-Goals
24x7 explicitly does not provide:
- Moment.js compatibility layer
- Large locale databases
- Full timezone engine
- Implicit magic behavior
These are intentional design decisions to keep the library focused and maintainable.
