diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..c2065bc26202b2d072aca3efc3d1c2efad3afcbf
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,37 @@
+HELP.md
+.gradle
+build/
+!gradle/wrapper/gradle-wrapper.jar
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+bin/
+!**/src/main/**/bin/
+!**/src/test/**/bin/
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+out/
+!**/src/main/**/out/
+!**/src/test/**/out/
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+
+### VS Code ###
+.vscode/
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..673eb56401daf19bd06692a80646a538e6edc1f3
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,90 @@
+variables:
+  GRADLE_OPTS: "-Dorg.gradle.daemon=false"
+  REGISTRY_USER: $REGISTRY_USER
+  IMAGE_NAME: $IMAGE_NAME
+  IMAGE_TAG: latest
+  CONTAINER_NAME: $CONTAINER_NAME
+  GCP_USERNAME: $GCP_USERNAME
+  GCP_STATIC_IP: $GCP_STATIC_IP
+
+stages:
+  - build
+  - test
+  - sonarqube-check
+  - publish
+  - deploy
+
+Build:
+  stage: build
+  image: gradle:jdk11-alpine
+  before_script:
+    - echo `pwd`
+    - export GRADLE_USER_HOME=`pwd`/.gradle
+  script:
+    - gradle wrapper
+    - ./gradlew assemble
+    - ls
+  artifacts:
+    when: always
+    paths:
+      - build/libs/*.jar
+    expire_in: 1 week
+
+Test:
+  stage: test
+  image: gradle:jdk11-alpine
+  dependencies:
+    - Build
+  services:
+    - "postgres:latest"
+  before_script:
+    - echo `pwd`
+    - export GRADLE_USER_HOME=`pwd`/.gradle
+    - export SPRING_PROFILES_ACTIVE=test
+  script:
+    - gradle check --info --stacktrace
+    - gradle test
+
+sonarqube-check:
+  stage: sonarqube-check
+  image: gradle:jdk11-alpine
+  only:
+    - merge_requests
+    - master
+  variables:
+    SONAR_HOST_URL: 'https://sonarqube.cs.ui.ac.id/'
+    GIT_DEPTH: 0
+  script: gradle sonarqube -Dsonar.qualitygate.wait=true
+  allow_failure: true
+
+Publish:
+  stage: publish
+  image: docker:latest
+  services:
+    - docker:dind
+  dependencies:
+    - Build
+  before_script:
+    - echo $DOCKER_PASSWORD| docker login -u $REGISTRY_USER --password-stdin docker.io
+  script:
+    - ls
+    - docker build -t $REGISTRY_USER/$IMAGE_NAME:$IMAGE_TAG .
+    - docker push $REGISTRY_USER/$IMAGE_NAME:$IMAGE_TAG
+  tags:
+    - dind
+  only:
+    - master
+
+Deploy:
+  stage: deploy
+  image: alpine:latest
+  before_script:
+    - chmod 400 $SSH_KEY
+    - apk update && apk add openssh-client
+  script:
+    - ssh -o StrictHostKeyChecking=no -i $SSH_KEY $GCP_USERNAME@$GCP_STATIC_IP "
+      docker container rm -f $CONTAINER_NAME || true &&
+      docker image rm -f $REGISTRY_USER/$IMAGE_NAME:$IMAGE_TAG || true &&
+      docker run --name $CONTAINER_NAME -d -p 80:8080 $REGISTRY_USER/$IMAGE_NAME:$IMAGE_TAG"
+  only:
+    - master
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..b2da75893eb1102a7d07d05a3bd687ece804a958
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,6 @@
+FROM gradle:jdk11-alpine
+
+WORKDIR /app
+COPY ./build/libs/tutorial-7-0.0.1-SNAPSHOT.jar /app
+EXPOSE 8080
+CMD ["java","-jar","tutorial-7-0.0.1-SNAPSHOT.jar"]
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 0000000000000000000000000000000000000000..893dbf57cdcc5f47e33f0a698607f34b3d0d5b93
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,44 @@
+plugins {
+    java
+    id("org.springframework.boot") version "2.7.12-SNAPSHOT"
+    id("io.spring.dependency-management") version "1.0.15.RELEASE"
+    id("org.sonarqube") version "3.0"
+}
+
+group = "id.ac.ui.cs.advprog"
+version = "0.0.1-SNAPSHOT"
+java.sourceCompatibility = JavaVersion.VERSION_11
+
+configurations {
+    compileOnly {
+        extendsFrom(configurations.annotationProcessor.get())
+    }
+}
+
+repositories {
+    mavenCentral()
+    maven { url = uri("https://repo.spring.io/milestone") }
+    maven { url = uri("https://repo.spring.io/snapshot") }
+}
+
+dependencies {
+    implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
+    implementation("org.springframework.boot:spring-boot-starter-web")
+    compileOnly("org.projectlombok:lombok")
+    developmentOnly("org.springframework.boot:spring-boot-devtools")
+    annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
+    annotationProcessor("org.projectlombok:lombok")
+    testImplementation("org.springframework.boot:spring-boot-starter-test")
+}
+
+tasks.withType<Test> {
+    useJUnitPlatform()
+}
+
+sonarqube {
+    properties {
+        property("sonar.projectKey", System.getenv("AdvProg_reguler-2023_mahasiswa_kelas-c_2006462696-Bintang-Hari-Pratama_tutorial-7_AYfHsAsX9YbzwjBXjQ6y"))
+        property("sonar.host.url", System.getenv("https://sonarqube.cs.ui.ac.id/"))
+        property("sonar.login", System.getenv("65f697c034f04b8a94b1b313f59c07446de47c2e"))
+    }
+}
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000000000000000000000000000000000000..249e5832f090a2944b7473328c07c9755baa3196
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000000000000000000000000000000000000..774fae87671b561056f25b9669a14c8beff8db5c
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000000000000000000000000000000000000..a69d9cb6c20655813e44515156e7253a2a239138
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,240 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+#
+#   Gradle start up script for POSIX generated by Gradle.
+#
+#   Important for running:
+#
+#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+#       noncompliant, but you have some other compliant shell such as ksh or
+#       bash, then to run this script, type that shell name before the whole
+#       command line, like:
+#
+#           ksh Gradle
+#
+#       Busybox and similar reduced shells will NOT work, because this script
+#       requires all of these POSIX shell features:
+#         * functions;
+#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+#         * compound commands having a testable exit status, especially «case»;
+#         * various built-in commands including «command», «set», and «ulimit».
+#
+#   Important for patching:
+#
+#   (2) This script targets any POSIX shell, so it avoids extensions provided
+#       by Bash, Ksh, etc; in particular arrays are avoided.
+#
+#       The "traditional" practice of packing multiple parameters into a
+#       space-separated string is a well documented source of bugs and security
+#       problems, so this is (mostly) avoided, by progressively accumulating
+#       options in "$@", and eventually passing that to Java.
+#
+#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+#       see the in-line comments for details.
+#
+#       There are tweaks for specific operating systems such as AIX, CygWin,
+#       Darwin, MinGW, and NonStop.
+#
+#   (3) This script is generated from the Groovy template
+#       https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+#       within the Gradle project.
+#
+#       You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+    APP_HOME=${app_path%"${app_path##*/}"}  # leaves a trailing /; empty if no leading path
+    [ -h "$app_path" ]
+do
+    ls=$( ls -ld "$app_path" )
+    link=${ls#*' -> '}
+    case $link in             #(
+      /*)   app_path=$link ;; #(
+      *)    app_path=$APP_HOME$link ;;
+    esac
+done
+
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+APP_NAME="Gradle"
+APP_BASE_NAME=${0##*/}
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+    echo "$*"
+} >&2
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in                #(
+  CYGWIN* )         cygwin=true  ;; #(
+  Darwin* )         darwin=true  ;; #(
+  MSYS* | MINGW* )  msys=true    ;; #(
+  NONSTOP* )        nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD=$JAVA_HOME/jre/sh/java
+    else
+        JAVACMD=$JAVA_HOME/bin/java
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD=java
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+    case $MAX_FD in #(
+      max*)
+        MAX_FD=$( ulimit -H -n ) ||
+            warn "Could not query maximum file descriptor limit"
+    esac
+    case $MAX_FD in  #(
+      '' | soft) :;; #(
+      *)
+        ulimit -n "$MAX_FD" ||
+            warn "Could not set maximum file descriptor limit to $MAX_FD"
+    esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+#   * args from the command line
+#   * the main class name
+#   * -classpath
+#   * -D...appname settings
+#   * --module-path (only if needed)
+#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+    APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+    CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+    JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    for arg do
+        if
+            case $arg in                                #(
+              -*)   false ;;                            # don't mess with options #(
+              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath
+                    [ -e "$t" ] ;;                      #(
+              *)    false ;;
+            esac
+        then
+            arg=$( cygpath --path --ignore --mixed "$arg" )
+        fi
+        # Roll the args list around exactly as many times as the number of
+        # args, so each arg winds up back in the position where it started, but
+        # possibly modified.
+        #
+        # NB: a `for` loop captures its iteration list before it begins, so
+        # changing the positional parameters here affects neither the number of
+        # iterations, nor the values presented in `arg`.
+        shift                   # remove old arg
+        set -- "$@" "$arg"      # push replacement arg
+    done
+fi
+
+# Collect all arguments for the java command;
+#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+#     shell script including quotes and variable substitutions, so put them in
+#     double quotes to make sure that they get re-expanded; and
+#   * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+        "-Dorg.gradle.appname=$APP_BASE_NAME" \
+        -classpath "$CLASSPATH" \
+        org.gradle.wrapper.GradleWrapperMain \
+        "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+    die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+#   readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+#   set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+        printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+        xargs -n1 |
+        sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+        tr '\n' ' '
+    )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000000000000000000000000000000000000..f127cfd49d4024c3e1e0d08ba56399221b4fb25d
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,91 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem      https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle.kts b/settings.gradle.kts
new file mode 100644
index 0000000000000000000000000000000000000000..772d03f7906e32787da563feeacb48535ea53196
--- /dev/null
+++ b/settings.gradle.kts
@@ -0,0 +1,8 @@
+pluginManagement {
+    repositories {
+        maven { url = uri("https://repo.spring.io/milestone") }
+        maven { url = uri("https://repo.spring.io/snapshot") }
+        gradlePluginPortal()
+    }
+}
+rootProject.name = "tutorial-7"
diff --git a/sonar-project.properties b/sonar-project.properties
new file mode 100644
index 0000000000000000000000000000000000000000..ddf1eaee547183ad15cd4ea385d62355a304ff09
--- /dev/null
+++ b/sonar-project.properties
@@ -0,0 +1,15 @@
+# must be unique in a given SonarQube instance
+sonar.projectKey=AdvProg_reguler-2023_mahasiswa_kelas-c_2006462696-Bintang-Hari-Pratama_tutorial-7_AYfHsAsX9YbzwjBXjQ6y
+
+# --- optional properties ---
+
+# defaults to project key
+#sonar.projectName=My project
+# defaults to 'not provided'
+#sonar.projectVersion=1.0
+ 
+# Path is relative to the sonar-project.properties file. Defaults to .
+#sonar.sources=.
+ 
+# Encoding of the source code. Default is default system encoding
+#sonar.sourceEncoding=UTF-8
\ No newline at end of file
diff --git a/src/main/java/id/ac/ui/cs/advprog/tutorial7/Tutorial7Application.java b/src/main/java/id/ac/ui/cs/advprog/tutorial7/Tutorial7Application.java
new file mode 100644
index 0000000000000000000000000000000000000000..9810ebf5e38a727ded397695fcd4f5155b799f55
--- /dev/null
+++ b/src/main/java/id/ac/ui/cs/advprog/tutorial7/Tutorial7Application.java
@@ -0,0 +1,13 @@
+package id.ac.ui.cs.advprog.tutorial7;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class Tutorial7Application {
+    
+    public static void main(String[] args) {
+        SpringApplication.run(Tutorial7Application.class, args);
+    }
+    
+}
diff --git a/src/main/java/id/ac/ui/cs/advprog/tutorial7/day/counter/controller/DayCounterController.java b/src/main/java/id/ac/ui/cs/advprog/tutorial7/day/counter/controller/DayCounterController.java
new file mode 100644
index 0000000000000000000000000000000000000000..ae4ee8d407d6bf61bf1f7964c2d34fd9f83139a5
--- /dev/null
+++ b/src/main/java/id/ac/ui/cs/advprog/tutorial7/day/counter/controller/DayCounterController.java
@@ -0,0 +1,48 @@
+package id.ac.ui.cs.advprog.tutorial7.day.counter.controller;
+
+import id.ac.ui.cs.advprog.tutorial7.day.counter.core.WeekDay;
+import id.ac.ui.cs.advprog.tutorial7.day.counter.dto.DayCounterDTO;
+import id.ac.ui.cs.advprog.tutorial7.day.counter.service.DayCounterService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Controller
+@RequestMapping(path = "/day-counter")
+public class DayCounterController {
+    private final DayCounterService dayCounterService;
+
+    @Autowired
+    public DayCounterController(DayCounterService dayCounterService){
+        this.dayCounterService = dayCounterService;
+    }
+
+    @GetMapping(path = {"", "/"})
+    public String getLeapYearPage(Model model) {
+        List<String> days = Arrays.stream(WeekDay.values())
+                .map(WeekDay::getDay)
+                .collect(Collectors.toList());
+        model.addAttribute("days", days);
+        model.addAttribute("dto", new DayCounterDTO());
+        return "day.counter/home";
+    }
+
+    @PostMapping(path = {"", "/"})
+    public String postLeapYearPage(Model model, DayCounterDTO dto) {
+        List<String> days = Arrays.stream(WeekDay.values())
+                .map(WeekDay::getDay)
+                .collect(Collectors.toList());
+        String result = dayCounterService.getWeekDayAsString(dto.getDay(), dto.getN());
+        model.addAttribute("days", days);
+        model.addAttribute("dto", new DayCounterDTO());
+        model.addAttribute("result", result);
+        return "day.counter/home";
+    }
+
+
+}
diff --git a/src/main/java/id/ac/ui/cs/advprog/tutorial7/day/counter/core/WeekDay.java b/src/main/java/id/ac/ui/cs/advprog/tutorial7/day/counter/core/WeekDay.java
new file mode 100644
index 0000000000000000000000000000000000000000..b3d43f3bb4c7ba6529328e5447716a3946ed16f8
--- /dev/null
+++ b/src/main/java/id/ac/ui/cs/advprog/tutorial7/day/counter/core/WeekDay.java
@@ -0,0 +1,33 @@
+package id.ac.ui.cs.advprog.tutorial7.day.counter.core;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public enum WeekDay {
+    SUN("Sunday"),
+    MON("Monday"),
+    TUE("Tuesday"),
+    WED("Wednesday"),
+    THU("Thursday"),
+    FRI("Friday"),
+    SAT("Saturday");
+
+    private final String day;
+
+    public String getDay() {
+        return this.day;
+    }
+
+    WeekDay(String day) {
+        this.day = day;
+    }
+
+    public static final Map<String, WeekDay> dayMap = new HashMap<>();
+
+    static {
+        for (WeekDay day : WeekDay.values()) {
+            dayMap.put(day.day.toLowerCase(), day);
+        }
+    }
+
+}
diff --git a/src/main/java/id/ac/ui/cs/advprog/tutorial7/day/counter/dto/DayCounterDTO.java b/src/main/java/id/ac/ui/cs/advprog/tutorial7/day/counter/dto/DayCounterDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..8c6e81612beae699efc407c108793e9bb56d196f
--- /dev/null
+++ b/src/main/java/id/ac/ui/cs/advprog/tutorial7/day/counter/dto/DayCounterDTO.java
@@ -0,0 +1,9 @@
+package id.ac.ui.cs.advprog.tutorial7.day.counter.dto;
+
+import lombok.Data;
+
+@Data
+public class DayCounterDTO {
+    String day;
+    int n;
+}
diff --git a/src/main/java/id/ac/ui/cs/advprog/tutorial7/day/counter/service/DayCounterService.java b/src/main/java/id/ac/ui/cs/advprog/tutorial7/day/counter/service/DayCounterService.java
new file mode 100644
index 0000000000000000000000000000000000000000..8d3f8e95785563f17feb42a2f06d135f69c72470
--- /dev/null
+++ b/src/main/java/id/ac/ui/cs/advprog/tutorial7/day/counter/service/DayCounterService.java
@@ -0,0 +1,5 @@
+package id.ac.ui.cs.advprog.tutorial7.day.counter.service;
+
+public interface DayCounterService {
+    String getWeekDayAsString(String day, int n);
+}
diff --git a/src/main/java/id/ac/ui/cs/advprog/tutorial7/day/counter/service/DayCounterServiceImpl.java b/src/main/java/id/ac/ui/cs/advprog/tutorial7/day/counter/service/DayCounterServiceImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..a8acf5c50121416e9d7f98ff709d2aa38e8bb929
--- /dev/null
+++ b/src/main/java/id/ac/ui/cs/advprog/tutorial7/day/counter/service/DayCounterServiceImpl.java
@@ -0,0 +1,44 @@
+package id.ac.ui.cs.advprog.tutorial7.day.counter.service;
+
+import id.ac.ui.cs.advprog.tutorial7.day.counter.core.WeekDay;
+import org.springframework.stereotype.Service;
+
+import static java.lang.Math.abs;
+
+@Service
+public class DayCounterServiceImpl implements DayCounterService {
+    @Override
+    public String getWeekDayAsString(String day, int n) {
+
+        WeekDay fromDayEnum = WeekDay.dayMap.get(day.toLowerCase());
+        // If fromDayEnum is null, it will throw Exception
+        if (fromDayEnum == null) {
+            throw new IllegalArgumentException("Invalid day: " + day);
+        }
+        String fromDay = fromDayEnum.getDay();
+
+        int newIndex = getNewIndex(fromDayEnum.ordinal(), n);
+
+        String toDay = WeekDay.values()[newIndex].getDay();
+
+        return getString(n, fromDay, toDay);
+    }
+
+    private String getString(int n, String fromDay, String toDay){
+        if (n < 0){
+            return String.format("%d day(s) ago before %s was %s.", abs(n), fromDay, toDay);
+        }
+        return String.format("%d day(s) in the future after %s is %s.", abs(n), fromDay, toDay);
+    }
+
+    private int getNewIndex(int fromDay, int n){
+        // Calculate the n-th day after or before a certain day
+        // 7 is the number of days in a week
+        int newIndex = (fromDay + n) % 7;
+        if (newIndex < 0) {
+            newIndex += 7;
+        }
+        return newIndex;
+    }
+
+}
diff --git a/src/main/java/id/ac/ui/cs/advprog/tutorial7/leap/year/controller/LeapYearController.java b/src/main/java/id/ac/ui/cs/advprog/tutorial7/leap/year/controller/LeapYearController.java
new file mode 100644
index 0000000000000000000000000000000000000000..3c5261ed0fc2ba5272eff5c5568becdc514b9949
--- /dev/null
+++ b/src/main/java/id/ac/ui/cs/advprog/tutorial7/leap/year/controller/LeapYearController.java
@@ -0,0 +1,34 @@
+package id.ac.ui.cs.advprog.tutorial7.leap.year.controller;
+
+import id.ac.ui.cs.advprog.tutorial7.leap.year.dto.LeapYearDTO;
+import id.ac.ui.cs.advprog.tutorial7.leap.year.service.LeapYearService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+@Controller
+@RequestMapping(path = "/leap-year")
+public class LeapYearController {
+    private final LeapYearService leapYearService;
+    @Autowired
+    public LeapYearController(LeapYearService leapYearService){
+        this.leapYearService = leapYearService;
+    }
+
+    @GetMapping(path = {"", "/"})
+    public String getLeapYearPage(Model model) {
+        model.addAttribute("dto", new LeapYearDTO());
+        return "leap.year/home";
+    }
+
+    @PostMapping(path = {"", "/"})
+    public String postLeapYearPage(Model model, LeapYearDTO dto) {
+        String result = this.leapYearService.getYearCategoryAsString(dto.getYear());
+        model.addAttribute("dto", new LeapYearDTO());
+        model.addAttribute("result", result);
+        return "leap.year/home";
+    }
+}
diff --git a/src/main/java/id/ac/ui/cs/advprog/tutorial7/leap/year/core/YearCategory.java b/src/main/java/id/ac/ui/cs/advprog/tutorial7/leap/year/core/YearCategory.java
new file mode 100644
index 0000000000000000000000000000000000000000..737e3bb27eec227b1e5edf3a715342302578b787
--- /dev/null
+++ b/src/main/java/id/ac/ui/cs/advprog/tutorial7/leap/year/core/YearCategory.java
@@ -0,0 +1,29 @@
+package id.ac.ui.cs.advprog.tutorial7.leap.year.core;
+
+
+public enum YearCategory {
+    NOT_DIVISIBLE_BY_4(false),
+    DIVISIBLE_BY_4(true),
+    DIVISIBLE_BY_100(false),
+    DIVISIBLE_BY_400(true);
+    
+    private final boolean leapYear;
+
+    YearCategory(boolean isLeapYear){
+        this.leapYear = isLeapYear;
+    }
+    
+    public boolean isLeapYear(){
+        return this.leapYear;
+    }
+    
+    @Override
+    public String toString() {
+        // Replace underscore with space
+        String yearCategory = this.name().replace('_', ' ').toLowerCase();
+        // If Condition, if isLeapYear is true it will retrun "is"
+        String toBe = this.isLeapYear() ? "is" : "isn't" ;
+        return String.format("%s a leap year because it is %s.", toBe, yearCategory);
+    }
+    
+}
diff --git a/src/main/java/id/ac/ui/cs/advprog/tutorial7/leap/year/dto/LeapYearDTO.java b/src/main/java/id/ac/ui/cs/advprog/tutorial7/leap/year/dto/LeapYearDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..5abafaa35879d18f922d5b68e5b1e6eafb626bcf
--- /dev/null
+++ b/src/main/java/id/ac/ui/cs/advprog/tutorial7/leap/year/dto/LeapYearDTO.java
@@ -0,0 +1,8 @@
+package id.ac.ui.cs.advprog.tutorial7.leap.year.dto;
+
+import lombok.Data;
+
+@Data
+public class LeapYearDTO {
+    int year;
+}
diff --git a/src/main/java/id/ac/ui/cs/advprog/tutorial7/leap/year/service/LeapYearService.java b/src/main/java/id/ac/ui/cs/advprog/tutorial7/leap/year/service/LeapYearService.java
new file mode 100644
index 0000000000000000000000000000000000000000..743b5cbf6a049bb0d16eb4a391e30cb292634d6e
--- /dev/null
+++ b/src/main/java/id/ac/ui/cs/advprog/tutorial7/leap/year/service/LeapYearService.java
@@ -0,0 +1,5 @@
+package id.ac.ui.cs.advprog.tutorial7.leap.year.service;
+
+public interface LeapYearService {
+    String getYearCategoryAsString(long year);
+}
diff --git a/src/main/java/id/ac/ui/cs/advprog/tutorial7/leap/year/service/LeapYearServiceImplement.java b/src/main/java/id/ac/ui/cs/advprog/tutorial7/leap/year/service/LeapYearServiceImplement.java
new file mode 100644
index 0000000000000000000000000000000000000000..d0f579d087a5a7e2cdf43a47cf08a6cd7d55ddd4
--- /dev/null
+++ b/src/main/java/id/ac/ui/cs/advprog/tutorial7/leap/year/service/LeapYearServiceImplement.java
@@ -0,0 +1,37 @@
+package id.ac.ui.cs.advprog.tutorial7.leap.year.service;
+
+import id.ac.ui.cs.advprog.tutorial7.leap.year.core.YearCategory;
+import org.springframework.stereotype.Service;
+
+@Service
+public class LeapYearServiceImplement implements LeapYearService {
+    /*
+    Hello.
+    Guess what!
+    I have written comments so that people can read my code easily.
+    Now I'm confident that you and all other people will be able to read my code in no time! xd
+    
+    - Code Author
+     */
+    
+    @Override
+    public String getYearCategoryAsString(long year) {  // yr = year
+        // check if the year is divisible by 4, by 100, by 400, or not divisible at all.
+        YearCategory yearCategory;
+        if (year % 400 == 0){
+            yearCategory = YearCategory.DIVISIBLE_BY_400;
+        }
+        else if (year % 100 == 0){
+            yearCategory = YearCategory.DIVISIBLE_BY_100;
+        }
+        else if (year % 4 == 0){
+            yearCategory = YearCategory.DIVISIBLE_BY_4;
+        }
+        else {
+            yearCategory = YearCategory.NOT_DIVISIBLE_BY_4;
+        }
+        
+        // return the result
+        return String.format("%d %s", year,  yearCategory.toString());
+    }
+}
diff --git a/src/main/java/id/ac/ui/cs/advprog/tutorial7/time/counter/controller/TimeCounterController.java b/src/main/java/id/ac/ui/cs/advprog/tutorial7/time/counter/controller/TimeCounterController.java
new file mode 100644
index 0000000000000000000000000000000000000000..3e0bf18870cae9260e93dcbe528ee2f3f44dc5ac
--- /dev/null
+++ b/src/main/java/id/ac/ui/cs/advprog/tutorial7/time/counter/controller/TimeCounterController.java
@@ -0,0 +1,38 @@
+package id.ac.ui.cs.advprog.tutorial7.time.counter.controller;
+
+import id.ac.ui.cs.advprog.tutorial7.time.counter.dto.TimeCounterDTO;
+import id.ac.ui.cs.advprog.tutorial7.time.counter.service.TimeCounterService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+@Controller
+@RequestMapping("/time-counter")
+public class TimeCounterController {
+
+    @Autowired
+    private TimeCounterService timeCounterService;
+
+    @GetMapping(path = {"", "/"})
+    public String getTimeCounterPage(Model model) {
+        model.addAttribute("ops", new String[]{"add", "subtract"});
+        model.addAttribute("dto", new TimeCounterDTO());
+        return "time.counter/home";
+    }
+
+    @PostMapping(path = {"", "/"})
+    public String postTimeCounterPage(Model model, TimeCounterDTO dto) {
+        String result = timeCounterService.calculateTime(
+                dto.getTime(),
+                dto.getHours(),
+                dto.getMinutes(),
+                dto.getOperation());
+        model.addAttribute("ops", new String[]{"add", "subtract"});
+        model.addAttribute("dto", new TimeCounterDTO());
+        model.addAttribute("result", result);
+        return "time.counter/home";
+    }
+}
diff --git a/src/main/java/id/ac/ui/cs/advprog/tutorial7/time/counter/dto/TimeCounterDTO.java b/src/main/java/id/ac/ui/cs/advprog/tutorial7/time/counter/dto/TimeCounterDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..c9c54c9d937f41590523f22b23c602ff505f44f0
--- /dev/null
+++ b/src/main/java/id/ac/ui/cs/advprog/tutorial7/time/counter/dto/TimeCounterDTO.java
@@ -0,0 +1,11 @@
+package id.ac.ui.cs.advprog.tutorial7.time.counter.dto;
+
+import lombok.Data;
+
+@Data
+public class TimeCounterDTO {
+    String time;
+    int hours;
+    int minutes;
+    String operation;
+}
diff --git a/src/main/java/id/ac/ui/cs/advprog/tutorial7/time/counter/service/TimeCounterService.java b/src/main/java/id/ac/ui/cs/advprog/tutorial7/time/counter/service/TimeCounterService.java
new file mode 100644
index 0000000000000000000000000000000000000000..dd32c58213ad86ab9df20735f3bac0c7f5738f38
--- /dev/null
+++ b/src/main/java/id/ac/ui/cs/advprog/tutorial7/time/counter/service/TimeCounterService.java
@@ -0,0 +1,5 @@
+package id.ac.ui.cs.advprog.tutorial7.time.counter.service;
+
+public interface TimeCounterService {
+    String calculateTime(String time, int hours, int minutes, String operation);
+}
diff --git a/src/main/java/id/ac/ui/cs/advprog/tutorial7/time/counter/service/TimeCounterServiceImpl.java b/src/main/java/id/ac/ui/cs/advprog/tutorial7/time/counter/service/TimeCounterServiceImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..693b4e634f1215cbba55c82129b7b22e096d5a72
--- /dev/null
+++ b/src/main/java/id/ac/ui/cs/advprog/tutorial7/time/counter/service/TimeCounterServiceImpl.java
@@ -0,0 +1,85 @@
+package id.ac.ui.cs.advprog.tutorial7.time.counter.service;
+
+import org.springframework.stereotype.Service;
+
+@Service
+public class TimeCounterServiceImpl implements TimeCounterService {
+
+    @Override
+    public String calculateTime(String time, int hours, int minutes, String operation) {
+        String[] parts = time.split(":");
+        int currentHours = Integer.parseInt(parts[0]);
+        int currentMinutes = Integer.parseInt(parts[1]);
+        int totalMinutes = calculateTotalMinute(currentHours, currentMinutes);
+        boolean isAdd = operation.equals("add");
+
+        String result = "";
+        if (isAdd) {
+            result = add(totalMinutes, hours, minutes);
+        } else {
+            result = subtract(totalMinutes, hours, minutes);
+        }
+        return printResult(time, hours, minutes, isAdd, result);
+    }
+
+    private String add(int totalMinutes, int hours, int minutes) {
+        totalMinutes += calculateTotalMinute(hours, minutes);
+        return getString(totalMinutes);
+    }
+
+    private String subtract(int totalMinutes, int hours, int minutes) {
+        totalMinutes -= calculateTotalMinute(hours, minutes);
+        // handle negative value
+        totalMinutes = (totalMinutes % (24 * 60)) + 24 * 60;
+        return getString(totalMinutes);
+    }
+
+    private String printResult(String time, int hours, int minutes, boolean add, String result) {
+        if (hours == 0 && minutes == 0) {
+            return result;
+        }
+        String toBe = getAfterOrBefore(add);
+        String hourText = getTextHour(hours);
+        String minuteText = getTextMinute(minutes);
+        return String.format("%s%s%s %s is %s", hourText, minuteText, toBe, time, result);
+    }
+
+    private String getAfterOrBefore(boolean add) {
+        if (add) {
+            return "after";
+        }
+        return "before";
+    }
+
+    private String getTextHour(int n) {
+        switch (n) {
+            case 0:
+                return "";
+            case 1:
+                return String.format("%d hour ", n);
+            default:
+                return String.format("%d hours ", n);
+        }
+    }
+
+    private String getTextMinute(int n) {
+        switch (n) {
+            case 0:
+                return "";
+            case 1:
+                return String.format("%d minute ", n);
+            default:
+                return String.format("%d minutes ", n);
+        }
+    }
+
+    private int calculateTotalMinute(int hours, int minutes){
+        return hours * 60 + minutes;
+    }
+
+    private String getString(int totalMinutes){
+        int newHours = (totalMinutes / 60) % 24;
+        int newMinutes = totalMinutes % 60;
+        return String.format("%02d:%02d", newHours, newMinutes);
+    }
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/main/resources/templates/day.counter/home.html b/src/main/resources/templates/day.counter/home.html
new file mode 100644
index 0000000000000000000000000000000000000000..53f599ce3a338f2c2330b99950b6bd782c010d2d
--- /dev/null
+++ b/src/main/resources/templates/day.counter/home.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html lang="en" xmlns:th="http://www.thymeleaf.org">
+<head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <title>Day Counter</title>
+
+    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
+    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
+</head>
+<body>
+    <div class="mt-5 mx-4 d-flex flex-column align-items-center">
+        <div  class="d-lg-flex flex-row w-50">
+            <div class="m-2 p-4 d-flex flex-column card w-100">
+                <div style="height: 4em" class="mb-2 row align-items-center">
+                    <h3 >Day Counter</h3>
+                </div>
+
+                <div class="my-2">
+                    <p>Calculate Day</p>
+                    <form th:action="@{/day-counter}" th:object="${dto}" method="post">
+                        <div class="input-group mb-3">
+                            <select name="day" class="form-select" required>
+                                <option value="" selected hidden>Choose day...</option>
+                                <option th:each="day:${days}" th:value="${day}" th:text="${day}"></option>
+                            </select>
+                            <input name="n" type="number" step="any" class="form-control" required placeholder="Enter n days (-n or n)...">
+                            <button class="btn btn-outline-secondary" type="submit">Calculate!</button>
+                        </div>
+                    </form>
+                </div>
+            </div>
+        </div>
+
+        <div th:if="${result != null}" class="mt-3 d-flex flex-column align-items-center">
+            <div class="input-group mb-3">
+                <span class="input-group-text">Result</span>
+                <span class="input-group-text" th:text="${result}"></span>
+            </div>
+        </div>
+    </div>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/main/resources/templates/leap.year/home.html b/src/main/resources/templates/leap.year/home.html
new file mode 100644
index 0000000000000000000000000000000000000000..a1f4eb87b2de9d7b32b4e002464c650cced357ef
--- /dev/null
+++ b/src/main/resources/templates/leap.year/home.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html lang="en" xmlns:th="http://www.thymeleaf.org">
+<head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <title>Leap Year Counter</title>
+
+    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
+    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
+</head>
+<body>
+    <div class="mt-5 mx-4 d-flex flex-column align-items-center">
+        <div  class="d-lg-flex flex-row w-50">
+            <div class="m-2 p-4 d-flex flex-column card w-100">
+                <div style="height: 4em" class="mb-2 row align-items-center">
+                    <h3 >Leap Year Counter</h3>
+                </div>
+
+                <div class="my-2">
+                    <p>Calculate Leap Year</p>
+                    <form th:action="@{/leap-year}" th:object="${dto}" method="post">
+                        <div class="input-group mb-3">
+                            <input name="year" type="number" step="any" min="0" class="form-control" required placeholder="Enter year...">
+                            <button class="btn btn-outline-secondary" type="submit">Calculate!</button>
+                        </div>
+                    </form>
+                </div>
+            </div>
+        </div>
+
+        <div th:if="${result != null}" class="mt-3 d-flex flex-column align-items-center">
+            <div class="input-group mb-3">
+                <span class="input-group-text">Result</span>
+                <span class="input-group-text" th:text="${result}"></span>
+            </div>
+        </div>
+    </div>
+</body>
+</html>
diff --git a/src/main/resources/templates/time.counter/home.html b/src/main/resources/templates/time.counter/home.html
new file mode 100644
index 0000000000000000000000000000000000000000..2e606d8236073ecd8d786f13b53422351e2d64d2
--- /dev/null
+++ b/src/main/resources/templates/time.counter/home.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html lang="en" xmlns:th="http://www.thymeleaf.org">
+<head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <title>Time Counter</title>
+
+    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
+    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
+</head>
+<body>
+    <div class="mt-5 mx-4 d-flex flex-column align-items-center">
+        <div  class="d-lg-flex flex-row w-50">
+            <div class="m-2 p-4 d-flex flex-column card w-100">
+                <div style="height: 4em" class="mb-2 row align-items-center">
+                    <h3 >Time Counter</h3>
+                </div>
+
+                <div class="my-2">
+                    <p>Calculate Time</p>
+                    <form th:action="@{/time-counter}" th:object="${dto}" method="post">
+                        <div class="input-group mb-2">
+                            <input name="time" type="time" class="form-control" required placeholder="Item amount...">
+                        </div>
+                        <div class="input-group mb-3">
+                            <select name="operation" class="form-select" required>
+                                <option value="" selected hidden>Choose operation...</option>
+                                <option th:each="op:${ops}" th:value="${op}" th:text="${op}"></option>
+                            </select>
+                            <input name="hours" type="number" step="any" min="0" class="form-control" required placeholder="Enter hours...">
+                            <input name="minutes" type="number" step="any" min="0" class="form-control" required placeholder="Enter minutes...">
+                            <button class="btn btn-outline-secondary" type="submit">Calculate!</button>
+                        </div>
+                    </form>
+                </div>
+            </div>
+        </div>
+
+        <div th:if="${result != null}" class="mt-3 d-flex flex-column align-items-center">
+            <div class="input-group mb-3">
+                <span class="input-group-text">Result</span>
+                <span class="input-group-text" th:text="${result}"></span>
+            </div>
+        </div>
+    </div>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/test/java/id/ac/ui/cs/advprog/tutorial7/Tutorial7ApplicationTest.java b/src/test/java/id/ac/ui/cs/advprog/tutorial7/Tutorial7ApplicationTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..d5ac1504e20ebd548c1e84a8932291f08e8b7c4e
--- /dev/null
+++ b/src/test/java/id/ac/ui/cs/advprog/tutorial7/Tutorial7ApplicationTest.java
@@ -0,0 +1,13 @@
+package id.ac.ui.cs.advprog.tutorial7;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class Tutorial7ApplicationTests {
+
+  @Test
+  void contextLoads() {
+  }
+
+}
diff --git a/src/test/java/id/ac/ui/cs/advprog/tutorial7/day/counter/controller/DayCounterControllerTest.java b/src/test/java/id/ac/ui/cs/advprog/tutorial7/day/counter/controller/DayCounterControllerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..16b8cdd94748973b6d011c2c8010d70e19408238
--- /dev/null
+++ b/src/test/java/id/ac/ui/cs/advprog/tutorial7/day/counter/controller/DayCounterControllerTest.java
@@ -0,0 +1,76 @@
+package id.ac.ui.cs.advprog.tutorial7.day.counter.controller;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.web.servlet.MockMvc;
+
+@SpringBootTest
+@AutoConfigureMockMvc
+public class DayCounterControllerTest {
+
+    @Autowired
+    private MockMvc mockMvc;
+
+    @Test
+    public void shouldShowPage() throws Exception {
+        this.mockMvc.perform(get("/day-counter")).andDo(print()).andExpect(status().isOk())
+                .andExpect(view().name("day.counter/home"))
+                .andExpect(model().attributeExists("days"))
+                .andExpect(model().attributeExists("dto"));
+    }
+
+    @Test
+    public void sundayPlus1() throws Exception {
+        this.mockMvc
+                .perform(post("/day-counter")
+                        .param("day", "Sunday")
+                        .param("n", "1"))
+                        .andExpect(content().string(containsString("1 day(s) in the future after Sunday is Monday.")));
+    }
+
+    @Test
+    public void wednesdayPlus100() throws Exception {
+        this.mockMvc
+                .perform(post("/day-counter")
+                        .param("day", "Wednesday")
+                        .param("n", "100"))
+                .andExpect(content().string(containsString("100 day(s) in the future after Wednesday is Friday.")));
+    }
+
+    @Test
+    public void saturdayMin60() throws Exception {
+        this.mockMvc
+                .perform(post("/day-counter")
+                        .param("day", "Saturday")
+                        .param("n", "-60"))
+                .andExpect(content().string(containsString("60 day(s) ago before Saturday was Tuesday.")));
+    }
+
+    @Test
+    public void mondayPlus0() throws Exception {
+        this.mockMvc
+                .perform(post("/day-counter")
+                        .param("day", "Monday")
+                        .param("n", "0"))
+                .andExpect(content().string(containsString("0 day(s) in the future after Monday is Monday.")));
+    }
+
+    @Test
+    public void thursdayMin3() throws Exception {
+        this.mockMvc
+                .perform(post("/day-counter")
+                        .param("day", "Thursday")
+                        .param("n", "-3"))
+                .andExpect(content().string(containsString("3 day(s) ago before Thursday was Monday.")));
+    }
+}
+
diff --git a/src/test/java/id/ac/ui/cs/advprog/tutorial7/leap/year/controller/LeapYearControllerTest.java b/src/test/java/id/ac/ui/cs/advprog/tutorial7/leap/year/controller/LeapYearControllerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..150cf7f8ddbb3fa168ab58c3dc807b90ef9bc579
--- /dev/null
+++ b/src/test/java/id/ac/ui/cs/advprog/tutorial7/leap/year/controller/LeapYearControllerTest.java
@@ -0,0 +1,92 @@
+package id.ac.ui.cs.advprog.tutorial7.leap.year.controller;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.web.servlet.MockMvc;
+
+@SpringBootTest
+@AutoConfigureMockMvc
+public class LeapYearControllerTest {
+
+        @Autowired
+        private MockMvc mockMvc;
+
+        @Test
+        public void shouldShowPage() throws Exception {
+                this.mockMvc.perform(get("/leap-year")).andDo(print()).andExpect(status().isOk())
+                                .andExpect(view().name("leap.year/home"))
+                                .andExpect(model().attributeExists("dto"));
+        }
+
+        @Test
+        public void year2001() throws Exception {
+                this.mockMvc
+                                .perform(post("/leap-year")
+                                                .param("year", "2001"))
+                                .andExpect(content().string(containsString(
+                                                "2001 isn&#39;t a leap year because it is not divisible by 4.")));
+        }
+
+        @Test
+        public void year0() throws Exception {
+                this.mockMvc
+                                .perform(post("/leap-year")
+                                                .param("year", "0"))
+                                .andExpect(content().string(
+                                                containsString("0 is a leap year because it is divisible by 400")));
+        }
+
+        @Test
+        public void year2004() throws Exception {
+                this.mockMvc
+                                .perform(post("/leap-year")
+                                                .param("year", "2004"))
+                                .andExpect(content().string(
+                                                containsString("2004 is a leap year because it is divisible by 4.")));
+        }
+
+        @Test
+        public void year2010() throws Exception {
+                this.mockMvc
+                                .perform(post("/leap-year")
+                                                .param("year", "2010"))
+                                .andExpect(content().string(containsString(
+                                                "2010 isn&#39;t a leap year because it is not divisible by 4.")));
+        }
+
+        @Test
+        public void year200() throws Exception {
+                this.mockMvc
+                                .perform(post("/leap-year")
+                                                .param("year", "200"))
+                                .andExpect(content().string(containsString(
+                                                "200 isn&#39;t a leap year because it is divisible by 100.")));
+        }
+
+        @Test
+        public void year2000() throws Exception {
+                this.mockMvc
+                                .perform(post("/leap-year")
+                                                .param("year", "2000"))
+                                .andExpect(content().string(
+                                                containsString("2000 is a leap year because it is divisible by 400.")));
+        }
+
+        @Test
+        public void year2012() throws Exception {
+                this.mockMvc
+                                .perform(post("/leap-year")
+                                                .param("year", "2012"))
+                                .andExpect(content().string(
+                                                containsString("2012 is a leap year because it is divisible by 4.")));
+        }
+}