如何获取Date.toString()以生成SimpleDateFormat可以针对1970年1月1日左右的日期正确解析的输出(我假设这也适用于1968年和1969年的冬天)
如果我运行以下命令,
System.out.println(TimeZone.getDefault());
Date date = new Date(0);
SimpleDateFormat sdf = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy");
Date date2 = sdf.parse(date.toString());
System.out.println("date: " + date);
System.out.println("date2: " + date2);
Date date3 = sdf.parse(date2.toString());
System.out.println("date3: " + date3);
此打印
sun.util.calendar.ZoneInfo[id="Europe/London",offset=0,dstSavings=3600000,useDaylight=true,transitions=242,lastRule=java.util.SimpleTimeZone[id=Europe/London,offset=0,dstSavings=3600000,useDaylight=true,startYear=0,startMode=2,startMonth=2,startDay=-1,startDayOfWeek=1,startTime=3600000,startTimeMode=2,endMode=2,endMonth=9,endDay=-1,endDayOfWeek=1,endTime=3600000,endTimeMode=2]]
date: Thu Jan 01 01:00:00 GMT 1970
date2: Thu Jan 01 02:00:00 GMT 1970
date3: Thu Jan 01 03:00:00 GMT 1970
问题在于伦敦是1970年1月1日在BST。所以正确的日期应该是
date: Thu Jan 01 01:00:00 BST 1970
或
date: Thu Jan 01 00:00:00 GMT 1970
但似乎两者混淆。
虽然我很想不支持java.util.Date,但这对我来说不是一个选择。
答案 0 :(得分:4)
我可以引用https://bugs.openjdk.java.net/browse/JDK-6609362?jql=text%20~%20%22epoch%20gmt%22:
请使用Z格式化和解析历史时区偏移量更改 为避免与历史时区名称更改混淆。
public class Test { public static void main(String[] args) throws ParseException { TimeZone.setDefault(TimeZone.getTimeZone("Europe/London")); SimpleDateFormat f = new SimpleDateFormat("EEE MMM dd HH:mm:ss Z yyyy"); Date d = new Date(0); for (int i = 0; i < 10; i++) { String s = f.format(d); System.out.println(s); d = f.parse(s); } } } ```
答案 1 :(得分:4)
您的输入无效,因为BST
(英国夏令时)在冬季无效。
BST
无法可靠地解析,因为它是非标准的非唯一伪区域。
无需弄乱SimpleDateFormat
。让现代的 java.time 类完成繁重的工作。
尽管我很想不支持
java.util.Date
,但对我来说这不是一个选择。
在代码的边缘,转换为旧类和现代类。
// Convert from legacy to modern.
Instant instant = myJavaUtilDate.toInstant() ;
// Convert from modern to legacy.
java.util.Date myJavaUtilDate = Date.from( instant ) ;
BST
您BST
中的“ B”显然是英国人。但是BST
在此上下文中表示British Summer Time。这意味着Daylight Saving Time (DST)处于夏季,而不是冬季。因此,您在一月份的输入字符串中加上BST
简直是。
您在1970年使用英国时区的时刻的例子进一步复杂化了。
在英国,夏令时使用标准时间的夏令时在夏令时(UTC)(+01:00)之前的一小时,冬季在标准时间在夏令时的零(+00:00)的做法是 current < / em>练习。并非总是如此。
早在1970年,英国就是trialling a “double-summertime”。在1968-1971年的实验中,冬天的时间比UTC早了一小时,而不是零,夏天的时间比UTC早了两个小时,而不是现在的一小时。 。这使英国人的时间与欧洲大陆更加相同,并希望减少事故的发生。
因此,如果我们调整1970年1月的某个时刻,则可以预期在时区Europe/London
之前跳到一小时。尽管在2019年1月这一刻,我们预计不会出现跳跃,但英国的一天中的时间将与UTC相同(相对于UTC的偏移量为零小时-分钟-秒)。
避免使用这些2-4个字符的伪区域,例如BST
。它们不是标准化的。它们甚至都不是唯一的!因此BST
可以解释为与British Summer Time
一样的时区Pacific/Bougainville
。
以Continent/Region
的格式指定proper time zone name,例如America/Montreal
,Africa/Casablanca
或Pacific/Auckland
。切勿使用2-4个字母的缩写,例如BST
或EST
或IST
,因为它们不是真正的时区,不是标准化的,甚至不是唯一的( !)。
您可以轻松地在传统日期时间类和现代日期时间类之间进行转换。新的转换方法已添加到旧类中。按the naming conventions查找from
,to
和valueOf
方法。
java.util.Date
⇄java.time.Instant
java.util.GregorianCalendar
⇄java.time.ZonedDateTime
java.sql.Date
⇄java.time.LocalDate
java.sql.Timestamp
⇄java.time.Instant
您在1970年1月1日的00:00输入字符串恰好是传统和现代日期时间类都使用的epoch reference date。我们对此值有一个常数。
Instant epoch = Instant.EPOCH ;
instant.toString():1970-01-01T00:00:00Z
在您的Europe/London
时区看到同一时刻。
ZoneId z = ZoneId.of( "Europe/London" ) ;
ZonedDateTime zdt = epoch.atZone( z ) ;
zdt.toString():1970-01-01T01:00 + 01:00 [欧洲/伦敦]
请注意,上述 Double-Summertime 实验即已生效。如果我们在2019年尝试相同的代码,则offset-from-UTC为零。
ZonedDateTime zdt2019 =
Instant
.parse( "2019-01-01T00:00Z" )
.atZone( ZoneId.of( "Europe/London" ) )
;
zdt2019.toString():2019-01-01T00:00Z [欧洲/伦敦]
要转换为java.util.Date
,我们需要一个java.time.Instant
对象。 Instant
代表UTC的时刻。我们可以从Instant
对象中提取ZonedDateTime
,从而有效地将区域调整为UTC。相同的时刻,不同的时钟时间。
Instant instant = zdt.toInstant():
现在应该回到1970年1月1日T00:00:00Z的纪元。
instant.toString():1970-01-01T00:00:00Z
要获取java.util.Date
对象,您可能需要与尚未更新为 java.time 类的旧代码进行互操作,请使用添加到旧类中的新Date.from
方法。
java.util.Date d = Date.from( instant ) ; // Same moment, but with possible data-loss as nanoseconds are truncated to milliseconds.
d.toString():格林尼治标准时间1970年1月1日00:00:00
顺便说一句,当从Instant
转换为Date
时,请注意可能的数据丢失。现代类的分辨率为nanoseconds,而旧类的分辨率为milliseconds。因此,您的小数秒的一部分可能会被截断。
查看run live at IdeOne.com以上的所有代码。
要转换另一个方向,请使用Date::toString
方法。
Instant instant = d.toInstant() ;
避免使用自定义格式的文本来交换日期时间值。将日期时间值序列化为可读文本时,请仅使用标准的ISO 8601格式。 java.time 类默认使用这些格式。
您正在尝试进行解析的那些字符串是糟糕的格式,因此切勿用于数据交换。
java.time框架已内置在Java 8及更高版本中。这些类取代了麻烦的旧legacy日期时间类,例如java.util.Date
,Calendar
和SimpleDateFormat
。
目前位于Joda-Time的maintenance mode项目建议迁移到java.time类。
要了解更多信息,请参见Oracle Tutorial。并在Stack Overflow中搜索许多示例和说明。规格为JSR 310。
您可以直接与数据库交换 java.time 对象。使用符合JDBC driver或更高版本的JDBC 4.2。不需要字符串,不需要java.sql.*
类。
在哪里获取java.time类?
ThreeTen-Extra项目使用其他类扩展了java.time。该项目为将来可能在java.time中添加内容提供了一个试验场。您可能会在这里找到一些有用的类,例如Interval
,YearWeek
,YearQuarter
和more。
答案 2 :(得分:2)
这就是为什么我讨厌日期库。
暗示,BST应该在夏季使用,日历也将其定义为这样。
sun.util.calendar.ZoneInfo[id="Europe/London",offset=0,dstSavings=3600000,useDaylight=true,transitions=242,lastRule=java.util.SimpleTimeZone[id=Europe/London,offset=0,dstSavings=3600000,useDaylight=true,startYear=0,startMode=2,startMonth=2,startDay=-1,startDayOfWeek=1,startTime=3600000,startTimeMode=2,endMode=2,endMonth=9,endDay=-1,endDayOfWeek=1,endTime=3600000,endTimeMode=2]]
除了The adventures of year-round British Summer Time!的TIL以外
在1966-67年间的进一步调查促使哈罗德·威尔逊(Harold Wilson)政府引入了英国标准时间实验,英国全年仍保留在GMT + 1上。 1968年10月和1971年10月31日,当时恢复了以前的安排。
如果您在此期间测试日期,则每次解析都会发现日期漂移一个小时,直到转换点为止。
欧洲/伦敦的时间代码是格林尼治标准时间,使用夏令时可以节省夏令时。
Date的toString
方法通过删除夏令时以选择要打印的时区来“标准化”输出。选项为GMT
和BST
。欧洲/伦敦时间01:00:00被打印为01:00:00 GMT (即使格林威治标准时间+1是格林尼治标准时间+ 1)。因此,换句话说,date.toString()
在该时期几乎无法正常工作,因为它使用GMT作为表面上不是GMT / CET的时区。 时间本身是正确的,但不是时区。
我可以提出的“最简单”的解决方案从健全性检查点来看比较讨厌,但是可能会变得更加优雅。
private static final Date experimentEnd = new Date(1971-1900, 11-1, 11);
private static final Date experimentStart = (new Date(1968-1900, 10-1, 26));
private static boolean bstExperimentTime(Date date) {
return date.after(experimentStart) && date.before(experimentEnd);
}
public static String forDateParsing(Date date) {
if(bstExperimentTime(date))
return date.toString().replace("GMT", "CET");
return date.toString();
}
public static String forDatePrinting(Date date) {
if(bstExperimentTime(date))
return date.toString().replace("GMT", "BST");
return date.toString();
}
您需要使用默认的“欧洲/伦敦”时区解析的任何日期都必须通过解析格式器来转换为GMT-> CET,这是正确的GMT + 1。
您需要使用默认的“欧洲/伦敦”时区进行打印的任何日期都必须通过解析格式器来转换为GMT-> BST,这是正确的显示。