Java Homework - 年度日期計數器

 Java 班第四份作業的其中一題,依年、月、日順序,輸入三個整數後求此日期是該年度的第幾天。有兩個作法。

作法 1

邏輯上很簡單,但因為用 switch case 判斷月份所以寫起來很長(SE 17 有更簡潔的寫法)。然後重要的地方在於開始熟悉 Java 裡對日期時間的處理。

關於 isValidInput() 裡為什麼使用位元運算的 & 運算子 (AND),是因為要避免掉 Java 裡 && 的短路求值 (short circuit evaluation) 特性,其意思是在計算一個邏輯表達式 (logical expression) 的真假值時,從左算到右若已經可以決定整句的真假,便會略過後面不執行。這本該是對於程式執行效率有利的性質,然而因為我想確保做過全部年月日的驗證,以印出相應提示訊息,在此短路求值就不是我想要的。


package junli.hw4;

// 請設計由鍵盤輸入三個整數,分別代表西元yyyy年,mm月,dd日,執行後會顯示是該年的第幾天 例:輸入 1984 9 8 三個號碼後,
// 程式會顯示「輸入的日期為該年第252天」
// (提示1:Scanner,陣列)
// (提示2:需將閏年條件加入)
// (提示3:擋下錯誤輸入:例如月份輸入為2,則日期不該超過29)

import java.util.Calendar;
import java.util.Scanner;

public class DayOfYearCounter {

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.println("請輸入 yyyy mm dd 三個正整數");
        int yearNum;
        int monthNum;
        int dayNum;
        do {
            yearNum = sc.nextInt();
            monthNum = sc.nextInt();
            dayNum = sc.nextInt();
        } while (!isValidInput(yearNum, monthNum, dayNum)); // Peng: isValidInputNew(...)

        Calendar calA = getCalendar(yearNum, monthNum, dayNum);
        Calendar calB = getCalendar(yearNum, 1, 1);
        long numOfDays = (calA.getTimeInMillis() - calB.getTimeInMillis()) / 86400000;
//        Date dateA = calA.getTime();
//        SimpleDateFormat format1 = new SimpleDateFormat("yyyy-MM-dd");
//        System.out.println(format1.format(dateA));
        System.out.printf("輸入的日期為該年第 %d 天\n", numOfDays + 1);

    }
    
    public static boolean isValidInput(int yearNum, int monthNum, int dayNum) {
        // Use & in the while condition, so that isYear(), isMonth() and isDay() will all check and print message
        return (isYear(yearNum) & isMonth(monthNum) & isDay(yearNum, monthNum, dayNum));
    }    

    private static boolean isYear(int num) {
        if(num >= 1) {
            return true;
        } else {
            System.out.println("年份 yyyy 請輸入大於等於 1 的數字");
            return false;
        }
    }

    private static boolean isMonth(int num) {
        if(num >= 1 && num <= 12) {
            return true;
        } else {
            System.out.println("月份 mm 請輸入介於 1 到 12 之間的數字");
            return false;
        }
    }

    private static boolean isDay(int year, int month, int num) {
        switch (month) {
            case 1:
            case 3:
            case 5:
            case 7:
            case 8:
            case 10:
            case 12:
                if(num >=1 && num <= 31) {
                    return true;
                } else {
                    System.out.printf("月份為 %d 月,日期請輸入介於 1 到 31 的數字\n", month);
                    return false;
                }
            case 4:
            case 6:
            case 9:
            case 11:
                if(num >=1 && num <= 30) {
                    return true;
                } else {
                    System.out.printf("月份為 %d 月,日期請輸入介於 1 到 30 的數字\n", month);
                    return false;
                }
            case 2:
                if(isLeapYear(year)) {
                    if(num >= 1 && num <= 29) {
                        return true;
                    } else {
                        System.out.printf("年份為閏年 %d 年,月份為 %d 月,日期請輸入介於 1 到 29 的數字\n", year, month);
                        return false;
                    }
                } else {
                    if(num >= 1 && num <= 28) {
                        return true;
                    } else {
                        System.out.printf("月份為 %d 月,日期請輸入介於 1 到 28 的數字\n", month);
                        return false;
                    }
                }
            default:
                if(num < 1 || num > 31) {
                    System.out.println("日期 dd 請輸入大於 1 的數字,上限視月份而定");
                    return false;
                }
        }
        return true;
    }

    private static boolean isLeapYear(int year) {
        return (year > 0) && (year % 4 == 0) && (year % 100 != 0 || year % 400 == 0);
    }

    public static Calendar getCalendar(int year, int month, int day) {
        Calendar c = Calendar.getInstance();
        c.set(Calendar.YEAR, year);
        c.set(Calendar.MONTH, month - 1); // JANUARY indexes at 0
        c.set(Calendar.DAY_OF_MONTH, day);
        c.set(Calendar.HOUR, 0); // If the HOUR to MILLISECOND fields are not set as 0, then the order of getting
        c.set(Calendar.MINUTE, 0); // milliseconds from calA and calB will affect the result
        c.set(Calendar.SECOND, 0);
        c.set(Calendar.MILLISECOND, 0);
        return c;
    }

}


作法 2

Peng 和我的差異來自於驗證日的方法,因此我以 isValidDay() 代表他的,和我用 switch case 的 isDay() 區別。

他的作法就是盡可能抽出方法,所以結果是 isValidDay() 本身內部精簡許多,類似的提示訊息也整合於 warnValidDay(),便於維護。

值得注意的是,他的寫法所實現的行為和我的不太一樣:他的 isValidDay() 裡一開始就判斷 num 是否超出 31 或小於 1,但我是先判斷月份,而兩邊輸出的訊息不太一樣,當月輸入正常而日輸入不正常時,兩邊都會擋下但提示的訊息不同。這沒有對錯,端看你對輸入流程的規劃和對訊息的要求。

Peng 也提到一種有趣的想法,先判斷日是否介於 1 ~ 28,再來判斷 29、30、31。這就留給有心人研究了。

    public static boolean isValidInputNew(int yearNum, int monthNum, int dayNum) {
        return (isYear(yearNum) & isMonth(monthNum) & isValidDay(yearNum, monthNum, dayNum));
    }

    private static boolean isValidDay(int year, int month, int num) {

        if(num < 1 || num > 31) {
            System.out.println("日期 dd 請輸入大於 1 的數字,上限視月份而定");
            return false;
        }
        int[] monthsHavingThirtyOneDays = {1, 3 , 5, 7, 8, 10, 12};
        int[] monthsHavingThirtyDays = {4, 6, 9, 11};

        if (contains(monthsHavingThirtyOneDays, month)) {
            return checkValidDay(month, 31, num);
        } else if (contains(monthsHavingThirtyDays, month)) {
            return checkValidDay(month, 30, num);
        } else {
            return checkFebValidDay(year, num);
        }

    }

    private static boolean checkValidDay(int month, int maxDay, int num){
        if (num >=1 && num <= maxDay) {
            return true;
        } else {
            warnInvalidDay(month, maxDay);
            return false;
        }
    }

    private static boolean checkFebValidDay(int year, int num){
        if(isLeapYear(year)){
            return checkLeapYearFebValidDay(year, num);
        } else{
            return checkValidDay(2, 28, num);
        }
    }

    private static boolean checkLeapYearFebValidDay(int year, int num){
        if(num >= 1 && num <= 29) {
            return true;
        } else {
            System.out.printf("年份為閏年 %d 年,月份為 2 月,日期請輸入介於 1 到 29 的數字\n", year);
            return false;
        }
    }

    private static void warnInvalidDay(int month, int maxDay){
        System.out.printf("月份為 %d 月,日期請輸入介於 1 到 %d 的數字\n", month, maxDay);
    }

    private static boolean contains(int[] array, int member){
        // Enhanced for loop
        for (int j : array) {
            if (j == member) {
                return true;
            }
        }
        return false;
    }

