将String转换为Date在不同的设备上生成不同的结果

时间:2018-09-20 16:47:05

标签: android timezone date-parsing

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

将其转换为Thu Oct 20 17:00:00 GMT+01:00 2018格式的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();
}

但是在不同的设备上我得到了不同的结果。一个给Thu Oct 20 17:00:00 BST 2018(英国夏令时,相当于格林尼治标准时间+01:00),但是后来证明这是有问题的。是否有办法确保日期采用GMT偏移量格式设置,即GMT + 01:00而不是BST?

3 个答案:

答案 0 :(得分:3)

java.time

    Instant convertInstant = Instant.parse(date);

Instant(就像Date一样)代表一个时间点,与时区无关。很好。另外,您的2018-09-20T17:00:00Z的String瞬时处于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表示偏移量为零,也称为“ Zulu时区”,偏移量为零的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 + "]";
    }

}

此类具有自然的顺序(即按日期和时间排序),因为它实现了Comparable,这意味着您不再需要DateSorter类。以下几行代码演示了如何使用新的getXx方法:

    String date = "2018-09-24T11:30:00Z";
    Fixture fixture = new Fixture(date);
    System.out.println("Date for user:     " + fixture.getFormattedDate());
    System.out.println("Date for Firebase: " + fixture.getDateForPersistence());

当我在欧洲/伦敦时区运行此代码片段时,我得到了:

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偏移量信息。而是SimpleDateFormat将使用JVM的时区设置。显然,一台设备之间存在差异,并解释了为什么在不同的设备上获得不同且相互矛盾的结果。

代码中发生的另一件事是Date.toString的特殊行为:此方法获取JVM的时区设置并将其用于生成字符串。因此,当一台设备设置为Europe / London且另一台设备设置为GMT + 01:00时,相等的Date对象将在这些设备上呈现不同的效果。这种行为使很多人感到困惑。

问题:我可以在Android上使用java.time吗?

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

  • 在Java 8和更高版本以及较新的Android设备上(我被告知,从API级别26开始),内置了现代API。
  • 在Java 6和7中,获取ThreeTen反向端口,即新类的反向端口(JSR 310的ThreeTen;请参见底部的链接)。上面的代码是通过反向端口org.threeten.bp.Duration开发和运行的。
  • 在(较旧的)Android上,使用Android版本的ThreeTen Backport。叫做ThreeTenABP。并确保您使用子包从org.threeten.bp导入日期和时间类。

链接

答案 1 :(得分:2)

您正在执行两步过程的第一步:

  1. 解析日期以将其转换为Date对象
  2. 获取这个经过解析的Date对象,然后再次使用formatSimpleDateFormat

因此,您已经正确完成了第一步,这是您必须执行的第2步,请尝试以下操作:

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

Source

答案 2 :(得分:0)

因此,只需在此处添加一些更新即可。人们提供的答案极大地帮助了我,而我现在拥有的代码可以完全满足我的要求,但是其中一项注释中的“旧版”一词使我感到可能会有更好,更持久的方式。这是代码中当前发生的情况。

1)我以2018-09-22T11:30:00Z的形式获取一个带有字符串utc日期的足球器材

2)然后我使用SimpleDateFormat convertUtcDate = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);convertUtcDate.setTimeZone(TimeZone.getTimeZone("GMT"));

解析日期

3)然后,我使用currentTime = Calendar.getInstance(TimeZone.getTimeZone("GMT")).getTime();获取当前时间,并使用if(convertedDate.after(currentTime))比较两者,以找到球队的下一个比赛。至此,我发现设备将使用BST或GMT + 01:00以相同的形式显示这两个日期,但是无论哪种方式,都可以准确比较这两个日期。

4)然后我使用SimpleDateFormat convertToGmt = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy", Locale.US);String dateString = convertToGmt.format(convertedDate);

将日期格式化为GMT偏移量。

5)对于1)中的utc日期,无论设备是什么,它都返回Sat Sep 22 12:30:00 GMT+01:00 2018。请注意,时间与utc日期不同。不太确定为什么会这样(可能是因为运行API的人位于德国,比我在英格兰这里要晚一个小时),但重要的是这次是正确的(它指的是富勒姆-沃特福德明天是格林尼治标准时间12:30(格林尼治标准时间+01:00)。

6)然后,我将此字符串以及有关灯具的其他一些信息发送到Firebase。重要的是,此时的日期格式应为GMT + 01:00而不是BST,因为其他设备在读取该信息时可能无法识别BST格式。

7)当涉及从Firebase回调该信息时,我可以使用SimpleDateFormat String2Date = new SimpleDateFormat("EEE MMM dd HH:mm:ss Z yyyy", Locale.US);进行解析,将其转换为日期,然后可以通过比较它们的日期按时间顺序排列。

我只想重申一下这种方法有效。当英国的时区更改回GMT + 00:00时,我已经检查了灯具,它仍然可以正常工作。我试图确保一切都按照格林尼治标准时间(GMT)进行,以便可以在任何地方使用。我不确定是否会是这种情况。有人看到这种方法有什么缺陷吗?可以改善吗?

编辑:这是我希望简单,准确地表示我在做什么的代码片段。

public class FragmentFixture extends Fragment {

SimpleDateFormat convertUtcDate = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
SimpleDateFormat String2Date = new SimpleDateFormat("EEE MMM dd HH:mm:ss Z yyyy", Locale.US);
SimpleDateFormat convertToGmt = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy", Locale.US);
private List<Fixture> fixtureList;
Date date1;
Date date2;

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    convertUtcDate.setTimeZone(TimeZone.getTimeZone("GMT"));
    fixtureList = new ArrayList<>();

    // retrieve fixtures from API and get date for a certain fixture. I will provide an example

    String date = "2018-09-22T11:30:00Z";

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

    Date currentTime = Calendar.getInstance(TimeZone.getTimeZone("GMT")).getTime();

    if (convertedDate.after(currentTime)) {
        String dateString = convertToGmt.format(convertedDate);
        Fixture fixture = new Fixture(dateString);
        fixtureList.add(fixture);
        Collections.sort(fixtureList, new DateSorter());
    }
}

public class DateSorter implements Comparator<Fixture> {

    @Override
    public int compare(Fixture fixture, Fixture t1) {
        try {
            date1 = String2Date.parse(fixture.getDate());
        } catch (ParseException e) {
            e.printStackTrace();
        }
        try {
            date2 = String2Date.parse(t1.getDate());
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return date1.compareTo(date2);
    }
}

public class Fixture {

    public String date;

    public Fixture() {

    }

    public Fixture(String date) {
        this.date = date;
    }

    public String getDate() {
        return date;
    }

    public void setDate(String date) {
        this.date = date;
    }

}

}