JavaRush /Java 博客 /Random-ZH /如何不迷失在时间中 - 日期时间和日历

如何不迷失在时间中 - 日期时间和日历

已在 Random-ZH 群组中发布
你好!今天我们将开始使用一种我们以前从未遇到过的新数据类型,即日期。 如何不迷失在时间中 - 日期时间和日历 - 1我认为没有必要解释什么是日期:)原则上,在Java中用常规字符串编写当前日期和时间是很有可能的。
public class Main {
   public static void main(String[] args) {

       String date = "June 11, 2018";
       System.out.println(date);
   }
}
但这种方法有很多缺点。该类String是为处理文本而创建的,并且具有适当的方法。如果我们需要以某种方式管理日期(例如,增加 2 小时),String这里就行不通了。或者,例如,显示程序编译到控制台时的当前日期和时间。它在这里String也没有帮助:当你编写代码并运行它时,时间会改变并且不相关的内容将显示在控制台中。因此,在 Java 中,它的创建者提供了几个用于处理日期和时间的类。第一个是上课java.util.Date

Java 日期类

我们给它起全名是因为Java中的另一个包中也有一个类java.sql.Date。不要混淆!您需要了解的第一件事是,它存储自 1970 年 1 月 1 日以来经过的日期(以毫秒为单位) 。这个日期甚至还有一个单独的名称——“Unix 时间”。这是一个非常有趣的方式,你不同意吗?:) 要记住的第二件事:如果您Date使用空构造函数创建对象,则结果将是创建对象时的当前日期和时间String您还记得我们是如何写到这样的任务对于日期格式来说是有问题的吗?班级Date很容易就解决了。
public class Main {
   public static void main(String[] args) {

       Date date = new Date();
       System.out.println(date);
   }
}
多次运行此代码,您将看到每次时间将如何变化:)这是可能的,因为它以毫秒为单位存储:它们是最小的时间单位,这就是结果如此准确的原因。还有另一个构造函数Date:您可以指定从 1970 年 1 月 1 日 00:00 到所需日期经过的确切毫秒数,并且将创建它:
public class Main {
   public static void main(String[] args) {

       Date date = new Date(1212121212121L);
       System.out.println(date);
   }
}
控制台输出:

Fri May 30 08:20:12 MSD 2008
我们是2008年5月30日拿到的。“Fri”表示一周中的某一天——“Friday”(星期五),而 MSD——“莫斯科夏令时”(莫斯科夏令时)。毫秒以 格式传输long,因为它们的数字通常不适合int. 那么,我们的工作中可能需要什么样的日期操作呢?嗯,最明显的当然是比较。确定一个日期是否晚于或早于另一个日期。这可以通过不同的方式来完成。例如,您可以调用 . 方法Date.getTime(),它将返回自 1970 年 1 月 1 日午夜以来经过的毫秒数。让我们在两个 Date 对象上调用它并将它们相互比较:
public class Main {
   public static void main(String[] args) {

       Date date1 = new Date();

       Date date2 = new Date();

       System.out.println((date1.getTime() > date2.getTime())?
               "date1 is later than date2" : "date1 is earlier than date2");
   }
}
结论:

date1 раньше date2
但还有一种更方便的方法,即使用类的特殊方法Datebefore(),after()equals()。它们都以boolean. 该方法before()检查我们的日期是否早于我们作为参数传递的日期:
public class Main {
   public static void main(String[] args) throws InterruptedException {

       Date date1 = new Date();

       Thread.sleep(2000);//pause the program for 2 seconds
       Date date2 = new Date();

       System.out.println(date1.before(date2));
   }
}
控制台输出:

true
该方法以类似的方式工作after();它检查我们的日期是否晚于我们作为参数传递的日期:
public class Main {
   public static void main(String[] args) throws InterruptedException {

       Date date1 = new Date();

       Thread.sleep(2000);//pause the program for 2 seconds
       Date date2 = new Date();

       System.out.println(date1.after(date2));
   }
}
控制台输出:

false
在我们的示例中,我们让程序休眠 2 秒,以便保证两个日期不同。date1在快速计算机上,创建和之间的时间date2可能小于一毫秒,在这种情况下, 和before()和 都after()将返回falseequals()但在这种情况下该方法会返回true!毕竟,它会准确比较每个日期自 1970 年 1 月 1 日 00:00 以来经过的毫秒数。仅当对象匹配到毫秒时才会被视为相等:
public static void main(String[] args) {

   Date date1 = new Date();
   Date date2 = new Date();

   System.out.println(date1.getTime());
   System.out.println(date2.getTime());

   System.out.println(date1.equals(date2));
}
这里还有一些你需要注意的事情。如果您在 Oracle 网站上打开该类的文档Date,您将看到它的许多方法和构造函数已被指定为Deprecated(“已弃用”)。这里,请看:Class Date 以下是 Java 的创建者自己对那些已被弃用的类部分的评价: “用 @Deprecated 注释的程序元素是程序员不鼓励使用的东西,通常是因为它是危险的,或者因为有更好的选择。” 这并不意味着这些方法根本不能使用。此外,如果您自己尝试在 IDEA 中使用它们运行代码,它很可能会起作用。让我们以已弃用的方法为例Date.getHours(),该方法返回对象的小时数Date
public static void main(String[] args) {

   Date date1 = new Date();

   System.out.println(date1.getHours());

}
例如,如果您运行代码时,时间是 14:21,它将显示数字 14。正如您所看到的,已弃用的方法被划掉了,但它工作得很好。这些方法没有完全删除,以免破坏一堆已经使用它们编写的代码。也就是说,这些方法并没有被“破坏”或“删除”,只是因为有更方便的替代方法,所以不建议使用它们。顺便说一句,文档中对此进行了描述: 如何不迷失在时间中 - 日期时间和日历 - 2Date 类的大多数方法已移至其改进的扩展版本 - 类Calendar。我们会进一步了解他:)

Java日历

Java 1.1 引入了一个新类 - Calendar. 他使 Java 中的日期处理比以前看起来更容易一些。我们将使用的类的唯一实现Calendar是类GregorianCalendar(它实现了公历,世界上大多数国家都按照公历生活)。它的主要便利之处在于它可以以更方便的格式处理日期。例如,他可以:
  • 在当前日期上添加一个月或一天
  • 检查年份是否为闰年;
  • 获取单独的日期组成部分(例如,从整个日期获取月份数)
  • 而且,它内部还开发了一个非常方便的常数系统(我们将在下面看到其中的许多常数)。
该类的另一个重要区别Calendar是它实现了一个常量Calendar.Era:您可以将日期设置为BC时代(“Before Christ” - 基督诞生之前,即“在我们的时代之前”)或AC(“After Christ” - “我们的时代”)。让我们通过示例来看看这一切。让我们创建一个日期为 2017 年 1 月 25 日的日历:
public static void main(String[] args) {

  Calendar calendar = new GregorianCalendar(2017, 0 , 25);
}
类中的月份Calendar(顺便说一下Date,如 中所示)从零开始,因此我们传递数字 0 作为第二个参数。使用类时最重要的Calendar是要理解这是一个日历,而不是一个单独的日期。 如何不迷失在时间中 - 日期时间和日历 - 3日期只是代表特定时间段的一系列数字。日历是一个完整的设备,您可以使用它对日期执行很多操作:) 如果您尝试将 Calendar 对象输出到控制台,则可以非常清楚地看到这一点: 输出:

