import { DateMarker , addMs , diffHours , diffMinutes , diffSeconds , diffWholeWeeks , diffWholeDays , startOfDay , startOfHour , startOfMinute , startOfSecond , weekOfYear , arrayToUtcDate , dateToUtcArray , dateToLocalArray , arrayToLocalDate , timeAsMs , isValidDate , } from './marker' ; import { CalendarSystem , createCalendarSystem } from './calendar-system' ; import { Locale } from './locale' ; import { NamedTimeZoneImpl , NamedTimeZoneImplClass } from './timezone' ; import { Duration , asRoughYears , asRoughMonths , asRoughDays , asRoughMs } from './duration' ; import { DateFormatter , CmdFormatterFunc } from './DateFormatter' ; import { buildIsoString } from './formatting-utils' ; import { parse } from './parsing' ; import { isInt } from '../util/misc' ; export type WeekNumberCalculation = 'local' | 'ISO' | ( ( m : Date ) => number ) ; export interface DateEnvSettings { timeZone : string ; namedTimeZoneImpl ? : NamedTimeZoneImplClass ; calendarSystem : string ; locale : Locale ; weekNumberCalculation ? : WeekNumberCalculation ; firstDay ? : number ; weekText ? : string ; weekTextLong ? : string ; cmdFormatter ? : CmdFormatterFunc ; defaultSeparator ? : string ; } export type DateInput = Date | string | number | number [ ] ; export interface DateMarkerMeta { marker : DateMarker ; isTimeUnspecified : boolean ; forcedTzo : number | null ; } export class DateEnv { timeZone : string ; namedTimeZoneImpl : NamedTimeZoneImpl ; canComputeOffset : boolean ; calendarSystem : CalendarSystem ; locale : Locale ; weekDow : number ; weekDoy : number ; weekNumberFunc : any ; weekText : string ; weekTextLong : string ; cmdFormatter ? : CmdFormatterFunc ; defaultSeparator : string ; constructor ( settings ) { let timeZone = this . timeZone = settings . timeZone ; let isNamedTimeZone = timeZone !== 'local' && timeZone !== 'UTC' ; if ( settings . namedTimeZoneImpl && isNamedTimeZone ) { this . namedTimeZoneImpl = new settings . namedTimeZoneImpl ( timeZone ) ; } this . canComputeOffset = Boolean ( ! isNamedTimeZone || this . namedTimeZoneImpl ) ; this . calendarSystem = createCalendarSystem ( settings . calendarSystem ) ; this . locale = settings . locale ; this . weekDow = settings . locale . week . dow ; this . weekDoy = settings . locale . week . doy ; if ( settings . weekNumberCalculation === 'ISO' ) { this . weekDow = 1 ; this . weekDoy = 4 ; } if ( typeof settings . firstDay === 'number' ) { this . weekDow = settings . firstDay ; } if ( typeof settings . weekNumberCalculation === 'function' ) { this . weekNumberFunc = settings . weekNumberCalculation ; } this . weekText = settings . weekText != null ? settings . weekText : settings . locale . options . weekText ; this . weekTextLong = ( settings . weekTextLong != null ? settings . weekTextLong : settings . locale . options . weekTextLong ) || this . weekText ; this . cmdFormatter = settings . cmdFormatter ; this . defaultSeparator = settings . defaultSeparator ; } createMarker ( input ) { let meta = this . createMarkerMeta ( input ) ; if ( meta === null ) { return null ; } return meta . marker ; } createNowMarker ( ) { if ( this . canComputeOffset ) { return this . timestampToMarker ( new Date ( ) . valueOf ( ) ) ; } return arrayToUtcDate ( dateToLocalArray ( new Date ( ) ) ) ; } createMarkerMeta ( input ) { if ( typeof input === 'string' ) { return this . parse ( input ) ; } let marker = null ; if ( typeof input === 'number' ) { marker = this . timestampToMarker ( input ) ; } else if ( input instanceof Date ) { input = input . valueOf ( ) ; if ( ! isNaN ( input ) ) { marker = this . timestampToMarker ( input ) ; } } else if ( Array . isArray ( input ) ) { marker = arrayToUtcDate ( input ) ; } if ( marker === null || ! isValidDate ( marker ) ) { return null ; } return { marker , isTimeUnspecified : false , forcedTzo : null } ; } parse ( s ) { let parts = parse ( s ) ; if ( parts === null ) { return null ; } let { marker } = parts ; let forcedTzo = null ; if ( parts . timeZoneOffset !== null ) { if ( this . canComputeOffset ) { marker = this . timestampToMarker ( marker . valueOf ( ) - parts . timeZoneOffset * 60 * 1000 ) ; } else { forcedTzo = parts . timeZoneOffset ; } } return { marker , isTimeUnspecified : parts . isTimeUnspecified , forcedTzo } ; } getYear ( marker ) { return this . calendarSystem . getMarkerYear ( marker ) ; } getMonth ( marker ) { return this . calendarSystem . getMarkerMonth ( marker ) ; } add ( marker , dur ) { let a = this . calendarSystem . markerToArray ( marker ) ; a [ 0 ] += dur . years ; a [ 1 ] += dur . months ; a [ 2 ] += dur . days ; a [ 6 ] += dur . milliseconds ; return this . calendarSystem . arrayToMarker ( a ) ; } subtract ( marker , dur ) { let a = this . calendarSystem . markerToArray ( marker ) ; a [ 0 ] -= dur . years ; a [ 1 ] -= dur . months ; a [ 2 ] -= dur . days ; a [ 6 ] -= dur . milliseconds ; return this . calendarSystem . arrayToMarker ( a ) ; } addYears ( marker , n ) { let a = this . calendarSystem . markerToArray ( marker ) ; a [ 0 ] += n ; return this . calendarSystem . arrayToMarker ( a ) ; } addMonths ( marker , n ) { let a = this . calendarSystem . markerToArray ( marker ) ; a [ 1 ] += n ; return this . calendarSystem . arrayToMarker ( a ) ; } diffWholeYears ( m0 , m1 ) { let { calendarSystem } = this ; if ( timeAsMs ( m0 ) === timeAsMs ( m1 ) && calendarSystem . getMarkerDay ( m0 ) === calendarSystem . getMarkerDay ( m1 ) && calendarSystem . getMarkerMonth ( m0 ) === calendarSystem . getMarkerMonth ( m1 ) ) { return calendarSystem . getMarkerYear ( m1 ) - calendarSystem . getMarkerYear ( m0 ) ; } return null ; } diffWholeMonths ( m0 , m1 ) { let { calendarSystem } = this ; if ( timeAsMs ( m0 ) === timeAsMs ( m1 ) && calendarSystem . getMarkerDay ( m0 ) === calendarSystem . getMarkerDay ( m1 ) ) { return ( calendarSystem . getMarkerMonth ( m1 ) - calendarSystem . getMarkerMonth ( m0 ) ) + ( calendarSystem . getMarkerYear ( m1 ) - calendarSystem . getMarkerYear ( m0 ) ) * 12 ; } return null ; } greatestWholeUnit ( m0 , m1 ) { let n = this . diffWholeYears ( m0 , m1 ) ; if ( n !== null ) { return { unit : 'year' , value : n } ; } n = this . diffWholeMonths ( m0 , m1 ) ; if ( n !== null ) { return { unit : 'month' , value : n } ; } n = diffWholeWeeks ( m0 , m1 ) ; if ( n !== null ) { return { unit : 'week' , value : n } ; } n = diffWholeDays ( m0 , m1 ) ; if ( n !== null ) { return { unit : 'day' , value : n } ; } n = diffHours ( m0 , m1 ) ; if ( isInt ( n ) ) { return { unit : 'hour' , value : n } ; } n = diffMinutes ( m0 , m1 ) ; if ( isInt ( n ) ) { return { unit : 'minute' , value : n } ; } n = diffSeconds ( m0 , m1 ) ; if ( isInt ( n ) ) { return { unit : 'second' , value : n } ; } return { unit : 'millisecond' , value : m1 . valueOf ( ) - m0 . valueOf ( ) } ; } countDurationsBetween ( m0 , m1 , d ) { let diff ; if ( d . years ) { diff = this . diffWholeYears ( m0 , m1 ) ; if ( diff !== null ) { return diff / asRoughYears ( d ) ; } } if ( d . months ) { diff = this . diffWholeMonths ( m0 , m1 ) ; if ( diff !== null ) { return diff / asRoughMonths ( d ) ; } } if ( d . days ) { diff = diffWholeDays ( m0 , m1 ) ; if ( diff !== null ) { return diff / asRoughDays ( d ) ; } } return ( m1 . valueOf ( ) - m0 . valueOf ( ) ) / asRoughMs ( d ) ; } startOf ( m , unit ) { if ( unit === 'year' ) { return this . startOfYear ( m ) ; } if ( unit === 'month' ) { return this . startOfMonth ( m ) ; } if ( unit === 'week' ) { return this . startOfWeek ( m ) ; } if ( unit === 'day' ) { return startOfDay ( m ) ; } if ( unit === 'hour' ) { return startOfHour ( m ) ; } if ( unit === 'minute' ) { return startOfMinute ( m ) ; } if ( unit === 'second' ) { return startOfSecond ( m ) ; } return null ; } startOfYear ( m ) { return this . calendarSystem . arrayToMarker ( [ this . calendarSystem . getMarkerYear ( m ) , ] ) ; } startOfMonth ( m ) { return this . calendarSystem . arrayToMarker ( [ this . calendarSystem . getMarkerYear ( m ) , this . calendarSystem . getMarkerMonth ( m ) , ] ) ; } startOfWeek ( m ) { return this . calendarSystem . arrayToMarker ( [ this . calendarSystem . getMarkerYear ( m ) , this . calendarSystem . getMarkerMonth ( m ) , m . getUTCDate ( ) - ( ( m . getUTCDay ( ) - this . weekDow + 7 ) % 7 ) , ] ) ; } computeWeekNumber ( marker ) { if ( this . weekNumberFunc ) { return this . weekNumberFunc ( this . toDate ( marker ) ) ; } return weekOfYear ( marker , this . weekDow , this . weekDoy ) ; } format ( marker , formatter , dateOptions = { } ) { return formatter . format ( { marker , timeZoneOffset : dateOptions . forcedTzo != null ? dateOptions . forcedTzo : this . offsetForMarker ( marker ) , } , this ) ; } formatRange ( start , end , formatter , dateOptions = { } ) { if ( dateOptions . isEndExclusive ) { end = addMs ( end , - 1 ) ; } return formatter . formatRange ( { marker : start , timeZoneOffset : dateOptions . forcedStartTzo != null ? dateOptions . forcedStartTzo : this . offsetForMarker ( start ) , } , { marker : end , timeZoneOffset : dateOptions . forcedEndTzo != null ? dateOptions . forcedEndTzo : this . offsetForMarker ( end ) , } , this , dateOptions . defaultSeparator ) ; } formatIso ( marker , extraOptions = { } ) { let timeZoneOffset = null ; if ( ! extraOptions . omitTimeZoneOffset ) { if ( extraOptions . forcedTzo != null ) { timeZoneOffset = extraOptions . forcedTzo ; } else { timeZoneOffset = this . offsetForMarker ( marker ) ; } } return buildIsoString ( marker , timeZoneOffset , extraOptions . omitTime ) ; } timestampToMarker ( ms ) { if ( this . timeZone === 'local' ) { return arrayToUtcDate ( dateToLocalArray ( new Date ( ms ) ) ) ; } if ( this . timeZone === 'UTC' || ! this . namedTimeZoneImpl ) { return new Date ( ms ) ; } return arrayToUtcDate ( this . namedTimeZoneImpl . timestampToArray ( ms ) ) ; } offsetForMarker ( m ) { if ( this . timeZone === 'local' ) { return - arrayToLocalDate ( dateToUtcArray ( m ) ) . getTimezoneOffset ( ) ; } if ( this . timeZone === 'UTC' ) { return 0 ; } if ( this . namedTimeZoneImpl ) { return this . namedTimeZoneImpl . offsetForArray ( dateToUtcArray ( m ) ) ; } return null ; } toDate ( m , forcedTzo ? ) { if ( this . timeZone === 'local' ) { return arrayToLocalDate ( dateToUtcArray ( m ) ) ; } if ( this . timeZone === 'UTC' ) { return new Date ( m . valueOf ( ) ) ; } if ( ! this . namedTimeZoneImpl ) { return new Date ( m . valueOf ( ) - ( forcedTzo || 0 ) ) ; } return new Date ( m . valueOf ( ) - this . namedTimeZoneImpl . offsetForArray ( dateToUtcArray ( m ) ) * 1000 * 60 ) ; } }