【问题标题】:Java Calendar TimeZone messJava 日历时区混乱
【发布时间】:2015-10-15 09:24:46
【问题描述】:

这是我的简单代码:

    String defaultSimpleDateFormatPattern = "MMM dd, yyyy HH:mm:ss";
    TimeZone tzNY = TimeZone.getTimeZone("America/New_York");
    TimeZone tzLos = TimeZone.getTimeZone("America/Los_Angeles");
    String dateToTest = "Jan 03, 2015 23:59:59";
    SimpleDateFormat df = new SimpleDateFormat(defaultSimpleDateFormatPattern);
    Calendar c = Calendar.getInstance();
    c.setTime(df.parse(dateToTest)); 
    c.setTimeZone(tzLos);

    System.out.println(c.getTimeZone());
    System.out.println(c.getTime());        
    System.out.println(df.format(c.getTime()));


    Calendar c1 = Calendar.getInstance();
    c1.setTime(df.parse(dateToTest));        
    c1.setTimeZone(tzNY);

    System.out.println(c1.getTimeZone());
    System.out.println(c1.getTime());
    System.out.println(df.format(c1.getTime()));

    System.out.println(c.after(c1)? "after" : (c.before(c1)? "before" : "equal"));

打印输出是“相等的”。那个怎么样?对此结果有何解释?

【问题讨论】:

  • 您使用的是什么 Java 版本?
  • defaultSimpleDateFormatPattern 是什么?如果您能提供简短但完整的示例,这真的很有帮助...
  • @Puce,我正在使用 JDK 7

标签: java calendar timezone


【解决方案1】:

