簡體   English   中英

java.util.GregorianCalendar類在不同Android版本中的奇怪行為

[英]Strange behavior of the java.util.GregorianCalendar class in different android versions

我正在Android應用程序中創建日歷。 日歷的第一天是星期日或星期一。 這取決於區域設置。 java.util的奇怪行為。 GregorianCalendar在不同的Android版本中

public class CurrentMonth extends AbstractCurrentMonth implements InterfaceCurrentMonth {

    public CurrentMonth(GregorianCalendar calendar, int firstDayOfWeek) {
        super(calendar, firstDayOfWeek);
    }

    @Override
    public List<ContentAbstract> getListContent() {
        int year = calendar.get(Calendar.YEAR);
        int month = calendar.get(Calendar.MONTH);

        GregorianCalendar currentCalendar = new GregorianCalendar(year, month, 1);

        List<ContentAbstract> list = new ArrayList<>();
        int weekDay = getDayOfWeek(currentCalendar);
        currentCalendar.add(Calendar.DAY_OF_WEEK, - (weekDay - 1));

        while (currentCalendar.get(Calendar.MONTH) != month) {
            list.add(getContent(currentCalendar));
            currentCalendar.add(Calendar.DAY_OF_MONTH, 1);
        }

        while (currentCalendar.get(Calendar.MONTH) == month) {
            list.add(getContent(currentCalendar));
            currentCalendar.add(Calendar.DAY_OF_MONTH, 1);
        }
        currentCalendar.add(Calendar.DAY_OF_MONTH, - 1);

        while (getDayOfWeek(currentCalendar) != 7) {
            currentCalendar.add(Calendar.DAY_OF_MONTH, 1);
            list.add(getContent(currentCalendar));
        }

        Log.i("text", "yaer: " + list.get(0).getYear());
        Log.i("text", "month: " + list.get(0).getMonth());
        Log.i("text", "day of month: " + list.get(0).getDay());
        Log.i("text", "day of week: " + list.get(0).getDayOfWeek());

        return list;
    }

    private int getDayOfWeek(GregorianCalendar currentCalendar) {
        int weekDay;
        if (firstDayOfWeek == Calendar.MONDAY) {
            weekDay = 7 - (8 - currentCalendar.get(Calendar.DAY_OF_WEEK)) % 7;
        }
        else weekDay = currentCalendar.get(Calendar.DAY_OF_WEEK);
        return weekDay;
    }

    private GraphicContent getContent(GregorianCalendar cal) {
        GraphicContent content = new GraphicContent();
        content.setYear(cal.get(Calendar.YEAR));
        content.setMonth(cal.get(Calendar.MONTH));
        content.setDay(cal.get(Calendar.DAY_OF_MONTH));
        content.setDayOfWeek(cal.get(Calendar.DAY_OF_WEEK));
        return content;
    }
}

public class GraphicContent extends ContentAbstract {
    private int year;
    private int month;
    private int day;
    private int dayOfWeek;

    @Override
    public int getYear() {
        return year;
    }

    @Override
    public void setYear(int year) {
        this.year = year;
    }

    @Override
    public int getMonth() {
        return month;
    }

    @Override
    public void setMonth(int month) {
        this.month = month;
    }

    @Override
    public int getDay() {
        return day;
    }

    @Override
    public void setDay(int day) {
        this.day = day;
    }

    @Override
    public int getDayOfWeek() {
        return dayOfWeek;
    }

    @Override
    public void setDayOfWeek(int dayOfWeek) {
        this.dayOfWeek = dayOfWeek;
    }
}

設置類構造函數(new GregorianCalendar(1994,3,1),Calendar.SUNDAY)。 在android 4.4,5.0 Logcat結果中:

10-12 14:32:28.332 27739-27739/*** I/text: yaer: 1994
10-12 14:32:28.332 27739-27739/*** I/text: month: 2
10-12 14:32:28.332 27739-27739/*** I/text: day of month: 26
10-12 14:32:28.332 27739-27739/*** I/text: day of week: 7

在android 8.0 Logcat結果中:

2018-10-12 11:50:59.549 6565-6565/*** I/text: yaer: 1994
2018-10-12 11:50:59.549 6565-6565/*** I/text: month: 2
2018-10-12 11:50:59.549 6565-6565/*** I/text: day of month: 27
2018-10-12 11:50:59.549 6565-6565/*** I/text: day of week: 1

你可以看到結果 - 不同的日子(26和27),它對應於一周中的不同日期。 如果您更改了日歷對象的初始化

@Override
    public List<ContentAbstract> getListContent() {
        int year = calendar.get(Calendar.YEAR);
        int month = calendar.get(Calendar.MONTH);

        GregorianCalendar currentCalendar = (GregorianCalendar) Calendar.getInstance();
        currentCalendar.set(year, month, 1);

所有Android版本的結果都是正確的:

10-12 15:12:56.400 28914-28914/*** I/text: yaer: 1994
10-12 15:12:56.400 28914-28914/*** I/text: month: 2
10-12 15:12:56.400 28914-28914/*** I/text: day of month: 27
10-12 15:12:56.400 28914-28914/*** I/text: week day: 1

在junit測試中,結果在所有情況下都是正確的 (27和SUNDAY)。 從代碼中刪除日志並檢查:

 public class TestCurrentMonth {

    @Test
    public void testGetListContent() {
        GregorianCalendar calendar = new GregorianCalendar(1994, 3, 1);
        int firstDay = Calendar.SUNDAY;
        CurrentMonth currentMonth = new CurrentMonth(calendar, firstDay);
        List<ContentAbstract> list = currentMonth.getListContent();
        Assert.assertEquals(27, list.get(0).getDay());
        Assert.assertEquals(Calendar.SUNDAY, list.get(0).getDayOfWeek());
    }
}

同樣是1993年4月,1992年的行為。為什么? 我已經打破了我的大腦。

java.time

好的解決方案是跳過CalendarGregorianCalendar類,並使用java.time中的LocalDate ,即現代Java日期和時間API。 CalendarGregorianCalendar已經過時且設計不佳。 現代API可以更好地使用。 而且LocalDate是一個沒有時間且沒有時區的日期,所以如果懷疑我在下面播放是正確的,它將保證將您的時區/夏令時問題留在后面。 要在較舊的Android上使用它,請進一步查看。

什么地方出了錯? 推測性解釋

以下解釋純粹是理論上的,但我能夠想到的最好。 它依賴於我無法驗證的幾個假設:

  • 您是(或您的某個設備之一)在1994年3月最后幾天夏令時(DST)開始的時區。
  • Android 4.4和5.0中的GregorianCalendar可能存在錯誤,因此currentCalendar.add(Calendar.DAY_OF_WEEK, - (weekDay - 1)); 只是24小時多次添加。

這是純粹的推測,但如果有這樣的錯誤,你的GregorianCalendar將在目標日期前一天晚上23:00結束,這將解釋你的結果。 例如,歐盟國家在3月的最后一個星期天開始夏令時。 這也是1994年的情況。這將非常適合你1994年3月27日星期日的目標日期,並且也會解釋你在1992年和1993年的錯誤結果。我已經做了一個簡短的互聯網搜索,提到了這樣一個錯誤。 Android GregorianCalendar並沒有找到任何支持它。

由於我懷疑解釋你的觀察,我們需要更多的東西:

  1. 我懷疑的錯誤只會出現在某些Android版本(4.4,5.0)中並在更高版本(8.0)中修復(或者您的Android 8.0設備將運行不同的時區)。 此外,您運行測試的環境要么沒有錯誤,要么具有不同的默認時區(要么解釋測試通過的原因)。
  2. 您從getInstance獲得的GregorianCalendar具有一天中的時間。 並在設置日期后保留它。 要說明設置日期的兩種方式之間的區別:假設您在9:05運行代碼。 new GregorianCalendar(1994, Calendar.APRIL, 1)將於1994年4月1日00:00給你。 Calendar.getInstance()后跟currentCalendar.set(year, month, 1); 給你1994年4月1日09:05。 這兩者之間的差異有點超過9小時。 在后一種情況下,可疑的錯誤將導致你在3月27日8:05(仍然是正確的日期),所以你沒有看到錯誤。 如果您在晚上0:35運行代碼,那么您將在3月26日23:35點擊,所以您也會看到該案例中的錯誤。

正如我已經說過的, LocalDate ,java.time和ThreeTenABP將形成良好的解決方案。 如果你選擇不依賴外部圖書館,而是選擇過時的課程,我相信以下內容會有所幫助:

    GregorianCalendar currentCalendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
    currentCalendar.set(year, month, 1);

TimeZone是一個又一個舊的和設計不佳的類,特別是我使用的getTimeZone方法有一些令人討厭的驚喜,但我相信上面的工作(手指交叉)。 這個想法是告訴Calendar使用UTC時間。 UTC沒有夏令時,這避免了問題。

你可能會嘗試的另一個更糟糕的事情是:

    currentCalendar.set(year, month, 1, 6, 0);

這會將一天中的小時數設置為6,這意味着當您回到夏季時間過渡時,您將在早上5點到達,這仍然​​是正確的日期(上面的調用沒有設置秒和毫秒;在一次運行中,我得到了1994年4月1日06:00:40.213 UTC)。

問題:我可以在Android上使用java.time嗎?

是的,java.time適用於較舊和較新的Android設備。 它至少需要Java 6

  • 在Java 8及更高版本和更新的Android設備上(來自API級別26),現代API內置。
  • 在Java 6和7中獲取ThreeTen Backport,新類的后端端口(適用於JSR 310的ThreeTen;請參見底部的鏈接)。
  • 在(較舊的)Android上使用Android版的ThreeTen Backport。 它被稱為ThreeTenABP。 並確保從子包中導入org.threeten.bp的日期和時間類。

鏈接

這里沒什么奇怪的。 與conststructor不同, getInstance將根據您的語言環境和時區返回數據。 不確定更新的Android版本,可能在這里發生了變化,或者您使用不同的時區/區域設置進行了測試?

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM