0

java.util.Calendar と commons-lang DateUtil の使用に問題があります。問題は、テストがローカル マシンでは正しく動作し、CloudBees では失敗することです。ロケールに問題があるようですが、よくわかりません。

コードは次のとおりです。

import org.testng.Assert;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

//bla-bla-bla

public static final String TEST_DATE_AS_STRING = "13 10 2012 20:50:44";
public static final int MILLIS_IN_HOUR = 3600000;
private static final String LEAP_WEEK_DATE_AS_STRING = "31 10 2012 20:50:44";

private final SimpleDateFormat sdf = new SimpleDateFormat("dd MM yyyy HH:mm:ss");

@Test
public void getWeekDatePair() throws ParseException{
    Date date = sdf.parse(TEST_DATE_AS_STRING);
    DatePair dp = Util.DateTime.getWeekDatePair(date);
    Assert.assertEquals(sdf.format(new Date(dp.getStart())), "08 10 2012 00:00:00");
    //java.lang.AssertionError: expected [14 10 2012 00:00:00] but found [07 10 2012 00:00:00]
    Assert.assertEquals(sdf.format(new Date(dp.getEnd())), "14 10 2012 00:00:00");
}

@Test
public void getLeapWeekDatePair() throws ParseException {
    Date leapDate = sdf.parse(LEAP_WEEK_DATE_AS_STRING);
    DatePair dp  = Util.DateTime.getWeekDatePair(leapDate);
    Assert.assertEquals(sdf.format(new Date(dp.getStart())), "29 10 2012 00:00:00");

    //java.lang.AssertionError: expected [04 11 2012 00:00:00] but found [28 10 2012 00:00:00]
    Assert.assertEquals(sdf.format(new Date(dp.getEnd())), "04 11 2012 00:00:00");
}

失敗したテスト出力は次のとおりです。

 java.lang.AssertionError: expected [04 11 2012 00:00:00] but found [28 10 2012 00:00:00]
    at org.testng.Assert.fail(Assert.java:94)
    at org.testng.Assert.failNotEquals(Assert.java:494)
    at org.testng.Assert.assertEquals(Assert.java:123)
    at org.testng.Assert.assertEquals(Assert.java:176)
    at org.testng.Assert.assertEquals(Assert.java:186)
    at ru.rating.utils.UtilDateTimeTest.getLeapWeekDatePair(UtilDateTimeTest.java:77)

expected [14 10 2012 00:00:00] but found [07 10 2012 00:00:00]
Stacktrace

java.lang.AssertionError: expected [14 10 2012 00:00:00] but found [07 10 2012 00:00:00]
at org.testng.Assert.fail(Assert.java:94)
at org.testng.Assert.failNotEquals(Assert.java:494)
at org.testng.Assert.assertEquals(Assert.java:123)
at org.testng.Assert.assertEquals(Assert.java:176)
at org.testng.Assert.assertEquals(Assert.java:186)
at ru.rating.utils.UtilDateTimeTest.getWeekDatePair(UtilDateTimeTest.java:69)

実装は次のとおりです。

    public static DatePair getWeekDatePair(){
        return getWeekDatePair(new Date());
    }


/**
 * This is test method
 * */
        static DatePair getWeekDatePair( Date date){
            Date truncDay = truncate(date.getTime(), Calendar.DAY_OF_MONTH);
            Calendar calStart = getCalendarInstance(date, Calendar.DAY_OF_MONTH);
            calStart.setTime(truncDay);
            calStart.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);        

            Calendar calEnd = Calendar.getInstance();
            calEnd.setTime(calStart.getTime());
            calEnd.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);

            return new DatePair(calStart.getTime(), calEnd.getTime());
        }
    public static Date truncate(long date, int calField) {
        Calendar cal = getCalendarInstance(new Date(date), calField);
    cal = DateUtils.truncate(cal, calField);
        return cal.getTime();
    }

