Android 将字符串转换为日期会在不同的设备上生成不同的结果

Android 将字符串转换为日期会在不同的设备上生成不同的结果,android,timezone,date-parsing,Android,Timezone,Date Parsing,例如,我正在以2018-09-20T17:00:00Z的形式检索名为date的字符串,并使用 SimpleDateFormat dateConvert = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss'Z'", Locale.US); convertedDate = new Date(); try { convertedDate = dateConvert.parse(date); } catch (ParseException e) {

例如,我正在以
2018-09-20T17:00:00Z
的形式检索名为
date
的字符串,并使用

SimpleDateFormat dateConvert = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss'Z'", Locale.US);

convertedDate = new Date();
try {
    convertedDate = dateConvert.parse(date);
} catch (ParseException e) {
    e.printStackTrace();
}

然而,在不同的设备上,我得到了不同的结果。有人给出了2018年10月20日星期四英国夏令时17:00:00(英国夏季时间,相当于格林尼治标准时间+01:00),但后来证明这有问题。是否有办法确保按照GMT偏移量(即GMT+01:00而不是BST)格式化日期?

您只是在执行两步流程中的第1步:

  • 解析日期以将其转换为
    date
    对象
  • 再次使用
    SimpleDateFormat
    获取解析后的
    Date
    对象和
    format
    格式 那么,您已经正确地完成了第一步,下面是您必须对第二步执行的操作,请尝试以下操作:

    final String formattedDateString = new SimpleDateFormat("EEE MMM dd HH:mm:ss 'GMT'XXX yyyy").format(convertedDate);
    

    您只需执行两步流程中的第1步:

  • 解析日期以将其转换为
    date
    对象
  • 再次使用
    SimpleDateFormat
    获取解析后的
    Date
    对象和
    format
    格式 那么,您已经正确地完成了第一步,下面是您必须对第二步执行的操作,请尝试以下操作:

    final String formattedDateString = new SimpleDateFormat("EEE MMM dd HH:mm:ss 'GMT'XXX yyyy").format(convertedDate);
    

    java.time
    即时
    (就像
    日期
    )表示独立于时区的时间点。所以你很好。作为额外的奖励,您的
    2018-09-20T17:00:00Z
    字符串暂时采用ISO 8601格式,因此
    instant
    类无需指定格式即可对其进行解析

    编辑:在英国夏季将其格式化为人类可读的字符串,使用明确的UTC偏移量,例如:

        DateTimeFormatter formatter 
                = DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss Z yyyy", Locale.UK);
        ZonedDateTime dateTime = convertInstant.atZone(ZoneId.of("Europe/London"));
        String formatted = dateTime.format(formatter);
        System.out.println(formatted);
    
    此代码段已打印:

    2018年9月20日星期四18:00:00+0100

    18:00是偏移量+01:00处的正确时间。原始字符串末尾的
    Z
    表示偏移量为零,也称为“祖鲁时区”,偏移量为零处的17与偏移量+01:00处的18:00是同一时间点。我从您自己的答案中接管了格式模式字符串

    编辑2

    我想根据您自己的答案向您提出我的建议,重新编写
    Fixture
    类:

    public class Fixture implements Comparable<Fixture> {
    
        private static DateTimeFormatter formatter 
                = DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss Z yyyy", Locale.UK);
    
        public Instant date;
    
        /** @param date Date string from either web service or persistence */
        public Fixture(String date) {
            this.date = Instant.parse(date);
        }
    
        /** @return a string for persistence, e.g., Firebase */
        public String getDateForPersistence() {
            return date.toString();
        }
    
        /** @return a string for the user in the default time zone of the device */
        public String getFormattedDate() {
            return date.atZone(ZoneId.systemDefault()).format(formatter);
        }
    
        @Override
        public int compareTo(Fixture other) {
            return date.compareTo(other.date);
        }
    
        @Override
        public String toString() {
            return "Fixture [date=" + date + "]";
        }
    
    }
    
    当我在欧洲/伦敦时区运行此片段时,我得到:

    Date for user:     Mon Sep 24 12:30:00 +0100 2018
    Date for Firebase: 2018-09-24T11:30:00Z
    
    因此,用户可以按照您的要求,使用自己的UTC偏移量获取日期和时间。在欧洲/柏林时区尝试相同的片段:

    Date for user:     Mon Sep 24 13:30:00 +0200 2018
    Date for Firebase: 2018-09-24T11:30:00Z
    
    我们看到德国的用户被告知比赛时间是13:30,而不是12:30,这与他或她的时钟一致。要在Firebase中持久化的日期保持不变,这也是您想要的

    你的代码出了什么问题 格式模式字符串中有两个错误,
    yyyy-MM-dd'T'hh:MM:ss'Z'

    • 小写的
      hh
      表示从01到12的AM或PM内的小时,并且只有AM/PM标记才有意义。在实践中,您将得到正确的结果,除非解析12小时,这将被理解为00
    • 通过将
      Z
      解析为文本,您不会从字符串中获得UTC偏移量信息。相反,
      SimpleDataFormat
      将使用JVM的时区设置。这一点在不同的设备上明显不同,并解释了为什么在不同的设备上会得到不同且相互冲突的结果
    代码中发生的另一件事是
    Date.toString
    的特殊行为:此方法获取JVM的时区设置并使用它生成字符串。因此,当一台设备设置为欧洲/伦敦,另一台设备设置为GMT+01:00时,相同的
    Date
    对象将在这些设备上以不同方式呈现。这种行为使许多人感到困惑

    问题:我可以在Android上使用java.time吗? 是的,
    java.time
    在较旧和较新的Android设备上运行良好。它至少需要Java6

    • 在Java8和更高版本以及更新的Android设备上(我听说是API级别26),现代API是内置的
    • 在Java6和Java7中,获取三个后端口,即新类的后端口(三个用于JSR310;请参阅底部的链接)。上面的代码是用
      org.threeten.bp.Duration
      从后台开发和运行的
    • 在(较旧的)Android上使用Android版本的ThreeTen Backport。它叫ThreeTenABP。并确保从带有子包的
      org.threeten.bp
      导入日期和时间类
    链接
    • 解释如何使用
      java.time
    • ,其中首先描述了
      java.time
    • ,java.time的后端口到Java6和Java7(JSR-310为三十)
    • ,Android版Three Ten Backport
    • ,解释得非常透彻
    java.time
    即时
    (就像
    日期
    )表示独立于时区的时间点。所以你很好。作为额外的奖励,您的
    2018-09-20T17:00:00Z
    字符串暂时采用ISO 8601格式,因此
    instant
    类无需指定格式即可对其进行解析

    编辑:在英国夏季将其格式化为人类可读的字符串,使用明确的UTC偏移量,例如:

        DateTimeFormatter formatter 
                = DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss Z yyyy", Locale.UK);
        ZonedDateTime dateTime = convertInstant.atZone(ZoneId.of("Europe/London"));
        String formatted = dateTime.format(formatter);
        System.out.println(formatted);
    
    此代码段已打印:

    2018年9月20日星期四18:00:00+0100

    18:00是偏移量+01:00处的正确时间。原始字符串末尾的
    Z
    表示偏移量为零,也称为“祖鲁时区”,偏移量为零处的17与偏移量+01:00处的18:00是同一时间点。我从您自己的答案中接管了格式模式字符串

    编辑2

    我想根据您自己的答案向您提出我的建议,重新编写
    Fixture
    类:

    public class Fixture implements Comparable<Fixture> {
    
        private static DateTimeFormatter formatter 
                = DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss Z yyyy", Locale.UK);
    
        public Instant date;
    
        /** @param date Date string from either web service or persistence */
        public Fixture(String date) {
            this.date = Instant.parse(date);
        }
    
        /** @return a string for persistence, e.g., Firebase */
        public String getDateForPersistence() {
            return date.toString();
        }
    
        /** @return a string for the user in the default time zone of the device */
        public String getFormattedDate() {
            return date.atZone(ZoneId.systemDefault()).format(formatter);
        }
    
        @Override
        public int compareTo(Fixture other) {
            return date.compareTo(other.date);
        }
    
        @Override
        public String toString() {
            return "Fixture [date=" + date + "]";
        }
    
    }
    
    当我在欧洲/伦敦时区运行此片段时,我得到:

    Date for user:     Mon Sep 24 12:30:00 +0100 2018
    Date for Firebase: 2018-09-24T11:30:00Z
    
    因此,用户可以按照您的要求,使用自己的UTC偏移量获取日期和时间。正在用欧元尝试相同的代码片段