在对故障进行故障排除时,会出现此方法行为中的一些奇怪现象。
有些国家通过改变时间来节省一些日光。例如,在时区"欧洲/巴黎",每年,时间从3月结束前1小时,10月结束1小时,均在凌晨2点到凌晨3点之间。例如,这导致10月,2016年30日凌晨02:15存在两次。
幸运的是,两个日期没有相同的时间戳(ms)数量,也没有可读的表示形式:
在巴黎时区实例化GregorianCalendar
对象后(使用SimpleDateFormat
),我们按照预期在向后移动之前02:15到达。
但是如果我们想使用.set()
为这个对象设置分钟,则+0200偏移信息会被破坏为+0100("同一时间",但是在时移之后)
有没有办法这样做,因为方法.add()
实际上保留了偏移信息?
// Instantiation
GregorianCalendar gc1 = new GregorianCalendar(TimeZone.getTimeZone("Europe/Paris"));
gc1.setTime(new SimpleDateFormat("yyyy-MM-dd hh:mm:ss Z").parse("2016-10-30 02:15:00 +0200"));
GregorianCalendar gc2 = (GregorianCalendar) gc1.clone();
System.out.println(gc1.getTime()); // Output : Sun Oct 30 02:15:00 CEST 2016 ; OK
System.out.println(gc2.getTime()); // Output : Sun Oct 30 02:15:00 CEST 2016 ; OK
// Set/add minutes
gc1.set(Calendar.MINUTE, 10);
gc2.add(Calendar.MINUTE, 10);
System.out.println(gc1.getTime()); // Output : Sun Oct 30 02:10:00 CET 2016 ; Unexpected
System.out.println(gc2.getTime()); // Output : Sun Oct 30 02:25:00 CEST 2016 ; OK
答案 0 :(得分:2)
一个丑陋的选择是在设置值后从gc1
减去1小时:
gc1.set(Calendar.MINUTE, 10);
gc1.add(Calendar.HOUR, -1);
结果为Sun Oct 30 02:10:00 CEST 2016
。
不幸的是,这似乎是Calendar
API可用的最佳解决方案。 set
方法的这种行为被报告为错误,而the recommendation in JDK bug tracker则使用add
来“修复”它:
在“后退”期间,日历不支持消歧,并且给定的当地时间被解释为标准时间。
要避免意外的DST达到标准时间更改,请调用add()以重置值。
我正在使用Java 8,所以似乎这个bug从未修复过。
旧类(Date
,Calendar
和SimpleDateFormat
)有lots of problems和design issues,它们将被新API取代。< / p>
如果您使用的是 Java 8 ,请考虑使用new java.time API。它更容易,less bugged and less error-prone than the old APIs。
如果您使用的是 Java&lt; = 7 ,则可以使用ThreeTen Backport,这是Java 8新日期/时间类的绝佳后端。对于 Android ,有ThreeTenABP(更多关于如何使用它here)。
以下代码适用于两者。 唯一的区别是:
java.time
,在ThreeTen Backport(或Android的ThreeTenABP)中为org.threeten.bp
),但类和方法名称是相同的。 Calendar
API转换要将GregorianCalendar
转换为新API,您可以执行以下操作:
// Paris timezone
ZoneId zone = ZoneId.of("Europe/Paris");
// convert GregorianCalendar to ZonedDateTime
ZonedDateTime z = Instant.ofEpochMilli(gc1.getTimeInMillis()).atZone(zone);
在Java 8中,您也可以这样做:
ZonedDateTime z = gc1.toInstant().atZone(zone);
要使用java.util.Date
生成的相同格式获取日期,您可以使用DateTimeFormatter
:
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss z yyyy", Locale.ENGLISH);
System.out.println(fmt.format(z));
输出结果为:
Sun Oct 30 02:15:00 CEST 2016
我使用java.util.Locale
强制将语言环境强制为英语,因此月份名称和星期几的格式是相同的。如果你没有指定一个语言环境,它将使用系统的默认值,并且不能保证总是英语(并且默认也可以在没有通知的情况下进行更改,即使在运行时也是如此,因此最好总是明确指出哪一个你正在使用。)
使用此API,您可以轻松添加或设置分钟:
// change the minutes to 10
ZonedDateTime z2 = z.withMinute(10);
System.out.println(fmt.format(z2)); // Sun Oct 30 02:10:00 CEST 2016
// add 10 minutes
ZonedDateTime z3 = z.plusMinutes(10);
System.out.println(fmt.format(z3)); // Sun Oct 30 02:25:00 CEST 2016
输出将是:
Sun Oct 30 02:10:00 CEST 2016
Sun Oct 30 02:25:00 CEST 2016
请注意,在新API中,类是不可变的,因此with
和plus
方法返回一个新实例。
要将ZonedDateTime
转换回GregorianCalendar
,您可以执行以下操作:
gc1.setTimeInMillis(z.toInstant().toEpochMilli());
在java 8中,你也可以这样做:
gc1 = GregorianCalendar.from(z);
要解析输入2016-10-30 02:15:00 +0200
,您必须使用另一个格式化程序:
DateTimeFormatter parser = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss XX")
.withZone(ZoneId.of("Europe/Paris"));
ZonedDateTime z = ZonedDateTime.parse("2016-10-30 02:15:00 +0200", parser);
我必须使用withZone
方法设置时区,因为只有偏移+0200
不足以确定它(more than one timezone can use this same offset并且API无法决定使用哪一个)