【问题标题】:SimpleDateFormat shows wrong local TimeSimpleDateFormat 显示错误的当地时间
【发布时间】:2023-03-05 18:11:01
【问题描述】:

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

第一个选项(来自SimpleDateFormat with TimeZone

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"));

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

【问题讨论】:

  • 为什么不将当前时间存储为 unix 时间戳?然后在需要时将 ms 转换为日期字符串。节省一些字节,并且可以轻松转换到任何其他时区。
  • 存储System.currentTimeMillis(); 解析为String。将它与Calendar 一起使用以再次将其显示为文本。如果您想知道如何执行此操作,请发表评论...我会发布答案。
  • 感谢 Viatcheslav 和 Vishnu 的 cmets。我已经在数据库中有一个条目,当前时间以毫秒为单位。这应该是第二个条目。第一个条目(以毫秒为单位)用于查询两个时间点之间的条目。作为人类不“可读”的毫秒条目,我还希望在数据库中有第二个可读条目
  • @VanessaF 我现在将发布一个答案,以使 MilliSeconds 的 人类可读 时间..
  • 顺便考虑扔掉早已过时且臭名昭著的麻烦SimpleDateFormat和朋友。看看您是否可以使用 desugaring 或将 ThreeTenABP 添加到您的 Android 项目中,以便使用现代 Java 日期和时间 API java.time。使用起来感觉好多了。

标签: java android simpledateformat


【解决方案1】:

ISO 8601

假设您的 SQLite 没有 datetime 数据类型,我建议您使用国际标准 ISO 8601 格式将日期时间作为字符串存储到 SQLite。接下来,考虑将现代 Java 日期和时间 API java.time 用于您的日期和时间工作。这两个建议很好地齐头并进。常见的建议是以 UTC 存储日期和时间,但我知道您更喜欢欧洲/柏林时间。

    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。

如何更改格式(?)

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

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

现在结果是:

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

进一步编辑:由于人类可读性是该列的唯一要求,因此全力以赴并使用 java 的预定义本地化格式,例如:

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

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

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

你的代码出了什么问题?

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

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

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

  • 在 Java 8 及更高版本以及更新的 Android 设备(从 API 级别 26 起)中,现代 API 是内置的。
  • 在非 Android 的 Java 6 和 7 中,获取 ThreeTen Backport,这是现代类的后向端口(对于 JSR 310,ThreeTen;请参阅底部的链接)。
  • 在较旧的 Android 上,请使用脱糖或 Android 版本的 ThreeTen Backport。它被称为 ThreeTenABP。在后一种情况下,请确保使用子包从 org.threeten.bp 导入日期和时间类。

链接

【讨论】:

  • 感谢 Ole 的详细回答和努力。首先我想说我没有在我的 SQLite 数据库中使用 Datatype datetime。基本上我有 2 个用于存储数据的数据库条目。第一个以毫秒为单位存储日期(用于查询),第二个将数据存储为字符串。
  • 我个人不太喜欢输出格式“2020-09-27T15:54:21.53+02:00”。我不想有“T”并有实时而不是“+02:00”。在 Java 或 Android 中是否不能仅存储设备上运行的时钟的时间?
  • 哦,请注意数据库中的冗余。如果您想使用 ISO 8601 进行查询,包括涉及 beforeafter 的查询,则需要采取两种措施:使用相同的小数位数存储所有时间(您可以构建正确的格式化程序),更重要的是,将所有时间存储在相同的 UTC 偏移量(我再次建议 UTC 本身)。
  • 是的,你可以有任何你喜欢的格式。通常建议遵循标准而不是发明自己的格式,但这显然取决于您。大多数人很快就习惯了T
  • 感谢 Ole 的回答。当您以毫秒为单位存储时间时,很容易查询。您不需要正确的格式化程序。这就是为什么我有一个数据库条目为毫秒,另一个为字符串。 String 条目只是供我个人查看条目的服务器。对于查询,我使用毫秒条目。由于我已经为此创建了几种方法,因此我不一定要更改此格式。
【解决方案2】:

使用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

一条建议:

我建议您从过时且容易出错的 java.util 日期时间 API 和 SimpleDateFormat 切换到 modern java.time 日期时间 API 和相应的格式化 API(包,java.time.format)。从 Trail: Date Time 了解有关现代日期时间 API 的更多信息。如果您的 Android API 级别仍然不符合 Java-8,请检查 Java 8+ APIs available through desugaringHow to use ThreeTenABP in Android Project

使用现代日期时间 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 不了解时区时,它会故障转移(默认)到GMT(与UTC 相同),即它已忽略CEST 并应用@ 987654344@ 在这种情况下(恕我直言,这不是一个好的功能?)。

【讨论】:

  • 感谢 Arvind 的回答。是否可以从本地设备获取时区,这意味着时间应该等于运行设备的时钟?
  • 谢谢阿文德。仅将 SimpleDateFormat 用于将值存储到甚至不起作用的数据库中时(意味着什么都不做)。使用 SimpleDateFormat 时可能会出现什么问题(在我的示例中)?
  • 不要将Z 硬编码为格式模式字符串中的文字。它将被理解为 UTC,时间是 不是 UTC,你确定。所以人们肯定会误读时间。而是像 VanessaF 在问题的第一个 sn-p 中那样打印偏移量。 @VanessaF 你已经遇到了SimpleDateFormat 的问题。如果你让它在夏季和冬季同时工作,我预计在你或其他人接触代码的那一天之前不会出现更多问题,这可能比你预期的更早发生,那么问题可能会重新开始。
  • @VanessaF - 我已经更新了我的答案,以显示 SimpleDateFormat 未能为您提供所需结果的地方,而现代日期时间 API 通过抛出相关异常巧妙地提醒您。跨度>
  • 然后只使用yyyy-MM-dd HH:mm:ss
【解决方案3】:

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

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

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)!!
    }

【讨论】:

  • 不要将“Z”硬编码为格式模式字符串中的文字。它是与 UTC 的偏移量(零),因此将其解析为正确的结果。我知道当字符串中没有其他区域或偏移量并且格式化程序的时区是 UTC 时,您仍然会得到正确的结果,但是有一天初级程序员更改了其中一个先决条件并且不会理解现在是时候了错误的。所以为了代码的可维护性。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-06-10
  • 2021-07-08
  • 2020-07-09
  • 2017-08-11
  • 2013-01-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多