2014年4月2日 星期三

Java 複習筆記 : 日期與時間 (續)

昨晚整理完 Java 的常用日期時間相關類別之應用, 發現還有幾個常用的 javascript 版本的函式, 改寫如下 :

1. 判斷閏年 :

閏年的判斷演算法有一個公式, 在 Javascript 是這樣 :

return !(year % 4) && (year % 100) || !(year % 400) ? true : false;

即年份是 4 與 100 的公倍數, 或者是 400 的倍數. Java 處理閏年有現成的 GregorianCalendar 類別可用, 只要先建立 GregorianCalendar 物件, 再呼叫其 isLeapYear() 即可 :

GregorianCalendar g=new GregorianCalendar();

API 參見 :

http://docs.oracle.com/javase/7/docs/api/java/util/GregorianCalendar.html

如下列範例所示 :

import java.util.*;
import java.text.*;
public class mytest {
  public static void main(String[] args) {
    System.out.println(isLeapYear());          //輸出 false (今年 2014 非閏年)
    System.out.println(isLeapYear(2012));  //輸出 true
    }
  public static int getYear() {
    SimpleDateFormat sdf=new SimpleDateFormat("yyyy");
    return Integer.parseInt(sdf.format(new Date()));
    }
  public static boolean isLeapYear() {
    return isLeapYear(getYear());
    }
  public static boolean isLeapYear(int year) {
    GregorianCalendar g=new GregorianCalendar();
    return g.isLeapYear(year);
    }
  }

此例中我使用了方法多載實現參數預設值功能, 無參數時預設為今年; 同時也用了 SimpleDateFormat 類別來求取今年年份.

2. 一年中的第幾天  :

一年 365/366 天, 若指定日期, 如何得知此為一年中的第幾天 ? 這可以利用 String 類別的 format() 方法, 將 Date 物件傳進去當地二參數, 利用第一參數 "%tj" 這個格式化字串傳回該日期是一年中的第幾天字串, 再轉成整數即可, 詳參 String 與 Formatter 兩類別 :

http://docs.oracle.com/javase/7/docs/api/java/lang/String.html
http://docs.oracle.com/javase/7/docs/api/java/util/Formatter.html

範例如下 :

import java.util.*;
import java.text.*;
public class mytest {
  public static void main(String[] args) {
    System.out.println(getDayOfYear());  //輸出 92 (今天是 2014-04-02)
    System.out.println(getDayOfYear("2012-01-01")); //輸出 1
    System.out.println(getDayOfYear("2012-12-31")); //輸出 366
    }
  public static String getDate() {
    SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
    return sdf.format(new Date());  //傳回今日的日期字串
    }
  public static int getDayOfYear() {
    return getDayOfYear(getDate());
    }
  public static int getDayOfYear(String date) {
    Date d=new Date();
    SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
    try {d=sdf.parse(date);}  //將傳入日期剖析為 Date 物件
    catch (ParseException e) {e.printStackTrace();}
    return Integer.parseInt(String.format("%tj",d));  //轉成一年中的第幾天
    }
  }

此例中同樣使用方法多載處理預設值, 無參數傳入時表示求今日為一年中第幾日.

3. 一年中的第幾周 : 

在 Calendar 類別中有一個 Calendar.WEEK_OF_YEAR 可以求得指定日期是一年中的第幾周. 先將指定日期字串分解為整數陣列後, 傳入 Calendar.set() 方法中將日曆設為指定日期, 即可由 Calendar.WEEK_OF_YEAR 取得答案.

http://docs.oracle.com/javase/7/docs/api/java/util/Calendar.html

範例如下 :

import java.util.*;
import java.text.*;
public class mytest {
  public static void main(String[] args) {
    System.out.println(getWeekOfYear()); //輸出 14 (今天是 2014-04-02)
    System.out.println(getWeekOfYear("2012-01-01"));  //輸出 1
    System.out.println(getWeekOfYear("2012-12-28"));  //輸出 52
    System.out.println(getWeekOfYear("2012-07-01"));  //輸出 27
    System.out.println(getWeekOfYear("2014-01-01"));  //輸出 1
    System.out.println(getWeekOfYear("2014-12-28"));  //輸出 1
    System.out.println(getWeekOfYear("2014-07-01"));  //輸出 27
    }
  public static String getDate() {  //傳回今日日期字串
    SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
    return sdf.format(new Date());
    }
  public static int[] parseDate(String date) { //將日期字串轉成陣列
    String[] d=date.split("-");
    int[] dt=new int[3];
    dt[0]=Integer.parseInt(d[0]);
    dt[1]=Integer.parseInt(d[1]);
    dt[2]=Integer.parseInt(d[2]);
    return dt;
    }
  public static int getWeekOfYear() {
    return getWeekOfYear(getDate());  //傳入今日日期字串
    }
  public static int getWeekOfYear(String date) {
    int[] d=parseDate(date);  //將日期字串轉成陣列
    Calendar c=Calendar.getInstance();
    c.set(d[0],d[1]-1,d[2]);   //設定日曆物件之日期 (注意月份要減 1)
    return c.get(Calendar.WEEK_OF_YEAR);
    }
  }

