@@ -2282,8 +2282,353 @@ describe('GeoJSON Validators', () => {
22822282 } ) ;
22832283 } ) ;
22842284 } ) ;
2285+
2286+ // ========== TEMPORAL VALIDATION TESTS ==========
2287+
2288+ describe ( 'Temporal Validation' , ( ) => {
2289+ describe ( 'ISO 8601 timestamp validation' , ( ) => {
2290+ it ( 'should accept valid timestamp with UTC timezone (Z)' , ( ) => {
2291+ const feature = {
2292+ type : 'Feature' ,
2293+ geometry : { type : 'Point' , coordinates : [ 5.0 , 45.0 ] } ,
2294+ properties : {
2295+ featureType : 'System' ,
2296+ uid : 'urn:test:1' ,
2297+ validTime : '2024-01-15T10:30:00Z'
2298+ }
2299+ } ;
2300+
2301+ const result = validateSystemFeature ( feature ) ;
2302+ expect ( result . valid ) . toBe ( true ) ;
2303+ } ) ;
2304+
2305+ it ( 'should accept valid timestamp with offset timezone' , ( ) => {
2306+ const feature = {
2307+ type : 'Feature' ,
2308+ geometry : { type : 'Point' , coordinates : [ 5.0 , 45.0 ] } ,
2309+ properties : {
2310+ featureType : 'System' ,
2311+ uid : 'urn:test:1' ,
2312+ validTime : '2024-01-15T10:30:00+05:30'
2313+ }
2314+ } ;
2315+
2316+ const result = validateSystemFeature ( feature ) ;
2317+ expect ( result . valid ) . toBe ( true ) ;
2318+ } ) ;
2319+
2320+ it ( 'should accept valid date-only format (YYYY-MM-DD)' , ( ) => {
2321+ const feature = {
2322+ type : 'Feature' ,
2323+ geometry : { type : 'Point' , coordinates : [ 5.0 , 45.0 ] } ,
2324+ properties : {
2325+ featureType : 'System' ,
2326+ uid : 'urn:test:1' ,
2327+ validTime : '2024-01-15'
2328+ }
2329+ } ;
2330+
2331+ const result = validateSystemFeature ( feature ) ;
2332+ expect ( result . valid ) . toBe ( true ) ;
2333+ } ) ;
2334+
2335+ it ( 'should accept valid timestamp with milliseconds' , ( ) => {
2336+ const feature = {
2337+ type : 'Feature' ,
2338+ geometry : { type : 'Point' , coordinates : [ 5.0 , 45.0 ] } ,
2339+ properties : {
2340+ featureType : 'System' ,
2341+ uid : 'urn:test:1' ,
2342+ validTime : '2024-01-15T10:30:00.123Z'
2343+ }
2344+ } ;
2345+
2346+ const result = validateSystemFeature ( feature ) ;
2347+ expect ( result . valid ) . toBe ( true ) ;
2348+ } ) ;
2349+
2350+ it ( 'should reject non-ISO 8601 format (US date format)' , ( ) => {
2351+ const feature = {
2352+ type : 'Feature' ,
2353+ geometry : { type : 'Point' , coordinates : [ 5.0 , 45.0 ] } ,
2354+ properties : {
2355+ featureType : 'System' ,
2356+ uid : 'urn:test:1' ,
2357+ validTime : '01/15/2024 10:30 AM'
2358+ }
2359+ } ;
2360+
2361+ const result = validateSystemFeature ( feature ) ;
2362+ expect ( result . valid ) . toBe ( false ) ;
2363+ expect ( result . errors ?. some ( e => e . includes ( 'Invalid ISO 8601 timestamp format' ) ) ) . toBe ( true ) ;
2364+ } ) ;
2365+
2366+ it ( 'should reject impossible date (Feb 30)' , ( ) => {
2367+ const feature = {
2368+ type : 'Feature' ,
2369+ geometry : { type : 'Point' , coordinates : [ 5.0 , 45.0 ] } ,
2370+ properties : {
2371+ featureType : 'System' ,
2372+ uid : 'urn:test:1' ,
2373+ validTime : '2024-02-30T00:00:00Z'
2374+ }
2375+ } ;
2376+
2377+ const result = validateSystemFeature ( feature ) ;
2378+ expect ( result . valid ) . toBe ( false ) ;
2379+ expect ( result . errors ?. some ( e => e . includes ( 'impossible date' ) ) ) . toBe ( true ) ;
2380+ } ) ;
2381+
2382+ it ( 'should reject impossible date (Month 13)' , ( ) => {
2383+ const feature = {
2384+ type : 'Feature' ,
2385+ geometry : { type : 'Point' , coordinates : [ 5.0 , 45.0 ] } ,
2386+ properties : {
2387+ featureType : 'System' ,
2388+ uid : 'urn:test:1' ,
2389+ validTime : '2024-13-01T00:00:00Z'
2390+ }
2391+ } ;
2392+
2393+ const result = validateSystemFeature ( feature ) ;
2394+ expect ( result . valid ) . toBe ( false ) ;
2395+ expect ( result . errors ?. some ( e => e . includes ( 'impossible date' ) ) ) . toBe ( true ) ;
2396+ } ) ;
2397+
2398+ it ( 'should warn about missing timezone' , ( ) => {
2399+ const feature = {
2400+ type : 'Feature' ,
2401+ geometry : { type : 'Point' , coordinates : [ 5.0 , 45.0 ] } ,
2402+ properties : {
2403+ featureType : 'System' ,
2404+ uid : 'urn:test:1' ,
2405+ validTime : '2024-01-15T10:30:00'
2406+ }
2407+ } ;
2408+
2409+ const result = validateSystemFeature ( feature ) ;
2410+ expect ( result . valid ) . toBe ( false ) ;
2411+ expect ( result . errors ?. some ( e => e . includes ( 'missing timezone' ) ) ) . toBe ( true ) ;
2412+ } ) ;
2413+
2414+ it ( 'should reject empty timestamp' , ( ) => {
2415+ const feature = {
2416+ type : 'Feature' ,
2417+ geometry : { type : 'Point' , coordinates : [ 5.0 , 45.0 ] } ,
2418+ properties : {
2419+ featureType : 'System' ,
2420+ uid : 'urn:test:1' ,
2421+ validTime : ''
2422+ }
2423+ } ;
2424+
2425+ const result = validateSystemFeature ( feature ) ;
2426+ expect ( result . valid ) . toBe ( false ) ;
2427+ expect ( result . errors ?. some ( e => e . includes ( 'cannot be empty' ) ) ) . toBe ( true ) ;
2428+ } ) ;
2429+ } ) ;
2430+
2431+ describe ( 'ISO 8601 interval validation' , ( ) => {
2432+ it ( 'should accept valid closed interval (start/end)' , ( ) => {
2433+ const feature = {
2434+ type : 'Feature' ,
2435+ geometry : { type : 'Point' , coordinates : [ 5.0 , 45.0 ] } ,
2436+ properties : {
2437+ featureType : 'System' ,
2438+ uid : 'urn:test:1' ,
2439+ validTime : '2024-01-15T00:00:00Z/2024-01-20T23:59:59Z'
2440+ }
2441+ } ;
2442+
2443+ const result = validateSystemFeature ( feature ) ;
2444+ expect ( result . valid ) . toBe ( true ) ;
2445+ } ) ;
2446+
2447+ it ( 'should accept valid open-ended start interval (../end)' , ( ) => {
2448+ const feature = {
2449+ type : 'Feature' ,
2450+ geometry : { type : 'Point' , coordinates : [ 5.0 , 45.0 ] } ,
2451+ properties : {
2452+ featureType : 'System' ,
2453+ uid : 'urn:test:1' ,
2454+ validTime : '../2024-01-20T23:59:59Z'
2455+ }
2456+ } ;
2457+
2458+ const result = validateSystemFeature ( feature ) ;
2459+ expect ( result . valid ) . toBe ( true ) ;
2460+ } ) ;
2461+
2462+ it ( 'should accept valid open-ended end interval (start/..)' , ( ) => {
2463+ const feature = {
2464+ type : 'Feature' ,
2465+ geometry : { type : 'Point' , coordinates : [ 5.0 , 45.0 ] } ,
2466+ properties : {
2467+ featureType : 'System' ,
2468+ uid : 'urn:test:1' ,
2469+ validTime : '2024-01-15T00:00:00Z/..'
2470+ }
2471+ } ;
2472+
2473+ const result = validateSystemFeature ( feature ) ;
2474+ expect ( result . valid ) . toBe ( true ) ;
2475+ } ) ;
2476+
2477+ it ( 'should reject reversed interval (end < start)' , ( ) => {
2478+ const feature = {
2479+ type : 'Feature' ,
2480+ geometry : { type : 'Point' , coordinates : [ 5.0 , 45.0 ] } ,
2481+ properties : {
2482+ featureType : 'System' ,
2483+ uid : 'urn:test:1' ,
2484+ validTime : '2024-01-20T00:00:00Z/2024-01-10T00:00:00Z'
2485+ }
2486+ } ;
2487+
2488+ const result = validateSystemFeature ( feature ) ;
2489+ expect ( result . valid ) . toBe ( false ) ;
2490+ expect ( result . errors ?. some ( e => e . includes ( 'Start time' ) && e . includes ( 'must be before end time' ) ) ) . toBe ( true ) ;
2491+ } ) ;
2492+
2493+ it ( 'should reject malformed interval (missing end)' , ( ) => {
2494+ const feature = {
2495+ type : 'Feature' ,
2496+ geometry : { type : 'Point' , coordinates : [ 5.0 , 45.0 ] } ,
2497+ properties : {
2498+ featureType : 'System' ,
2499+ uid : 'urn:test:1' ,
2500+ validTime : '2024-01-15T00:00:00Z/'
2501+ }
2502+ } ;
2503+
2504+ const result = validateSystemFeature ( feature ) ;
2505+ expect ( result . valid ) . toBe ( false ) ;
2506+ expect ( result . errors ?. some ( e => e . includes ( 'Timestamp cannot be empty' ) ) ) . toBe ( true ) ;
2507+ } ) ;
2508+
2509+ it ( 'should reject interval with invalid start timestamp' , ( ) => {
2510+ const feature = {
2511+ type : 'Feature' ,
2512+ geometry : { type : 'Point' , coordinates : [ 5.0 , 45.0 ] } ,
2513+ properties : {
2514+ featureType : 'System' ,
2515+ uid : 'urn:test:1' ,
2516+ validTime : '2024-02-30T00:00:00Z/2024-03-15T00:00:00Z'
2517+ }
2518+ } ;
2519+
2520+ const result = validateSystemFeature ( feature ) ;
2521+ expect ( result . valid ) . toBe ( false ) ;
2522+ expect ( result . errors ?. some ( e => e . includes ( '(start)' ) && e . includes ( 'impossible date' ) ) ) . toBe ( true ) ;
2523+ } ) ;
2524+
2525+ it ( 'should reject interval with invalid end timestamp' , ( ) => {
2526+ const feature = {
2527+ type : 'Feature' ,
2528+ geometry : { type : 'Point' , coordinates : [ 5.0 , 45.0 ] } ,
2529+ properties : {
2530+ featureType : 'System' ,
2531+ uid : 'urn:test:1' ,
2532+ validTime : '2024-01-15T00:00:00Z/2024-13-01T00:00:00Z'
2533+ }
2534+ } ;
2535+
2536+ const result = validateSystemFeature ( feature ) ;
2537+ expect ( result . valid ) . toBe ( false ) ;
2538+ expect ( result . errors ?. some ( e => e . includes ( '(end)' ) && e . includes ( 'impossible date' ) ) ) . toBe ( true ) ;
2539+ } ) ;
2540+ } ) ;
2541+
2542+ describe ( 'Integration with multiple feature types' , ( ) => {
2543+ it ( 'should validate validTime in Deployment features' , ( ) => {
2544+ const feature = {
2545+ type : 'Feature' ,
2546+ geometry : { type : 'Point' , coordinates : [ 5.0 , 45.0 ] } ,
2547+ properties : {
2548+ featureType : 'Deployment' ,
2549+ uid : 'urn:test:1' ,
2550+ system : { href : 'http://example.org/systems/1' } ,
2551+ validTime : '01/15/2024 10:30 AM' // Invalid format
2552+ }
2553+ } ;
2554+
2555+ const result = validateDeploymentFeature ( feature ) ;
2556+ expect ( result . valid ) . toBe ( false ) ;
2557+ expect ( result . errors ?. some ( e => e . includes ( 'validTime' ) && e . includes ( 'Invalid ISO 8601' ) ) ) . toBe ( true ) ;
2558+ } ) ;
2559+
2560+ it ( 'should validate validTime in Procedure features' , ( ) => {
2561+ const feature = {
2562+ type : 'Feature' ,
2563+ geometry : { type : 'Point' , coordinates : [ 5.0 , 45.0 ] } ,
2564+ properties : {
2565+ featureType : 'Procedure' ,
2566+ uid : 'urn:test:1' ,
2567+ validTime : '2024-02-30T00:00:00Z' // Impossible date
2568+ }
2569+ } ;
2570+
2571+ const result = validateProcedureFeature ( feature ) ;
2572+ expect ( result . valid ) . toBe ( false ) ;
2573+ expect ( result . errors ?. some ( e => e . includes ( 'validTime' ) && e . includes ( 'impossible date' ) ) ) . toBe ( true ) ;
2574+ } ) ;
2575+
2576+ it ( 'should validate phenomenonTime in Datastream features' , ( ) => {
2577+ const feature = {
2578+ type : 'Feature' ,
2579+ geometry : { type : 'Point' , coordinates : [ 5.0 , 45.0 ] } ,
2580+ properties : {
2581+ featureType : 'Datastream' ,
2582+ uid : 'urn:test:1' ,
2583+ system : { href : 'http://example.org/systems/1' } ,
2584+ observedProperty : { href : 'http://example.org/props/temp' } ,
2585+ phenomenonTime : '2024-01-15T10:30:00' // Missing timezone
2586+ }
2587+ } ;
2588+
2589+ const result = validateDatastreamFeature ( feature ) ;
2590+ expect ( result . valid ) . toBe ( false ) ;
2591+ expect ( result . errors ?. some ( e => e . includes ( 'phenomenonTime' ) && e . includes ( 'missing timezone' ) ) ) . toBe ( true ) ;
2592+ } ) ;
2593+
2594+ it ( 'should validate resultTime in Datastream features' , ( ) => {
2595+ const feature = {
2596+ type : 'Feature' ,
2597+ geometry : { type : 'Point' , coordinates : [ 5.0 , 45.0 ] } ,
2598+ properties : {
2599+ featureType : 'Datastream' ,
2600+ uid : 'urn:test:1' ,
2601+ system : { href : 'http://example.org/systems/1' } ,
2602+ observedProperty : { href : 'http://example.org/props/temp' } ,
2603+ resultTime : '2024-01-20T00:00:00Z/2024-01-10T00:00:00Z' // Reversed interval
2604+ }
2605+ } ;
2606+
2607+ const result = validateDatastreamFeature ( feature ) ;
2608+ expect ( result . valid ) . toBe ( false ) ;
2609+ expect ( result . errors ?. some ( e => e . includes ( 'resultTime' ) && e . includes ( 'must be before end time' ) ) ) . toBe ( true ) ;
2610+ } ) ;
2611+
2612+ it ( 'should validate samplingTime in SamplingFeature' , ( ) => {
2613+ const feature = {
2614+ type : 'Feature' ,
2615+ geometry : { type : 'Point' , coordinates : [ 5.0 , 45.0 ] } ,
2616+ properties : {
2617+ featureType : 'SamplingFeature' ,
2618+ uid : 'urn:test:1' ,
2619+ samplingTime : 'not-a-timestamp'
2620+ }
2621+ } ;
2622+
2623+ const result = validateSamplingFeature ( feature ) ;
2624+ expect ( result . valid ) . toBe ( false ) ;
2625+ expect ( result . errors ?. some ( e => e . includes ( 'samplingTime' ) && e . includes ( 'Invalid ISO 8601' ) ) ) . toBe ( true ) ;
2626+ } ) ;
2627+ } ) ;
2628+ } ) ;
22852629} ) ;
22862630
22872631
22882632
22892633
2634+
0 commit comments