指定期間のなかから条件に一致する日付をリストアップ(F#)


概要

F#プログラムの練習として「指定された期間のなかから条件を満たした日をリストアップするプログラム」を書きました。

まずは、条件例として「休日に指定される期間/日を除いた全ての月曜日」を与えるものを作成してみました。月曜授業日をリストアップするプログラムになります。

プログラム

DateTime が何回もでてくるので、type DT = System.DateTime によって短縮した別型名DTを与えています(TimeSpanについても同様です)。

Program.fs
open System

type DT = System.DateTime
type TS = System.TimeSpan

[<EntryPoint>]
let main argv =

  // f:開始日 から t:終了日 までの DateTime シーケンス生成
  let genDatesSeq (f:DT) (t:DT) = 
     { 0 .. (t-f).Days } 
     |> Seq.map (fun d -> f + TS.FromDays(float d)) 

  let y = 2019
  let fromDT = DT(y,4,8)   // 期間開始日
  let toDT   = DT(y,9,30)  // 期間終了日

  // 祝日や夏休みのセット
  let exclusionSet = 
    set [] 
    |> Set.union (Set.ofSeq( genDatesSeq (DT(y,4,27)) (DT(y,5,6) )))
    |> Set.add (DT(y,7,15)) // 海の日
    |> Set.union (Set.ofSeq( genDatesSeq (DT(y,8,8)) (DT(y,9,13) )))
    |> Set.add (DT(y,8,12)) // 山の日(振替休日)
    |> Set.add (DT(y,9,16)) // 敬老の日
    |> Set.add (DT(y,9,23)) // 秋分の日

  // リストアップ対象日を通過させるフィルタ ここでは休日外の月曜日を選択
  let passFilter (p:DT) = 
    ( p.DayOfWeek = DayOfWeek.Monday ) && not ( exclusionSet.Contains p )

  // 出力フォーマット
  let format = "yyyy/MM/dd (ddd)"

  // 日付の出力
  do genDatesSeq fromDT toDT
    |> Seq.filter passFilter
    |> Seq.map (fun p-> p.ToString(format))
    |> Seq.iter (fun p -> printfn "%s" p)

  Console.ReadKey() |> ignore
  0

実行結果

2019/04/08 (月)
2019/04/15 (月)
2019/04/22 (月)
2019/05/13 (月)
2019/05/20 (月)
2019/05/27 (月)
2019/06/03 (月)
2019/06/10 (月)
2019/06/17 (月)
2019/06/24 (月)
2019/07/01 (月)
2019/07/08 (月)
2019/07/22 (月)
2019/07/29 (月)
2019/08/05 (月)
2019/09/30 (月)

発展

期間中の第一金曜日をリストアップするためにはフィルタを次のように書き換えます(ここではexclusionSetで設定した休みを考慮していません)。

let passFilter (p:DT) = 
  ( p.DayOfWeek = DayOfWeek.Friday ) && ( p.Day <= 7 )

プレミアムフライデー(月の最終金曜日)をリストアップするためには、フィルタを次のように書き換えます。

let next m = if m = 12 then 1 else m+1
let passFilter (p:DT) = 
  ( p.DayOfWeek = DayOfWeek.Friday ) 
  && ( (DT(p.Year, next p.Month ,1)-TS.FromDays(8.)).Day < p.Day )

F#では、等値比較に==ではなく=を使用する関係で、慣れるまではlet next m = if m = 12 then 1 else m+1なんかは非常に読み取りずらいです。括弧をつけると多少は読み取りやすくなるのかな・・・

let next m = ( if ( m = 12) then 1 else ( m + 1 ) )