Task

Write a program or a script that returns the date of the last Fridays of each month of a given year.

The year may be given through any simple input method in your language (command line, std in, etc).



Example of an expected output:

./last_fridays 2012
2012-01-27
2012-02-24
2012-03-30
2012-04-27
2012-05-25
2012-06-29
2012-07-27
2012-08-31
2012-09-28
2012-10-26
2012-11-30
2012-12-28



Related tasks
 Five weekends
 Day of the week
 Find the last Sunday of each month


-- LAST FRIDAYS OF YEAR ------------------------------------------------------
 
--  lastFridaysOfYear :: Int -> [Date]
on lastFridaysOfYear(y)
 
    -- lastWeekDaysOfYear :: Int -> Int -> [Date]
    script lastWeekDaysOfYear
        on |λ|(intYear, iWeekday)
 
            -- lastWeekDay :: Int -> Int -> Date
            script lastWeekDay
                on |λ|(iLastDay, iMonth)
                    set iYear to intYear
 
                    calendarDate(iYear, iMonth, iLastDay - ¬
                        (((weekday of calendarDate(iYear, iMonth, iLastDay)) ¬
                            as integer) + (7 - (iWeekday))) mod 7)
                end |λ|
            end script
 
            map(lastWeekDay, lastDaysOfMonths(intYear))
        end |λ|
 
        -- isLeapYear :: Int -> Bool
        on isLeapYear(y)
            (0 = y mod 4) and (0 ≠ y mod 100) or (0 = y mod 400)
        end isLeapYear
 
        -- lastDaysOfMonths :: Int -> [Int]
        on lastDaysOfMonths(y)
            {31, cond(isLeapYear(y), 29, 28), ¬
                31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
        end lastDaysOfMonths
    end script
 
    lastWeekDaysOfYear's |λ|(y, Friday as integer)
end lastFridaysOfYear
 
 
-- TEST ----------------------------------------------------------------------
on run argv
 
    intercalate(linefeed, ¬
        map(isoRow, ¬
            transpose(map(lastFridaysOfYear, ¬
                apply(cond(class of argv is list and argv ≠ {}, ¬
                    singleYearOrRange, fiveCurrentYears), ¬
                    argIntegers(argv))))))
 
end run
 
 
-- ARGUMENT HANDLING ---------------------------------------------------------
 
-- Up to two optional command line arguments: [yearFrom], [yearTo]
-- (Default range in absence of arguments: 
--  from two years ago, to two years ahead)
 
-- ~ $ osascript ~/Desktop/lastFridays.scpt 
-- ~ $ osascript ~/Desktop/lastFridays.scpt 2013
-- ~ $ osascript ~/Desktop/lastFridays.scpt 2013 2016
 
-- singleYearOrRange :: [Int] -> [Int]
on singleYearOrRange(argv)
    apply(cond(length of argv > 0, my enumFromTo, my fiveCurrentYears), argv)
end singleYearOrRange
 
-- fiveCurrentYears :: () -> [Int]
on fiveCurrentYears(_)
    set intThisYear to year of (current date)
    enumFromTo(intThisYear - 2, intThisYear + 2)
end fiveCurrentYears
 
-- argIntegers :: maybe [String] -> [Int]
on argIntegers(argv)
    -- parseInt :: String -> Int
    script parseInt
        on |λ|(s)
            s as integer
        end |λ|
    end script
 
    if class of argv is list and argv ≠ {} then
        {map(parseInt, argv)}
    else
        {}
    end if
end argIntegers
 
 
-- GENERIC FUNCTIONS ---------------------------------------------------------
 
-- Dates and date strings ----------------------------------------------------
 
-- calendarDate :: Int -> Int -> Int -> Date
on calendarDate(intYear, intMonth, intDay)
    tell (current date)
        set {its year, its month, its day, its time} to ¬
            {intYear, intMonth, intDay, 0}
        return it
    end tell
end calendarDate
 
-- isoDateString :: Date -> String
on isoDateString(dte)
    (((year of dte) as string) & ¬
        "-" & text items -2 thru -1 of ¬
        ("0" & ((month of dte) as integer) as string)) & ¬
        "-" & text items -2 thru -1 of ¬
        ("0" & day of dte)
end isoDateString
 
-- Testing and tabulation ----------------------------------------------------
 
-- apply (a -> b) -> a -> b
on apply(f, a)
    mReturn(f)'s |λ|(a)
end apply
 
-- cond :: Bool -> (a -> b) -> (a -> b) -> (a -> b)
on cond(bool, f, g)
    if bool then
        f
    else
        g
    end if
end cond
 
-- enumFromTo :: Int -> Int -> [Int]
on enumFromTo(m, n)
    if m > n then
        set d to -1
    else
        set d to 1
    end if
    set lst to {}
    repeat with i from m to n by d
        set end of lst to i
    end repeat
    return lst
end enumFromTo
 
-- intercalate :: Text -> [Text] -> Text
on intercalate(strText, lstText)
    set {dlm, my text item delimiters} to ¬
        {my text item delimiters, strText}
    set strJoined to lstText as text
    set my text item delimiters to dlm
    return strJoined
end intercalate
 
-- isoRow :: [Date] -> String
on isoRow(lstDate)
    intercalate(tab, map(my isoDateString, lstDate))
end isoRow
 
-- map :: (a -> b) -> [a] -> [b]
on map(f, xs)
    tell mReturn(f)
        set lng to length of xs
        set lst to {}
        repeat with i from 1 to lng
            set end of lst to |λ|(item i of xs, i, xs)
        end repeat
        return lst
    end tell
end map
 
-- Lift 2nd class handler function into 1st class script wrapper 
-- mReturn :: Handler -> Script
on mReturn(f)
    if class of f is script then
        f
    else
        script
            property |λ| : f
        end script
    end if
end mReturn
 
-- transpose :: [[a]] -> [[a]]
on transpose(xss)
    script column
        on |λ|(_, iCol)
            script row
                on |λ|(xs)
                    item iCol of xs
                end |λ|
            end script
 
            map(row, xss)
        end |λ|
    end script
 
    map(column, item 1 of xss)
end transpose