在開發Android的過程中,出現過幾次由於日期時間導致的問題,而且主要是由於時區的原因導致,所以一直想總結一下,形成一個良好的開發規范。
一、Unix時間戳
Unix時間戳(Unix timestamp),或稱Unix時間(Unix time)、POSIX時間(POSIX time),是一種時間表示方法,定義為從格林威治時間1970年01月01日00時00分00秒起至現在的總秒數。Unix時間戳不僅被使用在Unix系統、類Unix系統中,也在許多其他操作系統中被廣泛采用。
二、關於時間標准和時區
1、原子時:International Atomic Time (IAT)
又稱國際原子時,是一種通過原子鐘得到的時間標准,原子鐘是世界上已知最准確的時間測量和頻率標准,原子鐘在 37 億年的誤差不超過 1 秒。
2、世界時:Universal Time(UT)
是一種以格林尼治子夜起算的平太陽時。世界時是以地球自轉為基准得到的時間尺度,其精度受到地球自轉不均勻變化和極移的影響,為了解決這種影響,1955年國際天文聯合會定義了UT0、UT1和UT2三個系統,他們三者之間是遞進的關系,後者都是發現前者的一些度量差別,通過修正得到更均勻的時標。
在格林尼治子午線上的平太陽時稱為世界時(UT0),又叫格林尼治平時(GMT)。格林尼治標准時間(舊譯格林尼治平均時間或格林威治標准時間,英語:Greenwich Mean Time,GMT)是指位於英國倫敦郊區的皇家格林尼治天文台的標准時間,因為本初子午線被定義在通過那裡的經線。自1924年2月5日開始,格林尼治天文台每隔一小時會向全世界發放調時信息。
2、協調世界時:Coordinated Universal Time(UTC)
又稱世界標准時間、世界統一時間。是經過平均太陽時(以格林威治時間GMT為准)、地軸運動修正後的新時標以及以“秒”為單位的國際原子時所綜合精算而成的時間,計算過程相當嚴謹精密,因此若以“世界標准時間”的角度來說,UTC比GMT更精准。其誤差值必須保持在0.9秒以內,若大於0.9秒則由位於巴黎的國際地球自轉事務中央局發布閏秒,使UTC與地球自轉周期一致。
基本上UTC的本質強調的是比GMT更精確的世界時間標准,UTC中多一個閏秒的調整,以確保協調世界時(UTC)與格林尼治平時(GMT)相差不會超過0.9秒,並在有需要的時候在協調世界時(UTC)內加上正或負閏秒。
UTC被應用在大多數的計算機以及網絡標准中。
3、夏令時與冬令時:Daylight Saving Time(DST)
又稱“日光節約時制”和“夏令時間”,是一種為節約能源而人為規定地方時間的制度,在這一制度實行期間所采用的統一時間稱為“夏令時間”。一般在天亮早的夏季人為將時間提前一小時,可以使人早起早睡,減少照明量,以充分利用光照資源,從而節約照明用電。各個采納夏時制的國家具體規定不同。目前全世界有近110個國家每年要實行夏令時。自2011年3月27日開始俄羅斯永久使用夏令時,把時間撥快一小時,不再調回。
簡單來說,使用夏令時和冬令時即在夏季將時間撥快一個小時,等到冬季再將時間撥慢一個小時。
4、CST
CST同時代表澳大利亞、美國、中國、古巴四個國家的標准時間,時區分別為:
澳洲中部時間,Central Standard Time (Australia) UT+9:30
中部標准時區(北美洲),Central Standard Time (North America) UT-6:00
北京時間,China Standard Time UT+8:00
古巴標准時間,Cuba Standard Time UT-4:00
在開發時我們需要注意不同國家的時區,並且UTC和GMT在計算機系統中使用並無明顯差別,可視為等同。
二、規范
在實際開發中,當時間用於顯示時,非特殊要求下一般使用系統默認的時區時間作為顯示時間。將時間做為數據存儲或傳遞給其他系統時(特別是跨平台調用),則最好使用標准的UTC/GMT時間(後面統稱GMT),除非事先約定或標識了時間的類型。
三、在Android中需要特別注意的事項
1、Android中表示日期時間的類型,有Date、Calendar,他們在沒有顯示設置其時區時,取到的當前時間均為系統默認時區的時間,即使給定一個時間,同樣是按系統默認時區來換算時間,所以說他們都是與時區相關的。
2、SimpleDateFormat對象本身也是跟時區相關。
當使用parse將一個字符串格式的日期轉換為Date對象,或者將一個Date對象轉換為字符串日期時,這個字符串日期的時區以SimpleDateFormat關聯的時區為准,如果通過setTimeZone修改了時區,則這個字符串日期以修改後的時區為准。例如:
// 2013-1-31 22:17:14
Date date = new Date(1359641834000L);
System.out.println(date);
String dateStr = "2013-1-31 22:17:14";
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
try
{
// 對於已經設定為GMT時間標准的dateFormat來說,一切需要他轉換的字符串日期都是GMT標准時間,轉換後返回的Date由於默認遵守系統默認時區,所以轉換給Date的日期需要+8(例如北京標准時區),也就是時區與標准不同導致的時差。
Date dateTmp = dateFormat.parse(dateStr);
System.out.println(dateTmp);
}
catch (ParseException e)
{
e.printStackTrace();
}
// Date還是按系統默認時區,而format格式化處來的字符串是GMT,所以要-8。
String dateStrTmp = dateFormat.format(date);
System.out.println(dateStrTmp);
輸出結果為:image
3、Calendar在不手動設置時區時,是與系統默認時區相關的。在手動修改時區後,不能使用calendar.getTime方法來直接獲取Date日期,因為此時的日期與setTime時的值相同,想要正確獲取修改時區後的時間,應該通過calendar的get方法。例如:
Date date = new Date(1359641834000L);
System.out.println(date);
Calendar calendar = Calendar.getInstance();
calendar.setTimeZone(TimeZone.getTimeZone("GMT"));
// 或者可以 Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
calendar.setTime(date);
System.out.println(calendar.get(Calendar.HOUR_OF_DAY) + ":" + calendar.get(Calendar.MINUTE));
Calendar calendar2 = Calendar.getInstance();
calendar2.set(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH), calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE), calendar.get(Calendar.SECOND));
System.out.println(calendar.getTime());
System.out.println(calendar2.getTime());
輸入結果為:image
4、TimeZone
在開發中,我們可以通過TimeZone對象獲取關於系統默認時區及其相關的詳細信息。
四、Android關於日期的工具類
/**
* Copyright (C) 2011, BHJ
* All rights reserved.
*
* 文件名稱:
* 文件標識:
* 文件摘要:
*
* 當前版本:
* 作 者:
* 完成日期:2013-12-20
*
* 取代版本:
* 修改時間:
* 修 改 人:
* 修改摘要:
*/
package com.bhj.timetest;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
/**
* 日期工具類(未特別說明的均為系統默認時區下的時間)
* */
public class DateUtil
{
/** 1s==1000ms */
private final static int TIME_MILLISECONDS = 1000;
/** 時間中的分、秒最大值均為60 */
private final static int TIME_NUMBERS = 60;
/** 時間中的小時最大值 */
private final static int TIME_HOURSES = 24;
/** 格式化日期的標准字符串 */
private final static String FORMAT = "yyyy-MM-dd HH:mm:ss";
/**
* 獲取時區信息
* */
public static TimeZone getTimeZone()
{
return TimeZone.getDefault();
}
/**
* 將日期字符串轉換為Date對象
* @param date 日期字符串,必須為"yyyy-MM-dd HH:mm:ss"
* @return 日期字符串的Date對象表達形式
* */
public static Date parseDate(String date)
{
return parseDate(date, FORMAT);
}
/**
* 將日期字符串轉換為Date對象
* @param date 日期字符串,必須為"yyyy-MM-dd HH:mm:ss"
* @param format 格式化字符串
* @return 日期字符串的Date對象表達形式
* */
public static Date parseDate(String date, String format)
{
Date dt = null;
SimpleDateFormat dateFormat = new SimpleDateFormat(format);
try
{
dt = dateFormat.parse(date);
}
catch(ParseException e)
{
e.printStackTrace();
}
return dt;
}
/**
* 將Date對象轉換為指定格式的字符串
* @param date Date對象
* @return Date對象的字符串表達形式"yyyy-MM-dd HH:mm:ss"
* */
public static String formatDate(Date date)
{
return formatDate(date, FORMAT);
}
/**
* 將Date對象轉換為指定格式的字符串
* @param date Date對象
* @param String format 格式化字符串
* @return Date對象的字符串表達形式
* */
public static String formatDate(Date date, String format)
{
SimpleDateFormat dateFormat = new SimpleDateFormat(format);
return dateFormat.format(date);
}
/**
* 格式化日期
* @param long unixTime unix時間戳
* @return 日期字符串"yyyy-MM-dd HH:mm:ss"
* */
public static String formatUnixTime(long unixTime)
{
return formatUnixTime(unixTime, FORMAT);
}
/**
* 格式化日期
* @param long unixTime unix時間戳
* @param String format 格式化字符串
* @return 日期字符串
* */
public static String formatUnixTime(long unixTime, String format)
{
SimpleDateFormat dateFormat = new SimpleDateFormat(format);
return dateFormat.format(unixTime);
}
/**
* 將GMT日期格式化為系統默認時區的日期字符串表達形式
* @param gmtUnixTime GTM時間戳
* @return 日期字符串"yyyy-MM-dd HH:mm:ss"
* */
public static String formatGMTUnixTime(long gmtUnixTime)
{
return formatGMTUnixTime(gmtUnixTime, FORMAT);
}
/**
* 將GMT日期格式化為系統默認時區的日期字符串表達形式
* @param gmtUnixTime GTM時間戳
* @param format 格式化字符串
* @return 日期字符串"yyyy-MM-dd HH:mm:ss"
* */
public static String formatGMTUnixTime(long gmtUnixTime, String format)
{
SimpleDateFormat dateFormat = new SimpleDateFormat(format);
return dateFormat.format(gmtUnixTime + TimeZone.getDefault().getRawOffset());
}
/**
* 獲取時間戳的Date表示形式
* @param unixTime unix時間戳
* @return Date對象
* */
public static Date getDate(long unixTime)
{
return new Date(unixTime);
}
/**
* 獲取GMT時間戳的Date表示形式(轉換為Date表示形式後,為系統默認時區下的時間)
* @param gmtUnixTime GMT Unix時間戳
* @return Date對象
* */
public static Date getGMTDate(long gmtUnixTime)
{
return new Date(gmtUnixTime + TimeZone.getDefault().getRawOffset());
}
/**
* 將系統默認時區的Unix時間戳轉換為GMT Unix時間戳
* @param unixTime unix時間戳
* @return GMT Unix時間戳
* */
public static long getGMTUnixTime(long unixTime)
{
return unixTime - TimeZone.getDefault().getRawOffset();
}
/**
* 將GMT Unix時間戳轉換為系統默認時區的Unix時間戳
* @param gmtUnixTime GMT Unix時間戳
* @return 系統默認時區的Unix時間戳
* */
public static long getCurrentTimeZoneUnixTime(long gmtUnixTime)
{
return gmtUnixTime + TimeZone.getDefault().getRawOffset();
}
/**
* 獲取當前時間的GMT Unix時間戳
* @return 當前的GMT Unix時間戳
* */
public static long getGMTUnixTimeByCalendar()
{
Calendar calendar = Calendar.getInstance();
// 獲取當前時區下日期時間對應的時間戳
long unixTime = calendar.getTimeInMillis();
// 獲取標准格林尼治時間下日期時間對應的時間戳
long unixTimeGMT = unixTime - TimeZone.getDefault().getRawOffset();
return unixTimeGMT;
}
/**
* 獲取當前時間的Unix時間戳
* @return 當前的Unix時間戳
* */
public static long getUnixTimeByCalendar()
{
Calendar calendar = Calendar.getInstance();
// 獲取當前時區下日期時間對應的時間戳
long unixTime = calendar.getTimeInMillis();
return unixTime;
}
/**
* 獲取更改時區後的時間
* @param date 時間
* @param oldZone 舊時區
* @param newZone 新時區
* @return 時間
*/
public static Date changeTimeZone(Date date, TimeZone oldZone, TimeZone newZone)
{
Date dateTmp = null;
if (date != null)
{
int timeOffset = oldZone.getRawOffset() - newZone.getRawOffset();
dateTmp = new Date(date.getTime() - timeOffset);
}
return dateTmp;
}
/**
* 將總秒數轉換為時分秒表達形式
* @param seconds 任意秒數
* @return %s小時%s分%s秒
*/
public static String formatTime(long seconds)
{
long hh = seconds / TIME_NUMBERS / TIME_NUMBERS;
long mm = (seconds - hh * TIME_NUMBERS * TIME_NUMBERS) > 0 ? (seconds - hh * TIME_NUMBERS * TIME_NUMBERS) / TIME_NUMBERS : 0;
long ss = seconds < TIME_NUMBERS ? seconds : seconds % TIME_NUMBERS;
return (hh == 0 ? "" : (hh < 10 ? "0" + hh : hh) + "小時")
+ (mm == 0 ? "" : (mm < 10 ? "0" + mm : mm) + "分")
+ (ss == 0 ? "" : (ss < 10 ? "0" + ss : ss) + "秒");
}
/**
* 獲取當前時間距離指定日期時差的大致表達形式
* @param long date 日期
* @return 時差的大致表達形式
* */
public static String getDiffTime(long date)
{
String strTime = "很久很久以前";
long time = Math.abs(new Date().getTime() - date);
// 一分鐘以內
if(time < TIME_NUMBERS * TIME_MILLISECONDS)
{
strTime = "剛剛";
}
else
{
int min = (int)(time / TIME_MILLISECONDS / TIME_NUMBERS);
if(min < TIME_NUMBERS)
{
if(min < 15)
{
strTime = "一刻鐘前";
}
else if(min < 30)
{
strTime = "半小時前";
}
else
{
strTime = "1小時前";
}
}
else
{
int hh = min / TIME_NUMBERS;
if(hh < TIME_HOURSES)
{
strTime = hh + "小時前";
}
else
{
int days = hh / TIME_HOURSES;
if(days <= 6)
{
strTime = days + "天前";
}
else
{
int weeks = days / 7;
if(weeks < 3)
{
strTime = weeks + "周前";
}
}
}
}
}
return strTime;
}
}