如何处理jodatime由于时区偏移过渡而导致非法瞬间

问题描述 投票:0回答:6

我想将 joda

DateTime
设置为今天凌晨 2 点(请参阅下面的示例代码)。但我遇到了这个例外:

Exception in thread "main" org.joda.time.IllegalFieldValueException: Value 2 for hourOfDay is not supported: Illegal instant due to time zone offset transition: 2011-03-27T02:52:05.239 (Europe/Prague)
at org.joda.time.chrono.ZonedChronology$ZonedDateTimeField.set(ZonedChronology.java:469)
at org.joda.time.MutableDateTime.setHourOfDay(MutableDateTime.java:702)

处理上述异常或在一天中的特定时间创建

DateTime
的正确方法是什么?

示例代码:

MutableDateTime now = new MutableDateTime();
now.setHourOfDay(2);
now.setMinuteOfHour(0);
now.setSecondOfMinute(0);
now.setMillisOfSecond(0);
DateTime myDate = now.toDateTime();

谢谢。

java jodatime
6个回答
38
投票

您似乎正在尝试从特定的本地时间到

DateTime
实例,并且您希望它能够抵御夏令时。 试试这个...(注意我在美国/东部,所以我们的过渡日期是 2011 年 3 月 13 日;我必须找到正确的日期才能获得今天的例外情况。更新了下面的 CET 代码,该代码今天过渡。 )这里的见解是,Joda 提供了
LocalDateTime
来让您推断本地挂钟设置以及它在您的时区是否合法。 在这种情况下,如果时间不存在,我只需添加一个小时(您的应用程序必须决定这是否是正确的策略。)

import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.LocalDateTime;

class TestTz {

  public static void main(String[] args)
  {
     final DateTimeZone dtz = DateTimeZone.forID("CET");
     LocalDateTime ldt = new LocalDateTime(dtz)
       .withYear(2011)
       .withMonthOfYear(3)
       .withDayOfMonth(27)
       .withHourOfDay(2);

    // this is just here to illustrate I'm solving the problem; 
    // don't need in operational code
    try {
      DateTime myDateBorken = ldt.toDateTime(dtz);
    } catch (IllegalArgumentException iae) {
      System.out.println("Sure enough, invalid instant due to time zone offset transition!");
    }

    if (dtz.isLocalDateTimeGap(ldt)) {
      ldt = ldt.withHourOfDay(3);
    }

    DateTime myDate = ldt.toDateTime(dtz);
    System.out.println("No problem: "+myDate);
  }

}

此代码产生:

果然,由于时区偏移转换而导致无效瞬间!
没问题:2011-03-27T03:00:00.000+02:00

13
投票

CET 在三月的最后一个星期日(恰巧是今天)切换到 DST(夏令时)。 时间从 1:59:59 到 3:00:00 – 没有 2,因此例外。

您应该使用 UTC 而不是本地时间,以避免此类时区问题。

MutableDateTime now = new MutableDateTime(DateTimeZone.UTC);

8
投票

我认为很多时候,您会希望 joda 自动为您解决这个问题。 您通常不知道确定间隔日期的正确方法,因为间隔的大小取决于区域和年份(当然通常是一个小时)。

一个例子是,如果您正在解析来自您无法控制的源的时间戳;例如,网络。 如果时间戳的发送者具有过时的区域文件,则可能会发生这种情况。 (如果您有过时的区域文件,那您就完蛋了)。

这里有一种方法可以做到这一点,当然,这种方法稍微复杂一些。 我让它在 joda 1.6 和 2.x 中都可以工作,因为我们的环境恰好停留在 1.6 上。

如果您像问题中那样从其他输入构建日期,则可以按照上面的建议从 UTC 日期或

LocalDate
开始,然后进行调整以自动修复您的偏移量。 特制酱汁在
DateTimeZone.convertLocalToUTC

危险:

public DateTime parse(String str) {
    formatter.parseDateTime(gapdate)
}

安全:

public DateTime parse(String str) {
    // separate date from zone; you may need to adjust the pattern,
    // depending on what input formats you support
    String[] parts = str.split("(?=[-+])");
    String datepart = parts[0];
    DateTimeZone zone = (parts.length == 2) ?
        DateTimeZone.forID(parts[1]) : formatter.getZone();

    // parsing in utc is safe, there are no gaps
    // parsing a LocalDate would also be appropriate, 
    // but joda 1.6 doesn't support that
    DateTime utc = formatter.withZone(DateTimeZone.UTC).parseDateTime(datepart);

    // false means don't be strict, joda will nudge the result forward by the
    // size of the gap.  The method is somewhat confusingly named, we're
    // actually going from UTC to local
    long millis = zone.convertLocalToUTC(utc.getMillis(), false);

    return new DateTime(millis, zone);
}

我已经在东半球和西半球以及豪勋爵岛地区进行了测试,该地区恰好有半小时夏令时。

如果 joda 格式化程序支持 setStrict(boolean) ,让他们为你处理这个问题,那就太好了......


3
投票

如果您需要从字符串中解析日期:

final DateTimeZone dtz = DateTimeZone.getDefault(); //DateTimeZone.forID("Europe/Warsaw")
LocalDateTime ldt = new LocalDateTime("1946-04-14", dtz);
if (dtz.isLocalDateTimeGap(ldt)){
    ldt = ldt.plusHours(1);
}
DateTime date = ldt.toDateTime();
Date date = date.toDate();

对我来说非常有效。也许有人会需要它。


0
投票

更新到jodatime 2.1并使用

LocalDate.parse()
:

DateTimeFormatter formatter = DateTimeFormat.forPattern("dd/MM/yyyy");
LocalDate.parse(date, formatter);

0
投票

java.time

以下为Joda-Time主页上的通知:

请注意,从 Java SE 8 开始,用户被要求迁移到

java.time
(JSR-310) - JDK 的核心部分,它取代了这个 项目。

使用现代日期时间 API 的解决方案

ZonedDateTime zdt = ZonedDateTime.of(
        LocalDate.of(2011, Month.MARCH, 27),
        LocalTime.of(2, 0),
        ZoneId.of("Europe/Paris") // Replace it as per your requirement
);

欧洲时间,夏令时于 2011 年 3 月 27 日开始;因此,时钟从 01:59 提前到 03:00。请查看 https://www.timeanddate.com/time/change/france?year=2011 获取插图。因此,

ZonedDateTime
巧妙地将输出调整为上例中的
2011-03-27T03:00+02:00[Europe/Paris]

演示:

import java.time.*;

public class Main {
    public static void main(String[] args) {
        ZonedDateTime zdt = ZonedDateTime.of(
                LocalDate.of(2011, Month.MARCH, 27),
                LocalTime.of(2, 0),
                ZoneId.of("Europe/Paris")
        );
        System.out.println(zdt);

        // If you want to convert to a different zone, use withZoneSameInstant
        ZonedDateTime zdt2 = zdt.withZoneSameInstant(ZoneId.of("Etc/UTC"));
        System.out.println(zdt2);

        // However, there is another way to do it for UTC. You can obtain an Instant from
        // a ZonedDateTime. An Instant is a point in time independent of any time zone.
        // But for practical purposes, we need a reference. UTC is used as the reference.
        Instant instant = zdt.toInstant();
        System.out.println(instant);
    }
}

输出:

2011-03-27T03:00+02:00[Europe/Paris]
2011-03-27T01:00Z[Etc/UTC]
2011-03-27T01:00:00Z

在线演示

Trail:日期时间了解有关现代日期时间 API 的更多信息。

© www.soinside.com 2019 - 2024. All rights reserved.