此例中, 2012-12-28 為 2012 年的第 52 周; 但 2014-12-28 卻是第 1 周, 這是因為年尾的幾天有可能已被歸入下年度的第一週.

4. 一個月中的第幾周 :

與上面一年中的第幾周類似, 也是利用 Calendar 類別的 Calendar.WEEK_OF_MONTH 欄位來求解, 範例如下, 僅最後一行改變而已 :

import java.util.*;
import java.text.*;
public class mytest {
  public static void main(String[] args) {
    System.out.println(getWeekOfMonth());
    System.out.println(getWeekOfMonth("2014-04-01"));
    System.out.println(getWeekOfMonth("2014-04-30"));
    }
  public static String getDate() {
    SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
    return sdf.format(new Date());
    }
  public static int[] parseDate(String date) {
    String[] d=date.split("-");
    int[] dt=new int[3];
    dt[0]=Integer.parseInt(d[0]);
    dt[1]=Integer.parseInt(d[1]);
    dt[2]=Integer.parseInt(d[2]);
    return dt;
    }
  public static int getWeekOfMonth() {
    return getWeekOfMonth(getDate());
    }
  public static int getWeekOfMonth(String date) {
    int[] d=parseDate(date);
    Calendar c=Calendar.getInstance();
    c.set(d[0],d[1]-1,d[2]);
    return c.get(Calendar.WEEK_OF_MONTH);
    }
  }

5. 取得星期名稱 : 

有時在記錄到 log 或資料庫時, 會順便紀錄星期幾, 在上一篇曾用 switch 來處理, 現在改用陣列來做, 同時改良功能, 加入語言參數, 可以選擇傳回繁體中文的 "星期日" 或英文的 "Sunday". 範例如下 :

import java.util.*;
import java.text.*;
public class mytest {
  public static void main(String[] args) {
    System.out.println(getDayOfWeek());  //輸出 星期三 (今天 2014-04-02)
    System.out.println(getDayOfWeek("2014-04-01"));  //輸出 星期二
    System.out.println(getDayOfWeek("2014-04-01","en"));  //輸出 Tuesday
    }
  public static String getDate() {
    SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
    return sdf.format(new Date());
    }
  public static int[] parseDate(String date) {
    String[] d=date.split("-");
    int[] dt=new int[3];
    dt[0]=Integer.parseInt(d[0]);
    dt[1]=Integer.parseInt(d[1]);
    dt[2]=Integer.parseInt(d[2]);
    return dt;
    }
  public static String getDayOfWeek() {
    return getDayOfWeek(getDate(),"zh");
    }
  public static String getDayOfWeek(String date) {
    return getDayOfWeek(date,"zh");
    }
  public static String getDayOfWeek(String date, String language) {
    int[] d=parseDate(date);
    Calendar c=Calendar.getInstance();
    c.set(d[0],d[1]-1,d[2]);
    String[] zh=
      {"星期日","星期一","星期二","星期三","星期四","星期五","星期六"};
    String[] en=
      {"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"};
    String w=null;
    if (language.equals("zh")) {w=zh[c.get(Calendar.DAY_OF_WEEK)-1];}
    else {w=en[c.get(Calendar.DAY_OF_WEEK)-1];}
    return w;
    }
  }

這裡要注意, Calendar.DAY_OF_WEEK 欄位之值為 1 (Sunday)~7 (Saturday), 而陣列索引為 0~6, 因此存取時要減 1. 第二參數 language 預設是 "zh" (中文), 若指定為 "en" 則傳回英文.

6. 取得月份名稱 :

如果給予日期, 要如何取得月份的中英文名稱? 跟上面取得星期名稱類似, 只要利用 Calendar.MONTH 欄位來判斷即可, 範例如下 :

import java.util.*;
import java.text.*;
public class mytest {
  public static void main(String[] args) {
    System.out.println(getMonthName());  //輸出 四月 (今天 2012-04-02)
    System.out.println(getMonthName("2014-12-01"));  //輸出 十二月
    System.out.println(getMonthName("2014-12-01","en"));  //輸出 December
    }
  public static String getDate() {
    SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
    return sdf.format(new Date());
    }
  public static int[] parseDate(String date) {
    String[] d=date.split("-");
    int[] dt=new int[3];
    dt[0]=Integer.parseInt(d[0]);
    dt[1]=Integer.parseInt(d[1]);
    dt[2]=Integer.parseInt(d[2]);
    return dt;
    }
  public static String getMonthName() {
    return getMonthName(getDate(),"zh");
    }
  public static String getMonthName(String date) {
    return getMonthName(date,"zh");
    }
  public static String getMonthName(String date, String language) {
    int[] d=parseDate(date);
    Calendar c=Calendar.getInstance();
    c.set(d[0],d[1]-1,d[2]);
    String[] zh={"一月","二月","三月","四月","五月","六月","七月","八月",
                 "九月","十月","十一月","十二月"};
    String[] en={"January","February","March","April","May","June","July",
                 "August","September","October","November","December"};
    String m=null;
    if (language.equals("zh")) {m=zh[c.get(Calendar.MONTH)];}
    else {m=en[c.get(Calendar.MONTH)];}
    return m;
    }
  }