这里有两个问题:

  • 您使用的时区 ID 无效(您需要 America/New_York
  • 您正在使用未设置时区的格式化程序进行解析(因此它将使用默认时区),然后在 Calendar 中设置时区...这不会改变所代表的瞬间

所以基本上你要解析到相同的Date 两次,做不影响Date 表示的事情,然后比较两个相等的Date 值。

如果全部可能,您应该使用 Joda Time 或 java.time 而不是 java.util.Calendar,但如果您真的需要使用它,只需创建两个不同的格式化程序,每个时区一个。 (如果您确实需要Calendar...,您还需要在Calendar 中设置时区...)

【讨论】:

  • "America/New York" 或 "America/New_York" 在这里没有区别(我试过了)。我知道我最好使用带有时区的 joda datetime 或 DateFormatter 来解决这个问题。我不明白为什么在日历上设置时区对表示的时间没有影响。我认为它应该自动触发日期更改。
  • @curiousman:““America/New York”或“America/New_York”在这里没有区别”——应该可以。看看返回的时区......“我不明白为什么在日历上设置时区对表示的时间没有影响。我认为它应该自动触发日期更改。”你为什么会这样假设?它只是设置时区 - 它不会改变所表示的时间瞬间。它将更改Calendar.get(...) 返回的值,但这是另一回事。基本上,如果要解析某个时区的值,请在格式化程序上进行设置。
  • 你是对的。我的系统时区与纽约相同,这就是为什么我没有看到结果的差异。您提到“它不会改变所表示的时间。它会改变 Calendar.get(...) 返回的值,但这是另一回事”。 ——这让我很困惑。调用 Calendar.get(...) 将触发更改。但调用 Calendar.getTime() 不会。
  • @curiousman:这不是触发变化的问题 - 这是看到它的问题。 Calendar 值表示一个时间点。如果您更改时区,您不会更改时间点 - 但您会更改应用时区时的表示方式,仅此而已。 “现在”的时间点对我来说是 20:39,但对你来说是 15:39 - 所以我可以拨打 calendar.get(Calendar.HOUR) 并返回 20,将时区更改为纽约并拨打相同的电话并获得 15。 .. 不改变所代表的时间点。
【解决方案2】:

java.time

java.util 日期时间 API 及其格式化 API SimpleDateFormat 已过时且容易出错。建议完全停止使用,改用modern Date-Time API*

另外,下面引用的是来自home page of Joda-Time的通知:

请注意,从 Java SE 8 开始,用户被要求迁移到 java.time (JSR-310) - JDK 的核心部分,它取代了这个项目。

使用现代日期时间 API java.time 的解决方案: 您的日期时间字符串没有时区信息,因此可以将其描述为本地日期时间。因此,将其解析为 LocalDateTime 并将时区应用于它以获取 ZonedDateTime

演示:

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

public class Main {
    public static void main(String[] args) {
        String defaultSimpleDateFormatPattern = "MMM dd, uuuu HH:mm:ss";
        ZoneId tzNY = ZoneId.of("America/New_York");
        ZoneId tzLos = ZoneId.of("America/Los_Angeles");
        String dateToTest = "Jan 03, 2015 23:59:59";

        DateTimeFormatter dtf = DateTimeFormatter.ofPattern(defaultSimpleDateFormatPattern, Locale.ENGLISH);
        LocalDateTime ldt = LocalDateTime.parse(dateToTest, dtf);

        ZonedDateTime zdtNY = ldt.atZone(tzNY);
        ZonedDateTime zdtLos = ldt.atZone(tzLos);

        System.out.println(zdtNY.isAfter(zdtLos) ? "after" : zdtNY.isBefore(zdtLos) ? "before" : "equal");
    }
}

输出:

before

ONLINE DEMO

或者,为每个时区创建单独的 DateTimeFormatter,即要求 Java 解析应用给定时区的本地日期时间字符串。

演示:

import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

public class Main {
    public static void main(String[] args) {
        String defaultSimpleDateFormatPattern = "MMM dd, uuuu HH:mm:ss";
        ZoneId tzNY = ZoneId.of("America/New_York");
        ZoneId tzLos = ZoneId.of("America/Los_Angeles");
        String dateToTest = "Jan 03, 2015 23:59:59";

        DateTimeFormatter dtfNY = DateTimeFormatter.ofPattern(defaultSimpleDateFormatPattern, Locale.ENGLISH)
                                    .withZone(tzNY);
        DateTimeFormatter dtfLos = DateTimeFormatter.ofPattern(defaultSimpleDateFormatPattern, Locale.ENGLISH)
                                    .withZone(tzLos);

        ZonedDateTime zdtNY = ZonedDateTime.parse(dateToTest, dtfNY);
        ZonedDateTime zdtLos = ZonedDateTime.parse(dateToTest, dtfLos);

        System.out.println(zdtNY.isAfter(zdtLos) ? "after" : zdtNY.isBefore(zdtLos) ? "before" : "equal");
    }
}

输出:

before

ONLINE DEMO

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

您的代码有什么问题?

  1. 您尚未为 SimpleDateFormat 设置时区: 与现代 Date-Time API 不同,您可以使用多种方法来创建特定于时区的 Date-Time 对象,您只有这种方式处理这种情况的遗留 API(因为java.util.Date does not hold timezone information)。它类似于上面显示的 alternative 示例。
  2. 您尚未将Locale 设置为您的SimpleDateFormat Never use SimpleDateFormat or DateTimeFormatter without a Locale。幸运的是,您的程序没有崩溃,因为您的 JVM 的时区必须是英语语言环境。

演示:

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Locale;
import java.util.TimeZone;

public class Main {
    public static void main(String[] args) throws ParseException {
        String defaultSimpleDateFormatPattern = "MMM dd, yyyy HH:mm:ss";
        TimeZone tzNY = TimeZone.getTimeZone("America/New_York");
        TimeZone tzLos = TimeZone.getTimeZone("America/Los_Angeles");
        String dateToTest = "Jan 03, 2015 23:59:59";
        SimpleDateFormat df = new SimpleDateFormat(defaultSimpleDateFormatPattern, Locale.ENGLISH);

        df.setTimeZone(tzNY);
        Calendar c = Calendar.getInstance();
        c.setTime(df.parse(dateToTest));

        df.setTimeZone(tzLos);
        Calendar c1 = Calendar.getInstance(tzNY);
        c1.setTime(df.parse(dateToTest));

        System.out.println(c.after(c1) ? "after" : (c.before(c1) ? "before" : "equal"));
    }
}

输出:

before

ONLINE DEMO


* 出于任何原因,如果您必须坚持使用 Java 6 或 Java 7,您可以使用 ThreeTen-Backport,它将大部分 java.time 功能向后移植到 Java 6 和 7 . 如果您正在为一个 Android 项目工作并且您的 Android API 级别仍然不符合 Java-8,请检查 Java 8+ APIs available through desugaringHow to use ThreeTenABP in Android Project

【讨论】:

    猜你喜欢
    • 2015-10-02
    • 2011-07-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多