我正在逐行处理文件。
每行都有一个日期,格式如下:YYMMDD HHMM
该文件基本上每 15 分钟记录一次读数。录制时使用了所在时区的夏令时。我遇到的问题是在 Spring 和秋季期间。记录是重复发生回退时的日期和发生回退时的间隙。
后备示例:
141102 0100
141102 0115
141102 0130
141102 0145
141102 0200
141102 0115 - 重复
141102 0130 - 重复
141102 0145 - 重复
141102 0200 - 重复
Spring 向前示例:
150308 0200
150308 0315
我需要做的就是在夏令时将所有日期向前移动一小时。
我正在开发的程序当前正在使用java.util.Calendar。我尝试使用 java 日历进行轮类,但在添加日期时遇到了很多问题。我认为问题在于它试图自行纠正 DST 问题,而我想做的就是将日期移动一个小时。它还存在检测该差距的问题。例如,它认为第一个 1:15 - 2:00 是白天,但事实并非如此。
基本上,我想要的是将后备示例更改为该日期范围内的所有内容,以向后移动一小时:
后备示例:
141102 0100 无变化
141102 0115 无变化
141102 0130 无变化
141102 0145 无变化
141102 0200 无变化
141102 0115 至 141102 0215
141102 0130 至 141102 0230
141102 0145 至 141102 0245
141102 0200 至 141102 0300
不断改变日期,直到它向前推进
150308 0200 至 150308 0300
150308 0315 没有变化。
我尝试了很多方法,但似乎无法找到解决方案。我并不反对使用 Joda Time,因为我也对此进行了一些研究。任何帮助将不胜感激,谢谢。
最佳答案
世界标准时间
始终使用UTC用于数据交换和此类读数日志。始终、始终、始终使用 UTC。将 UTC 视为一个真实时间,将其他时区视为与 UTC 的偏差。
顺便说一下,Daylight Saving Time (DST) 不是对于分区日期时间值唯一需要担心的问题。其他异常发生在各个时区,导致 wall-clock time向前或向后移动。解决方案很简单:避开所有时区 - 使用 UTC。
即时
Instant
类以纳秒的分辨率捕获 UTC 时间线上的时刻。这应该是你约会工作的必修课。
Instant instant = Instant.now();
字符串
对于日志记录,生成标准 ISO 8601 中的字符串格式。坚持使用经过验证的 ISO 8601 格式,而不是发明自己的格式。
包括 Instant
在内的 java.time 类在解析/生成字符串时默认使用 ISO 8601 格式。因此无需指定格式模式。
String output = instant.toString();
2016-11-02T21:10:05.321Z
Instant instant = Instant.parse( "2016-11-02T21:10:05.321Z" );
并始终在日期中包含世纪 20
。省略只会造成很多误解错误的机会。存储和内存确实足够便宜,我们可以负担得起额外的两位数。
时区指示器
始终在序列化的日期时间值中包含时区或相对于 UTC 的偏移量指示器。
在上面看到的 Instant::toString
结果中,Z
代表 Zulu
,即 UTC。
解析字符串
这里是解析日期时间字符串的代码。我们解析为LocalDateTime
首先,没有任何时区或偏移量,因为您的输入缺少任何时区或偏移量指示符。然后我们分配一个时区得到ZonedDateTime
,看看它如何处理 DST 切换时刻。
我假设您所在的时区使用凌晨 2 点的 DST 回退切换。我使用美国/蒙特利尔
作为一个例子。
List<String> inputs = new ArrayList<>( 2 );
inputs.add( "141102 0115" ); // 1 AM
inputs.add( "141102 0215" ); // 2 AM
for( String input : inputs ) {
DateTimeFormatter f = DateTimeFormatter.ofPattern( "uuMMdd HHmm" );
LocalDateTime ldt = LocalDateTime.parse( input , f );
// At 2 AM in this zone, the clock falls back one hour for DST cutover. The 1 AM hour repeats.
ZoneId z = ZoneId.of( "America/Montreal" );
ZonedDateTime zdt = ldt.atZone( z );
System.out.println( "input: " + input );
System.out.println( "ldt: " + ldt );
System.out.println( "zdt: " + zdt );
System.out.println( "instant: " + zdt.toInstant() );
System.out.println("");
}
input: 141102 0115
ldt: 2014-11-02T01:15
zdt: 2014-11-02T01:15-04:00[America/Montreal]
instant: 2014-11-02T05:15:00Z
…还有…
input: 141102 0215
ldt: 2014-11-02T02:15
zdt: 2014-11-02T02:15-05:00[America/Montreal]
instant: 2014-11-02T07:15:00Z
您可以see this code live in IdeOne.com 。请注意 UTC 值的两小时差异 (Z
)。
我们看到的行为已被记录。
In most cases, there is only one valid offset for a local date-time. In the case of an overlap, where clocks are set back, there are two valid offsets. This method uses the earlier offset typically corresponding to "summer".
如果 1:15 AM 不明确,可能是第一次出现,也可能是第二次出现,java.time 会选择第一次出现。
您可以玩猜谜游戏来尝试调整这些设计不良的字符串。如果您确定每个时钟小时内至少有一个样本,则可以跟踪以前的样本并查看正在处理的样本的时钟时间是否早于前一个样本。如果是这样,您可以假设这是 DST 回退切换,并使用 plusHours( 1 )
添加一个小时。
if( processingZdt.toLocalDateTime().isBefore( previousZdt.toLocalDateTime() ) {
processingZdt.plusHours( 1 ) ; // A hack; I am *not* recommending this.
…
但这是一种冒险的解决方案。我不确定这是否可行,因为我还没有考虑清楚。如果绝望的话,也许你可以让它发挥作用。
更明智的方法是预防:使用 UTC。
关于java - 在夏令时时将日期移动一小时,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40388834/