參考 :

# JAVA String.format 方法使用介紹

6. 計算時間差 : 

這在比較兩個演算法的運算效能時會用到, System 套件有一個 currentTimeMillis() 方法可以用來計算時間差, 它會傳回當時的時間戳 (毫秒, 長整數), 因此只要在演算法開始前後呼叫此方法, 再將兩值相減, 就可以得到所經過時間的毫秒值.

long start=System.currentTimeMillis();
......
long end=System.currentTimeMillis();
System.out.println(Arrays.toString(a));

# http://docs.oracle.com/javase/7/docs/api/java/lang/System.html

下列範例要利用此方法配合 Runtime 類別的 freeMemory() 方法來比較 String 與 StringBuffer 在執行速度與記憶體佔用的情形. Runtime 類別參見 :

# http://docs.oracle.com/javase/7/docs/api/java/lang/Runtime.html

import java.util.*;
public class mytest {
  public static void main(String[] args) {
    String str="Java";
    long start=System.currentTimeMillis();
    long mem1=Runtime.getRuntime().freeMemory();
    for (int i=0; i<1000; i++) {str += "!";}
    long end=System.currentTimeMillis();
    long mem2=Runtime.getRuntime().freeMemory();
    System.out.println("Elapsed : " + (end-start) + " ms");
    System.out.println("Used : " + (mem1-mem2) + " bytes");
    }
  }

在我的電腦執行結果 :

Elapsed : 16 ms
Used : 39000 bytes

注意, 這裡迴圈若太大會導致執行時期錯誤 :  java.lang.OutOfMemoryError: Java heap space.

# Java 内存溢出(java.lang.OutOfMemoryError)的常见情况


另外, 常用到的時間差計算是, 當使用者登入後顯示 "您上次登入是 3 天 14 時 23 分 45 秒前", 這通常是比對兩組時間字串, 例如 "2014-04-02 13:01:23" 與 "2013-02-09 21:34:00" 的時間差.

import java.util.*;
public class mytest {
  public static void main(String[] args) {
    String dt1="2012-01-01 00:00:00";
    String dt2="2013-01-01 00:00:00";
    System.out.println(timeDiff(dt1,dt2)); //輸出 31622400000
    System.out.println(getDuration(dt1,dt2)); //輸出 366 日 0 時 0 分 0 秒
    System.out.println(getDuration(dt1,dt2,"en")); //輸出 366 days 0 hours 0 minutes 0 seconds
    }
  public static int[] parseDateTime(String datetime) {
    String[] a=datetime.split(" ");
    String[] d=a[0].split("-");
    String[] t=a[1].split(":");
    int[] dt=new int[6];
    dt[0]=Integer.parseInt(d[0]);
    dt[1]=Integer.parseInt(d[1]);
    dt[2]=Integer.parseInt(d[2]);
    dt[3]=Integer.parseInt(t[0]);
    dt[4]=Integer.parseInt(t[1]);
    dt[5]=Integer.parseInt(t[2]);
    return dt;
    }
  public static long timeDiff(String datetime1, String datetime2) {
    int[] d=parseDateTime(datetime1);
    Calendar c1=Calendar.getInstance();
    c1.set(d[0],d[1]-1,d[2],d[3],d[4],d[5]);
    d=parseDateTime(datetime2);
    Calendar c2=Calendar.getInstance();
    c2.set(d[0],d[1]-1,d[2],d[3],d[4],d[5]);
    return c2.getTimeInMillis()-c1.getTimeInMillis();
    }
  public static String getDuration(String dt1, String dt2) {
    return getDuration(dt1, dt2, "zh");
    }
  public static String getDuration(String dt1, String dt2, String language) {
    long ms=Math.abs(timeDiff(dt1, dt2));
    long d=ms/(1000 * 60 * 60 * 24); 
    long h=(ms%(1000 * 60 * 60 * 24))/(1000 * 60 * 60); 
    long m=(ms%(1000 * 60 * 60))/(1000 * 60); 
    long s=(ms%(1000 * 60))/1000;
    String str=null;
    if (language.equals("en")) {
      str=d + " days " + h + " hours " + m + " minutes " + s + " seconds";
      }
    else {str=d + " 日 " + h + " 時 " + m + " 分 " + s + " 秒";}
    return str;
    }
  }

執行結果 :

31622400000
366 日 0 時 0 分 0 秒
366 days 0 hours 0 minutes 0 seconds

此例中, timeDiff() 方法會傳回 dt2-dt1 毫秒差值, 故有正有負, 但在轉換為日時分秒前會先取絕對值再計算.

參考 :

# Java中将毫秒数转为*天*小时*分*秒 
 請問如何算得兩個時間的時間差

沒有留言 :