Function Date_Services(@Service, @Params)
/***********************************************************************************************************************

    This program is proprietary and is not to be used by or disclosed to others, nor is it to be copied without written
    permission from SRP Computer Solutions, Inc.

    Name        :   Date_Services

    Description :   Handler program for all module related services.

    Notes       :   The generic parameters should contain all the necessary information to process the services. Often
                    this will be information like the data Record and Key ID.

    Parameters  :
        Service         [in] -- Name of the service being requested
        Error          [out] -- Any errors that were created
        Param1-10   [in/out] -- Additional request parameter holders
        Response       [out] -- Response to be sent back to the Controller (MCP) or requesting procedure

    Metadata    :

    History     :   (Date, Initials, Notes)
        10/31/17    dmb     Original programmer.

***********************************************************************************************************************/

#pragma precomp SRP_PreCompiler

$insert LOGICAL
$insert SERVICE_SETUP

// Self referencing declarations.
Declare function    Memory_Services, Date_Services, SRP_Math
Declare subroutine  Memory_Services

GoToService else
    Error_Services('Add', Service : ' is not a valid service request within the ' : ServiceModule : ' module.')
end

Return Response OR ''


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Service Parameter Options
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Options BOOLEAN     = True$, False$


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Services
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

//----------------------------------------------------------------------------------------------------------------------
// GetNumISOWeeks
//
// Returns the number of weeks within the indicated year based on the ISO 8601 standards.
// (cf. https://en.wikipedia.org/wiki/ISO_week_date)
//----------------------------------------------------------------------------------------------------------------------
Service GetNumISOWeeks(Year)

    ServiceKeyID   := '*' : Year
    NumISOWeeks     = Memory_Services('GetValue', ServiceKeyID)

    If NumISOWeeks EQ '' then
        If (Year NE '') AND (Num(Year)) then
            ModCurrent      = Mod((Year + Int(Year / 4) - Int(Year / 100) + Int(Year / 400)), 7)
            YearPrevious    = Year - 1
            ModPrevious     = Mod((YearPrevious + Int(YearPrevious / 4) - Int(YearPrevious / 100) + Int(YearPrevious / 400)), 7)
            NumISOWeeks     = 52 + (If ModCurrent EQ 4 OR ModPrevious EQ 3 then 1 else 0)
            Memory_Services('SetValue', ServiceKeyID, NumISWeeks)
        end else
            Error_Services('Add', 'The Year argument was missing or was not a number in the ' : Service : ' service.')
        end
    end

    Response    = NumISOWeeks

end service


//----------------------------------------------------------------------------------------------------------------------
// GetISOWeekDate
//
// Returns the ISO formatted week date for the indicated Gregorian date based on the ISO 8601 standards.
// (cf. https://en.wikipedia.org/wiki/ISO_week_date)
//----------------------------------------------------------------------------------------------------------------------
Service GetISOWeekDate(Date)

    If Not(Num(Date)) then Date = Iconv(Date, 'D')

    ServiceKeyID   := '*' : Date
    ISOWeekDate     = Memory_Services('GetValue', ServiceKeyID)

    If ISOWeekDate EQ '' then
        If Date NE '' then
            // The ISO week date system places the 4 January in the first week. Using this information, the first day of
            // the ISO year can be calculated.
            Year            = Field(Oconv(Date, 'D4/'), '/', 3, 1)  ; // Format the date and get the calendar year.
            Jan4            = Iconv('01/04/' : Year, 'D')           ; // Compulate the 4 January for this year.
            Jan4DOW         = Mod(Jan4, 7)                          ; // Get the day of week value (0=Sunday, 6=Saturday).
            If Jan4DOW EQ 0 then Jan4DOW = 7                        ; // Give Sunday a value of 7 rather than 0 for computational purposes.
            FirstDay        = Jan4 - (Jan4DOW - 1)                  ; // Computes the date of Monday, which is always the first day of the ISO week.

            // Using the same logic, compute the first day of the previous calendar year.
            PrevYear        = Year - 1
            PrevJan4        = Iconv('01/04/' : PrevYear, 'D')
            PrevJan4DOW     = Mod(PrevJan4, 7)
            If PrevJan4DOW EQ 0 then PrevJan4DOW = 7
            PrevFirstDay    = PrevJan4 - (PrevJan4DOW - 1)

            // Again, using the same logic, compute the first day of the next calendar year.
            NextYear        = Year + 1
            NextJan4        = Iconv('01/04/' : NextYear, 'D')
            NextJan4DOW     = Mod(NextJan4, 7)
            If NextJan4DOW EQ 0 then NextJan4DOW = 7
            NextFirstDay    = NextJan4 - (NextJan4DOW - 1)

            // Compare the indicated date to see which ISO year it falls within.
            Begin Case
                Case Date GE FirstDay AND Date LT NextFirstDay
                    WeekYear    = Field(Oconv(FirstDay + 7, 'D4/'), '/', 3, 1)
                    WeekNumber  = Fmt(Int((Date - FirstDay) / 7) + 1, 'R(0)#2')
                Case Date LT FirstDay
                    WeekYear    = Field(Oconv(FirstDay - 7, 'D4/'), '/', 3, 1)
                    WeekNumber  = Fmt(Int((Date - PrevFirstDay) / 7) + 1, 'R(0)#2')
                Case Date GE NextFirstDay
                    WeekYear    = Field(Oconv(NextFirstDay + 7, 'D4/'), '/', 3, 1)
                    WeekNumber  = Fmt(Int((Date - NextFirstDay) / 7) + 1, 'R(0)#2')
            End Case
            DOW         = Mod(Date, 7)
            If DOW EQ 0 then DOW = 7
            ISOWeekDate = WeekYear : '-W' : WeekNumber : '-' : DOW
            Memory_Services('SetValue', ServiceKeyID, ISOWeekDate)
        end else
            Error_Services('Add', 'The Date argument was missing in the ' : Service : ' service.')
        end
    end

    Response    = ISOWeekDate

