學(xué)習(xí)在java中計(jì)算基本的時(shí)間段 概述 如果你知道怎樣在java中使用日期,那么使用時(shí)間和它才不多一樣簡(jiǎn)單。這篇文章告訴你怎樣把他們的差別聯(lián)系起來(lái)。Robert Nielsen還告訴你怎樣使用java來(lái)計(jì)算抵達(dá)航班和制造過(guò)程的時(shí)間。 作者:Robert Nielsen 翻譯:Cocia Lin
這篇文章是在我發(fā)表過(guò)的<計(jì)算Java時(shí)間>(譯者:已經(jīng)翻譯完成)的基礎(chǔ)上的。在這里,我列出那篇文章幾個(gè)你應(yīng)該熟悉得關(guān)鍵點(diǎn)。如果這幾點(diǎn)你不太清楚,我建議你讀一下<計(jì)算Java時(shí)間>,了解一下。 1. Java計(jì)算時(shí)間依靠1970年1月1日開(kāi)始的毫秒數(shù). 2. Date類的構(gòu)造函數(shù)Date()返回代表當(dāng)前創(chuàng)建的時(shí)刻的對(duì)象。Date的方法getTime()返回一個(gè)long值在數(shù)值上等于1970年1月1日之前或之后的時(shí)刻。 3. DateFormat類用來(lái)轉(zhuǎn)換Date到String,反之亦然。靜態(tài)方法getDateInstance()返回DateFormat的缺省格式;getDateInstance(DateFormat.FIELD)返回指定的DateFormat對(duì)象格式。Format(Date d)方法返回String表示日期,例如"January 1,2002."反過(guò)來(lái),parse(String s)方法返回以參數(shù)字符串表示的Date對(duì)象。 4. format()方法返回的字符串格式根據(jù)不同地區(qū)的時(shí)間設(shè)置而有所不同。 5. GregorianCalendear類有兩個(gè)重要的構(gòu)造函數(shù):GregorianCalerdar(),返回代表當(dāng)前創(chuàng)建時(shí)間的對(duì)象;GregorianCalendar(int year,int month,int date)返回代表任意日期的對(duì)象。GregorianCalendar類的getTime()方法返回日期對(duì)象。Add(int field,int amount)方法通過(guò)加或減時(shí)間單位,象天數(shù),月數(shù)或年數(shù)來(lái)計(jì)算日期。 GregorianCalendar和 時(shí)間 兩個(gè)GregorianCalendar的構(gòu)造函數(shù)可以用來(lái)處理時(shí)間。前者創(chuàng)建一個(gè)表示日期,小時(shí)和分鐘的對(duì)象:
GregorianCalendar(int year, int month, int date, int hour, int minute)
第二個(gè)創(chuàng)建一個(gè)表示一個(gè)日期,小時(shí),分鐘和秒:
GregorianCalendar(int year, int month, int date, int hour, int minute, int second)
首先,我應(yīng)該提醒一下,每一個(gè)構(gòu)造函數(shù)需要時(shí)間信息中的日期信息(年,月,日)。如果你想說(shuō)2:30 p.m.,你必須指出日期。 同樣,每一個(gè)GregorianCalendar構(gòu)造函數(shù)創(chuàng)建一個(gè)在時(shí)間上使用毫秒計(jì)算的對(duì)象。所以,如果你的構(gòu)造函數(shù)只提供年,月,日參數(shù),那小時(shí),分鐘,秒和毫秒的值將被置0. DateFormat和時(shí)間 你可以使用靜態(tài)方法getDateTimeInstance(int dateStyle,int timeStyle)來(lái)建立DateFormat對(duì)象來(lái)顯示時(shí)間和日期。這個(gè)方法表明你想要的日期和時(shí)間格式。如果你喜歡使用缺省格式,可以使用getDateTimeInstance()來(lái)代替它。 你可以使用靜態(tài)方法getTimeInstance(int timeStyle)創(chuàng)建DateFormat對(duì)象來(lái)顯示正確的時(shí)間。 下面的程序示范了getDateTimeInstance()和getTimeInstance()怎樣工作:
import java.util.*; import java.text.*;
public class Apollo { public static void main(String[] args) { GregorianCalendar liftOffApollo11 = new GregorianCalendar(1969, Calendar.JULY, 16, 9, 32); Date d = liftOffApollo11.getTime(); DateFormat df1 = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM); DateFormat df2 = DateFormat.getTimeInstance(DateFormat.SHORT); String s1 = df1.format(d); String s2 = df2.format(d); System.out.println(s1); System.out.println(s2); } }
在我的電腦上,上面的程序顯示如下:
Jul 16, 1969 9:32:00 AM 9:32 AM (輸出根據(jù)你所在得地區(qū)有所不同)
計(jì)算時(shí)間間隔 你可能有時(shí)需要計(jì)算過(guò)去的時(shí)間;例如,給你開(kāi)始和結(jié)束時(shí)間,你想知道制造流程的持續(xù)時(shí)間。一個(gè)出租公司按小時(shí)或天數(shù)出租東西,計(jì)算時(shí)間對(duì)他們也很有用。同樣的,在金融界,經(jīng)常需要計(jì)算重要的支付時(shí)間。 將問(wèn)題復(fù)雜化,人類至少是用兩種方法計(jì)算時(shí)間。你可以說(shuō)一天已經(jīng)結(jié)束當(dāng)24小時(shí)過(guò)去了,或者日歷從今天翻到明天。我們將討論我們想到的這兩種情況。 時(shí)間段,情況 1:嚴(yán)格時(shí)間單位 在這種情況中,只有24小時(shí)過(guò)去,這天才過(guò)去,60分鐘過(guò)去,這個(gè)小時(shí)才過(guò)去,60秒過(guò)去,這個(gè)分鐘才過(guò)去,以此類推。在這個(gè)方法中,23小時(shí)的時(shí)間將被認(rèn)為是0天。 使用這種方法計(jì)算時(shí)間段,你從計(jì)算過(guò)去的毫秒開(kāi)始。為了做到這一點(diǎn),首先轉(zhuǎn)換每個(gè)日期為從1970年1月1日起得毫秒數(shù)。你可以從第二個(gè)毫秒值中減去第一個(gè)毫秒值。這里有一個(gè)簡(jiǎn)單的計(jì)算:
import java.util.*;
public class ElapsedMillis { public static void main(String[] args) { GregorianCalendar gc1 = new GregorianCalendar(1995, 11, 1, 3, 2, 1); GregorianCalendar gc2 = new GregorianCalendar(1995, 11, 1, 3, 2, 2); // the above two dates are one second apart Date d1 = gc1.getTime(); Date d2 = gc2.getTime(); long l1 = d1.getTime(); long l2 = d2.getTime(); long difference = l2 - l1; System.out.println("Elapsed milliseconds: " + difference); } }
上面的程序打印如下:
Elapsed milliseconds: 1000
這個(gè)程序也帶來(lái)一點(diǎn)混淆。GregorianCalendar類的getTime()返回一個(gè)Date對(duì)象,Date類的getTime()方法返回從1970年1月1日到這個(gè)時(shí)間的long類型的毫秒數(shù)值。雖然他們的方法名字相同,返回值卻不一樣! 下面的程序片斷用簡(jiǎn)單的整數(shù)除法轉(zhuǎn)換毫秒到秒:
long milliseconds = 1999; long seconds = 1999 / 1000;
這種方法舍去小數(shù)部分轉(zhuǎn)換毫秒到秒,所以1,999毫秒等于1秒,2,000毫秒等于2秒。 計(jì)算更大的單位-例如天數(shù),小時(shí)和分鐘-給定一個(gè)時(shí)間數(shù)值,可以使用下面的過(guò)程: 1. 計(jì)算最大的單位,減去這個(gè)數(shù)值的秒數(shù) 2. 計(jì)算第二大單位,減去這個(gè)數(shù)值的秒數(shù) 3. 重復(fù)操作直到只剩下秒 例如,如果你的時(shí)間的10,000秒,你想知道這個(gè)數(shù)值相應(yīng)的是多少小時(shí),多少分鐘,多少秒,你從最大的單位開(kāi)始:小時(shí)。10,000除以3600(一個(gè)小時(shí)的秒數(shù))得到小時(shí)數(shù)。使用整數(shù)除法,答案是2小時(shí)(整數(shù)除法中小數(shù)舍去)計(jì)算剩下的秒數(shù),10,000-(3,600 x 2) = 2,800秒。所以你有2小時(shí)和2,800秒。 將2,800秒轉(zhuǎn)換成分鐘,2,800除以60。使用整數(shù)除法,答案是46。2,800 - (60 x 46) = 40秒。最后答案是2小時(shí),46分,40秒。 下面的Java程序使用上面的計(jì)算方法:
import java.util.*;
public class Elapsed1 { public void calcHMS(int timeInSeconds) { int hours, minutes, seconds; hours = timeInSeconds / 3600; timeInSeconds = timeInSeconds - (hours * 3600); minutes = timeInSeconds / 60; timeInSeconds = timeInSeconds - (minutes * 60); seconds = timeInSeconds; System.out.println(hours + " hour(s) " + minutes + " minute(s) " + seconds + " second(s)"); }
public static void main(String[] args) { Elapsed1 elap = new Elapsed1(); elap.calcHMS(10000); } }
輸出結(jié)果如下:
2 hour(s) 46 minute(s) 40 second(s)
上面的程序甚至在時(shí)間少于一個(gè)小時(shí)也可以正確的計(jì)算小時(shí)數(shù)。例如,你用上面的程序計(jì)算1,000秒,輸出入下: 0 hour(s) 16 minute(s) 40 second(s) 舉一個(gè)現(xiàn)實(shí)世界的例子,下面的程序計(jì)算阿波羅11飛到月球使用得時(shí)間:
import java.util.*;
public class LunarLanding {
public long getElapsedSeconds(GregorianCalendar gc1, GregorianCalendar gc2) { Date d1 = gc1.getTime(); Date d2 = gc2.getTime(); long l1 = d1.getTime(); long l2 = d2.getTime(); long difference = Math.abs(l2 - l1); return difference / 1000; }
public void calcHM(long timeInSeconds) { long hours, minutes, seconds; hours = timeInSeconds / 3600; timeInSeconds = timeInSeconds - (hours * 3600); minutes = timeInSeconds / 60; System.out.println(hours + " hour(s) " + minutes + " minute(s)" ); }
public static void main(String[] args) { GregorianCalendar lunarLanding = new GregorianCalendar(1969, Calendar.JULY, 20, 16, 17); GregorianCalendar lunarDeparture = new GregorianCalendar(1969, Calendar.JULY, 21, 13, 54); GregorianCalendar startEVA = new GregorianCalendar(1969, Calendar.JULY, 20, 22, 56); GregorianCalendar endEVA = new GregorianCalendar(1969, Calendar.JULY, 21, 1, 9);
LunarLanding apollo = new LunarLanding();
long eva = apollo.getElapsedSeconds(startEVA, endEVA); System.out.print("EVA duration = "); apollo.calcHM(eva);
long lunarStay = apollo.getElapsedSeconds(lunarLanding, lunarDeparture); System.out.print("Lunar stay = "); apollo.calcHM(lunarStay); } }
上面程序輸出如下:
EVA duration = 2 hour(s) 13 minute(s) Lunar stay = 21 hour(s) 37 minute(s)
目前為止,我們計(jì)算的基礎(chǔ)公式是這樣的:1分鐘=60秒,1小時(shí)=60分,1天=24小時(shí)。 "1個(gè)月=?天,1年=?天"怎么辦? 月份的天數(shù)有28,29,30,31;一年可以是365或366天。因此,當(dāng)你試圖計(jì)算嚴(yán)格單位的月份和年時(shí),問(wèn)題就產(chǎn)生了。例如,如果你使用月份的平均天數(shù)(近似30.4375),并且計(jì)算下面的時(shí)間間隔:
* July 1, 2:00 a.m. to July 31, 10:00 p.m. * February 1, 2:00 a.m. to February 29, 10:00 p.m.
第一個(gè)計(jì)算結(jié)果是1個(gè)月;第二個(gè)結(jié)果是0個(gè)月! 所以,在計(jì)算嚴(yán)格單位時(shí)間的月份和年份是要想好。 時(shí)間段,情況 2:時(shí)間單位變化 時(shí)間單位的變化相當(dāng)?shù)暮?jiǎn)單:如果你要統(tǒng)計(jì)天數(shù),你可以簡(jiǎn)單的統(tǒng)計(jì)日期變化次數(shù)。例如,如果某事15日開(kāi)始,17日結(jié)束,經(jīng)過(guò)2天。(日期先是便到16,再到17)同樣的,一個(gè)步驟下午3:25開(kāi)始,4:10 p.m結(jié)束,歷時(shí)1個(gè)小時(shí),因?yàn)樾r(shí)數(shù)值變了一次(從3到4)。 圖書(shū)館經(jīng)常使用這種習(xí)慣計(jì)算時(shí)間。例如,如果你從圖書(shū)館接一本書(shū),我不能占有這本書(shū)最少24小時(shí),會(huì)認(rèn)為圖書(shū)館這樣才給你算一天。而是,我的賬號(hào)上記錄我借書(shū)的日期。日期以變成下一天,我就已經(jīng)結(jié)這本書(shū)一天了,即使總計(jì)不足24小時(shí)。 當(dāng)使用單位的變化來(lái)計(jì)算時(shí)間段,通常感覺(jué)計(jì)算的時(shí)間沒(méi)有多于一個(gè)時(shí)間單位。例如,如果9:00 p.m.我借了一本圖書(shū)館的書(shū),第二天中午還回去,我能算出我借了這本書(shū)一天了??墒牵幸环N感覺(jué)在問(wèn):"1天和幾個(gè)小時(shí)呢?"這本說(shuō)總計(jì)借出15個(gè)小時(shí),答案是一天還差9個(gè)小時(shí)呢?因此,這篇文章里,我將以一個(gè)時(shí)間單位變化計(jì)算時(shí)間。 單位變化的時(shí)間算法 這是你怎樣計(jì)算兩個(gè)日期的時(shí)間變化: 1. 制作兩個(gè)日期的拷貝。Close()方法能制作拷貝。 2. 使用日期拷貝,將所有的小于時(shí)間單位變化的部分設(shè)置成它的最小單位。例如,如果計(jì)算天數(shù),那么將小時(shí),分鐘,秒和毫秒設(shè)置成0。這種情況中,使用clear()方法將時(shí)間值設(shè)置稱他們各自的最小值。 3. 取出較早的日期,將你要計(jì)算的單位加1,重復(fù)直到兩個(gè)日期相等。你加1的次數(shù)就是答案??梢允褂胋efore()和after()方法,他們返回boolean值,來(lái)判斷是否一個(gè)日期在另一個(gè)日期之前或之后。 下面的類的方法用來(lái)計(jì)算天數(shù)和月數(shù)。
import java.util.*;
public class ElapsedTime {
public int getDays(GregorianCalendar g1, GregorianCalendar g2) { int elapsed = 0; GregorianCalendar gc1, gc2;
if (g2.after(g1)) { gc2 = (GregorianCalendar) g2.clone(); gc1 = (GregorianCalendar) g1.clone(); } else { gc2 = (GregorianCalendar) g1.clone(); gc1 = (GregorianCalendar) g2.clone(); }
gc1.clear(Calendar.MILLISECOND); gc1.clear(Calendar.SECOND); gc1.clear(Calendar.MINUTE); gc1.clear(Calendar.HOUR_OF_DAY);
gc2.clear(Calendar.MILLISECOND); gc2.clear(Calendar.SECOND); gc2.clear(Calendar.MINUTE); gc2.clear(Calendar.HOUR_OF_DAY);
while ( gc1.before(gc2) ) { gc1.add(Calendar.DATE, 1); elapsed++; } return elapsed; }
public int getMonths(GregorianCalendar g1, GregorianCalendar g2) { int elapsed = 0; GregorianCalendar gc1, gc2;
if (g2.after(g1)) { gc2 = (GregorianCalendar) g2.clone(); gc1 = (GregorianCalendar) g1.clone(); } else { gc2 = (GregorianCalendar) g1.clone(); gc1 = (GregorianCalendar) g2.clone(); }
gc1.clear(Calendar.MILLISECOND); gc1.clear(Calendar.SECOND); gc1.clear(Calendar.MINUTE); gc1.clear(Calendar.HOUR_OF_DAY); gc1.clear(Calendar.DATE);
gc2.clear(Calendar.MILLISECOND); gc2.clear(Calendar.SECOND); gc2.clear(Calendar.MINUTE); gc2.clear(Calendar.HOUR_OF_DAY); gc2.clear(Calendar.DATE);
while ( gc1.before(gc2) ) { gc1.add(Calendar.MONTH, 1); elapsed++; } return elapsed; } }
你可以在上面的類中補(bǔ)充另外的方法來(lái)處理小時(shí)和分鐘。同樣,計(jì)算時(shí)間段的算法能更高效一些,尤其是時(shí)間相隔很長(zhǎng)??墒?,作為介紹目的,這個(gè)算法有短小和簡(jiǎn)單的優(yōu)勢(shì)。 下面的例子使用ElapsedTime類來(lái)計(jì)算兩個(gè)日期之間的天使,而后是月數(shù):
import java.util.*;
public class Example { public static void main(String[] args) { GregorianCalendar gc1 = new GregorianCalendar(2001, Calendar.DECEMBER, 30); GregorianCalendar gc2 = new GregorianCalendar(2002, Calendar.FEBRUARY, 1);
ElapsedTime et = new ElapsedTime(); int days = et.getDays(gc1, gc2); int months = et.getMonths(gc1, gc2);
System.out.println("Days = " + days); System.out.println("Months = " + months); } }
當(dāng)計(jì)算時(shí),上面的程序可能有用,例如,最近的航班。它顯示下面的輸出:
Days = 33 Months = 2
(OK,關(guān)于航班的計(jì)算有些夸張;這個(gè)天數(shù)算法很適合像圖書(shū)館借書(shū)這樣的應(yīng)用,你看到了她怎樣工作) 告誡 在進(jìn)行時(shí)間工作時(shí)要謹(jǐn)慎:你看到的時(shí)間段的例子,你精確仔細(xì)的考慮非常重要。本文介紹了兩種通常計(jì)算時(shí)間段的想法,但是人們能想到的時(shí)間段的計(jì)算方法僅僅受到人類想象力的限制。 所以,當(dāng)寫一個(gè)Java程序的時(shí)候,確信你的精確度能讓使用和以來(lái)這些程序的人滿意。同樣,徹底的測(cè)試程序?qū)μ幚頃r(shí)間的程序非重重要。 總結(jié) 本文是在我的前一篇文章 Java時(shí)間計(jì)算介紹怎樣使用GregorianCalendar 和 DateFormat類處理時(shí)間問(wèn)題的基礎(chǔ)上的。你已經(jīng)看到了兩種方法來(lái)思考時(shí)間段問(wèn)題和兩種相應(yīng)的途徑使用Java來(lái)處理時(shí)間問(wèn)題。這里提供的信息,很基礎(chǔ),提供給你一個(gè)在Java中處理時(shí)間問(wèn)題的有力工具。
關(guān)于作者 Robert Nielsen是SCJP。他擁有碩士學(xué)位,專攻計(jì)算機(jī)教育,并且在計(jì)算機(jī)領(lǐng)域執(zhí)教多年。他也在各樣的雜志上發(fā)表過(guò)很多計(jì)算機(jī)相關(guān)的文章。 關(guān)于譯者 Cocia Lin(cocia@163.com)是程序員。它擁有學(xué)士學(xué)位,現(xiàn)在專攻Java相關(guān)技術(shù),剛剛開(kāi)始在計(jì)算機(jī)領(lǐng)域折騰。
|