export function parseTime ( $time ) { const parts = $time . split ( ':' ) . slice ( 0 , 2 ) ; const lowercased = $time . trim ( ) . toLowerCase ( ) ; const isAM = lowercased . endsWith ( 'am' ) || lowercased . endsWith ( 'a.m.' ) ; const isPM = lowercased . endsWith ( 'pm' ) || lowercased . endsWith ( 'p.m.' ) ; let hours = parts . length > 0 ? parseInt ( parts [ 0 ] ) : 0 ; if ( isNaN ( hours ) || hours > 23 ) { hours = 0 ; } if ( isAM && hours === 12 ) { hours = 0 ; } if ( isPM && hours < 12 ) { hours += 12 ; } let minutes = parts . length > 1 ? parseInt ( parts [ 1 ] ) : 0 ; if ( isNaN ( minutes ) || minutes > 59 ) { minutes = 0 ; } return [ hours , minutes ] ; } function parse24HTime ( time ) { return time . split ( ':' ) . map ( ( x ) => parseInt ( x ) ) ; } function compareTime ( time1 , time2 ) { if ( time1 [ 0 ] === time2 [ 0 ] && time1 [ 1 ] === time2 [ 1 ] ) { return 0 ; } if ( time1 [ 0 ] < time2 [ 0 ] || ( time1 [ 0 ] === time2 [ 0 ] && time1 [ 1 ] < time2 [ 1 ] ) ) { return - 1 ; } return 1 ; } export function nextTimeInterval ( time0 , time1 , date = new Date ( ) ) { const a = parse24HTime ( time0 ) ; const b = parse24HTime ( time1 ) ; const t = [ date . getHours ( ) , date . getMinutes ( ) ] ; if ( compareTime ( a , b ) > 0 ) { return nextTimeInterval ( time1 , time0 , date ) ; } if ( compareTime ( a , b ) === 0 ) { return null ; } if ( compareTime ( t , a ) < 0 ) { date . setHours ( a [ 0 ] ) ; date . setMinutes ( a [ 1 ] ) ; return date . getTime ( ) ; } if ( compareTime ( t , b ) < 0 ) { date . setHours ( b [ 0 ] ) ; date . setMinutes ( b [ 1 ] ) ; return date . getTime ( ) ; } return ( new Date ( date . getFullYear ( ) , date . getMonth ( ) , date . getDate ( ) + 1 , a [ 0 ] , a [ 1 ] ) ) . getTime ( ) ; } export function isInTimeIntervalLocal ( time0 , time1 , date = new Date ( ) ) { const a = parse24HTime ( time0 ) ; const b = parse24HTime ( time1 ) ; const t = [ date . getHours ( ) , date . getMinutes ( ) ] ; if ( compareTime ( a , b ) > 0 ) { return compareTime ( a , t ) <= 0 || compareTime ( t , b ) < 0 ; } return compareTime ( a , t ) <= 0 && compareTime ( t , b ) < 0 ; } function isInTimeIntervalUTC ( time0 , time1 , timestamp ) { if ( time1 < time0 ) { return timestamp <= time1 || time0 <= timestamp ; } return time0 < timestamp && timestamp < time1 ; } interface Duration { days ? : number ; hours ? : number ; minutes ? : number ; seconds ? : number ; } export function getDuration ( time ) { let duration = 0 ; if ( time . seconds ) { duration += time . seconds * 1000 ; } if ( time . minutes ) { duration += time . minutes * 60 * 1000 ; } if ( time . hours ) { duration += time . hours * 60 * 60 * 1000 ; } if ( time . days ) { duration += time . days * 24 * 60 * 60 * 1000 ; } return duration ; } export function getDurationInMinutes ( time ) { return getDuration ( time ) / 1000 / 60 ; } function getSunsetSunriseUTCTime ( latitude , longitude , date ) { const dec31 = Date . UTC ( date . getUTCFullYear ( ) , 0 , 0 , 0 , 0 , 0 , 0 ) ; const oneDay = getDuration ( { days : 1 } ) ; const dayOfYear = Math . floor ( ( date . getTime ( ) - dec31 ) / oneDay ) ; const zenith = 90.83333333333333 ; const D2R = Math . PI / 180 ; const R2D = 180 / Math . PI ; const lnHour = longitude / 15 ; function getTime ( isSunrise ) { const t = dayOfYear + ( ( ( isSunrise ? 6 : 18 ) - lnHour ) / 24 ) ; const M = ( 0.9856 * t ) - 3.289 ; let L = M + ( 1.916 * Math . sin ( M * D2R ) ) + ( 0.020 * Math . sin ( 2 * M * D2R ) ) + 282.634 ; if ( L > 360 ) { L -= 360 ; } else if ( L < 0 ) { L += 360 ; } let RA = R2D * Math . atan ( 0.91764 * Math . tan ( L * D2R ) ) ; if ( RA > 360 ) { RA -= 360 ; } else if ( RA < 0 ) { RA += 360 ; } const Lquadrant = ( Math . floor ( L / ( 90 ) ) ) * 90 ; const RAquadrant = ( Math . floor ( RA / 90 ) ) * 90 ; RA += ( Lquadrant - RAquadrant ) ; RA /= 15 ; const sinDec = 0.39782 * Math . sin ( L * D2R ) ; const cosDec = Math . cos ( Math . asin ( sinDec ) ) ; const cosH = ( Math . cos ( zenith * D2R ) - ( sinDec * Math . sin ( latitude * D2R ) ) ) / ( cosDec * Math . cos ( latitude * D2R ) ) ; if ( cosH > 1 ) { return { alwaysDay : false , alwaysNight : true , time : 0 , } ; } else if ( cosH < - 1 ) { return { alwaysDay : true , alwaysNight : false , time : 0 , } ; } const H = ( isSunrise ? ( 360 - R2D * Math . acos ( cosH ) ) : ( R2D * Math . acos ( cosH ) ) ) / 15 ; const T = H + RA - ( 0.06571 * t ) - 6.622 ; let UT = T - lnHour ; if ( UT > 24 ) { UT -= 24 ; } else if ( UT < 0 ) { UT += 24 ; } return { alwaysDay : false , alwaysNight : false , time : Math . round ( UT * getDuration ( { hours : 1 } ) ) , } ; } const sunriseTime = getTime ( true ) ; const sunsetTime = getTime ( false ) ; if ( sunriseTime . alwaysDay || sunsetTime . alwaysDay ) { return { alwaysDay : true } ; } else if ( sunriseTime . alwaysNight || sunsetTime . alwaysNight ) { return { alwaysNight : true } ; } return { sunriseTime : sunriseTime . time , sunsetTime : sunsetTime . time } ; } export function isNightAtLocation ( latitude , longitude , date = new Date ( ) ) { const time = getSunsetSunriseUTCTime ( latitude , longitude , date ) ; if ( time . alwaysDay ) { return false ; } else if ( time . alwaysNight ) { return true ; } const sunriseTime = time . sunriseTime ; const sunsetTime = time . sunsetTime ; const currentTime = ( date . getUTCHours ( ) * getDuration ( { hours : 1 } ) + date . getUTCMinutes ( ) * getDuration ( { minutes : 1 } ) + date . getUTCSeconds ( ) * getDuration ( { seconds : 1 } ) + date . getUTCMilliseconds ( ) ) ; return isInTimeIntervalUTC ( sunsetTime , sunriseTime , currentTime ) ; } export function nextTimeChangeAtLocation ( latitude , longitude , date = new Date ( ) ) { const time = getSunsetSunriseUTCTime ( latitude , longitude , date ) ; if ( time . alwaysDay ) { return date . getTime ( ) + getDuration ( { days : 1 } ) ; } else if ( time . alwaysNight ) { return date . getTime ( ) + getDuration ( { days : 1 } ) ; } const [ firstTimeOnDay , lastTimeOnDay ] = time . sunriseTime < time . sunsetTime ? [ time . sunriseTime , time . sunsetTime ] : [ time . sunsetTime , time . sunriseTime ] ; const currentTime = ( date . getUTCHours ( ) * getDuration ( { hours : 1 } ) + date . getUTCMinutes ( ) * getDuration ( { minutes : 1 } ) + date . getUTCSeconds ( ) * getDuration ( { seconds : 1 } ) + date . getUTCMilliseconds ( ) ) ; if ( currentTime <= firstTimeOnDay ) { return Date . UTC ( date . getUTCFullYear ( ) , date . getUTCMonth ( ) , date . getUTCDate ( ) , 0 , 0 , 0 , firstTimeOnDay ) ; } if ( currentTime <= lastTimeOnDay ) { return Date . UTC ( date . getUTCFullYear ( ) , date . getUTCMonth ( ) , date . getUTCDate ( ) , 0 , 0 , 0 , lastTimeOnDay ) ; } return Date . UTC ( date . getUTCFullYear ( ) , date . getUTCMonth ( ) , date . getUTCDate ( ) + 1 , 0 , 0 , 0 , firstTimeOnDay ) ; }