end service


//----------------------------------------------------------------------------------------------------------------------
// GetWeekDate
//
// Returns the week date for the indicated Gregorian date.
//----------------------------------------------------------------------------------------------------------------------
Service GetWeekDate(Date)

    If Not(Num(Date)) then Date = Iconv(Date, 'D')

    ServiceKeyID   := '*' : Date
    ISOWeekDate     = Memory_Services('GetValue', ServiceKeyID)

    If ISOWeekDate EQ '' then
        If Date NE '' then
            // The ISO week date system places the 4 January in the first week. Using this information, the first day of
            // the ISO year can be calculated.
            Year            = Field(Oconv(Date, 'D4/'), '/', 3, 1)  ; // Format the date and get the calendar year.
            Jan1            = Iconv('01/01/' : Year, 'D')           ; // Compulate the 4 January for this year.
            Jan1DOW         = Mod(Jan1, 7)                          ; // Get the day of week value (0=Sunday, 6=Saturday).
            If Jan1DOW EQ 0 then Jan1DOW = 7                        ; // Give Sunday a value of 7 rather than 0 for computational purposes.
            FirstDay        = Jan1 - (Jan1DOW - 1)                  ; // Computes the date of Monday, which is always the first day of the ISO week.

            // Using the same logic, compute the first day of the previous calendar year.
            PrevYear        = Year - 1
            PrevJan1        = Iconv('01/01/' : PrevYear, 'D')
            PrevJan1DOW     = Mod(PrevJan1, 7)
            If PrevJan1DOW EQ 0 then PrevJan1DOW = 7
            PrevFirstDay    = PrevJan1 - (PrevJan1DOW - 1)

            // Again, using the same logic, compute the first day of the next calendar year.
            NextYear        = Year + 1
            NextJan1        = Iconv('01/01/' : NextYear, 'D')
            NextJan1DOW     = Mod(NextJan1, 7)
            If NextJan1DOW EQ 0 then NextJan1DOW = 7
            NextFirstDay    = NextJan1 - (NextJan1DOW - 1)

            // Compare the indicated date to see which ISO year it falls within.
            Begin Case
                Case Date GE FirstDay AND Date LT NextFirstDay
                    WeekYear    = Field(Oconv(FirstDay + 7, 'D4/'), '/', 3, 1)
                    WeekNumber  = Fmt(Int((Date - FirstDay) / 7) + 1, 'R(0)#2')
                Case Date LT FirstDay
                    WeekYear    = Field(Oconv(FirstDay - 7, 'D4/'), '/', 3, 1)
                    WeekNumber  = Fmt(Int((Date - PrevFirstDay) / 7) + 1, 'R(0)#2')
                Case Date GE NextFirstDay
                    WeekYear    = Field(Oconv(NextFirstDay + 7, 'D4/'), '/', 3, 1)
                    WeekNumber  = Fmt(Int((Date - NextFirstDay) / 7) + 1, 'R(0)#2')
            End Case
            DOW         = Mod(Date, 7)
            If DOW EQ 0 then DOW = 7
            ISOWeekDate = WeekYear : '-W' : WeekNumber : '-' : DOW
            Memory_Services('SetValue', ServiceKeyID, ISOWeekDate)
        end else
            Error_Services('Add', 'The Date argument was missing in the ' : Service : ' service.')
        end
    end

    Response    = ISOWeekDate

end service


Service GetWeekNum(InputDate)
    
    If Num(InputDate) then InputDate = OConv(InputDate, 'D4/')
    Year        = Field(InputDate, '/', 3, 1)  
    Jan1        = Iconv('01/01/' : Year, 'D')           
    Jan1DOW     = Mod(Jan1, 7)
    // Get Sunday's date
    FirstSunday = Jan1 - (Jan1DOW + 1)
    InputDate   = IConv(InputDate, 'D') 
    Weeks       = SRP_Math('CEILING', ( (InputDate - FirstSunday) / 7) )
    Dec31       = IConv('12/31/' : Year, 'D')
    WeeksInYear = Abs(SRP_Math('FLOOR', ( (Dec31 - FirstSunday) / 7) ))
    If Weeks GT WeeksInYear then Weeks = Mod(Weeks, WeeksInYear)
    Response    = Fmt(Weeks, 'R(0)#2')
   
end service


Service ConvertDateTimeToISO8601(DatetimeToConv)

    Response  = OConv(DatetimeToConv, "[SRP_DATETIME,()YYYY-MM-DD'T'hh:mm:ss.000Z]")
    
end service


Service ConvertISO8601ToDateTime(ISO8601Dtm)
    
    Response = IConv(ISO8601Dtm, "[SRP_DATETIME,()YYYY-MM-DD'T'hh:mm:ss.000Z]")
    
end service


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Internal GoSubs
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



