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;
}
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;
}
}
(抽出方法)
回覆刪除bool isInputValid
(改名,更好理解)
isYear(), isMonth() and isDay()改成 isValidXXX)
(抽出方法,去除switch case)
bool CheckValidDay
bool InvalidDayWarn
bool CheckLerpYearFebValidDay
bool CheckFebValidDay
private static boolean Contans(int[] array, int member){ 這行拼錯了,少了一個i
回覆刪除