Android时间处理详解

时间术语:

Greenwich:格林威治/格林尼治,是位于伦敦市中心东南部的一个区,1675国王查理二世在此建立了皇家格林威治天文台,1851年御用天文学家艾里在天文台设置了中星仪并确定了格林威治子午线,1884年在美国华盛顿特区举行的国际本初子午线大会上正式将此线定之为经度的起点。

GMT(Greenwich Mean Time):格林尼治标准时间/格林威治标准时间, 格林威治标准时间的正午是指当太阳横穿格林威治子午线(本初子午线)时(也就是在格林威治上空最高点时)的时间。由于地球在它的椭圆轨道里的运动速度不均匀,这个时刻可能与实际的太阳时有误差,最大误差达16分钟。由于地球每天的自转是有些不规则的,而且正在缓慢减速,因此格林威治时间已经不再被作为标准时间使用。现在的标准时间,是由原子钟报时的协调世界时(UTC)。

UTC(Coordinated Universal Time):协调世界时/世界协调时间/世界标准时间,UTC基于国际原子时,并通过不规则的加入闰秒来抵消地球自转变慢的影响,比GMT时间更加精确,也就是说一天不一定是86400秒,可能会根据情况加入一个闰秒。闰秒在必要的时候会被插入到UTC中,以保证协调世界时UTC与世界时UT1相差不超过0.9秒。UTC表示时间偏移量时,书写格式为±[hh]:[mm]或±[hh][mm]或±[hh]。UTC的0时区表示会在时间后面加一个”Z”,如“01:30Z”或“0130Z”表示0时区的1时30分,而此时的北京时间为“09:30 UTC+08:00”或“09:30 UTC+0800”,会比标准UTC时间早8个小时。

Epoch(Unix epoch):操作系统的“纪元时间”,为“1970-01-01T00:00:00Z”,即格林威治当地时间1970年1月1日0时0分0秒,至于为什么会选择这个时间说法不一,最好的解释是设计Unix/类Unix操作系统时为了方便决定采用的时间,并不是Unix/类Unix系统的诞生时间。

Unix time:Unix时间戳/POSIX time/Epoch time,定义为从“1970-01-01T00:00:00Z”开始所经过的秒数。由于不考虑闰秒,所以它不是线性的时间表示,也不是真正的UTC时间表示。

Java中的时间处理:

java.util.Date

用来表示一个特定时刻,精确到毫秒(1/1000秒)。所表示的时间总是UTC时间,不考虑系统的时区。由于该类“历史悠久”,很多方法已经被废弃了。如果想使用展示日期相关的方法,建议使用java.text.DateFormat类相关方法。如果想使用计算或划分日期的方法,建议使用java.util.Calendar类相关方法。

public Date()该构造器会根据系统当前时间(System.currentTimeMillis())构造时间。
public Date(long milliseconds)该构造器会使用从1970-01-01T00:00:00Z所经过的毫秒数构造时间,即Unix时间*1000。

其他构造器不建议使用,如public Date(int year, int month, int day) @deprecated 该构造器会使用GregorianCalendar(格里高利历/公历)默认时区来构造日期,参数中的year为从1900年后多少年,如果year为0则表示1900+0=1900年。month为0 - 11,day为1 - 31。其他类似构造器:public Date(int year, int month, int day, int hour, int minute)hour为0 - 23,minute为0 - 59。public Date(int year, int month, int day, int hour, int minute, int second)second为0 - 59。public Date(String string)虽然可以解析很多格式的时间表示字符串,但是也已不建议使用了。

Date类可以很方便的进行时间比较,如public boolean after(Date date)可以判断该时间是否在给定时间之后,public boolean before(Date date)可以判断该时间是否在给定时间之前,同时也为比较实现了compareToequals方法。可以通过public long getTime()方法拿到从1970-01-01T00:00:00Z所经过的毫秒数。

其它所有方法都已经废弃了,不建议使用。因此Date通常只作为存储时间类型的数据结构使用。

java.util.Calendar

用来让Date对象和一系列整型日历字段(如YEAR,MONTH)很方便地进行转换。Calendar是一个抽象类,其子类可以根据情况将Date解释成不同的日期形式,已知的也是我们最常用到的子类就是GregorianCalendar(格里高利历/公历)。
像其它的本地化敏感类一样,Calendar会提供getInstance一系列方法,返回基于系统设置的GregorianCalendar实例并将时间和日期初始化为当前的时间日期。
如果Calendar是宽松模式的,它可以接受比它所生成的日历字段范围更大范围内的值,如一个宽松的GregorianCalendar会把MONTH==JANUARY,DAY_OF_MONTH==32解释为2月1日,而非宽松的GregorianCalendar则会抛出字段越界异常。
Calendar使用两个参数定义了指定情况下的7 天制星期:firstDayOfWeek(每周的第一天是周几/周几作为每周的开始,通常为SUNDAY)和minimalDaysInFirstWeek(新年的第一个周所包含的最小天数,从1-7,如GregorianCalendar日历中该值为1,那么新年的第一个周就必须包含1月1日,而如果该值为7,则只有当1月1号那天的星期等于firstDayOfWeek时,1月1日所在的周才会最为新年的第一周,否则将作为上一年的最后一周,因此firstDayOfWeek和minimalDaysInFirstWeek共同决定了1月1日所在的那一周会不会成为新年的第一周)。

Calendar的常量:

  • JANUARY=0, FEBRUARY=1, ……UNDECIMBER=12。各个月份(MONTH)常量,从0开始,UNDECIMBER在GregorianCalendar中不会被用到,但在中国农历日历中会被用到(因为可能会多闰一个月,即农历一年可能有13个月)。
  • SUNDAY=1, MONDAY=2, SATURDAY=7。各个星期(DAY_OF_WEEK)常量,从1开始。

Calendar的字段:

  • ERA:纪年。如AD、BC
  • YEAR:年。如1970
  • MONTH:月。如1(FEBRUARY)
  • WEEK_OF_YEAR:当前年的周数。由getFirstDayOfWeek()getMinimalDaysInFirstWeek()决定。
  • WEEK_OF_MONTH:当前月的周数。由getFirstDayOfWeek()getMinimalDaysInFirstWeek()决定。
  • DATE:日。与DAY_OF_MONTH同义,指当月多少日/多少号。
  • DAY_OF_MONTH:日。与DATE同义。
  • DAY_OF_YEAR:当前年的总天数。
  • DAY_OF_WEEK:周几。取值为SUNDAY - SATURDAY。
  • DAY_OF_WEEK_IN_MONTH:当前月的第几周。与DAY_OF_WEEK字段一起使用时,就可以唯一地指定某月中的某一天。不会受getFirstDayOfWeek()getMinimalDaysInFirstWeek()影响。DAY_OF_MONTH 1-7总会对应DAY_OF_WEEK_IN_MONTH 1,也就是说每个月的1-7号总是当前月的第一周,2-8号是第二周,以此类推。DAY_OF_MONTH 0表示第一周的前一周,而负值表示当前月的倒数第几周,由于对齐方式和正向不一样,所以一个月的最后一个星期天被指定为DAY_OF_WEEK=SUNDAY, DAY_OF_WEEK_IN_MONTH=-1。
  • AM_PM:上午/下午。判断HOUR是指上午(AM)还是下午(PM)。
  • HOUR:时。12小时制的时。
  • HOUR_OF_DAY:时。24小时制的时。
  • MINUTE:分。
  • SECOND:秒。
  • MILLISECOND:毫秒。
  • ZONE_OFFSET:非夏令时时区偏移的毫秒数。相当于TimeZone#getRawOffset()
  • DST_OFFSET:夏令时时区偏移的毫秒数。相当于TimeZone#getDSTSavings()

