import { parseISO, subDays, subHours, subMinutes, } from "date-fns"; import factory from "factoria"; import initI18next from "i18n/initI18next"; import i18next from "i18next"; import { formatApiDatetime, formatDifferenceForHumans, formatISONoSeconds, formatProjectsApiDatetimeLong, getNowISO, } from "sharedHelpers/dateAndTime"; const remoteObservation = factory( "RemoteObservation", { created_at: "2015-02-12T20:41:10-08:00", } ); const remoteIdentification = factory( "RemoteIdentification", { created_at: "2015-02-13T05:12:05+00:00", } ); const remoteComment = factory( "RemoteComment", { created_at: "2015-02-13T05:15:38+00:00", updated_at: "2015-02-12T20:41:10-08:00", } ); describe( "formatApiDatetime", ( ) => { describe( "in default locale", ( ) => { beforeAll( async ( ) => { await initI18next( { lng: "en" } ); } ); it( "should return missing date string if no date is present", async ( ) => { expect( formatApiDatetime( null, i18next ) ).toEqual( "Missing Date" ); } ); it( "should return missing date string if date is empty", ( ) => { const date = ""; expect( formatApiDatetime( date, i18next ) ).toEqual( "Missing Date" ); } ); it( "should return a localized date when a date string is passed in", ( ) => { const date = "2022-11-02"; expect( formatApiDatetime( date, i18next ) ).toEqual( "11/2/22" ); } ); it( "should return a localized datetime when a datetime string is passed in", ( ) => { const date = "2022-11-02T18:43:00+00:00"; expect( formatApiDatetime( date, i18next ) ).toEqual( "11/2/22 6:43 PM UTC" ); } ); it( "should return a localized datetime for a remote observation created_at date", ( ) => { expect( formatApiDatetime( remoteObservation.created_at, i18next, { inViewerTimeZone: true } ), ).toEqual( "2/13/15 4:41 AM UTC" ); } ); it( "should return a localized datetime for a remote identification created_at date", ( ) => { expect( formatApiDatetime( remoteIdentification.created_at, i18next ), ).toEqual( "2/13/15 5:12 AM UTC" ); } ); it( "should return a localized datetime for a remote comment created_at date", ( ) => { expect( formatApiDatetime( remoteComment.created_at, i18next ), ).toEqual( "2/13/15 5:15 AM UTC" ); } ); it( "should return the date in the local time zone by default", () => { expect( process.env.TZ ).toEqual( "UTC" ); expect( formatApiDatetime( "2023-01-02T08:00:00+01:00", i18next ), ).toEqual( "1/2/23 7:00 AM UTC" ); } ); it( "should return the date in a requested time zone", () => { expect( process.env.TZ ).toEqual( "UTC" ); expect( formatApiDatetime( "2023-01-02T08:00:00+01:00", i18next, { timeZone: "Asia/Tokyo" } ), ).toEqual( "1/2/23 4:00 PM GMT+9" ); } ); it.todo( "should return a localized datetime for a local observation created_at date", ); it.todo( "should optionally show the date in the original time zone" ); } ); describe( "in es-MX", ( ) => { beforeAll( async ( ) => { await initI18next( { lng: "es-MX" } ); } ); it( "should return a localized date when a date string is passed in", ( ) => { const date = "2022-11-02"; expect( formatApiDatetime( date, i18next ) ).toEqual( "2/11/22" ); } ); } ); } ); describe( "formatProjectsApiDatetimeLong", ( ) => { beforeAll( async ( ) => { await initI18next( { lng: "en" } ); } ); it( "returns null for malformed timezone project dates when missing is null", ( ) => { expect( formatProjectsApiDatetimeLong( "2023-06-080 0 +04:00", i18next, { missing: null } ), ).toBeNull( ); } ); it( "returns Missing Date for malformed timezone project dates by default", ( ) => { expect( formatProjectsApiDatetimeLong( "2023-06-080 0 +04:00", i18next ), ).toEqual( "Missing Date" ); } ); } ); describe( "getNowISO", ( ) => { it( "should return a valid ISO8601 string", ( ) => { const dateString = getNowISO( ); expect( parseISO( dateString ) ).not.toBeNull( ); } ); it( "should not have a time zone", ( ) => { const dateString = getNowISO( ); expect( parseISO( dateString ) ).not.toContain( "Z" ); } ); } ); describe( "formatISONoSeconds", ( ) => { it( "should not include seconds", ( ) => { const dateString = formatISONoSeconds( new Date( ) ); expect( dateString.split( ":" ).length ).toEqual( 2 ); } ); } ); describe( "formatDifferenceForHumans", ( ) => { beforeAll( async ( ) => { await initI18next( { lng: "en" } ); } ); it( "should show difference in minutes", ( ) => { expect( formatDifferenceForHumans( subMinutes( new Date(), 3 ), i18next ) ).toMatch( /3m/ ); } ); it( "should show difference in hours", ( ) => { expect( formatDifferenceForHumans( subHours( new Date(), 3 ), i18next ) ).toMatch( /3h/ ); } ); it( "should show difference in days", ( ) => { expect( formatDifferenceForHumans( subDays( new Date(), 3 ), i18next ) ).toMatch( /3d/ ); } ); it( "should show not show fractional days", ( ) => { expect( formatDifferenceForHumans( subHours( new Date(), 26 ), i18next ) ).toMatch( /1d/ ); } ); it( "should show difference in weeks", ( ) => { expect( formatDifferenceForHumans( subDays( new Date(), 14 ), i18next ) ).toMatch( /2w/ ); } ); it( "should show not show fractional weeks", ( ) => { expect( formatDifferenceForHumans( subDays( new Date(), 16 ), i18next ) ).toMatch( /2w/ ); } ); it( "should show day and month if over 30 days ago but still this year", ( ) => { const now = new Date(); // This test will only work after the first 40 days of the year have // passed if ( now.getUTCMonth() <= 1 ) return; if ( now.getUTCMonth() <= 2 && now.getDay() < 10 ) return; const date = subDays( now, 40 ); const dateString = formatDifferenceForHumans( date, i18next ); const pattern = new RegExp( `\\w+ ${date.getDate()}` ); expect( dateString ).toMatch( pattern ); } ); it( "should show full date for prior years", ( ) => { const date = subDays( new Date(), 400 ); const dateString = formatDifferenceForHumans( date, i18next ); const pattern = new RegExp( [ date.getMonth() + 1, date.getDate(), date.getFullYear() % 1000, ].join( "/" ) ); expect( dateString ).toMatch( pattern ); } ); } );