Calendar
- 获取当前时间
- SimpleDateFormat
- 获取年月日等
- 设置特定时间、时区
- 日期的计算(加减)
- 计算日期差
- 计算某年二月有几天
- Calendar常用方法合集
- 夏令时是什么
简介:
Java中,Calendar是一个抽象类,所以这个类是不能通过new来直接实现的,并且调用getInstance后y一般返回的是Calendar的子类:GregorianCalendar。
附getInstance中private static Calendar createCalendar(TimeZone zone,Locale aLocale)部分代码
if (cal == null) {
// 如果没有明确指定已知的日历类型,则执行传统方式来创建日历:为 th_TH 语言环境创建一个佛教日历,
//为 ja_JP_JP 语言环境创建一个 JapaneseImperialCalendar,
//或者为任何其他语言环境创建一个 GregorianCalendar。
if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") {
cal = new BuddhistCalendar(zone, aLocale);
} else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja"
&& aLocale.getCountry() == "JP") {
cal = new JapaneseImperialCalendar(zone, aLocale);
} else {
cal = new GregorianCalendar(zone, aLocale);
}
}
return cal;
获取当前时间
Calendar calendar=Calendar.getInstance();
calendar.getTime();//只有使用了getTime方法,返回的是Date对象
//也可以使用calendar.setTime(new Date());
调用抽象类中的静态方法getInstance来返回一个当前的calendar对象。Calendar对象较大较复杂,所以使用的是单例模式,使用单例模式的好处是每次都返回同一个对象,这样保证了每次返回都是同一个对象
源码
/**
使用默认时区和区域设置获取日历。 返回的Calendar基于默认时区中的当前时间和默认FORMAT语言环境。
返回:Calendar
* {@link Locale.Category#FORMAT FORMAT} locale.
*
* @return a Calendar.
*/
public static Calendar getInstance()
{
return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));
}
如何规范输出日期的格式
Date date=c.getTime();//返回的是Date对象
SimpleDateFormat sdf2=new SimpleDateFormat("yyyy年MM月dd分hh时ss分mm秒");
String strTime=sdf2.format(date);
System.out.println(strTime);
yyyy:年
MM:月
dd:日
hh:1~12小时制(1-12)
HH:24小时制(0-23)
mm:分
ss:秒
S:毫秒
E:星期几
D:一年中的第几天
F:一月中的第几个星期(会把这个月总共过的天数除以7)
w:一年中的第几个星期
W:一月中的第几星期(会根据实际情况来算)
a:上下午标识
k:和HH差不多,表示一天24小时制(1-24)。
K:和hh差不多,表示一天12小时制(0-11)。
z:表示时区
解析Calendar生成方式:通过时区(TimeZone)和区域(Local)来生成时间,因为不同时区有时间差。并且Calendar对象是可变的,可以通过设置时区、区域、年月日来改变Calendar对象中存储的值。
举例:跨国业务中,美国的消费者下单时间和服务器中接收到订单的时间不一样,这里就需要使用到时区和地区的知识(想知道怎么做继续往下看)
获取年月日
Calendar calendar=Calendar.getInstance();
calendar.getTime();//只有使用了getTime方法,calendar才会有存储的日期
System.out.println("年:"+calendar.get(calendar.YEAR));
System.out.println("一个月的第几周:"+calendar.get(calendar.WEEK_OF_MONTH));
System.out.println("一周的第几天:"+calendar.get(calendar.DAY_OF_WEEK));
System.out.println("早为0,晚为1(以中午12点为界限):"+calendar.get(Calendar.AM_PM));
System.out.println("一天的第几个小时:"+calendar.get(Calendar.HOUR_OF_DAY));
System.out.println("秒:"+calendar.get(Calendar.SECOND));
System.out.println("注意:周日是第一天,序号为1:"+calendar.get(Calendar.SUNDAY));
field | 说明 |
---|---|
public final static int ERA = 0; | 公元前BC(0) 公元后(1) |
public final static int YEAR = 1; | 年 |
public final static int MONTH = 2; | 日期 |
public final static int WEEK_OF_YEAR = 3; | 一年的第几周 |
public final static int WEEK_OF_MONTH = 4; | 一个月的第几周 |
public final static int DATE = 5; | 一个月的第几天 |
public final static int DAY_OF_MONTH = 6; | 一个月的第几天 |
public final static int DAY_OF_WEEK = 7; | 一周的第几天 |
public final static int AM_PM = 9; | 特别注意:1是日 早上or下午AM:0 PM:1,以中午十二点为界 |
public final static int HOUR = 10; | 小时 |
public final static int HOUR_OF_DAY = 11; | 一天的第几个小时 |
public final static int MINUTE = 12; | 分钟 |
public final static int SECOND = 13; | 秒 |
public final static int MILLISECOND = 14; | 毫秒 |
public final static int ZONE_OFFSET = 15; | 指示与 GMT 的原始偏移量(以毫秒为单位) |
public final static int DST_OFFSET = 16; | 指示以毫秒为单位的夏令时偏移量 |
public final static int FIELD_COUNT = 17; | 字段数量 |
设置特定时间、时区
Calendar calendar=Calendar.getInstance();
calendar.getTime();//只有使用了getTime方法,calendar才会有存储的日期
calendar.set(Calendar.YEAR,2022);
calendar.set(Calendar.MONTH,5);
calendar.set(Calendar.DAY_OF_MONTH,2);
System.out.println(calendar.getTime());
calendar.set(1990,3,5);//这里是四月嗷
System.out.println(calendar.getTime());
//结果
//Thu Jun 02 23:32:48 CST 2022
//Thu Apr 05 23:32:48 CST 1990
时区有哪些
for (String timezone: TimeZone.getAvailableIDs()) {
System.out.println(timezone);
}
//很多,大家运行一下就知道了
点击跳转至:世界时区
时区有什么用:不同时区的时间不一样,假如平台有海外业务,如果在存入数据库的时候没有考虑时区问题,即数据库只保存年月日,那么可能会发生2021.10.1 18:00:00服务器接收到了订单,并且立刻数据库中存入一笔时间2021.10.1 19:00:00的订单记录,这里订单时间相当于变成"一小时后的下单记录时间",所以我们要考虑转换时区来保证数据库时间存储的正确性。(虽然可以在数据库中加入timezone这个字段,但是可能会麻烦一些)
不同时区的时间转换
public class TimeZoneTransform {
private static String dateTransformBetweenTimeZone(Date sourceDate, DateFormat formatter,
TimeZone sourceTimeZone, TimeZone targetTimeZone) {
Long targetTime = sourceDate.getTime() - sourceTimeZone.getRawOffset() + targetTimeZone.getRawOffset();
return getTime(new Date(targetTime), formatter);
}
private static String getTime(Date date, DateFormat formatter) {
return formatter.format(date);
}
private static String getTimeZone() {
Calendar cal = Calendar.getInstance();
// getOffset will access to offset and contains DaylightTime
int timeZone = cal.getTimeZone().getOffset(System.currentTimeMillis()) / (3600000);
if (timeZone >= 0) {
return String.valueOf("+" + timeZone);
}
return String.valueOf(timeZone);
}
public static String getGMTTime(Date date, SimpleDateFormat formatter) {
TimeZone srcTimeZone = TimeZone.getTimeZone("GMT" + getTimeZone());
TimeZone destTimeZone = TimeZone.getTimeZone("GMT+8");
return dateTransformBetweenTimeZone(date, formatter, srcTimeZone, destTimeZone);
}
public static void main(String[] args) {
System.out.println(getGMTTime(new Date(System.currentTimeMillis()), new SimpleDateFormat()));
}
}
Set有延迟性
set(f, value) 将日历字段 f 更改为 value。此外,它设置了一个内部成员变量,以指示日历字段 f 已经被更改。尽管日历字段 f 是立即更改的,但是直到下次调用 get()、getTime()、getTimeInMillis()、add() 或 roll() 时才会重新计算日历的时间值(以毫秒为单位)。因此,多次调用 set() 不会触发多次不必要的计算。
通俗的来说就是
public void set(int field, int value)
{
//areFieldsSet:如果与当前设置的时间同步,则为真。如果为 false,则下一次获取字段值的尝试将强制从当前值重新计算所有字段
if (areFieldsSet && !areAllFieldsSet) {
computeFields();
}
internalSet(field, value);//这里的操作仅仅只是更改存储的值,但是无法判断某些不合法的值,如11月没有31号
isTimeSet = false;
areFieldsSet = false;
isSet[field] = true;
stamp[field] = nextStamp++;
if (nextStamp == Integer.MAX_VALUE) {
adjustStamp();
}
}
看到这里可能你会很懵,不知道它的延迟性可能会引发什么情况,下面来说一个情况,首先你给calendar设置了9月31号(不报错并且会存储,当调用上述几种方法后会计算然后变成十月一日),此时假如调用了上述几种方法后,设置为五日,就变成了十月五日;假如没调用那几种方法,再设置五日,就是九月五日。
简要来说:假如你阴差阳错把天数设置超过范围,并且你设置的月份是你认为正确的,此时假如你调用了get等方法,那么calendar会把日期计算成一个系统认为合法的日期(月份和天数可能都不是你想要的)。此时如果你只修改了日期,那么你月份仍然是错误的。
cal.setLenient(true);
cal.set(2021,8,31);//这里是9月嗷
// System.out.println(cal.getTime());//调用此部分就刷新了数据
// cal.set(Calendar.MONTH,9);
cal.set(Calendar.DATE,10);
System.out.println(cal.getTime());
//假如注释那两行,结果Fri Sep 10 19:56:31 GMT+08:00 2021
//假如不注释,结果:Sun Oct 10 19:57:13 GMT+08:00 2021
日期的计算(加减)
Calendar calendar=Calendar.getInstance();
calendar.setTime(new Date());
//日期的加减
calendar.add(Calendar.YEAR,10);
calendar.add(Calendar.MONTH,-2);//可以为负数的嗷,其他的字段就不展示了
其他的字段操作自己探索就好了
计算日期差
//计算相隔天数的方法
public int getDaysBetween (Calendar d1, Calendar d2){
if (d1.after(d2)){ // swap dates so that d1 is start and d2 is end
java.util.Calendar swap = d1;
d1 = d2;
d2 = swap;
}
int days = d2.get(Calendar.DAY_OF_YEAR) - d1.get(Calendar.DAY_OF_YEAR);
int y2 = d2.get(Calendar.YEAR);
if (d1.get(Calendar.YEAR) != y2){
d1 = (Calendar) d1.clone();
do{
days += d1.getActualMaximum(Calendar.DAY_OF_YEAR);//得到当年的实际天数
d1.add(Calendar.YEAR, 1);
} while (d1.get(Calendar.YEAR) != y2);
}
return days;
}
这里有个大佬讲的很全
获取某年二月有几天
虽然获取某年二月有几天很容易,但是这里仅是提供一个思路
calendar.set(Calendar.YEAR,2021);
calendar.set(Calendar.MONTH,1);
System.out.println(calendar.getTime());
System.out.println(calendar.getActualMaximum(Calendar.DATE));
//输出28
下面来解释getActualMaximum方法:返回当前日期时,该字段的最大值
还有另一个长得很像的方法getMaximum:用于获取此Calendar的给定字段的最大值,例如使用getMaximum(Calendar.DATE),那么返回的是31,因为十二个月中DATE最大是31
常见方法
上面涉及到的不在写了
abstract void add(int field, int amount) | 根据日历的规则,为给定的日历字段添加或减去指定的时间量。 |
boolean after(Object when) | 判断此 Calendar 表示的时间是否在指定 Object 表示的时间之后,返回判断结果。 |
boolean before(Object when) | 判断此 Calendar 表示的时间是否在指定 Object 表示的时间之前,返回判断结果。 |
void clear() | 将此 Calendar 的所日历字段值和时间值(从历元至现在的毫秒偏移量)设置成未定义。 |
void clear(int field) | 将此 Calendar 的给定日历字段值和时间值(从历元至现在的毫秒偏移量)设置成未定义。 |
int compareTo(Calendar anotherCalendar) | 比较两个 Calendar 对象表示的时间值(从历元至现在的毫秒偏移量)。 |
protected void complete() | 填充日历字段中所有未设置的字段。 |
boolean equals(Object obj) | 将此 Calendar 与指定 Object 比较。 |
int get(int field) | 返回给定日历字段的值。 |
int getActualMaximum(int field) | 给定此 Calendar 的时间值,返回指定日历字段可能拥有的最大值。 |
int getActualMinimum(int field) | 给定此 Calendar 的时间值,返回指定日历字段可能拥有的最小值。 |
static Locale[] getAvailableLocales() | 返回所有语言环境的数组,此类的 getInstance 方法可以为其返回本地化的实例。 |
int getFirstDayOfWeek() | 获取一星期的第一天 |
Date getTime() | 返回一个表示此 Calendar 时间值(从历元至现在的毫秒偏移量)的 Date 对象。 |
long getTimeInMillis() | 返回此 Calendar 的时间值,以毫秒为单位。 |
boolean isLenient() | 判断日期/时间的解释是否为宽松的。 |
boolean isSet(int field) | 确定给定日历字段是否已经设置了一个值,其中包括因为调用 get 方法触发内部字段计算而导致已经设置该值的情况。 |
abstract void roll(int field, boolean up) | 在给定的时间字段上添加或减去(上/下)单个时间单元,不更改更大的字段。 |
void roll(int field, int amount) | 向指定日历字段添加指定(有符号的)时间量,不更改更大的字段。 |
void setFirstDayOfWeek(int value) | 设置一星期的第一天是哪一天 |
void setLenient(boolean lenient) | 指定日期/时间解释是否是宽松的。 |
void setTime(Date date) | 使用给定的 Date 设置此 Calendar 的时间。 |
void setTimeInMillis(long millis) | 用给定的 long 值设置此 Calendar 的当前时间值。 |
void setTimeZone(TimeZone value) | 使用给定的时区值来设置时区。 |
宽松模式:是否允许字段的值超过范围,即设置HOUR=25就是明天的1点
注意:当 Calendar 处于 non-lenient 模式时,如果其日历字段中存在任何不一致性,它都会抛出一个异常。
calendar.setLenient(true);
calendar.set(Calendar.YEAR,2021);
calendar.set(Calendar.MONTH,13);
System.out.println(calendar.getTime());
//结果:Sat Feb 05 01:14:31 CST 2022
roll和add的区别:
2021.9.12对MONTH字段-10,会是roll:2021.10.12,
add:2020.10.12
roll:对字段MONTH回滚时,只回滚该字段,不更改其更大的字段。
add:对字段MONTH回滚时,超出范围时,会更新其更大的字段
在宽松模式下也可以通过add方法来计算两个日期之间的时间差
什么是夏令时
什么是夏令时?
通俗简单的讲:在夏天日光充裕的时候,人们为了充分利用日光时间人为的将时钟拨快了一个小时。
出发点和目的都很简单:夏天亮的早,白天时间长,调整为夏令时促使人们早睡早起,以充分利用光照资源,从而节约照明用电。
例如:晚上两点的时候,时间调到三点,那么这天你会少睡一小时,这样到了晚上,那么你会提前一小时睡觉,那么就省了电。
有关Calendar的夏令时部分请看这位大佬的博客
添加链接描述