java.util.GregorianCalendar[time=?,areFieldsSet=false,areAllFieldsSet=false,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Europe/Moscow",offset=10800000,dstSavings=0,useDaylight=false,transitions=79,lastRule=null],firstDayOfWeek=2,minimalDaysInFirstWeek=1,ERA=?,YEAR=2017,MONTH=0,WEEK_OF_YEAR=?,WEEK_OF_MONTH=?,DAY_OF_MONTH=25,DAY_OF_YEAR=?,DAY_OF_WEEK=?,DAY_OF_WEEK_IN_MONTH=?,AM_PM=0,HOUR=0,HOUR_OF_DAY=0,MINUTE=0,SECOND=0,MILLISECOND=?,ZONE_OFFSET=?,DST_OFFSET=?]
看看有多少信息!日历有一堆常规日期没有的属性,所有这些属性都会输出到控制台(这就是toString()类中方法的工作原理Calendar)。如果,在工作时,您只需要从日历中获取一个简单的日期,即 object Date- 这是使用方法完成的Calendar.getTime()(名称不是最符合逻辑,但什么也做不了):
public static void main(String[] args) {

   Calendar calendar = new GregorianCalendar(2017, 0 , 25);
   Date date = calendar.getTime();
   System.out.println(date);
}
结论:

Wed Jan 25 00:00:00 MSK 2017
现在我们已经将日历“简化”为常规日期。让我们继续。除了月份的数字符号外,Calendar课堂上还可以使用常量。常量是类的静态字段,Calendar其值已设置且无法更改。这个选项实际上更好,因为它提高了代码的可读性。
public static void main(String[] args) {
   GregorianCalendar calendar = new GregorianCalendar(2017, Calendar.JANUARY , 25);
}
Calendar.JANUARY— 指示月份的常量之一。通过这个命名选项,没有人会忘记,例如,数字“3”表示四月,而不是我们习惯的第三个月 - 三月。您只需编写Calendar.APRIL- 就是这样:) 所有日历字段(日、月、分、秒等)都可以使用方法单独设置set()。这非常方便,因为Calendar每个字段在类中都有自己的常量,并且最终的代码看起来尽可能简单。例如,在前面的示例中,我们创建了一个日期,但没有为其设置当前时间。我们将时间设置为 19:42:12
public static void main(String[] args) {
   Calendar calendar = new GregorianCalendar();
   calendar.set(Calendar.YEAR, 2017);
   calendar.set(Calendar.MONTH, 0);
   calendar.set(Calendar.DAY_OF_MONTH, 25);
   calendar.set(Calendar.HOUR_OF_DAY, 19);
   calendar.set(Calendar.MINUTE, 42);
   calendar.set(Calendar.SECOND, 12);

   System.out.println(calendar.getTime());
}
结论:

Wed Jan 25 19:42:12 MSK 2017
我们调用该方法set(),向其传递一个常量(取决于我们要更改的字段)以及该字段的新值。事实证明,该方法set()是一种“超级设置器”,它可以不为一个字段设置值,而是为多个字段设置值:) 在类中添加和减去值Calendar是使用add(). 您需要向其中传递您想要更改的字段以及数字 - 您想要从当前值中添加/减去的确切数量。例如,让我们将创建的日期设置回 2 个月前:
public static void main(String[] args) {
   Calendar calendar = new GregorianCalendar(2017, Calendar.JANUARY , 25);
   calendar.set(Calendar.HOUR, 19);
   calendar.set(Calendar.MINUTE, 42);
   calendar.set(Calendar.SECOND, 12);

   calendar.add(Calendar.MONTH, -2);//to subtract a value - a negative number must be passed to the method
   System.out.println(calendar.getTime());
}
结论:

Fri Nov 25 19:42:12 MSK 2016
伟大的!我们把日期定在两个月前。这样一来,不仅月份变了,年份也变了,从2017年变成了2016年。移动日期时当前年份的计算当然是自动进行的,不需要手动控制。但如果出于某种目的您需要禁用此行为,您可以这样做。一种特殊的方法roll()可以对值进行加减操作而不影响其他值。例如,像这样:
public static void main(String[] args) {
   Calendar calendar = new GregorianCalendar(2017, Calendar.JANUARY , 25);
   calendar.set(Calendar.HOUR, 10);
   calendar.set(Calendar.MINUTE, 42);
   calendar.set(Calendar.SECOND, 12);

   calendar.roll(Calendar.MONTH, -2);
   System.out.println(calendar.getTime());
}
我们所做的与上一个示例完全相同 - 我们从当前日期减去 2 个月。但现在代码的工作方式有所不同:月份从 1 月更改为 11 月,但年份仍与 2017 年相同!结论:

Sat Nov 25 10:42:12 MSK 2017
更远。上面我们说过,一个对象的所有字段都Calendar可以单独获取。该方法负责get()
public static void main(String[] args) {
   GregorianCalendar calendar = new GregorianCalendar(2017, Calendar.JANUARY , 25);
   calendar.set(Calendar.HOUR, 10);
   calendar.set(Calendar.MINUTE, 42);
   calendar.set(Calendar.SECOND, 12);

   System.out.println("Year: " + calendar.get(Calendar.YEAR));
   System.out.println("Month: " + calendar.get(Calendar.MONTH));
   System.out.println("Number of the week in the month: " + calendar.get(Calendar.WEEK_OF_MONTH));// serial number of the week in the month

   System.out.println("Number: " + calendar.get(Calendar.DAY_OF_MONTH));

   System.out.println("Watch: " + calendar.get(Calendar.HOUR));
   System.out.println("Minutes: " + calendar.get(Calendar.MINUTE));
   System.out.println("Seconds: " + calendar.get(Calendar.SECOND));
   System.out.println("Milliseconds: " + calendar.get(Calendar.MILLISECOND));

}
结论:

