SimpleDateFormat 显示错误的本地时间

SimpleDateFormat shows wrong local Time

我想将具有当前时间和日期的字符串存储到 Android 应用程序的数据库 (SQLite) 中。为此,我正在使用 SimpleDateFormat。不幸的是,它没有显示正确的时间。我尝试了两种选择。

第一个选项(来自

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z", Locale.getDefault());
        sdf.format(new Date());

第二个选项(来自Java SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'") gives timezone as IST

    SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss'Z'");
    sdf2.setTimeZone(TimeZone.getTimeZone("CEST"));

在这两种情况下,时间都是错误的。这不是我的笔记本电脑或 phone 显示的当地时间,但输出时间早 2 小时。我该如何改变它?我希望在我的计算机上也显示柏林 (CEST) 的当前时间。我感谢每一条评论。

ISO 8601

假设您的 SQLite 没有 datetime 数据类型,我建议您使用国际标准 ISO 8601 格式将您的 date-times 作为字符串存储到 SQLite。接下来,考虑使用 java.time,现代 Java 日期和时间 API,作为您的日期和时间工作。这两个建议可以很好地结合在一起。常见的建议是使用 UTC 存储日期和时间,但我知道您更喜欢 Europe/Berlin 时间。

    ZoneId databaseTimeZone = ZoneId.of("Europe/Berlin");
    
    ZonedDateTime now = ZonedDateTime.now(databaseTimeZone);
    String databaseTime = now.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
    
    System.out.println(databaseTime);

刚才运行时上面的输出:

2020-09-27T15:54:21.53+02:00

我特意在字符串中包含了与 UTC 的偏移量。这将允许任何检索字符串的人将时间转换为 UTC 或他们偏好的时区。如果用户前往印度参观泰姬陵并在那里检索数据,则转换为印度标准时间没有问题。当柏林从夏令时 (DST) 更改为标准时间并重复相同的时钟时间时,偏移量还消除了 10 月晚上的时间歧义。更改前的时间偏移量为 +02:00,更改后的时间偏移量为 +01:00。

How can I change the format(?)

编辑:如果您坚持自己的信息格式和人类可读性,请为此构建一个格式化程序。 ZonedDateTime 已经有你选择的时区的时间,所以这个时间也是你格式化时的时间:

    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss Z");
    String databaseTime = now.format(formatter);

现在的结果是:

2020-09-27 16:22:23 +0200

进一步编辑:由于人类可读性是该专栏的唯一要求,因此转到 all-in 并使用 java 的预定义本地化格式,例如:

    DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG)
            .withLocale(Locale.GERMAN);
  1. September 2020 19:06:03 MESZ

如果对您来说太长,请改用 FormatStyle.MEDIUM

进一步编辑:为什么?问题是 27. September 2020 19:06:03 MESZ 是否比 2020-09-27 16:22:23 +0200 更容易正确阅读和理解。你应该尽可能让自己轻松一些。但是,包含偏移量 +0200 是有一点意义的,因为它是明确的,而像 MESZ 这样的时区缩写不能保证是(许多时区缩写 模棱两可)。

你的代码出了什么问题?

您可能 运行 计算机上的代码的时区设置为 UTC(或当前比柏林时间晚两个小时的其他时区)。在您的第二个片段中,您试图通过将格式化程序的时区设置为 CEST(中欧夏令时)来弥补这一事实。你这样做的方式不是你想要的,它也行不通。两者都与 CEST 不是时区这一事实有关。 CEST 比 UTC 早两个小时,如果它有效,那么在一年中的标准时间,柏林比 UTC 只早 1 小时,你也会比 UTC 早两个小时,也就是说,错误的时间。由于 CEST 不是时区,因此 TimeZone 不将其识别为时区。这和 TimeZone class 一样令人困惑:它没有反对,而是默认给你格林威治标准时间,所以你无处可去。我真的建议避免使用 class。柏林的正确时区标识符是 Europe/Berlin,我也在我的代码中使用了这个标识符。时区标识符采用 region/city 格式。

问题:java.time 不需要 Android API 26 级吗?

java.time 在新旧 Android 设备上都能很好地工作。它只需要至少 Java 6.

  • 在 Java 8 和更新的 Android 设备上(从 API 级别 26)现代 API 出现 built-in.
  • 在 non-Android Java 6 和 7 中获取 ThreeTen Backport,现代 classes 的 backport(ThreeTen 用于 JSR 310;请参阅底部的链接)。
  • 在较旧的 Android 上使用脱糖或 ThreeTen Backport 的 Android 版本。它叫做 ThreeTenABP。在后一种情况下,请确保使用子包从 org.threeten.bp 导入日期和时间 classes。

链接

好吧,一周前我遇到了同样的问题,我发现问题出在时区设置中

如果您以字符串形式获取日期并且需要将其格式化为另一种格式,请使用以下代码

public String getCalendarDate(String inputDate){
        Date date = getDateFromSource(inputDate);
        SimpleDateFormat formatter = new SimpleDateFormat("EEEE, d MMMM yyyy", Locale.getDefault());
        formatter.setTimeZone(TimeZone.getDefault());
        return formatter.format(date);
    }

    Date getDateFromSource(String apiDate){
        Date newFormattedDate = null;
        SimpleDateFormat parser = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH);
        parser.setTimeZone(TimeZone.getTimeZone("UTC"));
        try {
             newFormattedDate = parser.parse(apiDate);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return newFormattedDate;
    }

getDateFromSource函数中将日期格式更改为源格式,而在getCalendarDate函数中,将格式更改为您需要的格式。

如果已经有Date对象,可以忽略getDateFromSource函数,直接放在第二个

对于那些使用 Kotlin 的人来说,这是等效的代码

    fun getCalendarDate(apiDate: String): String{
        val date = getDateFromApi(apiDate)
        val formatter = SimpleDateFormat("EEEE, d MMMM yyyy", Locale.getDefault())
        formatter.timeZone = TimeZone.getDefault()
        return formatter.format(date)
    }

    private fun getDateFromApi(apiDate: String) :Date{
        val parser = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH)
        parser.timeZone = TimeZone.getTimeZone("UTC")
        return parser.parse(apiDate)!!
    }

