Java中常用的日期类图文详解
目录
- 前言
- Date
- 为什么Date的大部分方法被弃用
- 注释
- 翻译
- 目前可用方法的测试示例
- 可用方法
- 示例
- Date小结
- Calendar
- 简单介绍
- 常用的方法
- 获取实例
- 获取日期里的信息
- 日期的加减与滚动
- 日期的设置
- 测试实例代码
- DateFormat与SimpleDateFormat
- DateFormat
- 常用方法
- 测试实例
- SimpleDateFormat
- 主要方法
- 测试示例
- 编写一个简单的日期工具类
- 工具类
- 测试示例
- 总结
前言
本文将分析Java中的Date、Calendar、DateFormat、SimpleDateFormat,并熟悉及使用其中的常用方法。
阅读本文你将了解上述的各类。并且熟练日期的常用操作,包括但不限于:日期的比较、获取日期中的信息、日期的加减、日期指定格式化等等
Date
日期类,这个大家应该是比较熟悉的,常用的一条语句是
Date date = new Date();
上述代码,用于获取一个当前的时间的Date对象。但是在源码的注释中明确表明,其中的很多方法已被废弃。
为什么Date的大部分方法被弃用
那么为什么废弃呢?我们来看注释
注释
原注释如下
The class Date represents a specific instant in time, with millisecond
precision.Prior to JDK 1.1, the class Date had two additional functions.
It allowed the interpretation of dates as year, month, day, hour,
minute, and second values. It also allowed the formatting and parsing of date
strings.
Unfortunately, the API for these functions was not amenable to
internationalization. As of JDK 1.1, the Calendar class should be used to
convert between dates and time fields and the DateFormat class should be used to
format and parse date strings.The corresponding methods in Date are deprecated.
翻译
Date表示一个时间的瞬间,精确到毫秒。
在JDK1.1之前,该类有额外的两个方法。它允许以年、月、日、小时、分和秒的值解析日期。还允许格式化和处理日期字符串。
可惜的是,这些方法的API并不适用于国际化。在JDK1.1中,Calender类应该被用于日期和时间字段的间的转换,DateFormat类应该被用于格式化和处理日期字符串。
Date中相关的方法已弃用。
目前可用方法的测试示例
那么目前Date的主要作用是什么呢?我们挨个看看现在Date类中可用的方法。
可用方法
获取时间
获取日期的毫秒数(自1970年开始)
long getTime()
日期前后的比较
boolean after(Date date) boolean befor(Date date) boolean compareTo(Date date)
设置时间
以毫秒数设置时间
void setTime(long l);
相等判断
boolean equals(Date date)
示例
public class Test { public static void main(String[] args) throws InterruptedException { Date date = new Date(); //睡眠一毫秒 否则可能会导致date和date1相等 Thread.sleep(1); Date date1 = new Date(); //获取当前时间的毫秒数 long l = date.getTime(); System.out.println(l); if(date1.after(date)) //以调用者为主 调用者是否在参数之后 System.out.println("date1在date2之后"); if(date.before(date1)) //以调用者为主 调用者是否在参数之前 System.out.println("date在date1之前"); System.out.println("默认的输出格式:" + date.toString()); System.out.println("与比自己大的日期比较:" + date.compareTo(date1)); System.out.println("与比自己的日期比较:" + date.compareTo(date)); System.out.println("与比自己小的日期比较:" + date1.compareTo(date)); date.setTime(l + 5000); //以长整型设置新的时间 加五秒 System.out.println("五秒后的时间:" + date.toString()); //获取一个与当前时间相同的Instant对象 Instant instant = date.toInstant(); if(date.equals(date1)) { //这里其实比较的就是Date的毫秒数 System.out.println("日期不等。"); } } }
默认日期格式的分析
从左到右依次为:星期 月份 日 时:分:秒 日期类型 年份
这里的CTS指的是北京时间
Date小结
Date类主要的作用,从上面的方法看来,很明显是用于获取一个当前日期的对象以及日期间简单的前后比较。
Calendar
主要用于获取日期中的信息以及操作日期。
简单介绍
日历类,主要用于设置和操作日期。以及获取一些日期里的信息,比如当前日期是星期几,今天是今年的第多少周等等。
常用的方法
获取实例
Calendar getInstance();
获取日期里的信息
这个是重点,获取许多日期中的信息基本都依赖于这个方法。比如当前日期是星期几、今天是今年第几天等。实例里会详细说明各个信息的获取方式。
int get(int field)
这里再提一个方法,这个方法就是用于获取日历里面的日期对象的。
Date getTime()
日期的加减与滚动
加减
加减很好理解,下面两个字段的含义为:
field : 表示加减操作的字段。比如Calendar.Date,表示天,Calendar.YEAR,表示年,还有Calendar.MONTH等等。
amount : 操作的数目。如果是正数则表示加,反之则表示减。比如10,则表示加十次。-10则表示减十次。
void add(int field, int amount);
下面这段代码则表示,当前日期加十天。
calendar.add(Calendar.Date , 10);
滚动
void roll(int field, int amount);
再说滚动,与add不同,roll方法只操作单个字段,其字段含义与add相同。注意我的用词,是只,不是之,不是值,更不是只因。
那什么叫只操作单个字段呢?比如当前日期为11.1号,分别调用以下方法
add(Calendar.DATE , -1);
日期减一天,此时日期为 10.31
但调用
roll(Calendar.DATE , -1);
获取到的日期为 11.30
看出来了吗,add方法是正常的日期加减,而roll的操作是该列滚动操作。可以看作设置闹钟时候的那种滚动,而且他的部分子类(这取决于地区)是不会有年月日的联动的。也就造成了上面的情况。
日期的设置
相较于Date类中只能用毫秒数设置日期,Calendar中可以以多种方式设置时间。
void setTime(Date date); void set(int year, int month, int date) void set(int year, int month, int date, int hourOfDay, int minute) void set(int year, int month, int date, int hourOfDay, int minute, int second)
还有以指定的周数及星期来设置时间等等。
setWeekDate(int weekYear, int weekOfYear, int dayOfWeek)
当然还有一些用的其他的方法,这里就不一一列举了。
测试实例代码
public class CalendarTest { public static void main(String[] args) { //根据地区获取对应的Calender-日历 默认为当前时间,值得注意的是这里的获取实例 // 用的很明显的提供者模式 Calendar calendar = Calendar.getInstance(); //get方法的返回值为int 不同的参数对应不同的值 System.out.println("年:" + calendar.get(Calendar.YEAR)); System.out.println("月:" + calendar.get(Calendar.MONTH)); System.out.println("日:" + calendar.get(Calendar.DATE)); System.out.println("时(12小时制):" + calendar.get(Calendar.HOUR)); System.out.println("时(24小时制):" + calendar.get(Calendar.HOUR_OF_DAY)); System.out.println("分:" + calendar.get(Calendar.MINUTE)); System.out.println("秒:" + calendar.get(Calendar.SECOND)); // 日期与天 // 这里需要注意的是星期里面 周日的为1 周一为2 周二为3 以此类推 所以这里要减一 System.out.println("今天是这周第几天:" + (calendar.get(Calendar.DAY_OF_WEEK) - 1)); System.out.println("今天是这个月第几天:" + calendar.get(Calendar.DAY_OF_MONTH)); System.out.println("今天是今年第几天::" + calendar.get(Calendar.DAY_OF_YEAR)); //日期与周 System.out.println("现在是这月第几周:" + calendar.get(Calendar.WEEK_OF_MONTH)); System.out.println("现在是今年第几周:" + calendar.get(Calendar.WEEK_OF_YEAR)); //表示当前时间是在上午还是在下午 0 - 上午(午夜到正午) 1-下午 System.out.println("上午:" + calendar.get(Calendar.AM_PM)); System.out.println("===================日期的设置======================="); //设置日期为2022年1月1号 精确到天 calendar.set(2022 , Calendar.JANUARY , 1); //设置为 2022-2-1 10:12 精确到分 calendar.set(2022 , Calendar.FEBRUARY , 1 , 10 , 12); //设置为 2022-3-1 10:12:1 日期 精确到秒 calendar.set(2022 , Calendar.MAY , 1 , 10, 10,1); System.out.println("设置后的时间:" + calendar.getTime().toString()); calendar.clear(); //还可以指定年、周数来设置日期为 这里是设置日期为 2022年的第1周的周三 calendar.setWeekDate(2022 , 1, Calendar.WEDNESDAY); //这里需要注意 如果2022.1.1号是周六 那么之前周一到周五也算第一周 即使它们在2021年里 //这也就解释了为什么2022年第一周周三的日期为2021-12-29 System.out.println("2022年的第1周的周三: " + calendar.getTime().toString()); System.out.println("===================日期的改变======================="); //日期的改变 calendar.clear();//设置时间为2022-11-1 calendar.set(2022 , Calendar.NOVEMBER , 1); System.out.println("当前时间:" + calendar.getTime().toString()); //日期的滚动 只操作单列而不影响其他的信息 calendar.roll(Calendar.DATE , -1);//正数为向上滚(+) 负数为向下滚(-) System.out.println("向下滚动一天的时间(11-30):" + calendar.getTime().toString()); //日期的加减 前一个参数为操作的类型 后一个参数为改变的数量 calendar.clear();//设置时间为2022-11-1 calendar.set(2022 , Calendar.NOVEMBER , 1); calendar.add(Calendar.DATE , -1); System.out.println("当前时间减去一天为(10-31):" + calendar.getTime().toString()); //加一天 calendar.add(Calendar.DATE , 1); System.out.println("再减一天为:" + calendar.getTime().toString()); System.out.println("===================一些信息的设置======================="); //获取字段对应最大的值 System.out.println("一年中天数最大可能为:" + calendar.getMaximum(Calendar.DAY_OF_YEAR)); System.out.println("星期最大为:" + calendar.getMaximum(Calendar.DAY_OF_WEEK)); System.out.println("一月天数最大为:" + calendar.getMaximum(Calendar.DAY_OF_MONTH)); } }
DateFormat与SimpleDateFormat
DateFormat
是一个抽象类,其实这个类有点鸡肋,你别看名字叫做日期格式化。但其实它只能转换和处理固定的格式日期。跳过不看也是可以的。
值得注意的是,该类的属性种包含一个Calendar。我们后面讲到的SimpleDateFormat是DateFormat的子类,所以需要注意一下。
比如将一个Date转换为String,或者String转换为Date。固定格式为:xx-xx-xx。比如2022-11-1。
常用方法
这里来认识两个种方法:日期转字符的format以及与之相反的parse
日期转字符
这其中fieldPosition参数的我会在测试示例里具体说明
StringBuffer format(Date date, StringBuffer toAppendTo,FieldPosition fieldPosition); String format(Date date);
字符转日期
这其中pos参数的我会在测试示例里具体说明
Date parse(String source); Date parse(String source, ParsePosition pos);
测试实例
public class DateFormatTest { public static void main(String[] args) throws ParseException { Date date = new Date(); //获取一个实例 跟Calendar一样使用的是提供者模式 DateFormat dateFormat = DateFormat.getDateInstance(); System.out.println("处理字符转为日期1:" + dateFormat.parse("2022-10-1").toString()); //将字符串处理为日期 只支持 xx-xx-xx 这种格式 且无法精确到时分秒 ParsePosition pos = new ParsePosition(1);//从第1位 0开始将字符转换为日期 System.out.println("处理字符转为日期2:" + dateFormat.parse("2022-10-11", pos).toString()); //格式化日期1 默认格式为 2022-11-3 这种 System.out.println(dateFormat.format(date)); //格式化日期2 StringBuffer stringBuffer = new StringBuffer(); //参数分别为 日期、用于输出的StringBuffer、指定的跟踪字段 // 跟踪字段 - MONTH_FIELD 指向的是月份的位置 FieldPosition fieldPosition = new FieldPosition(DateFormat.MONTH_FIELD); dateFormat.format(date , stringBuffer , fieldPosition); System.out.println("日期转为字符: :" + stringBuffer); //获取指定字段的索引信息 int begin = fieldPosition.getBeginIndex(); int end = fieldPosition.getEndIndex(); System.out.println("指定字段在StringBuffer中的开始索引:" + begin); System.out.println("指定字段在StringBuffer中结束索引:" + end); String fieldValue = ""; for(int i = begin; i < end; i++) { fieldValue = fieldValue + stringBuffer.charAt(i); } System.out.println("指定字段(月份)的值为:" + fieldValue); } }
SimpleDateFormat
该类是DateFormat的子类。主要用于日期的处理和格式化。相较于它的父类,它的功能更多,也更加便捷,更符合用户的使用习惯。
主要方法
获取实例
相较于抽象类(Calendar、DateFormat)使用的提供者模式获取实例,现在我们可以直接new一个对象了。该类主要就是这一步。
SimpleDateFormat();//默认格式为22-11-4 上午10:36 SimpleDateFormat(String pattern);//指定格式 SimpleDateFormat(String pattern, Locale locale);//指定格式 指定地区 SimpleDateFormat(String pattern, DateFormatSymbols formatSymbols)//指定地区、指定日期符号
这里的格式我要强调一下,各字母代表的含义如下
(注意以下数据的转换跟地区有关-Local类,不同的地区显示不同,比如我们显示MM显示为11,但其他地区可能是Nov来表示月份)
字母 | 值 | 说明 |
---|---|---|
y | 年份 | yyyy表示四位数年份,如2022 |
M | 月份 | MM表示当前月,如11 |
m | 分钟 | mm表示分钟,如10 |
d | 本月天数 | dd表示本月天数,如04 |
D | 今年天数 | D表示今年天数,如185 |
E | 星期 | E表示星期,如星期三 |
h | 12小时制 | hh表示小时(0-12),如10 |
H | 24小时制 | HH表示小时(0-24),如18 |
s | 秒 | ss表示两位秒数,如02 |
S | 毫秒 | SSS表示三位毫秒数,588 |
a | 时段 | a表示上午或者下午,如上午 |
当然还有一部分字母,如:L、k、z、x、F、U等等用的比较少的就不再列举了,有兴趣自行了解。
常用的格式
下面我会列举一下比较常用的格式。
不带毫秒格式
yyyy-MM-dd HH:mm:ss
2022-11-04 10:07:26
带毫秒的
yyyy-MM-dd HH:mm:ss.SSS
2022-11-04 10:07:26.729
带年月日
yyyy年MM月dd日 HH:mm:ss
2022年11月04日 10:20:32
带年月日 星期 上午
yyyy年MM月dd日 E a HH:mm:ss
2022年11月04日 星期五 上午 10:20:32
横杠带星期
yyyy-MM-dd E HH:mm:ss
2022-11-04 星期五 10:11:10
横杠带 星期 时段
yyyy-MM-dd E a HH:mm:ss
2022-11-04 星期五 上午 10:17:02
无分隔符 精确到秒 一般用于作为生成文件的文件名称
yyyyMMddHHmmss
20221104101110
精确到毫秒
yyyyMMddHHmmssSSS
20221104101110136
左斜杠格式
yyyy/MM/dd HH:mm:ss
2022/11/04 10:11:10
左斜杠带毫秒
yyyy/MM/dd HH:mm:ss.SSS
2022/11/04 10:11:10.136
左斜杠带星期
yyyy/MM/dd E HH:mm:ss
2022/11/04 星期五 10:11:10
日期转为字符串
照样来看,日志转为字符的format(格式化)方法
String format(Date date); StringBuffer format(Date date, StringBuffer toAppendTo ,Position pos);//参考DateFormat StringBuffer format(Date date, StringBuffer toAppendTo, FieldDelegate delegate);//跟上面的差不多只是做了一个字段替换 void subFormat(int patternCharIndex, int count, FieldDelegate delegate, StringBuffer buffer, boolean useDateFormatSymbols)//真正的格式化日期方法
其实实际应用中,我们基本只会用到第一个方法。
字符串转日期
同上 基本只用一个parse方法
Date parse(String source) throws ParseException;
测试示例
public class SimpleDateFormatTest { public static void main(String[] args) throws ParseException { Date date = new Date(); SimpleDateFormat simpleDateFormat = new SimpleDateFormat(); System.out.println("默认格式化日期:" + simpleDateFormat.format(date)); //自己设置的格式 simpleDateFormat.applyPattern("yyyy-MM-dd hh:mm:ss.SSS"); System.out.println("自己设置的格式:" + simpleDateFormat.format(date)); Date date1 = simpleDateFormat.parse("2022-11-03 10:49:06.973"); System.out.println("指定字符串转为日期对象:" + date1.toString()); System.out.println("===================================="); simpleDateFormat.applyPattern("yyyy-MM-dd HH:mm:ss"); System.out.println(simpleDateFormat.format(date)); simpleDateFormat.applyPattern("yyyy-MM-dd HH:mm:ss.SSS"); System.out.println(simpleDateFormat.format(date)); simpleDateFormat.applyPattern("yyyyMMddHHmmss"); System.out.println(simpleDateFormat.format(date)); simpleDateFormat.applyPattern("yyyyMMddHHmmssSSS"); System.out.println(simpleDateFormat.format(date)); simpleDateFormat.applyPattern("yyyy/MM/dd HH:mm:ss"); System.out.println(simpleDateFormat.format(date)); simpleDateFormat.applyPattern("yyyy/MM/dd HH:mm:ss.SSS"); System.out.println(simpleDateFormat.format(date)); simpleDateFormat.applyPattern("yyyy/MM/dd E HH:mm:ss.SSS"); System.out.println(simpleDateFormat.format(date)); simpleDateFormat.applyPattern("yyyy-MM-dd E HH:mm:ss"); System.out.println(simpleDateFormat.format(date)); simpleDateFormat.applyPattern("yyyy-MM-dd E a HH:mm:ss"); System.out.println(simpleDateFormat.format(date)); simpleDateFormat.applyPattern("yyyy年MM月dd日 HH:mm:ss"); System.out.println(simpleDateFormat.format(date)); simpleDateFormat.applyPattern("yyyy年MM月dd日 E a HH:mm:ss"); System.out.println(simpleDateFormat.format(date)); } }
编写一个简单的日期工具类
工具类
package com.date; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; /** * @author 三文鱼先生 * @title * @description * @date 2022/11/4 **/ public class DateUtil { //常用的日期格式 //这里可以考虑用一个List把单元字符组合起来 用的时候再去拼接 public static final String SecondPattern = "yyyy-MM-dd HH:mm:ss"; public static final String yyyyMMddHHmmss = "yyyyMMddHHmmss"; public static final String MillisecondPattern = "yyyy-MM-dd HH:mm:ss.SSS"; public static final String yyyyMMddHHmmssSSS = "yyyyMMddHHmmssSSS"; public static final String WeekSecond = "yyyy-MM-dd E HH:mm:ss"; public static final String WeekIntervalSecond = "yyyy-MM-dd E a HH:mm:ss"; public static final String YMDChinese = "yyyy年MM月dd日 HH:mm:ss"; public static final String SlashSecond = "yyyy/MM/dd HH:mm:ss"; public static final String SlashWeekSecond = "yyyy/MM/dd E HH:mm:ss"; public static final String SlashWeekIntervalSecond = "yyyy/MM/dd E a HH:mm:ss"; //只用于获取星期 public static final String[] weeks = {"星期天","星期一","星期二","星期三","星期四","星期五","星期六"}; //主要的格式化功能类 private static final SimpleDateFormat format = new SimpleDateFormat(SecondPattern); //主要的日期操作类 private static Calendar calendar = null; /** * @description 根据日期获取指定天数前的日期 * @author 三文鱼先生 * @date 17:07 2022/11/4 * @param date 日期 * @param amount 天数 正数为加 负数未减 * @return java.util.Date **/ public static Date getDateWithDay(Date date , int amount) { return operateDate(date , Calendar.DATE , amount); } /** * @description 获取七天前的时间 * @author 三文鱼先生 * @date 17:08 2022/11/4 * @param date 日期 * @return java.util.Date **/ public static Date getDateWithSevenDays(Date date) { return operateDate(date , Calendar.DATE , -6); } /** * @description 以指定参数操作当前时间 可以加减 * @author 三文鱼先生 * @date 17:09 2022/11/4 * @param date 操作的日期 * @param field 操作的时间字段 比如Calendar.DATE、Calendar.YEAR等 * @param amount 操作的数量 正数为加 负数未减 * @return java.util.Date **/ public static Date operateDate(Date date ,int field , int amount) { if(calendar == null) calendar = format.getCalendar(); else calendar.clear(); calendar.setTime(date); calendar.add(field , amount); return calendar.getTime(); } /** * @description 根据指定的格式从字符串种获取日期 再进行操作 * @author 三文鱼先生 * @date 17:11 2022/11/4 * @param str 日期字符串 * @param pattern 字符串格式 * @param field 操作的字段 比如Calendar.DATE、Calendar.YEAR等 * @param amount 操作的数量 * @return java.util.Date **/ public static Date operateDateWithString(String str , String pattern, int field , int amount) { if(calendar == null) calendar = format.getCalendar(); else calendar.clear(); calendar.setTime(parseStr(str , pattern)); calendar.add(field , amount); return calendar.getTime(); } /** * @description 获取日期中的星期 * @author 三文鱼先生 * @date 17:13 2022/11/4 * @param date 日期 * @return java.lang.String **/ public static String getWeekFromDate(Date date) { if(calendar == null) calendar = format.getCalendar(); else calendar.clear(); calendar.setTime(date); return weeks[calendar.get(Calendar.DAY_OF_WEEK) - 1]; } /** * @description 以默认的格式将日期转为字符串 * @author 三文鱼先生 * @date 17:13 2022/11/4 * @param date 日期 * @return java.lang.String **/ public static String formatStr(Date date) { return format.format(date); } /** * @description 以指定的日期和格式 获取字符串 * @author 三文鱼先生 * @date 17:14 2022/11/4 * @param date 日期 * @param pattern 日期格式 * @return java.lang.String **/ public static String formatStr(Date date, String pattern) { if(!pattern.equals(format.toPattern())) { format.applyPattern(pattern); } return format.format(date); } /** * @description 以默认的格式接受字符串 转为日期 * @author 三文鱼先生 * @date 17:15 2022/11/4 * @param str 日期字符串 * @return java.util.Date **/ public static Date parseStr(String str) { return parseStr(str , SecondPattern); } /** * @description 以指定的格式从字符串里获取日期 * @author 三文鱼先生 * @date 17:15 2022/11/4 * @param str 日期字符串 * @param pattern 日期格式 * @return java.util.Date **/ public static Date parseStr(String str, String pattern) { if(!pattern.equals(format.toPattern())) { format.applyPattern(pattern); } Date date = null; try { date = format.parse(str); } catch (ParseException parseException) { parseException.printStackTrace(); } return date; } }
测试示例
package com.date; import java.util.Calendar; import java.util.Date; /** * @author 三文鱼先生 * @title * @description * @date 2022/11/4 **/ public class UtilTest { public static void main(String[] args) { System.out.println("以默认格式获取当前时间字符串:" + DateUtil.formatStr(new Date())); System.out.println("以指定格式获取当前时间字符串:" + DateUtil.formatStr(new Date() , DateUtil.WeekSecond)); //以指定格式从字符中获取日期对象 自行选择要用的数据 Date date1 = DateUtil.parseStr("2022-11-04 16:28:07"); System.out.println("以默认格式从字符串中获取的日期对象:" + DateUtil.formatStr(date1)); Date date2 = DateUtil.parseStr("2022年11月04日 16:28:07" , DateUtil.YMDChinese); System.out.println("以指定格式从字符串中获取的日期对象:" + DateUtil.formatStr(date2 , DateUtil.YMDChinese)); System.out.println("获取日期中的星期 今天是:" + DateUtil.getWeekFromDate(new Date())); //日期的加减 System.out.println("================日期的加减===================="); Date date4 = new Date(); System.out.println("现在的日期是:" + DateUtil.formatStr(date4 , DateUtil.WeekSecond)); Date date3 = DateUtil.getDateWithDay(date4, -1); System.out.println("昨天的日期为:" + DateUtil.formatStr(date3 , DateUtil.WeekSecond)); System.out.println("一周前的日期为:" + DateUtil.formatStr(DateUtil.getDateWithSevenDays(date4) , DateUtil.WeekSecond)); System.out.println("去年今天的日期为:" + DateUtil.formatStr(DateUtil.operateDate(date4 , Calendar.YEAR , -1))); Date date5 = DateUtil.operateDateWithString("2022-11-04 16:28:07" , DateUtil.SecondPattern , Calendar.DATE , -1); System.out.println("以字符串形式操作日期减去一天:" + DateUtil.formatStr(date5 , DateUtil.SecondPattern)); } }
总结
就是理一下Date、Calendar、DateFormat三者之间的关系。
到此这篇关于Java中常用的日期类的文章就介绍到这了,更多相关Java常用日期类内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!