Год: 2017 
Месяц: 0 
Порядковый номер недели в месяце: 4 
Число: 25 
Часы: 10 
Минуты: 42 
Секунды: 12 
Миллисекунды: 0
也就是说,类中除了“super-setter”之外,Calendar还有一个“super-getter”:) 另一个有趣的点当然是与eras 一起工作。要创建日期“BC”,您需要使用字段Calendar.Era 例如,让我们创建一个指示坎尼战役的日期,汉尼拔在这场战役中击败了罗马军队。这发生在公元前 216 年 8 月 2 日。即:
public static void main(String[] args) {
   GregorianCalendar cannes = new GregorianCalendar(216, Calendar.AUGUST, 2);
   cannes.set(Calendar.ERA, GregorianCalendar.BC);

   DateFormat df = new SimpleDateFormat("dd MMM yyy GG");
   System.out.println(df.format(cannes.getTime()));
}
这里我们使用该类SimpleDateFormat以我们更容易理解的格式显示日期(字母“GG”负责显示时代)。结论:

02 авг 216 до н.э.
类中Calendar有更多方法和常量 ,请在文档中阅读它们:

迄今为止的换行符

要将 String 转换为 Date,您可以使用 Java 帮助器类 - SimpleDateFormat。这是将日期转换为您定义的格式所需的类。 如何不迷失在时间中 - 日期时间和日历 - 5反过来,它与DateFormat非常相似。两者之间唯一显着的区别是 SimpleDateFormat 可用于格式化(将日期转换为字符串)并将字符串解析为区域设置感知的日期,而 DateFormat 不支持区域设置。此外,DateFormat 是一个抽象类,为格式化和解析日期提供基本支持,而 SimpleDateFormat 是一个扩展 DateFormat 类的具体类。这是创建 SimpleDateFormat 对象并格式化 Date 的示例:
SimpleDateFormat formater = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = new Date(1212121212121L);

System.out.println(formatter.format(date));
在上面的示例中,我们使用了模式“yyyy-MM-dd HH:mm:ss”,这意味着:
  • 4 位数字表示年份 (yyyy);
  • 2 位数字表示月份 (MM);
  • 2 位数字表示日期 (dd);
  • 2 位数字表示小时,采用 24 小时格式 (HH);
  • 2 位数字表示分钟 (mm);
  • 2 位数字表示秒 (ss)。