留言

  1. import java.text.SimpleDateFormat;
    import java.util.Calendar;
    import java.util.Date;
    import java.util.Scanner;

    public class DateCounter {

    int[] month31Days = {1, 3, 5, 7, 8, 10, 12};
    int[] month30Days = {4, 6, 9, 11};

    public static void main(String[] args) {

    Scanner sc = new Scanner(System.in);
    System.out.println("請輸入 yyyy mm dd 三個正整數");


    int yearNum;
    int monthNum;
    int dayNum;
    do {
    yearNum = sc.nextInt();
    monthNum = sc.nextInt();
    dayNum = sc.nextInt();
    // Use & in the while condition, so that isYear(), isMonth() and isDay() will all check and print message
    } while (!isInputValid(yearNum, monthNum, dayNum));

    Calendar calA = getCalendar(yearNum, monthNum, dayNum);
    Calendar calB = getCalendar(yearNum, 1, 1);
    long numOfDays = (calA.getTimeInMillis() - calB.getTimeInMillis()) / 86400000;
    // Date dateA = calA.getTime();
    // SimpleDateFormat format1 = new SimpleDateFormat("yyyy-MM-dd");
    // System.out.println(format1.format(dateA));
    System.out.printf("輸入的日期為該年第 %d 天", numOfDays + 1);

    }

    private static boolean isInputValid(yearNum, monthNum, dayNum){

    return isValidYear(yearNum) & isValidMonth(monthNum) & isValidDay(yearNum, monthNum, dayNum));
    }

    private static boolean isValidYear(int num) {
    if(num >= 1) {
    return true;
    } else {
    System.out.println("年份 yyyy 請輸入大於等於 1 的數字");
    return false;
    }
    }

    private static boolean isValidMonth(int num) {
    if(num >= 1 && num <= 12) {
    return true;
    } else {
    System.out.println("月份 mm 請輸入介於 1 到 12 之間的數字");
    return false;
    }
    }

    private static boolean isValidDay(int year, int month, int num) {

    if(num < 1 || num > 31) {
    System.out.println("日期 dd 請輸入大於 1 的數字,上限視月份而定");
    return false;
    }

    if (Contains(month31Days, month){
    return CheckValidDay(month, 31, num);
    }
    else if (Contains(month30Days, month){
    return CheckValidDay(month, 30, num);
    }
    else (month == 2){
    return CheckFebValidDay(year, num);
    }

    return true;
    }

    private static boolean CheckValidDay(int month, int maxDay, int num){
    return (num >=1 && num <= maxDay) ? true : InvalidDayWarn(month, maxDay);
    }

    private static boolean CheckFebValidDay(int year, int num){
    if(isLeapYear(year)){
    return CheckLerpYearFebValidDay(year, num);
    } else{
    return CheckValidDay(month, 28, num);
    }
    }

    private static boolean isLeapYear(int year) {
    return (year > 0) && (year % 4 == 0) && (year % 100 != 0 || year % 400 == 0);
    }

    private static boolean CheckLerpYearFebValidDay(int year, int num){
    if(num >= 1 && num <= 29) {
    return true;
    } else {
    System.out.printf("年份為閏年 %d 年,月份為 2 月,日期請輸入介於 1 到 29 的數字\n", year);
    return false;
    }
    }

    private static boolean InvalidDayWarn(int month, int maxDay){
    System.out.printf("月份為 %d 月,日期請輸入介於 1 到 %d 的數字\n", month, maxDay);
    return false;
    }

    private static boolean Contans(int[] array, int member){
    for(int i = 0; i < array.length; i++){
    if (array[i] == member){
    return true;
    }
    return false;
    }
    }

    private static Calendar getCalendar(int year, int month, int day) {
    Calendar c = Calendar.getInstance();
    c.set(Calendar.YEAR, year);
    c.set(Calendar.MONTH, month - 1); // JANUARY indexes at 0
    c.set(Calendar.DAY_OF_MONTH, day);
    c.set(Calendar.HOUR, 0); // If the HOUR to MILLISECOND fields are not set as 0, then the order of getting
    c.set(Calendar.MINUTE, 0); // milliseconds from calA and calB will affect the result
    c.set(Calendar.SECOND, 0);
    c.set(Calendar.MILLISECOND, 0);
    return c;
    }
    }

    回覆刪除
  2. (抽出方法)
    bool isInputValid

    (改名,更好理解)
    isYear(), isMonth() and isDay()改成 isValidXXX)

    (抽出方法,去除switch case)
    bool CheckValidDay
    bool InvalidDayWarn
    bool CheckLerpYearFebValidDay
    bool CheckFebValidDay

    回覆刪除
  3. private static boolean Contans(int[] array, int member){ 這行拼錯了,少了一個i

    回覆刪除

張貼留言

這個網誌中的熱門文章

那天