static Calendar getCalendarInstance(Date date, int calendarField){
    //Calendar cal = Calendar.getInstance();
    Calendar cal = new GregorianCalendar(Locale.ENGLISH);

    cal.setTime(date);
    if(calendarField!=Calendar.HOUR){
        cal.set(Calendar.HOUR_OF_DAY, 0);
    }
    cal.set(Calendar.MINUTE, 0);
    cal.set(Calendar.SECOND, 0);
    cal.set(Calendar.MILLISECOND, 0);
    return cal;
}
4

1 に答える 1

4

ここでは重要な情報 (実装方法) が欠落していますが、デフォルトを使用してインスタンス化し、週の最初の曜日を検索しUtil.DateTime.getWeekDatePair(java.util.Date)ていると思われます。java.util.CalendarLocale

getWeekDatePair()私の疑いは、 Locale インスタンスをメソッドに渡さないという事実から来ました。

さて、ここで何が問題なのですか?そうですね、週の最初の曜日は Locale によって異なります。したがって、次のCalendarようにインスタンス化すると: Calendar.getInstance()、実際に行うことは:Calendar.getInstance(Locale.getDefault(Locale.Category.FORMAT)です。もちろん、ロケールが異なる可能性があるため、2 つの異なるマシンでは週の最初の曜日が異なる場合があります。
たとえば、週の最初の曜日は米国では日曜日ですが、ポーランドでは月曜日です (ロシアではそうだと思いますよね?) したがって、このテストを 2 台の異なるマシンで実行すると、そのうちの 1 台には Locale が設定されています。 en-US に続き、ru-RU に次ぐ場合、異なる結果予想される場合があります。

テストの問題だけである場合は、デフォルトの Locale を設定するだけで問題なく動作するはずです。ただし、Web アプリケーションをテストしている場合、デフォルトのロケールを使用するのは良くないことに注意してください。これは、Web ブラウザー (またはユーザー プロファイルがある場合はユーザー プロファイル) から取得したものではなく、サーバーのロケールを返すためです。このロケールが異なる場合、エンド ユーザーを混乱させるものを使用する可能性があります。


編集

どうしてこうなるのかは実装を見れば一目瞭然で、先ほどヒントを出しました。次のコードを検討してください。

Calendar cal = Calendar.getInstance(Locale.ENGLISH);
System.out.println(sdf.format(cal.getTime()));
// Cloning so that Calendar could be re-used
Calendar calEnd = (Calendar) cal.clone();
// Setting start of the week date
cal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
System.out.println(sdf.format(cal.getTime()));
calEnd.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);
System.out.println(sdf.format(calEnd.getTime()));

これは(正しく)印刷されます:

13 10 2012 00:00:00
08 10 2012 00:00:00
07 10 2012 00:00:00

それでは、カレンダーのインスタンス化を次のように変更しましょう。

Calendar cal = Calendar.getInstance(Locale.forLanguageTag("ru-RU"));

ほら、今あなたは見るでしょう:

13 10 2012 00:00:00
08 10 2012 00:00:00
14 10 2012 00:00:00

これが正しい動作である理由を確認するために、次のコードもテストしてみましょう。

System.out.println(cal.getFirstDayOfWeek());

ロシア語ロケールの結果である 2 (月曜日) とは対照的に、英語ロケールの場合は 1 (日曜日) を返します。このコードは、指定された週の月曜日と日曜日を返すため、正しく動作します。唯一の「問題」は、その週が世界中で別のことを意味するという事実です。

ご覧のとおり、これは DateUtils とは何の関係もありません。Calendar の動作に関連しているだけです。そして、このコードのおかげでCalendar cal = new GregorianCalendar(Locale.ENGLISH);、動作は実際には一貫している必要があるため、コードをテストしているマシンに関係なく、常にエラーが発生するはずです。そうではありません、私は本当に理由を理解できません。

達成しようとしていることに応じて、Locale をパラメーターとしてメソッドに追加し (それに応じて Calendar をインスタンス化する)、いくつかの Locales をカバーするいくつかのテストを作成することが理にかなっている場合があります (一部のアラビア語 Locale も、最初の日に誰も言わなかったので興味深いかもしれません)週は日曜日または月曜日のいずれかである必要があります) 、または単にテスト日を変更して、正しいカレンダーの動作と一致するようにします。

于 2012-12-15T20:41:20.437 に答える