import { tap } from '@git.zone/tstest/tapbundle'; import * as plugins from '../plugins.js'; import { EInvoice } from '../../../ts/index.js'; import { PerformanceTracker } from '../performance.tracker.js'; const performanceTracker = new PerformanceTracker('EDGE-10: Time Zone Edge Cases'); tap.test('EDGE-10: Time Zone Edge Cases - should handle complex timezone scenarios', async (t) => { const einvoice = new EInvoice(); // Test 1: Date/time across timezone boundaries const timezoneBoundaries = await performanceTracker.measureAsync( 'timezone-boundary-crossing', async () => { const boundaryTests = [ { name: 'midnight-utc', dateTime: '2024-01-15T00:00:00Z', timezone: 'UTC', expectedLocal: '2024-01-15T00:00:00' }, { name: 'midnight-cross-positive', dateTime: '2024-01-15T23:59:59+12:00', timezone: 'Pacific/Auckland', expectedUTC: '2024-01-15T11:59:59Z' }, { name: 'midnight-cross-negative', dateTime: '2024-01-15T00:00:00-11:00', timezone: 'Pacific/Midway', expectedUTC: '2024-01-15T11:00:00Z' }, { name: 'date-line-crossing', dateTime: '2024-01-15T12:00:00+14:00', timezone: 'Pacific/Kiritimati', expectedUTC: '2024-01-14T22:00:00Z' } ]; const results = []; for (const test of boundaryTests) { const xml = ` TZ-TEST-001 ${test.dateTime} ${test.dateTime} `; try { const parsed = await einvoice.parseXML(xml); const dates = await einvoice.normalizeDates(parsed, { targetTimezone: test.timezone }); results.push({ test: test.name, parsed: true, originalDateTime: test.dateTime, normalizedDate: dates?.IssueDate, isDatePreserved: dates?.dateIntegrity || false, crossesDateBoundary: dates?.crossesDateLine || false }); } catch (error) { results.push({ test: test.name, parsed: false, error: error.message }); } } return results; } ); timezoneBoundaries.forEach(result => { t.ok(result.parsed, `Timezone boundary ${result.test} should be handled`); }); // Test 2: DST (Daylight Saving Time) transitions const dstTransitions = await performanceTracker.measureAsync( 'dst-transition-handling', async () => { const dstTests = [ { name: 'spring-forward-gap', dateTime: '2024-03-10T02:30:00', timezone: 'America/New_York', description: 'Time that does not exist due to DST' }, { name: 'fall-back-ambiguous', dateTime: '2024-11-03T01:30:00', timezone: 'America/New_York', description: 'Time that occurs twice due to DST' }, { name: 'dst-boundary-exact', dateTime: '2024-03-31T02:00:00', timezone: 'Europe/London', description: 'Exact moment of DST transition' }, { name: 'southern-hemisphere-dst', dateTime: '2024-10-06T02:00:00', timezone: 'Australia/Sydney', description: 'Southern hemisphere DST transition' } ]; const results = []; for (const test of dstTests) { const xml = ` DST-${test.name} ${test.dateTime} ${test.dateTime} `; try { const parsed = await einvoice.parseXML(xml); const dstAnalysis = await einvoice.analyzeDSTIssues(parsed); results.push({ scenario: test.name, handled: true, hasAmbiguity: dstAnalysis?.isAmbiguous || false, isNonExistent: dstAnalysis?.isNonExistent || false, suggestion: dstAnalysis?.suggestion, adjustedTime: dstAnalysis?.adjusted }); } catch (error) { results.push({ scenario: test.name, handled: false, error: error.message }); } } return results; } ); dstTransitions.forEach(result => { t.ok(result.handled, `DST transition ${result.scenario} should be handled`); if (result.hasAmbiguity || result.isNonExistent) { t.ok(result.suggestion, 'DST issue should have suggestion'); } }); // Test 3: Historic timezone changes const historicTimezones = await performanceTracker.measureAsync( 'historic-timezone-changes', async () => { const historicTests = [ { name: 'pre-timezone-standardization', dateTime: '1850-01-01T12:00:00', location: 'Europe/London', description: 'Before standard time zones' }, { name: 'soviet-time-changes', dateTime: '1991-03-31T02:00:00', location: 'Europe/Moscow', description: 'USSR timezone reorganization' }, { name: 'samoa-dateline-change', dateTime: '2011-12-30T00:00:00', location: 'Pacific/Apia', description: 'Samoa skipped December 30, 2011' }, { name: 'crimea-timezone-change', dateTime: '2014-03-30T02:00:00', location: 'Europe/Simferopol', description: 'Crimea timezone change' } ]; const results = []; for (const test of historicTests) { const xml = ` HISTORIC-${test.name} ${test.dateTime} ${test.location} `; try { const parsed = await einvoice.parseXML(xml); const historicAnalysis = await einvoice.handleHistoricDate(parsed, { validateHistoric: true }); results.push({ test: test.name, processed: true, isHistoric: historicAnalysis?.isHistoric || false, hasTimezoneChange: historicAnalysis?.timezoneChanged || false, warnings: historicAnalysis?.warnings || [] }); } catch (error) { results.push({ test: test.name, processed: false, error: error.message }); } } return results; } ); historicTimezones.forEach(result => { t.ok(result.processed, `Historic timezone ${result.test} should be processed`); }); // Test 4: Fractional timezone offsets const fractionalTimezones = await performanceTracker.measureAsync( 'fractional-timezone-offsets', async () => { const fractionalTests = [ { name: 'newfoundland-half-hour', offset: '-03:30', timezone: 'America/St_Johns', dateTime: '2024-01-15T12:00:00-03:30' }, { name: 'india-half-hour', offset: '+05:30', timezone: 'Asia/Kolkata', dateTime: '2024-01-15T12:00:00+05:30' }, { name: 'nepal-quarter-hour', offset: '+05:45', timezone: 'Asia/Kathmandu', dateTime: '2024-01-15T12:00:00+05:45' }, { name: 'chatham-islands', offset: '+12:45', timezone: 'Pacific/Chatham', dateTime: '2024-01-15T12:00:00+12:45' } ]; const results = []; for (const test of fractionalTests) { const xml = ` FRAC-${test.name} ${test.dateTime} ${test.dateTime} `; try { const parsed = await einvoice.parseXML(xml); const normalized = await einvoice.normalizeToUTC(parsed); results.push({ test: test.name, offset: test.offset, parsed: true, correctlyHandled: normalized?.timezoneHandled || false, preservedPrecision: normalized?.precisionMaintained || false }); } catch (error) { results.push({ test: test.name, offset: test.offset, parsed: false, error: error.message }); } } return results; } ); fractionalTimezones.forEach(result => { t.ok(result.parsed, `Fractional timezone ${result.test} should be parsed`); if (result.parsed) { t.ok(result.correctlyHandled, 'Fractional offset should be handled correctly'); } }); // Test 5: Missing or ambiguous timezone info const ambiguousTimezones = await performanceTracker.measureAsync( 'ambiguous-timezone-info', async () => { const ambiguousTests = [ { name: 'no-timezone-info', xml: ` 2024-01-15 14:30:00 ` }, { name: 'conflicting-timezones', xml: ` 2024-01-15T14:30:00+02:00 America/New_York ` }, { name: 'local-time-only', xml: ` 2024-01-15T14:30:00 ` }, { name: 'invalid-offset', xml: ` 2024-01-15T14:30:00+25:00 ` } ]; const results = []; for (const test of ambiguousTests) { const fullXml = `${test.xml}`; try { const parsed = await einvoice.parseXML(fullXml); const timezoneAnalysis = await einvoice.resolveTimezones(parsed, { defaultTimezone: 'UTC', strict: false }); results.push({ test: test.name, resolved: true, hasAmbiguity: timezoneAnalysis?.ambiguous || false, resolution: timezoneAnalysis?.resolution, confidence: timezoneAnalysis?.confidence || 0 }); } catch (error) { results.push({ test: test.name, resolved: false, error: error.message }); } } return results; } ); ambiguousTimezones.forEach(result => { t.ok(result.resolved || result.error, `Ambiguous timezone ${result.test} should be handled`); if (result.resolved && result.hasAmbiguity) { t.ok(result.confidence < 100, 'Ambiguous timezone should have lower confidence'); } }); // Test 6: Leap seconds handling const leapSeconds = await performanceTracker.measureAsync( 'leap-seconds-handling', async () => { const leapSecondTests = [ { name: 'leap-second-23-59-60', dateTime: '2016-12-31T23:59:60Z', description: 'Actual leap second' }, { name: 'near-leap-second', dateTime: '2016-12-31T23:59:59.999Z', description: 'Just before leap second' }, { name: 'after-leap-second', dateTime: '2017-01-01T00:00:00.001Z', description: 'Just after leap second' } ]; const results = []; for (const test of leapSecondTests) { const xml = ` LEAP-${test.name} ${test.dateTime} `; try { const parsed = await einvoice.parseXML(xml); const timeHandling = await einvoice.handlePreciseTime(parsed); results.push({ test: test.name, handled: true, isLeapSecond: timeHandling?.isLeapSecond || false, adjusted: timeHandling?.adjusted || false, precision: timeHandling?.precision }); } catch (error) { results.push({ test: test.name, handled: false, error: error.message }); } } return results; } ); leapSeconds.forEach(result => { t.ok(result.handled || result.error, `Leap second ${result.test} should be processed`); }); // Test 7: Format-specific timezone handling const formatSpecificTimezones = await performanceTracker.measureAsync( 'format-specific-timezone-handling', async () => { const formats = [ { format: 'ubl', xml: ` 2024-01-15 14:30:00+02:00 ` }, { format: 'cii', xml: ` 20240115143000 ` }, { format: 'facturx', xml: ` 2024-01-15T14:30:00 +0200 ` } ]; const results = []; for (const test of formats) { try { const parsed = await einvoice.parseDocument(test.xml); const standardized = await einvoice.standardizeDateTime(parsed, { sourceFormat: test.format }); results.push({ format: test.format, parsed: true, hasDateTime: !!standardized?.dateTime, hasTimezone: !!standardized?.timezone, normalized: standardized?.normalized || false }); } catch (error) { results.push({ format: test.format, parsed: false, error: error.message }); } } return results; } ); formatSpecificTimezones.forEach(result => { t.ok(result.parsed, `Format ${result.format} timezone should be handled`); }); // Test 8: Business day calculations across timezones const businessDayCalculations = await performanceTracker.measureAsync( 'business-day-calculations', async () => { const businessTests = [ { name: 'payment-terms-30-days', issueDate: '2024-01-15T23:00:00+12:00', terms: 30, expectedDue: '2024-02-14' }, { name: 'cross-month-boundary', issueDate: '2024-01-31T22:00:00-05:00', terms: 1, expectedDue: '2024-02-01' }, { name: 'weekend-adjustment', issueDate: '2024-01-12T18:00:00Z', // Friday terms: 3, expectedDue: '2024-01-17' // Skip weekend } ]; const results = []; for (const test of businessTests) { const xml = ` BUSINESS-${test.name} ${test.issueDate} ${test.terms} `; try { const parsed = await einvoice.parseXML(xml); const calculated = await einvoice.calculateDueDate(parsed, { skipWeekends: true, skipHolidays: true, timezone: 'UTC' }); results.push({ test: test.name, calculated: true, dueDate: calculated?.dueDate, matchesExpected: calculated?.dueDate === test.expectedDue, businessDaysUsed: calculated?.businessDays }); } catch (error) { results.push({ test: test.name, calculated: false, error: error.message }); } } return results; } ); businessDayCalculations.forEach(result => { t.ok(result.calculated, `Business day calculation ${result.test} should work`); }); // Test 9: Timezone conversion errors const timezoneConversionErrors = await performanceTracker.measureAsync( 'timezone-conversion-errors', async () => { const errorTests = [ { name: 'invalid-timezone-name', timezone: 'Invalid/Timezone', dateTime: '2024-01-15T12:00:00' }, { name: 'deprecated-timezone', timezone: 'US/Eastern', // Deprecated, use America/New_York dateTime: '2024-01-15T12:00:00' }, { name: 'military-timezone', timezone: 'Z', // Zulu time dateTime: '2024-01-15T12:00:00' }, { name: 'three-letter-timezone', timezone: 'EST', // Ambiguous dateTime: '2024-01-15T12:00:00' } ]; const results = []; for (const test of errorTests) { const xml = ` ERROR-${test.name} ${test.dateTime} `; try { const parsed = await einvoice.parseXML(xml); const converted = await einvoice.convertTimezone(parsed, { from: test.timezone, to: 'UTC', strict: true }); results.push({ test: test.name, handled: true, converted: !!converted, fallbackUsed: converted?.fallback || false, warning: converted?.warning }); } catch (error) { results.push({ test: test.name, handled: false, error: error.message, isTimezoneError: error.message.includes('timezone') || error.message.includes('time zone') }); } } return results; } ); timezoneConversionErrors.forEach(result => { t.ok(result.handled || result.isTimezoneError, `Timezone error ${result.test} should be handled appropriately`); }); // Test 10: Cross-format timezone preservation const crossFormatTimezones = await performanceTracker.measureAsync( 'cross-format-timezone-preservation', async () => { const testData = { dateTime: '2024-01-15T14:30:00+05:30', timezone: 'Asia/Kolkata' }; const sourceUBL = ` TZ-PRESERVE-001 2024-01-15 ${testData.dateTime} `; const conversions = ['cii', 'xrechnung', 'facturx']; const results = []; for (const targetFormat of conversions) { try { const converted = await einvoice.convertFormat(sourceUBL, targetFormat); const reparsed = await einvoice.parseDocument(converted); const extractedDateTime = await einvoice.extractDateTime(reparsed); results.push({ targetFormat, converted: true, timezonePreserved: extractedDateTime?.timezone === testData.timezone, offsetPreserved: extractedDateTime?.offset === '+05:30', dateTimeIntact: extractedDateTime?.iso === testData.dateTime }); } catch (error) { results.push({ targetFormat, converted: false, error: error.message }); } } return results; } ); crossFormatTimezones.forEach(result => { t.ok(result.converted, `Conversion to ${result.targetFormat} should succeed`); if (result.converted) { t.ok(result.timezonePreserved || result.offsetPreserved, 'Timezone information should be preserved'); } }); // Print performance summary performanceTracker.printSummary(); }); // Run the test tap.start();