/**
* Tests for fixed offset strategy in filename parsing
* Validates that non-AudioMoth files use a consistent offset throughout the cluster
* even when the recording period spans DST transitions
*/
import { describe, it, expect, vi } from 'vitest';
import { parseClusterFilenames } from '@/utils/filenameParser';
describe('Filename Parser Fixed Offset Strategy', () => {
describe('DST transition handling', () => {
it('should use earliest file offset for entire cluster spanning DST transition', () => {
// Test files spanning the Auckland DST transition in April 2021
// DST ended on April 4, 2021 (UTC+13 -> UTC+12)
const filenames = [
"20210401_120000.wav", // April 1st - DST still active (UTC+13)
"20210410_120000.wav", // April 10th - DST ended (would be UTC+12 if DST applied)
"20210420_120000.wav" // April 20th - Standard time (would be UTC+12 if DST applied)
];
const result = parseClusterFilenames(filenames, "Pacific/Auckland");
expect(result).toHaveLength(3);
// All files should use the same offset (from April 1st - earliest file)
const offsets = result.map(r => extractOffsetFromTimestamp(r.timestamp.timestampLocal));
const uniqueOffsets = new Set(offsets);
expect(uniqueOffsets.size).toBe(1); // All files must have same offset
// The offset should be UTC+13 (from the earliest file: April 1st)
expect(offsets[0]).toBe("+13:00");
// Verify actual UTC conversion uses the fixed offset consistently
const utcTimes = result.map(r => new Date(r.timestamp.timestampUTC));
// All files at 12:00 local should convert to the same UTC hour (with UTC+13 offset)
// 12:00 Auckland time - 13 hours = 23:00 UTC previous day
utcTimes.forEach(utcDate => {
expect(utcDate.getUTCHours()).toBe(23);
});
// Most importantly: verify all files use the SAME offset consistently
// This is the core requirement for the fixed offset strategy
const allOffsetsMatch = offsets.every(offset => offset === offsets[0]);
expect(allOffsetsMatch).toBe(true);
});
it('should handle out-of-order filenames correctly', () => {
// Files not in chronological order - should still use earliest file for offset
const filenames = [
"20210410_120000.wav", // April 10th (later)
"20210401_120000.wav", // April 1st (earliest - should determine offset)
"20210405_120000.wav" // April 5th (middle)
];
const result = parseClusterFilenames(filenames, "Pacific/Auckland");
expect(result).toHaveLength(3);
// All files should use UTC+13 offset (from April 1st, the earliest)
const offsets = result.map(r => extractOffsetFromTimestamp(r.timestamp.timestampLocal));
expect(offsets.every(offset => offset === "+13:00")).toBe(true);
// Results should maintain original filename order
expect(result[0].fileName).toBe("20210410_120000.wav");
expect(result[1].fileName).toBe("20210401_120000.wav");
expect(result[2].fileName).toBe("20210405_120000.wav");
});
it('should apply fixed offset consistently across large time spans', () => {
// Test files spanning multiple months with different DST periods
const filenames = [
"20210215_120000.wav", // February 15th (summer, UTC+13)
"20210615_120000.wav", // June 15th (winter, would be UTC+12 if DST applied)
"20210815_120000.wav" // August 15th (winter, would be UTC+12 if DST applied)
];
const result = parseClusterFilenames(filenames, "Pacific/Auckland");
expect(result).toHaveLength(3);
// All files should use the same offset from the earliest file (February)
const offsets = result.map(r => extractOffsetFromTimestamp(r.timestamp.timestampLocal));
expect(offsets.every(offset => offset === "+13:00")).toBe(true);
// Verify UTC conversion is consistent with fixed offset
const utcTimes = result.map(r => new Date(r.timestamp.timestampUTC));
utcTimes.forEach(utcDate => {
expect(utcDate.getUTCHours()).toBe(23); // 12 - 13 = -1 hour (23:00 previous day)
});
});
});
describe('US timezone DST transitions', () => {
it('should handle US DST transitions with fixed offset', () => {
// Test US spring DST transition (March 14, 2021)
const filenames = [
"20210310_120000.wav", // March 10th - before DST (UTC-5)
"20210320_120000.wav" // March 20th - after DST (would be UTC-4 if DST applied)
];
const result = parseClusterFilenames(filenames, "America/New_York");
expect(result).toHaveLength(2);
// All files should use the same offset from earliest file (March 10th)
const offsets = result.map(r => extractOffsetFromTimestamp(r.timestamp.timestampLocal));
expect(offsets.every(offset => offset === "-05:00")).toBe(true);
// Verify UTC conversion uses fixed offset
const utcTimes = result.map(r => new Date(r.timestamp.timestampUTC));
utcTimes.forEach(utcDate => {
expect(utcDate.getUTCHours()).toBe(17); // 12 + 5 = 17
});
});
});
describe('Fixed offset validation', () => {
it('should log the fixed offset strategy in console', () => {
// This test verifies the logging behavior mentioned in the code
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
const filenames = ["20210401_120000.wav", "20210410_120000.wav"];
parseClusterFilenames(filenames, "Pacific/Auckland");
// Should log the fixed offset being used
expect(consoleSpy).toHaveBeenCalledWith(
expect.stringContaining("Fixed offset for entire cluster: +13 hours")
);
consoleSpy.mockRestore();
});
it('should maintain offset consistency even with extreme date ranges', () => {
// Test files spanning different years with DST changes
const filenames = [
"20201215_120000.wav", // December 2020 (summer in Auckland, UTC+13)
"20210215_120000.wav", // February 2021 (summer, UTC+13)
"20210615_120000.wav" // June 2021 (winter, would be UTC+12 if DST applied)
];
const result = parseClusterFilenames(filenames, "Pacific/Auckland");
// All should use December 2020 offset (earliest file)
const offsets = result.map(r => extractOffsetFromTimestamp(r.timestamp.timestampLocal));
expect(offsets.every(offset => offset === "+13:00")).toBe(true);
});
});
});
/**
* Helper function to extract timezone offset from ISO timestamp
*/
function extractOffsetFromTimestamp(timestamp: string): string {
const match = timestamp.match(/([+-]\d{2}:\d{2})$/);
return match ? match[1] : '';
}