【Java盲点攻克】「时间与时区系列」让我们一起完全吃透对于时区和日期相关的功能开发原理

news2025/9/18 21:39:15

技术简介

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 < defaultCenturyStart) year += 100;
            }
            ... ...
    }

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<DateFormat> df = new ThreadLocal<DateFormat>() { 
        @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> T parse(CharSequence text, TemporalQuery<T> query) {
   ... ...
}
LocalDateTime::from调用的方法是
public static LocalDateTime from(TemporalAccessor temporal) {
    .... ...     
}

其中temporal是LocalDateTime的接口

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/48632.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

自然算法 - AI面试基础补全

手撕BP神经网络手写Bert和Transformer&#xff08;BERT很细节的地方&#xff0c;比如文字标签CLS&#xff0c;par&#xff09;学习pytorch&#xff0c;tensorflow AI算法岗位 可看网站 牛客网站 面经回复 github 项目连接 算法工程师岗位必备知识 问答 ELMO、GPT、…

Linux系统下KVM虚拟机的基本管理和操作

Linux系统下KVM虚拟机的基本管理和操作一、检查本地环境1.检查系统版本2.检查防火墙状态3.检查selinux3.检查libvirtd服务状态4.检查kvm安装结果5.检查kvm虚拟机状态6.检查virsh版本二、virsh常用命令1.列出虚拟机2.虚拟机开关机操作3.删除虚拟机4.设置虚拟机在宿主机开机时自启…

装在笔记本里的私有云环境:K8s 集群准备

本篇是系列中的第六篇内容&#xff0c;继续聊聊如何把一个简化过的私有云环境部署在笔记本里&#xff0c;以满足低成本、低功耗、低延时的实验环境。 在前几篇内容中&#xff0c;我们聊过了&#xff1a;虚拟化、监控、基础的存储、持续集成等内容&#xff0c;接下来的内容中&a…

告诉你如果对一个新产品进行测试

初入一家公司&#xff0c;当一个全新的产品摆在你的面前&#xff0c;你会如何快速入手呢&#xff1f;点、点、点。。。虽说实践是熟悉系统的第一要素&#xff0c;但我们需要静静思考一下。我是谁&#xff1f;--QA我在哪&#xff1f;--**产品组我要做什么&#xff1f;--保质量有…

Nature:“我还有用!“凋亡细胞释放的代谢物充当组织信使

代谢组学文献分享&#xff0c;细胞凋亡是指为维持内环境稳定&#xff0c;由基因控制的细胞自主的有序的死亡。2002年授予在“发现细胞凋亡的重要调控分子并阐述其作用机制”方面做出重要贡献的三位科学家诺贝尔生理和医学奖&#xff0c;caspase依赖的细胞凋亡约占机体内稳态细胞…

多线程初阶(一)

目录 前言&#xff1a; 认识多线程 创建线程 run方法和start区别 继承Thread类 实现Runnable接口 匿名内部类实现继承Thread类 匿名内部类实现Runnable接口实例 Lambda表达式 中断线程 等待线程 线程休眠 线程状态 线程状态之间切换 代码观察线程的状态 线程安…

多点DMALL × Apache Kyuubi:构建统一SQL Proxy探索实践

伴随着国家产业升级的推进和云原生技术成熟&#xff0c;多点 DMALL 大数据技术也经历了从存算一体到存算分离的架构调整变迁。本文将从引入 Kyuubi 实现统一 SQL Proxy 的角度讲述这一探索实践的历程。 多点 DMALL 成立于2015年&#xff0c;提供一站式全渠道数字零售解决方案 D…

STL的常用算法-查找 (20221130)

STL的常用算法 概述&#xff1a; 算法主要是由头文件<algorithm> <functional> <numeric> 组成。 <algorithm>是所有STL头文件中最大的一个&#xff0c;涉及比较、交换、查找、遍历等等&#xff1b; <functional>定义了一些模板类&#xff0…

使用记账软件记录生活收支明细,如何防止收支不被他人修改

坚持记账是每个人都必须要做的事情&#xff0c;日常生活中的生活开支都是一笔笔的支出&#xff0c;一个月挣来的工资&#xff0c;在不知不觉之中就花完了&#xff0c;可以使用——晨曦记账本记录生活明细&#xff0c;为了防止被他人修改&#xff0c;该如何操作呢&#xff1f;一…

Allegro调丝印规范操作指导

Allegro调丝印规范操作指导 Allegro和其它PCB设计软件一样,丝印的排布也是类似的,具体规范介绍如下 以下图为例 打开Setup-Design Parameter 选择text 设置丝印字体的参数 设置需要丝印的字体,比如3号字体,参数如下 然后点击OK Edit-Change需要调整丝印的字体

Linux-Hadoop部署

部署Hadoop一、Hadoop部署模式1、独立模式2、伪分布式模式3、完全分布式模式二、Hadoop集群规划1、集群拓扑2、角色分配三、JDK安装与配置1、下载JDK压缩包2、上传到master虚拟机3、在master虚拟机上安装配置JDK4、将JDK分发到slave1和slave2虚拟机5、将环境配置文件分发到slav…

图的初识·存储结构

邻接矩阵存储结构 用矩阵表示表示图中各个顶点之间的邻接关系和权值。如图G(V,E)G(V,E)G(V,E)&#xff0c;其中有N个结点&#xff0c;使用NxNNxNNxN的矩阵表示。 不带权值的图 Gij{1,无向图(vi,vj)或有向图的<vi,vj>是图中的边0,无向图的(vi,vj)或有向图的<vi,vj>…

猕猴桃的红色果肉受到特定的激活-抑制系统的控制

文章信息 题目&#xff1a;The red flesh of kiwifruit is differentially controlled by specific activation–repression systems 刊名&#xff1a;New Phytologist 作者&#xff1a;Wen-qiu Wang&#xff0c;Andrew C. Allan,Xue-ren Yin et al 单位&#xff1a;Zhejia…

猿如意开发工具|Sublime Text(4126)

文章目录 一、猿如意是什么&#xff1f; 二、如何使用猿如意下载安装Sublime Text 三、总结 一、猿如意是什么&#xff1f; 猿如意是一款面向开发者的辅助开发工具箱&#xff0c;包含了效率工具、开发工具下载&#xff0c;教程文档&#xff0c;代码片段搜索&#xff0c;全网搜…

【Pandas数据处理100例】(九十九):Pandas使用at_time()筛选出特定时间点的数据行

前言 大家好,我是阿光。 本专栏整理了《Pandas数据分析处理》,内包含了各种常见的数据处理,以及Pandas内置函数的使用方法,帮助我们快速便捷的处理表格数据。 正在更新中~ ✨ 🚨 我的项目环境: 平台:Windows10语言环境:python3.7编译器:PyCharmPandas版本:1.3.5N…

如何建立一套完善的销售管理体系?

怎样创建两套健全的产品销售体系&#xff1f;用工程建设观念管理工作产品销售项目组&#xff0c;创建健全的产品销售体系&#xff01; 逐步形成精确的最终目标虽说重要&#xff0c;但缺乏有效率的方式来破冰&#xff0c;最终目标可能将仅是这份无用。 篮球赛事球手的最终目标…

IOC 的底层原理和Bean管理XML方式、xml注入集合属性

目录 什么是IOC IOC底层管理 工厂模式 IOC 的过程 IOC 接口 IOC 操作Bean 原理 Bean 管理操作有两种方式 1. 基于xml 配置方式创建对象 2. 基于xml方式注入属性 第二种使用有参数构造注入 p 名称空间注入 ICO操作Bean管理&#xff08;xml 注入其他类型属性&#xff…

微信小程序的 websocket 以及 微信开发者工具测试 ws 协议没有数据的 离奇解决方案 记录

微信小程序的 websocket 在本地web能够使用ws协议去链接websocket&#xff0c;但是小程序不能使用。一、WSS 协议与 WS 协议二、业务场景记录 : 使用 ws 协议的 websocekt 做测试,但是在 h5中可以拿到实时数据,在微信开发者工具中以及真机调试中拿不到模拟数据的问题1. 首先在 …

2022安洵杯babybf

babybf 赛后分析了下&#xff0c;发现是一道很有意思的题目 Brainfuck是一种极小化的计算机语言&#xff0c;它是由Urban Mller在1993年创建的。由于fuck在英语中是脏话&#xff0c;这种语言有时被称为brainf*ck或brainf**k&#xff0c;甚至被简称为BF。 其实本题是一个c语言实…

三、【redux】异步action

文章目录1、不成熟的异步修改1.1、CODE1.1.1、count_action.js1.1.1、count_reducer.js1.2、异常2、异步action代码修正2.1、store.js2.2、count_action.js3、小总结action分两类&#xff1a; 同步&#xff1a;指action的值是Object类型的一般对象异步&#xff1a;指action的值…