分隔标记和图案符号的顺序被保留。控制台输出:
2008-05-30 08:20:12
SimpleDateFormat类有相当多的 模板字母。为了避免您感到困惑,我们将它们收集在表格中:
象征 描述 例子
G 时代(英语本地化 - AD 和 BC) 广告
y 年份(4 位数字) 2020年
yy 年份(最后 2 位数字) 20
yyyy 年份(4 位数字) 2020年
中号 月份数字(不带前导零) 8
毫米 月份编号(如果月份编号 < 10,则带有前导零) 04
MMM 三个字母的月份缩写(根据本地化)
MMMM 月份全名 六月
w 一年中的第几周(不带前导零) 4
WW 一年中的第几周(带前导零) 04
一个月中的一周(不带前导零) 3
WW 一个月中的一周(带前导零) 03
D 一年中的某一天 67
d 一个月中的某一天(不带前导零) 9
DD 一个月中的某一天(带前导零) 09
F 该月中的星期几(不带前导零) 9
FF 一个月中的星期几(带前导零) 09
星期几(缩写)
电子电气设备 星期几(完整) 星期五
星期几(不带前导零) 5
一周中的第几天(带前导零) 05
A AM/PM 标记 是。
H 24 小时格式的小时,不带前导零 6
赫赫 24 小时格式的时钟,带前导零 06
k 24 小时格式的小时数 18
K 12 小时格式的小时数 6
H 12 小时格式的时间,不带前导零 6
呵呵 12 小时格式的时间,带前导零 06
不带前导零的分钟 32
毫米 分钟带前导零 32
s 没有前导零的秒 十一
SS 带前导零的秒 十一
S 毫秒 第297章
z 时区 欧洲东部时间
Z RFC 822 格式的时区 300
模式字符组合示例:
样本 例子
日-月-年 2020年1月11日
年-月-日 2019-10-01
时:分:秒.SSS 23:59.59.999
yyyy-MM-dd HH:mm:ss 2018-11-30 03:09:02
yyyy-MM-dd HH:mm:ss.SSS 2016-03-01 01:20:47.999
yyyy-MM-dd HH:mm:ss.SSS Z 2013-13-13 23:59:59.999 +0100
如果你在格式上犯了一个小错误,你就可能成为 java.text.ParseException 的所有者,而这并不是一个特别令人愉快的成就。好了, SimpleDateFormat的简短介绍就结束了——让我们回到java string to date的翻译。 SimpleDateFormat为我们提供了这样的功能,我们将逐步完成这个过程。
  1. 创建需要设置日期的行:

    String strDate = "Sat, April 4, 2020";
  2. 我们创建一个新的SimpleDateFormat对象,其模板与字符串中的内容相匹配(否则我们将无法解析它):

    SimpleDateFormat formatter = new SimpleDateFormat("EEE, MMMM d, yyyy", Locale.ENGLISH);

    正如您所看到的,我们这里有一个 Locale 参数。如果我们省略它,它将使用默认的区域设置,该区域设置并不总是英语。

    如果语言环境与输入字符串不匹配,则绑定到该语言的字符串数据(例如我们的MonApril)将不会被识别,并且会抛出 java.text.ParseException,即使模式匹配也是如此。

    但是,如果我们使用非特定于语言的模板,则不必指定格式。例如 - yyyy-MM-dd HH:mm:ss

  3. 我们使用格式化程序创建一个日期,然后格式化程序从输入字符串中解析它:

    try {
      Date date = formatter.parse(strDate);
      System.out.println(date);
    }
    catch (ParseException e) {
      e.printStackTrace();
    }

    控制台输出:

    
    Sat Apr 04 00:00:00 EEST 2020

    嗯...但是格式不再一样了!

    为了制作相同的格式,我们再次使用格式化程序:

    System.out.println(formatter.format(date));

    控制台输出:

    
    Sat, April 4, 2020

SimpleDateFormat 和日历

SimpleDateFormat 允许您格式化您创建的所有日期和日历对象以供以后使用。让我们考虑一下与eras 合作这样一个有趣的观点。要创建“BC”日期,您需要使用Calendar.Era字段。例如,让我们创建一个表示坎尼战役的日期,汉尼拔在这场战役中击败了罗马军队。这发生在公元前 216 年 8 月 2 日。即:
public static void main(String[] args) {
   GregorianCalendar cannes = new GregorianCalendar(216, Calendar.AUGUST, 2);
   cannes.set(Calendar.ERA, GregorianCalendar.BC);

   DateFormat df = new SimpleDateFormat("dd MMM yyy GG");
   System.out.println(df.format(cannes.getTime()));
}
这里我们使用 SimpleDateFormat 类以我们更容易理解的格式显示日期(如上所示,字母“GG”负责显示时代)。结论:

02 авг 216 до н.э.

Java 日期格式

这是另一个案例。假设这种日期格式不适合我们:

Sat Nov 25 10:42:12 MSK 2017
所以就是这样。使用我们的 java 日期格式功能,您可以轻松地将其更改为您自己的日期格式:
public static void main(String[] args) {

   SimpleDateFormat dateFormat = new SimpleDateFormat("EEEE, d MMMM yyyy");
   Calendar calendar = new GregorianCalendar(2017, Calendar.JANUARY , 25);
   calendar.set(Calendar.HOUR, 10);
   calendar.set(Calendar.MINUTE, 42);
   calendar.set(Calendar.SECOND, 12);

   calendar.roll(Calendar.MONTH, -2);
   System.out.println(dateFormat.format(calendar.getTime()));
}
结论:

суббота, 25 Ноябрь 2017
好多了,对吧?:)
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION