-33

如何java.util.DateDateTimeFormatter便携式格式化?

我无法使用

Date in = readMyDateFrom3rdPartySource();
LocalDateTime ldt = LocalDateTime.ofInstant(in.toInstant(), ZoneId.systemDefault());
ldt.format(dateTimeFormatter);

因为我担心使用 ofZoneId.systemDefault()会带来一些变化。

我需要完全格式化我拥有的那个对象。

更新

注意:时间就是时间。不是空间。时区是非常粗略的经度度量,即空间。我不需要它。只有时间(和日期)。

更新 2

我编写了以下程序,证明它Date不仅包含正确的“即时”:

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class DataNature2 {

   public static void main(String[] args) throws ParseException {

      SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

      String dateTimeString = "1970-01-01 00:00:01";

      Date date = simpleDateFormat.parse(dateTimeString);

      System.out.println("1 second = " + date.getTime());

   }
}

输出如下:

1 second = -10799000

虽然它应该是

1 second = 1000

如果Date是“即时”。

数字107990003*60*60*1000-1000- 我当地时间的时区偏移量。

这意味着,Date该类是双重的。它的毫秒部分可能会相对于hh mm ss部分偏移时区偏移。

这意味着,如果任何实用程序Date根据其部件 () 返回对象,hh mm ss则它会隐式转换为本地时间。并且getTime()同时意味着不同的时间。我的意思是在不同的机器上,如果这个程序同时运行,getTime()将是相同的,而时间部分将是不同的。

因此,开头的代码示例是正确的:它采用 的“即时”部分Date,并提供系统时区部分,该部分在Date. 即它将对偶Date对象转换为LocalDateTime具有相同部分的显式对象。因此,之后的格式是正确的。

更新 3

活动更有趣:

Date date = new Date(70, 0, 1, 0, 0, 1);
assertEquals(1000, date.getTime());

这个测试失败了。

UDPATE 4

新代码。献给所有信徒。

public class DataNature3 {

   public static class TZ extends java.util.TimeZone {


      private int offsetMillis;

      public TZ(int offsetHours) {
         this.offsetMillis = offsetHours * 60 * 60 * 1000;
      }

      @Override
      public int getOffset(int era, int year, int month, int day, int dayOfWeek, int milliseconds) {
         throw new UnsupportedOperationException();
      }

      @Override
      public void setRawOffset(int offsetMillis) {
         this.offsetMillis = offsetMillis;
      }

      @Override
      public int getRawOffset() {
         return offsetMillis;
      }

      @Override
      public boolean useDaylightTime() {
         return false;
      }

      @Override
      public boolean inDaylightTime(Date date) {
         return false;
      }
   }

   public static void main(String[] args) {

      Date date = new Date(0);

      for(int i=0; i<10; ++i) {

         TimeZone.setDefault(new TZ(i));

         if( i<5 ) {
            System.out.println("I am date, I am an instant, I am immutable, my hours property is " + date.getHours() + ", Amen!");
         }
         else {
            System.out.println("WTF!? My hours property is now " + date.getHours() + " and changing! But I AM AN INSTANT! I AM IMMUTABLE!");
         }

      }

      System.out.println("Oh, please, don't do that, this is deprecated!");

   }
}

输出:

I am date, I am an instant, I am immutable, my hours property is 0, Amen!
I am date, I am an instant, I am immutable, my hours property is 1, Amen!
I am date, I am an instant, I am immutable, my hours property is 2, Amen!
I am date, I am an instant, I am immutable, my hours property is 3, Amen!
I am date, I am an instant, I am immutable, my hours property is 4, Amen!
WTF!? My hours property is now 5 and changing! But I AM AN INSTANT! I AM IMMUTABLE!
WTF!? My hours property is now 6 and changing! But I AM AN INSTANT! I AM IMMUTABLE!
WTF!? My hours property is now 7 and changing! But I AM AN INSTANT! I AM IMMUTABLE!
WTF!? My hours property is now 8 and changing! But I AM AN INSTANT! I AM IMMUTABLE!
WTF!? My hours property is now 9 and changing! But I AM AN INSTANT! I AM IMMUTABLE!
Oh, please, don't do that, this is deprecated!
4

3 回答 3

36

TL;DR:你关心系统本地时区的使用是对的,但你应该在这个过程的早期就关心,当你首先使用系统本地时区来构造aDate时。

如果您只希望格式化字符串具有与,等返回的相同组件,Date.getDate()那么您的原始代码是合适的:Date.getMonth()Date.getYear()

LocalDateTime ldt = LocalDateTime.ofInstant(in.toInstant(), ZoneId.systemDefault());

你说你“害怕使用 ofZoneId.systemDefault()会带来一些变化”——但这正是Date.getDate()etc 使用的。

Date没有任何类型的“双重合同”可以让您将其视为无时区表示。这只是一个瞬间。几乎每一个让您将其构造或解构为组件的方法都被清楚地记录为使用系统默认时区,就像您使用ZoneId.systemDefault(). (一个值得注意的例外是UTC方法。)

隐式使用系统默认时区与有效的Date时区表示不同,很容易证明原因:它很容易丢失数据。考虑“2017 年 3 月 26 日,凌晨 1:30”的无时区日期和时间。您可能希望能够对其进行文本表示,对其进行解析,然后再对其进行重新格式化。如果您在欧洲/伦敦时区执行此操作,则会遇到问题,如下所示:

import java.util.*;
import java.time.*;
import java.time.format.*;

public class Test {

    public static void main(String[] args) {
        TimeZone.setDefault(TimeZone.getTimeZone("Europe/London"));
        Date date = new Date(2017 - 1900, 3 - 1, 26, 1, 30);

        Instant instant = date.toInstant();
        ZoneId zone = ZoneId.systemDefault();
        LocalDateTime ldt = LocalDateTime.ofInstant(instant, zone);
        System.out.println(ldt); // Use ISO-8601 by default
    }
}

输出为 2017-03-26T02:30。这并不是说代码中有一个错误 - 如果您将其更改为显示上午 9:30,那将正常工作。

问题是由于 DST ,欧洲/伦敦时区不存在2017-03-26T01:30 - 凌晨 1 点,时钟跳到凌晨 2 点。

因此,如果您对这种损坏感到满意,那么请使用Date系统本地时区。否则,请勿尝试Date用于此目的。

如果您绝对必须Date这种破碎的方式使用,使用已被弃用约 20 年的方法,因为它们具有误导性,但您可以更改系统时区,然后将其更改为没有的东西 -从来没有 - DST。UTC 是显而易见的选择。此时,您可以在本地日期/时间之间进行转换,而Date不会丢失数据。仍然是一个不好的使用Date,它只是一个瞬间Instant,但至少你不会丢失数据。

或者,您可以确保每当您Date从本地日期/时间构造 a 时,都使用 UTC 进行转换,当然,而不是系统本地时区......无论是通过Date.UTC方法还是使用解析文本SimpleDateFormat那是UTC,或者不管它是什么。不幸的是,您还没有告诉我们任何有关您的Date价值从何而来的信息……

于 2017-04-22T06:36:01.107 回答
7
于 2018-02-20T01:59:31.677 回答
2

您可以根据您的要求使用。

   java.util.Date
   DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
   Date date = new Date();
   System.out.println(dateFormat.format(date));

   java.util.Calendar
   DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
   Calendar cal = Calendar.getInstance();
   System.out.println(dateFormat.format(cal.getTime()));

   java.time.LocalDateTime
   DateTimeFormatter dateTimeFormat = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
   LocalDateTime localDateTime = LocalDateTime.now();
   System.out.println(dateTimeFormat.format(localDateTime));
于 2017-04-20T18:48:37.733 回答