热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

☕【Java深层系列】「技术盲区」让我们一起完全吃透针对于时间和日期相关的API指南

java,深层,系列,技术,盲区,让我,们,一起,完全,吃透,针对,于,时间,和,日期,相

技术简介

java中的日期处理一直是个问题,没有很好的方式去处理,所以才有第三方框架的位置比如joda。文章主要对java日期处理的详解,用1.8可以不用joda。

时间概念

首先我们对一些基本的概念做一些介绍,其中可以将GMT和UTC表示时刻大小等同。

UT时间

UT反应了地球自转的平均速度。是通过观测星星来测量的。

UTC

UTC是用原子钟时间做参考,但保持和UT1在0.9秒内的时间,也就是说定时调整。

目前采用的时间标准是世界协调时UTC(Universal Time Coordinated)。如果计算机不联网即使再精确也是不准的,因为UTC会进行调整,而且一般走的时间也是不精确的。

NTP

现在计算机一般用的网络时间协议NTP(Network Time Protocol)是用于互联网中时间同步的标准互联网协议。用途是把计算机的时间同步到某些时间标准。

GMT(UT1)

GMT是完全符合地球自转的时间,也被称为UT1,格林尼治标准时间被用作英国的民用时间,或UTC。GMT被称为“UT1”,它直接对应于地球的自转,并受到该自转轻微不规则的影响。正是UT1和UTC之间的差异通过应用闰秒保持>低于0.9秒。

ISO 8601

一种时间交换的国际格式。有些接口调用表示UTC/GMT时间的时候用"yyyy-MM-dd'T'HH:mm:ss'Z'"格式显示。带毫秒格式"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"。

joda中实现如下
// Alternate ISO 8601 format without fractional seconds private static final String ALTERNATIVE_ISO8601_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'"; private static DateFormat getAlternativeIso8601DateFormat() { SimpleDateFormat df = new SimpleDateFormat(ALTERNATIVE_ISO8601_DATE_FORMAT, Locale.US); df.setTimeZone(new SimpleTimeZone(0, "GMT")); return df; } 

RFC 822

STANDARD FOR THE FORMAT OF ARPA INTERNET TEXT MESSAGES

其中ARPA网络其实就是互联网的前身。

有些地方会用RFC 822里的时间格式,格式如下

 date-time = [ day "," ] date time ; dd mm yy ; hh:mm:ss zzz 
第二个相当于现在格式
"EEE, dd MMM yyyy HH:mm:ss z" 

有些头设置采用该格式。

joda中实现如下

// RFC 822 Date Format private static final String RFC822_DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss z"; private static DateFormat getRfc822DateFormat() { SimpleDateFormat rfc822DateFormat = new SimpleDateFormat(RFC822_DATE_FORMAT, Locale.US); rfc822DateFormat.setTimeZone(new SimpleTimeZone(0, "GMT")); return rfc822DateFormat; } 

创建SimpleDateFormat的Locale.US可以决定格式字符串某些字符的代替用哪个语言,比如EEE等。

SimpleDateFormat df1=new SimpleDateFormat("GGGG yyyy/MMMM/dd HH:mm:ss EEE aaa zzzz",Locale.CHINA); SimpleDateFormat df2=new SimpleDateFormat("GGGG yyyy/MMMM/dd HH:mm:ss EEE aaa zzzz",Locale.US); //公元 2016/三月/27 23:32:10 星期日 下午 中国标准时间 //AD 2016/March/27 23:32:10 Sun PM China Standard Time 

gregorian Calendar, julian Calendar:这是两种历法,我们一般用的通用的gregorian Calendar。

jdk1.8之前

主要的类有记录时间戳的Date,时间和日期进行转换的Calendar,用来格式化和解析时间字符串的DateFormat

java.util.Date

使用前要注意时间表示的规则。

还有这个类有很多过期方法不推荐使用,很多已经被Calendar代替。

构造方法

这个类代表某个时刻的毫秒值,既然是毫秒值也就说需要有一个参考值。

在接受或返回年、月、日期、小时、分钟和秒值的所有类日期方法中,使用以下表示形式:

年份y由整数y-1900表示。一个月由0到11的整数表示;0是一月,1是二月,依此类推;因此,11月是12月。日期(月的某一天)通常由1到31之间的整数表示。小时由0到23之间的整数表示。因此,从午夜到凌晨1点的时间是0小时,从中午到下午1点的时间是12小时。一分钟通常由0到59之间的整数表示。第二个由0到61之间的整数表示;值60和61仅在闰秒内出现,甚至仅在实际正确跟踪闰秒的Java实现中出现。由于目前引入闰秒的方式,在同一分钟内出现两个闰秒的可能性极低,但本规范遵循ISO C的日期和时间约定。

当我们创建一个Date的时候获取的是哪一个毫秒值?

public Date() { this(System.currentTimeMillis()); } public Date(long date) { fastTime = date; } 

System.currentTimeMillis()是本地方法,the difference, measured in milliseconds, between the current time and midnight, January 1, 1970 UTC。

这个可能会因为操作系统的时间而不准。有些操作系统不一定是用毫秒表示的。这个时间都是用的UTC时间,不和时区有关的,这个无关的意思是同一时刻每个时区下获得的值应该是一致的,可以简单用程序验证一下获取的时间表达内容。

long time = System.currentTimeMillis(); System.out.println(time=(time/1000)); System.out.println("秒:"+ time%60); System.out.println(time=(time/60)); System.out.println("分钟:"+time%60); System.out.println(time=(time/60)); System.out.println("小时:"+time%24); 

可以理解成和UTC的1970年1月1日零点的差值。而fastTime就是Date类保存这个时刻的变量。

成员变量

Date对象打印出来是本地时间,而构造方法是没有时区体现的。那么哪里体现了时区呢?

下面是Date的成员变量

gcal

获取的是以下的对象。其中并没有自定义字段。可以说只是一个gregorian(公历)时间工厂获取CalendarDate的子类。

jcal

在以下方法中用到

private static final BaseCalendar getCalendarSystem(BaseCalendar.Date cdate) { if (jcal == null) { return gcal; } if (cdate.getEra() != null) { return jcal; } return gcal; } synchronized private static final BaseCalendar getJulianCalendar() { if (jcal == null) { jcal = (BaseCalendar) CalendarSystem.forName("julian"); } return jcal; } 

当时间戳在以下情况下用儒略历,并且,在用到的时候会自动设置儒略历,所以在clone的时候也没有这个参数。所以这个可以忽略。

 private static final BaseCalendar getCalendarSystem(int year) { if (year >= 1582) { return gcal; } return getJulianCalendar(); } private static final BaseCalendar getCalendarSystem(long utc) { // Quickly check if the time stamp given by `utc' is the Epoch // or later. If it's before 1970, we convert the cutover to // local time to compare. if (utc >= 0 || utc >= GregorianCalendar.DEFAULT_GREGORIAN_CUTOVER - TimeZone.getDefaultRef().getOffset(utc)) { return gcal; } return getJulianCalendar(); } 

fastTime

保存了一个时间戳表示时刻。最重要的参数。创建Date就是对这个值的赋值。

cdate

保存了时间相关内容,包括时区,语言等

 public static final int FIELD_UNDEFINED = -2147483648; public static final long TIME_UNDEFINED = -9223372036854775808L; private Era era; private int year; private int month; private int dayOfMonth; private int dayOfWeek; private boolean leapYear; private int hours; private int minutes; private int seconds; private int millis; private long fraction; private boolean normalized; private TimeZone zoneinfo; private int zoneOffset; private int daylightSaving; private boolean forceStandardTime; private Locale locale; 
defalutCenturyStart

这个值可以忽略,在过期方法中用到。

@Deprecated public static long parse(String s) { ... ... // Parse 2-digit years within the correct default century. if (year <100) { synchronized (Date.class) { if (defaultCenturyStart == 0) { defaultCenturyStart = gcal.getCalendarDate().getYear() - 80; } } year += (defaultCenturyStart / 100) * 100; if (year 

serialVersionUID

验证版本一致性的UID

wtb

保存toString格式化用到的值

ttb

保存toString 格式化用到的值

主要方法

java.util.Calendar

主要也是其中保存的毫秒值time字段,下面是我们常用的方法,用了默认的时区和区域语言:

public static Calendar getInstance() { return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT)); } 

国内环境默认GregorianCalendar,但是TH-th用的BuddhistCalendar等 一些坑:

set(int,int,int,int,int,int)方法

方法不能设置毫秒值,所以当用getInstance后即使用设置相同的值,最后毫秒值也是不一致的。所以如果有需要,将MILLISECOND清零。

set,add,get,roll

set方法不会马上计算时间,指是修改了对应的成员变量,只有get()、getTime()、getTimeInMillis()、add() 或 roll()的时候才会做调整

 //2000-8-31 Calendar cal1 = Calendar.getInstance(); cal1.set(2000, 7, 31, 0, 0 , 0); //应该是 2000-9-31,也就是 2000-10-1 cal1.set(Calendar.MONTH, Calendar.SEPTEMBER); //如果 Calendar 转化到 2000-10-1,那么现在的结果就该是 2000-10-30 cal1.set(Calendar.DAY_OF_MONTH, 30); //输出的是2000-9-30,说明 Calendar 不是马上就刷新其内部的记录 System.out.println(cal1.getTime()); 

也就是说多次设置的时候如果中间有需要调整的时间,但是实际是不会做调整的。所以尽量将无法确定的设置之后不要再进行其他调整,防止最后实际值与正常值不准。

add方法会马上做时间修改

roll与add类似,但是roll不会修改更大的字段的值。

java.text.SimpleDateFormat

创建设置pattern字符串,可以表示的格式如下:

日期格式是不同步的。建议为每个线程创建独立的格式实例。如果多个线程同时访问一个格式,则它必须是外部同步的。

SimpleDateFormat 是线程不安全的类,其父类维护了一个Calendar,调用相关方法有可能会修改Calendar。一般不要定义为static变量,如果定义为 static,必须加锁,或者使用 DateUtils 工具类。 正例:注意线程安全,使用 DateUtils。org.apache.commons.lang.time.DateUtils,也推荐如下处理:

private static final ThreadLocal df = new ThreadLocal() { @Override protected DateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd"); } }; 

java.sql.Date/Time/Timestamp

这几个类都继承了java.util.Date。

相当于将java.util.Date分开表示了。Date表示年月日等信息。Time表示时分秒等信息。Timestamp多维护了纳秒,可以表示纳秒。

如果是 JDK8 的应用,可以使用 Instant 代替 Date,LocalDateTime 代替 Calendar, DateTimeFormatter 代替SimpleDateFormat,官方给出的解释:simple beautiful strong immutable thread-safe。

jdk1.8的时间类

1.8增加了新的date-time包,遵循JSR310。核心代码主要放在java.time包下。默认的日历系统用的ISO-8601(基于格里高利历)。 java.time下主要内容包括:

java.time -主要包括,日期,时间,日期时间,时刻,期间,和时钟相关的类。

  • java.time.chrono -其他非ISO标准的日历系统可以用java.time.chrono,里面已经定义了一部分年表,你也可以自定义。
  • java.time.format -格式化和解析日期时间的类
  • java.time.temporal -扩展API,主要是提供给写框架和写库的人,允许日期时间相互操作,访问,和调整。字段和单位在这个包下定义。
  • java.time.zone -定义了时区,相对于时区的偏移量,时区规则等。

该包的API提供了大量相关的方法,这些方法一般有一致的方法前缀:

  • of:静态工厂方法。
  • parse:静态工厂方法,关注于解析。
  • get:获取某些东西的值。
  • is:检查某些东西的是否是true。
  • with:不可变的setter等价物。
  • plus:加一些量到某个对象。
  • minus:从某个对象减去一些量。
  • to:转换到另一个类型。
  • at:把这个对象与另一个对象组合起来,例如: date.atTime(time)。

相互转化和Instant

可以看到老的时间日期类里面都有了Instant的转化。Instant可以说是新旧转换的中转站。Instant主要维护了秒和纳秒字段,可以表示纳秒范围。当然不支持的话会抛出异常。主要还是java.util.Date转换成新的时间类。

Clock

提供了访问当前时间的方法,也可以获取当前Instant。Clock是持有时区或者时区偏移量的。如果只是获取当前时间戳,推荐还是用System.currentTimeMillis()

ZoneId/ZoneOffset/ZoneRules

zone id 主要包括两个方面,一个是相对于对于UTC/Greenwich的固定偏移量相当于一个大时区,另一个是时区内有特殊的相对于UTC/Greenwich偏移量的地区。通常固定偏移量部分可以用ZoneOffset表示,用normalized()判断是否可以用ZoneOffset表示。判断主要用到了时区规则ZoneRules。时区的真正规则定义在ZoneRules中,定义了什么时候多少偏移量。使用这种方式是因为ID是固定不变的,但是规则是政府定义并且经常变动。

LocalDateTime/LocalTime/LocalDate/ZoneDateTime

LocalDateTIme/LocalTime/LocalDate都是没有时区概念的。这句话并不是 说不能根据时区获取时间,而是因为这些类不持有表示时区的变量。而 ZoneDateTime持有时区和偏移量变量。

这些类都可以对时间进行修改其实都是生成新对象。所以这里的时间类都是天然支持多线程的。

这些时间类中都提供了获取时间对象,修改时间获取新的时间对象,格式化时间等。

注意点

LocaDateTime的atZone是调整本地时间的时区的。并不会改变时间。要使用其他时间需要获取的LocalDateTime.now的时候的就要传入时区变量。

DateTimeFormatter

时间对象进行格式化时间的需要用到格式化和解析日期和时间的时候需要用到DateTimeFormatter。

扩展及思考

用SimpleDateFormat格式化的时候不要用12小时制即hh,因为很容易导致上午下午不分,比如“2017-01-01 00:00:00“可能就变显示成”2017-01-01 12:00:00” ::符号

LocalDateTime的方法
public static LocalDateTime parse(CharSequence text, DateTimeFormatter formatter) { Objects.requireNonNull(formatter, "formatter"); return formatter.parse(text, LocalDateTime::from); } 
parse调用的方法是
public  T parse(CharSequence text, TemporalQuery query) { ... ... } 
LocalDateTime::from调用的方法是
public static LocalDateTime from(TemporalAccessor temporal) { .... ... } 

其中temporal是LocalDateTime的接口

这里其实大家都有一个疑问就是LocalDateTime::from到底代表什么意思。

LocalDateTime::from
//与下列表示相同 x -> LocalDateTime.from(x) //相当于 new TemporalQuery(){ @Override public LocalDateTime queryFrom(TemporalAccessor temporal) { return LocalDateTime.from(temporal); } }; 

推荐阅读
  • 深入剖析Java中SimpleDateFormat在多线程环境下的潜在风险与解决方案
    深入剖析Java中SimpleDateFormat在多线程环境下的潜在风险与解决方案 ... [详细]
  • 技术分享:使用 Flask、AngularJS 和 Jinja2 构建高效前后端交互系统
    技术分享:使用 Flask、AngularJS 和 Jinja2 构建高效前后端交互系统 ... [详细]
  • 使用 ListView 浏览安卓系统中的回收站文件 ... [详细]
  • 本文详细介绍了在 Android 7.1 系统中调整屏幕分辨率和默认音量设置的方法。针对系统默认音量过大的问题,提供了具体的步骤来降低系统、铃声、媒体和闹钟的默认音量,以提升用户体验。此外,还涵盖了如何通过系统设置或使用第三方工具来优化屏幕分辨率,确保设备显示效果更加清晰和流畅。 ... [详细]
  • Hyperledger Fabric 1.4 节点 SDK 快速入门指南
    本文将详细介绍如何利用 Hyperledger Fabric 1.4 的 Node.js SDK 开发应用程序。通过最新版本的 Fabric Node.js SDK,开发者可以更高效地构建和部署基于区块链的应用,实现数据的安全共享和交易处理。文章将涵盖环境配置、SDK 安装、示例代码以及常见问题的解决方法,帮助读者快速上手并掌握核心功能。 ... [详细]
  • Silverlight 实战指南:深入解析用户提交数据的验证与捕获机制
    本文深入探讨了Silverlight中用户提交数据的验证与捕获机制,详细分析了四种主要的验证方法:基本异常处理、DataAnnotation注解、IDataErrorInfo客户端同步验证以及自定义验证策略。通过实例解析,帮助开发者更好地理解和应用这些机制,提升应用程序的数据处理能力和用户体验。 ... [详细]
  • OpenAI首席执行官Sam Altman展望:人工智能的未来发展方向与挑战
    OpenAI首席执行官Sam Altman展望:人工智能的未来发展方向与挑战 ... [详细]
  • 本文详细解析了使用C++实现的键盘输入记录程序的源代码,该程序在Windows应用程序开发中具有很高的实用价值。键盘记录功能不仅在远程控制软件中广泛应用,还为开发者提供了强大的调试和监控工具。通过具体实例,本文深入探讨了C++键盘记录程序的设计与实现,适合需要相关技术的开发者参考。 ... [详细]
  • 在对WordPress Duplicator插件0.4.4版本的安全评估中,发现其存在跨站脚本(XSS)攻击漏洞。此漏洞可能被利用进行恶意操作,建议用户及时更新至最新版本以确保系统安全。测试方法仅限于安全研究和教学目的,使用时需自行承担风险。漏洞编号:HTB23162。 ... [详细]
  • ### 优化后的摘要本学习指南旨在帮助读者全面掌握 Bootstrap 前端框架的核心知识点与实战技巧。内容涵盖基础入门、核心功能和高级应用。第一章通过一个简单的“Hello World”示例,介绍 Bootstrap 的基本用法和快速上手方法。第二章深入探讨 Bootstrap 与 JSP 集成的细节,揭示两者结合的优势和应用场景。第三章则进一步讲解 Bootstrap 的高级特性,如响应式设计和组件定制,为开发者提供全方位的技术支持。 ... [详细]
  • C++ 异步编程中获取线程执行结果的方法与技巧及其在前端开发中的应用探讨
    本文探讨了C++异步编程中获取线程执行结果的方法与技巧,并深入分析了这些技术在前端开发中的应用。通过对比不同的异步编程模型,本文详细介绍了如何高效地处理多线程任务,确保程序的稳定性和性能。同时,文章还结合实际案例,展示了这些方法在前端异步编程中的具体实现和优化策略。 ... [详细]
  • 使用Maven JAR插件将单个或多个文件及其依赖项合并为一个可引用的JAR包
    本文介绍了如何利用Maven中的maven-assembly-plugin插件将单个或多个Java文件及其依赖项打包成一个可引用的JAR文件。首先,需要创建一个新的Maven项目,并将待打包的Java文件复制到该项目中。通过配置maven-assembly-plugin,可以实现将所有文件及其依赖项合并为一个独立的JAR包,方便在其他项目中引用和使用。此外,该方法还支持自定义装配描述符,以满足不同场景下的需求。 ... [详细]
  • 使用 `git stash` 可以将当前未提交的修改保存到一个临时存储区,以便在后续恢复工作目录时使用。例如,在处理中间状态时,可以通过 `git stash` 命令将当前的所有未提交更改推送到一个新的储藏中,从而保持工作目录的整洁。此外,本文还将详细介绍如何解决 `git stash pop` 时可能出现的冲突问题,帮助用户高效地管理代码变更。 ... [详细]
  • V8不仅是一款著名的八缸发动机,广泛应用于道奇Charger、宾利Continental GT和BossHoss摩托车中。自2008年以来,作为Chromium项目的一部分,V8 JavaScript引擎在性能优化和技术创新方面取得了显著进展。该引擎通过先进的编译技术和高效的垃圾回收机制,显著提升了JavaScript的执行效率,为现代Web应用提供了强大的支持。持续的优化和创新使得V8在处理复杂计算和大规模数据时表现更加出色,成为众多开发者和企业的首选。 ... [详细]
  • 在Android平台中,播放音频的采样率通常固定为44.1kHz,而录音的采样率则固定为8kHz。为了确保音频设备的正常工作,底层驱动必须预先设定这些固定的采样率。当上层应用提供的采样率与这些预设值不匹配时,需要通过重采样(resample)技术来调整采样率,以保证音频数据的正确处理和传输。本文将详细探讨FFMpeg在音频处理中的基础理论及重采样技术的应用。 ... [详细]
author-avatar
大师兄v断水流
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有