Calendar的字段操作:

  • set(f, value)方法可以更改f字段的值为value,而且是马上更改内部字段的值,但是calendar的milliseconds值要在下次调用get()getTime()getTimeInMillis()方法时才会重新计算,因此多次调用set()方法不会触发多次不必要的计算。由于set()方法因为修改了字段的值,其他日历字段也可能会发生更改,这取决于日历字段、日历字段的值和日历系统。此外get(f)没有必要返回计算后的字段值,因为这些细节会由具体的Calendar决定,如GregorianCalendar最初被设置为1999年8月31日。调用set(Calendar.MONTH, Calendar.SEPTEMBER)将该日期设置为1999年9月31日。如果随后调用 getTime(),将解析为1999年10月1日(因为9月没有31日)的一个暂时内部表示。但是,在调用 getTime() 之前调用 set(Calendar.DAY_OF_MONTH, 30) 会将该日期设置为 1999年9月30日,因为在调用 set() 之后没有发生马上重新计算。
  • add(f, delta)方法可以给f字段的值设置delta偏移,相当于set(f, get(f) + delta)。为了防止add(f, delta)方法产生相应字段值地溢出,约定两个规则:
    规则一:如果设置了delta后f字段的值发生了溢出,则将该字段的值取模以调整回取值范围内,并且下一个更大的字段的值会相应的增加或减少。
    规则二:如果你想调整某个字段的值并且不希望比它更小的字段的值发生变化,很多情况下这是不可能的,因为更小字段的最大最小值可能已经改变了。
    如GregorianCalendar最初被设置为1999年8月31日。调用add(Calendar.MONTH, 13),由于8+13=21>12造成了MONTH字段的值越界,所以MONTH值对12取模为9,同时更大的字段YEAR需要增加为2000,由由于9月没有31日,所以将DAY_OF_MONTH调整为最接近的30,所以最终的时间为2000年9月30日。
    注意:add(f, delta)方法会立即重新计算calendar的milliseconds值和所有字段。
  • roll(f, delta)方法同样可以给f字段的值设置delta偏移,但不会对更大的字段产生影响。相当于“调用后更大字段的值不会发生改变”规则的add(f, delta)方法调用。
    注意:使用add(f, delta)roll(f, delta)方法时一定要谨慎,如1999年1月31日当用户按下一个月的按钮时最好的结果是1999年2月28日,如果继续按下一个月按钮,那显示1999年3月31日还是1999年3月28日。

Calendar的一些方法:

  • public int get(int field) 获取给定字段的值
  • public boolean after(Object calendar) 比较两个Calendar所持有的Date对象,不依赖Calendar时区。
  • public boolean before(Object calendar) 比较两个Calendar所持有的Date对象,不依赖Calendar时区。
  • public final void clear() 清除所有时间字段,标记为reset并清零。
  • public final void clear(int field) 清除某个字段,标记为reset并清零。
  • public long getTimeInMillis() 返回Calendar所表示的时间毫秒数(1970-01-01T00:00:00Z所经过的毫秒数),如果必要会重新计算时间。
  • public final Date getTime()getTimeInMillis()返回的时间包装成Date对象返回。
  • public final void set(int year, int month, int day)系列方法 其中month从0开始算起,所以month最好使用Calendar.JANUARY、Calendar.FEBRUARY等常量。
  • public final void setTime(Date date)
  • public void setTimeInMillis(long milliseconds)

Calendar的使用:

        Date date = new Date(946742340000L);// 2000-01-01 23:59:00
        Calendar calendar = Calendar.getInstance(Locale.getDefault());
        calendar.setTime(date);
        Log.e("time", calendar.get(Calendar.YEAR) + "-" +
                calendar.get(Calendar.MONTH) + "-" +
                calendar.get(Calendar.DAY_OF_MONTH));// 2000-0-1

注意:MONTH字段的值从0开始算起,因此get时要加一,set时要减一。

java.text.DateFormat

用来格式化/解析 日期和时间。DateFormat是一个抽象类,最常用的是它的子类SimpleDateFormat
可以根据想要格式化/解析的时Date还是Time还是DateTime的使用getDateInstance()getDateInstance(int style)getDateInstance(int style, Locale locale)getTimeInstance()getTimeInstance(int style)getTimeInstance(int style, Locale locale)getDateTimeInstance()getDateTimeInstance(int dateStyle, int timeStyle)getDateTimeInstance(int dateStyle, int timeStyle, Locale locale)不同的静态方法获取不同的DateFormat实例。
public final StringBuffer format(Object object, StringBuffer buffer, FieldPosition field)方法,可以将Date/Number日期类型的数据根据指定模式串格式化成想要显示的字符串。其中第一个参数必须是DateNumber对象。
public Date parse(String string)方法可以利用指定规则将字符串解析成Date对象。

java.text.SimpleDateFormat

以本地化敏感的方式格式化/解析时间数据,可以使用formatDate转成String,也可以使用parseString转成Date
格式化时间的time pattern语法:
在模式字符串中,任何A~Z,a~z的字符都是特殊字符,都会被特殊对待处理,而其它字符则会按其字面意思处理。每个ASCII字符的解释如下表,除了表中字符的其它ASCII字符作为保留字符备用,不建议使用。

符号 含义 类型 示例
D day in year(位于一年中的第几天) (Number) 189
E day of week(星期几) (Text) E/EE/EEE:Tue,EEEE:Tuesday,EEEEE:T
F day of week in month(当前月的第几周几) (Number) 2(七月的第二个周三)
G era designator(纪元标志) (Text) AD
H hour in day (24小时制的时,0-23) (Number) 0
K hour in am/pm (12小时制的时,0-11) (Number) 0
L stand-alone month(独立的月份) (Text) L:1,LL:01,LLL:Jan,LLLL:January,LLLLL:J
M month in year(一年中的几月) (Text) M:1,MM:01,MMM:Jan,MMMM:January,MMMMM:J
S fractional seconds(小数秒,不是毫秒) (Number) S:0,SSS:006,SSSSSS:006000
W week in month(当前月的第几周) (Number) 2
Z time zone(时区) (Time Zone) Z/ZZ/ZZZ:-0800,ZZZZ:GMT-08:00,ZZZZZ:-08:00
a am/pm marker(上下午标志) (Text) PM
c stand-alone day of week(独立的周几) (Text) c/cc/ccc:Tue,cccc:Tuesday,ccccc:T
d day in month(日) (Number) 10
h hour in am/pm(12小时制的时,1-12) (Number) 12
k hour in day(24小时制的时,1-24) (Number) 24
m minute in hour(分) (Number) 30
s second in minute(秒) (Number) 55
w week in year(当前年的第几周) (Number) 27
y year(年) (Number) yy:10,y/yyy/yyyy:2010
z time zone(时区) (Time Zone) z/zz/zzz:PST,zzzz:Pacific Standard Time
' escape for text(转义符) (Delimiter) 'Date=':Date=
'' single quote(单引号) (Literal) 'o''clock':o'clock


每个SimpleDateFormat实例都会有持有时区(如果你不指定会采用系统默认时区),因为Date是一个绝对的UTC时间,不会持有任何时区信息。
SimpleDateFormat不是线程安全的,所以最好每个线程都有自己的实例,不要多线程操作同一个SimpleDateFormat实例。
SimpleDateFormat可以很方便地将时间格式化成想要格式的字符串,如:

        Date date = new Date(946742340000L);// 2000-01-01 23:59:00
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.getDefault());
        Log.e("time", simpleDateFormat.format(date));// 星期六, 01 1月 2000 23:59:00 +0800

Android中的时间处理:

android.text.format.DateUtils

Android自己封装的工具类,用于方便地对时间数据格式化。因为是工具类,不需要实例化,直接使用其提供的静态方法即可,如:

  • public static CharSequence getRelativeTimeSpanString(long startTime) 获得相对时间的跨度,如”42 minutes ago”/”42 分钟前”、”In 42 minutes”/”42 分钟内”。更多相关方法像getRelativeTimeSpanString(long time, long now, long minResolution)getRelativeTimeSpanString(long time, long now, long minResolution, int flags)、“其中最小分辨率minResolution可以为0、DateUtils.MINUTE_IN_MILLIS、DateUtils.HOUR_IN_MILLIS、DateUtils.DAY_IN_MILLIS、DateUtils.WEEK_IN_MILLIS。
  • public static String formatElapsedTime(long elapsedSeconds) 格式化消耗的时间(即将秒数格式化成时分秒格式),格式如”MM:SS”或”H:MM:SS”,DateUtils.formatElapsedTime(60)会返回”01:00”。
  • public static final CharSequence formatSameDayTime(long then, long now, int dateStyle, int timeStyle) 判断then和now是否是同一天,如果是同一天会返回时间,不是同一天的话会返回日期。如DateUtils.formatSameDayTime(1440604740000L, 1472190629000L, SimpleDateFormat.FULL, SimpleDateFormat.FULL)会返回”2015年8月26日星期三”。
  • public static boolean isToday(long when) 如果when是今天的话返回true,否则返回false。
  • public static String formatDateRange(Context context, long startMillis, long endMillis, int flags) 将两个时间格式化成时间段格式,如DateUtils.formatDateRange(this, 1470067140000L, 1472190629000L, DateUtils.FORMAT_SHOW_DATE|DateUtils.FORMAT_SHOW_TIME)会返回”8 月 1 日 23:59 – 8 月 26 日 13:50”

看起来除了isToday方法其它也并无太多用处,毕竟业务逻辑不同要显示的字符格式也不同。
同时Android还提供了android.text.format.Time类,作用和java.util.Calendar一样,不过由于存在几个问题也被废弃了,建议使用Java自己的java.util.Calendar

你可能感兴趣的:(Android)