使用 Europe/Berlin 而不是 CEST,您将得到预期的结果。

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;

public class Main {
    public static void main(String[] args) {
        SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z");
        sdf2.setTimeZone(TimeZone.getTimeZone("Europe/Berlin"));
        System.out.println(sdf2.format(new Date()));
    }
}

输出:

2020-09-27 18:38:04 +0200

一条建议:

我建议您从过时的 error-prone java.util date-time API 和 SimpleDateFormat 切换到 modern java.time date-time API and the corresponding formatting API (package, java.time.format). Learn more about the modern date-time API from Trail: Date Time. If your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring and .

使用现代date-time API:

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

public class Main {
    public static void main(String[] args) {
        ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("Europe/Berlin"));

        // Default format
        System.out.println(zdt);

        // Some custom format
        System.out.println(zdt.format(DateTimeFormatter.ofPattern("EEEE dd uuuu hh:mm:ss a z")));
    }
}

输出:

2020-09-27T18:42:53.620168+02:00[Europe/Berlin]
Sunday 27 2020 06:42:53 pm CEST

现代 API 会提醒您,而旧版 API 可能会进行故障转移:

import java.time.ZoneId;
import java.time.ZonedDateTime;

public class Main {
    public static void main(String[] args) {
        ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("CEST"));
        // ...
    }
}

输出:

Exception in thread "main" java.time.zone.ZoneRulesException: Unknown time-zone ID: CEST
    at java.base/java.time.zone.ZoneRulesProvider.getProvider(ZoneRulesProvider.java:279)
    at java.base/java.time.zone.ZoneRulesProvider.getRules(ZoneRulesProvider.java:234)
    at java.base/java.time.ZoneRegion.ofId(ZoneRegion.java:120)
    at java.base/java.time.ZoneId.of(ZoneId.java:408)
    at java.base/java.time.ZoneId.of(ZoneId.java:356)
    at Main.main(Main.java:6)

如您所见,在这种情况下会出现异常,而 SimpleDateFormat 会给您带来如下所示的不良结果:

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;

public class Main {
    public static void main(String[] args) {
        SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z");
        sdf2.setTimeZone(TimeZone.getTimeZone("CEST"));
        System.out.println(sdf2.format(new Date()));
    }
}

输出:

2020-09-27 16:47:45 +0000

您可能想知道这个不良结果指的是什么。 答案是:当SimpleDateFormat不理解time-zone时,它会故障转移(默认)到GMT(与UTC相同) 即它忽略了 CEST 并在这种情况下应用了 GMT(恕我直言,这不是一个好的功能)。