diff --git a/aufgaben/src/Date.elm b/aufgaben/src/Date.elm index 3d5e767..4d6a909 100644 --- a/aufgaben/src/Date.elm +++ b/aufgaben/src/Date.elm @@ -1,203 +1,96 @@ +module Pflichtuebung_01.Date exposing (..) module Date exposing (..) -import String exposing (fromInt) - -type Weekday - = Mon - | Tue - | Wed - | Thu - | Fri - | Sat - | Sun - - type alias Day = Int - type alias Month = Int - type alias Year = Int - type alias Date = ( Day, Month, Year ) +-- 1 Punkt: year : Date -> Year -year ( _, _, y ) = - y - - +year (_, _, c) = c month : Date -> Month -month ( _, m, _ ) = - m - - +month (_, b, _) = b day : Date -> Day -day ( d, _, _ ) = - d - +day (a, _, _) = a +-- 1 Punkt: lt : Date -> Date -> Bool -lt ( d1, m1, y1 ) ( d2, m2, y2 ) = - (y1 < y2) || (y1 == y2 && m1 < m2) || (y1 == y2 && m1 == m2 && d1 < d2) - - +lt date1 date2 = + let (d1, m1, y1) = date1 + (d2, m2, y2) = date2 + in y1 < y2 || (y1 == y2 && (m1 < m2 || (m1 == m2 && d1 < d2))) eq : Date -> Date -> Bool -eq ( d1, m1, y1 ) ( d2, m2, y2 ) = - y1 == y2 && m1 == m2 && d1 == d2 - - +eq date1 date2 = + let (d1, m1, y1) = date1 + (d2, m2, y2) = date2 + in y1 == y2 && m1 == m2 && d1 == d2 gt : Date -> Date -> Bool -gt ( d1, m1, y1 ) ( d2, m2, y2 ) = - (y1 > y2) || (y1 == y2 && m1 > m2) || (y1 == y2 && m1 == m2 && d1 > d2) - +gt date1 date2 = + let (d1, m1, y1) = date1 + (d2, m2, y2) = date2 + in y1 > y2 || (y1 == y2 && (m1 > m2 || (m1 == m2 && d1 > d2))) +-- 1 Punkt: toString : Date -> String -toString ( d, m, y ) = - let - d0 = - if (d // 10) == 0 then - "0" +toString (a,b,c) = (if a < 10 then "0" ++ String.fromInt(a) else String.fromInt(a)) ++ "." + ++ (if b < 10 then "0" ++ String.fromInt(b) else String.fromInt(b)) ++ "." + ++ (if c < 10 then "000" ++ String.fromInt(c) + else if c < 100 then "00" ++ String.fromInt(c) + else if c < 1000 then "0" ++ String.fromInt(c) + else String.fromInt(c)) - else - "" - m0 = - if (m // 10) == 0 then - "0" +-- https://web.archive.org/web/20170507133619/https://alcor.concordia.ca/~gpkatch/gdate-algorithm.html - else - "" +convertDateToDayNumber : Date -> Int +convertDateToDayNumber (a, b, c) = + let m = (modBy 12 (b + 9)) + y = c - m // 10 + in 365 * y + y // 4 - y // 100 + y // 400 + (m * 306 + 5) // 10 + ( a - 1 ) + +convertDayNumberToDate : Int -> Date +convertDayNumberToDate g = + let y = (10000 * g + 14780) // 3652425 + ddd = g - (365 * y + y // 4 - y // 100 + y // 400) in - d0 ++ fromInt d ++ "." ++ m0 ++ fromInt m ++ "." ++ fromInt y - - -next : Date -> Date -next ( d, m, y ) = - case ( d, m, y ) of - ( 31, 12, _ ) -> - ( 1, 1, y + 1 ) - - ( 30, 11, _ ) -> - ( 1, 12, y ) - - ( 31, 10, _ ) -> - ( 1, 11, y ) - - ( 30, 9, _ ) -> - ( 1, 10, y ) - - ( 31, 8, _ ) -> - ( 1, 9, y ) - - ( 31, 7, _ ) -> - ( 1, 8, y ) - - ( 30, 6, _ ) -> - ( 1, 7, y ) - - ( 31, 5, _ ) -> - ( 1, 6, y ) - - ( 30, 4, _ ) -> - ( 1, 5, y ) - - ( 31, 3, _ ) -> - ( 1, 4, y ) - - ( 29, 2, _ ) -> - ( 1, 3, y ) - - ( 28, 2, _ ) -> - if leapyear y then - ( 29, 2, y ) - - else - ( 1, 3, y ) - - ( 31, 1, _ ) -> - ( 1, 2, y ) - - _ -> - ( d + 1, m, y ) - - -prev : Date -> Date -prev ( d, m, y ) = - case ( d, m, y ) of - ( 1, 12, _ ) -> - ( 30, 11, y ) - - ( 1, 11, _ ) -> - ( 31, 10, y ) - - ( 1, 10, _ ) -> - ( 30, 9, y ) - - ( 1, 9, _ ) -> - ( 31, 8, y ) - - ( 1, 8, _ ) -> - ( 31, 7, y ) - - ( 1, 7, _ ) -> - ( 30, 6, y ) - - ( 1, 6, _ ) -> - ( 31, 5, y ) - - ( 1, 5, _ ) -> - ( 30, 4, y ) - - ( 1, 4, _ ) -> - ( 31, 3, y ) - - ( 1, 3, _ ) -> - if leapyear y then - ( 29, 2, y ) - - else - ( 28, 2, y ) - - ( 1, 2, _ ) -> - ( 31, 1, y ) - - ( 1, 1, _ ) -> - ( 31, 12, y - 1 ) - - _ -> - ( d - 1, m, y ) - + if (ddd < 0) then + let y2 = y - 1 -- y = y2 + ddd2 = g - (365 * y2 + y2 // 4 - y2 // 100 + y2 // 400) -- ddd = ddd2 + mi = (100 * ddd2 + 52) // 3060 + mm = (modBy (12) (mi + 2)) + 1 + y3 = y2 + (mi + 2) // 12 -- y = y3 + dd = ddd2 - (mi * 306 + 5) // 10 + 1 + in (dd, mm, y3) + else + let mi = (100 * ddd + 52) // 3060 + mm = (modBy (12) (mi + 2)) + 1 + y2 = y + (mi + 2) // 12 -- y = y2 + dd = ddd - (mi * 306 + 5) // 10 + 1 + in (dd, mm, y2) +-- 1 Punkt: leapyear : Year -> Bool -leapyear y = - (modBy 4 y == 0) && ((modBy 100 y /= 0) || (modBy 400 y == 0)) +leapyear y = ((modBy 4 y == 0) && (modBy 100 y /= 0)) || (modBy 400 y == 0) +-- 2 Punkte: +next : Date -> Date +next date = convertDayNumberToDate ((convertDateToDayNumber date) + 1) + +-- 2 Punkte: +prev : Date -> Date +prev date = convertDayNumberToDate ((convertDateToDayNumber date) - 1) + +-- 2 Punkte: sub : Date -> Date -> Int -sub d1 d2 = - subHelp 0 d1 d2 - - - -{- um optimierbare End-Rekursion zu erreichen: Akkumulator als Argument -} - - -subHelp : Int -> Date -> Date -> Int -subHelp diff d1 d2 = - if gt d1 d2 then - subHelp (diff + 1) d1 (next d2) - - else if lt d1 d2 then - subHelp (diff - 1) d1 (prev d2) - - else - diff - +sub date1 date2 = convertDateToDayNumber date1 - convertDateToDayNumber date2 diff --git a/pu2/src/Date.elm b/pu2/src/Date.elm index 59da7a3..ad9f34a 100644 --- a/pu2/src/Date.elm +++ b/pu2/src/Date.elm @@ -179,6 +179,8 @@ leapyear : Year -> Bool leapyear y = (modBy 4 y == 0) && ((modBy 100 y /= 0) || (modBy 400 y == 0)) +{- +BROKEN IMPLEMENTATION: sub : Date -> Date -> Int sub d1 d2 = @@ -199,5 +201,39 @@ subHelp diff d1 d2 = else diff - - + +-} + + +sub : Date -> Date -> Int +sub date1 date2 = convertDateToDayNumber date1 - convertDateToDayNumber date2 + + +-- https://web.archive.org/web/20170507133619/https://alcor.concordia.ca/~gpkatch/gdate-algorithm.html + +convertDateToDayNumber : Date -> Int +convertDateToDayNumber (a, b, c) = + let m = (modBy 12 (b + 9)) + y = c - m // 10 + in 365 * y + y // 4 - y // 100 + y // 400 + (m * 306 + 5) // 10 + ( a - 1 ) + +convertDayNumberToDate : Int -> Date +convertDayNumberToDate g = + let y = (10000 * g + 14780) // 3652425 + ddd = g - (365 * y + y // 4 - y // 100 + y // 400) + in + if (ddd < 0) then + let y2 = y - 1 -- y = y2 + ddd2 = g - (365 * y2 + y2 // 4 - y2 // 100 + y2 // 400) -- ddd = ddd2 + mi = (100 * ddd2 + 52) // 3060 + mm = (modBy (12) (mi + 2)) + 1 + y3 = y2 + (mi + 2) // 12 -- y = y3 + dd = ddd2 - (mi * 306 + 5) // 10 + 1 + in (dd, mm, y3) + else + let mi = (100 * ddd + 52) // 3060 + mm = (modBy (12) (mi + 2)) + 1 + y2 = y + (mi + 2) // 12 -- y = y2 + dd = ddd - (mi * 306 + 5) // 10 + 1 + in (dd, mm, y2) + diff --git a/pu2/src/Main.elm b/pu2/src/Main.elm index ac9dd6f..70a881d 100644 --- a/pu2/src/Main.elm +++ b/pu2/src/Main.elm @@ -12,12 +12,50 @@ import Time exposing (Posix) import Date exposing (..) +{- +NOTE: + The Date.sub implementation provided is broken. It will not terminate given specific parameters, such as (29, 2, 2022) (21, 12, 2022). + We have replaced it with our own implementation seen below, which is also more efficient, running in O(1) compared to O(n): + + sub : Date -> Date -> Int + sub date1 date2 = convertDateToDayNumber date1 - convertDateToDayNumber date2 + + -- https://web.archive.org/web/20170507133619/https://alcor.concordia.ca/~gpkatch/gdate-algorithm.html + convertDateToDayNumber : Date -> Int + convertDateToDayNumber (a, b, c) = + let m = (modBy 12 (b + 9)) + y = c - m // 10 + in 365 * y + y // 4 - y // 100 + y // 400 + (m * 306 + 5) // 10 + ( a - 1 ) + + convertDayNumberToDate : Int -> Date + convertDayNumberToDate g = + let y = (10000 * g + 14780) // 3652425 + ddd = g - (365 * y + y // 4 - y // 100 + y // 400) + in + if (ddd < 0) then + let y2 = y - 1 -- y = y2 + ddd2 = g - (365 * y2 + y2 // 4 - y2 // 100 + y2 // 400) -- ddd = ddd2 + mi = (100 * ddd2 + 52) // 3060 + mm = (modBy (12) (mi + 2)) + 1 + y3 = y2 + (mi + 2) // 12 -- y = y3 + dd = ddd2 - (mi * 306 + 5) // 10 + 1 + in (dd, mm, y3) + else + let mi = (100 * ddd + 52) // 3060 + mm = (modBy (12) (mi + 2)) + 1 + y2 = y + (mi + 2) // 12 -- y = y2 + dd = ddd - (mi * 306 + 5) // 10 + 1 + in (dd, mm, y2) +-} + + type alias Model = { arbeit : Arbeit , beginndatum : Date , besprechungsWochentag : Weekday , anzahlWochenZwischenBesprechung : Int + , status : String , zone : Time.Zone , time : Time.Posix } @@ -42,6 +80,7 @@ init _ = ( , beginndatum = (21, 12, 2022) , besprechungsWochentag = Mon , anzahlWochenZwischenBesprechung = 1 + , status = "" , zone = Time.utc , time = Time.millisToPosix 0 } , Task.perform AdjustTimeZone Time.here @@ -67,17 +106,14 @@ getWeekday date = addWeekdays : Int -> Weekday -> Weekday addWeekdays count day = - if count < 0 - then - case count of - 0 -> day - _ -> addWeekdays (count + 1) (prevWeekDay day) - else - if count == 0 - then - case count of - 0 -> day - _ -> addWeekdays (count - 1) (nextWeekDay day) + if count < 0 then + case count of + 0 -> day + _ -> addWeekdays (count + 1) (prevWeekDay day) + else if count > 0 then + case count of + 0 -> day + _ -> addWeekdays (count - 1) (nextWeekDay day) else day nextWeekDay : Weekday -> Weekday @@ -94,13 +130,13 @@ nextWeekDay day = prevWeekDay : Weekday -> Weekday prevWeekDay day = case day of - Mon -> Tue - Tue -> Wed - Wed -> Thu - Thu -> Fri - Fri -> Sat - Sat -> Sun - Sun -> Mon + Mon -> Sun + Tue -> Mon + Wed -> Tue + Thu -> Wed + Fri -> Thu + Sat -> Fri + Sun -> Sat getMonthLength : Date -> Int getMonthLength date = @@ -119,6 +155,8 @@ getMonthLength date = 12 -> 31 _ -> 30 +{- will increment the month count by the given amount. Supports a max of 12 months to be added. Will limit the day to + the amount of days the new month has. -} addMonthsLiterally : Int -> Date -> Date addMonthsLiterally count date = let newMonth = (Date.month date) + count @@ -126,6 +164,32 @@ addMonthsLiterally count date = newDateMonthLength = getMonthLength newDate in if Date.day newDate > newDateMonthLength then (Date.day date, newDateMonthLength, Date.year date) else newDate +skipWeekend : Date -> Date +skipWeekend date = + case getWeekday date of + Sat -> addDays 2 date + Sun -> addDays 1 date + _ -> date + +{- will advance the date so long until all days in [Mon, Tue, Wed, Thu, Fri] are covered. + This does not include the first day -} +advanceByEntireWorkWeek : Date -> Date +advanceByEntireWorkWeek date = + date + |> skipDaysWhilstSkippingWeekend 5 + |> skipWeekend + +skipDaysWhilstSkippingWeekend : Int -> Date -> Date +skipDaysWhilstSkippingWeekend count date = + if count <= 0 then date else + skipDaysWhilstSkippingWeekend (count - 1) (date + |> skipWeekend + |> Date.next) + +formatDateOutput : Date -> String +formatDateOutput date = + String.fromInt (Date.day date) ++ "." ++ String.fromInt (Date.month date) ++ "." ++ String.fromInt (Date.year date) + calculateAbgabeEndDate : Arbeit -> Date -> Date calculateAbgabeEndDate arbeit date = case arbeit of @@ -133,15 +197,41 @@ calculateAbgabeEndDate arbeit date = Master -> Date.prev (addMonthsLiterally 6 date) -validiereModel : Model -> Result String String -validiereModel model = +validateModel : Model -> Result String String +validateModel model = case model.besprechungsWochentag of Sat -> Err "Datum darf kein Samstag sein" Sun -> Err "Datum darf kein Sonntag sein" - _ -> Ok "Alles klärchen" + _ -> case model.beginndatum of + (day, month, year) -> if day > getMonthLength (day, month, year) || day < 0 then + Err "Tag ist außerhalb des Monats" + else if month < 1 || month > 12 then + Err "Monat muss zwischen 1 und 12 liegen" + else if year < 1000 || year > 9999 then + Err "Jahr muss größer als 1000 und kleiner als 9999 sein" + else Ok "" + +findNextDateOfWeekday : Date -> Weekday -> Date +findNextDateOfWeekday date weekday = + if getWeekday date == weekday then date else findNextDateOfWeekday (Date.next date) weekday berechneDaten : Model -> List String -berechneDaten model = [] {- TODO -} +berechneDaten model = + let beginndatum = model.beginndatum + enddatum = calculateAbgabeEndDate model.arbeit beginndatum + besprechungsWochentag = model.besprechungsWochentag + anzahlWochenZwischenBesprechung = model.anzahlWochenZwischenBesprechung + in List.concat [ + ((formatDateOutput beginndatum) ++ " (Beginn)") :: (berechneZwischentermine (findNextDateOfWeekday (Date.next beginndatum) besprechungsWochentag) enddatum anzahlWochenZwischenBesprechung besprechungsWochentag) + , [(formatDateOutput enddatum) ++ " (Ende)"] + ] + +berechneZwischentermine : Date -> Date -> Int -> Weekday -> List String +berechneZwischentermine datum enddatum wochenAbstand wochentag = + let nextBesprechung = addDays (wochenAbstand * 7) datum + isOver = Date.gt datum enddatum || Date.eq datum enddatum + in if isOver then [] else + ((formatDateOutput datum) ++ " (Besprechung)") :: (berechneZwischentermine nextBesprechung enddatum wochenAbstand wochentag) monthToIndex : Time.Month -> Int monthToIndex month = @@ -161,47 +251,50 @@ monthToIndex month = getCurrentDate : Model -> Date getCurrentDate model = - let date = Time.now - in - ( - Time.toDay model.zone model.time - , monthToIndex (Time.toMonth model.zone model.time) - , Time.toYear model.zone model.time - ) + ( + Time.toDay model.zone model.time + , monthToIndex (Time.toMonth model.zone model.time) + , Time.toYear model.zone model.time + ) update : Msg -> Model -> (Model, Cmd Msg) update msg model = - (case msg of - SetStartDate date -> - {model | beginndatum = ( - case (String.split " " date) of - ["asap"] -> getCurrentDate model - ["ASAP"] -> getCurrentDate model - [d, m, y] -> case [String.toInt d, String.toInt m, String.toInt y] of - [Just a, Just b, Just c] -> (a, b, c) - _ -> (21, 12, 2022) - _ ->(21, 12, 2022) - ) } - SetDegree arbeits -> - case arbeits of - "Bachelor" -> {model | arbeit = Bachelor} - "Master" -> {model | arbeit = Master} - _ -> model - SetInterval str -> - {model | anzahlWochenZwischenBesprechung = String.toInt str |> Maybe.withDefault 0} - SetBesprechundsWochentag weekday -> - {model | besprechungsWochentag = case weekday of - "Montag" -> Mon - "Dienstag" -> Tue - "Mittwoch" -> Wed - "Donnerstag" -> Thu - "Freitag" -> Fri - _ -> Mon - } - Tick newTime -> { model | time = newTime } - AdjustTimeZone newZone -> { model | zone = newZone } - , Cmd.none) - + let newModel = case msg of + SetStartDate date -> + {model | beginndatum = ( + case (String.split " " date) of + ["asap"] -> advanceByEntireWorkWeek (getCurrentDate model) + ["ASAP"] -> advanceByEntireWorkWeek (getCurrentDate model) + [d, m, y] -> case [String.toInt d, String.toInt m, String.toInt y] of + [Just a, Just b, Just c] -> (a, b, c) + _ -> (21, 12, 2022) + _ -> (21, 12, 2022) + ) } + SetDegree arbeits -> + case arbeits of + "Bachelor" -> {model | arbeit = Bachelor} + "Master" -> {model | arbeit = Master} + _ -> model + SetInterval str -> + {model | anzahlWochenZwischenBesprechung = String.toInt str |> Maybe.withDefault 0} + SetBesprechundsWochentag weekday -> + {model | besprechungsWochentag = case weekday of + "Montag" -> Mon + "Dienstag" -> Tue + "Mittwoch" -> Wed + "Donnerstag" -> Thu + "Freitag" -> Fri + _ -> Mon + } + Tick newTime -> { model | time = newTime } + AdjustTimeZone newZone -> { model | zone = newZone } + in case validateModel newModel of + Ok a -> ({ newModel | status = case msg of + Tick i -> newModel.status + AdjustTimeZone i -> newModel.status + _ -> a + }, Cmd.none) + Err a -> ({ model | status = a }, Cmd.none) view : Model -> Html Msg @@ -235,15 +328,6 @@ view model = (model |> berechneDaten |> List.map (\datum -> li [] [text datum])) - , text (Debug.toString model.besprechungsWochentag), br[][] - , text (Debug.toString model.beginndatum), br[][] - , text (Debug.toString model.arbeit), br[][] - , text (Debug.toString model.anzahlWochenZwischenBesprechung), br[][] - , text (Debug.toString (getWeekday model.beginndatum)), br[][] - , text (Debug.toString (calculateAbgabeEndDate Bachelor model.beginndatum)), br[][] - , text (Debug.toString (calculateAbgabeEndDate Master model.beginndatum)), br[][] - , text (Debug.toString (getMonthLength model.beginndatum)), br[][] - , text (Debug.toString (Date.sub model.beginndatum (21, 12, 2022))), br[][] ] subscriptions : Model -> Sub Msg @@ -255,4 +339,5 @@ main = Browser.element { , update = update , view = view , subscriptions = subscriptions - } \ No newline at end of file + } +