[ 
https://issues.apache.org/jira/browse/LANG-1791?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Igor Rodchenkov updated LANG-1791:
----------------------------------
    Description: 
Lately, we discovered a bug which is better demonstrate with this test case 
(it's not perfect  because always passes in GMT zone, but e.g. in Toronto it 
passes when commons-lang3 v3.0.1 used and fails when v3.18.0 or 3.19.0 is used; 
and yes, in this special case, we have somewhat legacy java code using Calendar 
etc., but that was what broke in our app by this library upgrade, which 
supoosed to be backward compatible):

 

 
{code:java}
/*
* org.apache.commons.lang3.time.FastDateFormat
* In dateFormatter.format(timeCal), when time was not set explicitly, would 
make e.g.
* the following timestamps in Toronto at 10 am on 2025-09-17:
* "2025091714" - GMT, as expected, when using commons-lang3 v3.0.1
* "2025091710" - EDT, when using commons-lang3 v3.18.0 or 3.19.0 (likely some 
other versions <3.18.0 too, but I did not test that)
*/
@Test
public void fastDateFormatFormatterUsingCalendarShouldMakeGmtTimestamp() {     
  Instant now = Instant.now();
  ZoneId zoneId = ZoneId.systemDefault(); //e.g. America/Toronto
  ZoneOffset offset = zoneId.getRules().getOffset(now);
  System.out.printf("Current time: %s, zone: %s, offset: %sh\n", now, zoneId, 
offset);

  //some legacy code that should still work the same after commons-lang3 minor 
ver. upgrade but it does not:
  FastDateFormat dateFormatter = FastDateFormat.getInstance("yyyyMMddHH");
  TimeZone timeZone = TimeZone.getTimeZone("GMT");
  Calendar timeCal = Calendar.getInstance(timeZone);
  String timestamp = dateFormatter.format(timeCal); //makes local zone 
timestamp when commons-lang3 v3.18.0, 3.19.0 is used (ignores Calendar's zone).

  //expected GMT timestamp
  String expected = 
DateTimeFormatter.ofPattern("yyyyMMddHH").withZone(ZoneId.of("GMT")).format(now);

  assertEquals(expected, timestamp);
}{code}
 

I think the bug is in 3.18.0 the FastDatePrinter here -
{code:java}
@Override
public <B extends Appendable> B format(Calendar calendar, final B buf) {
    // do not pass in calendar directly, this will cause TimeZone of 
FastDatePrinter to be ignored
    if (!calendar.getTimeZone().equals(timeZone)) {
        calendar = (Calendar) calendar.clone();
        calendar.setTimeZone(timeZone);
    }
    return applyRules(calendar, buf);
}{code}
 - note misleading comment, - it's calendar's timeZone is in fact there ignored 
instead of date printer's timeZone!
 - we cannot require this "do{color:#808080} not pass in calendar 
directly{color}" nor change the behavior here, not in v3.x.x (perhaps in v4.x.x 
- aye), according to Semantic versioning rules.

 

 

  was:
Lately, we discovered a bug which is better demonstrate with this test case 
(it's not perfect  because always passes in GMT zone, but e.g. in Toronto it 
passes when commons-lang3 v3.0.1 used and fails when v3.18.0 or 3.19.0 is used; 
and yes, in this special case, we have somewhat legacy java code using Calendar 
etc., but that was what broke in our app by this library upgrade, which 
supoosed to be backward compatible):

 

 
{code:java}
/*
* org.apache.commons.lang3.time.FastDateFormat
* In dateFormatter.format(timeCal), when time was not set explicitly, would 
make e.g.
*   the following timestamps in Toronto at 10 am on 2025-09-17:
*   "2025091714" - UTC, as expected, when using commons-lang3 v3.0.1
*   "2025091710" - EDT, when using commons-lang3 v3.18.0 :(
*/
@Test
public void fastDateFormatFormatterUsingCalendarShouldMakeGmtTimestamp() {
    FastDateFormat dateFormatter = FastDateFormat.getInstance("yyyyMMddHH");
    TimeZone timeZone = TimeZone.getTimeZone("GMT");
    Calendar timeCal = Calendar.getInstance(timeZone);
    Date time1 = timeCal.getTime(); //in local time zone
    ZoneId zone = ZoneId.systemDefault();
    ZoneOffset off = zone.getRules().getOffset(time1.toInstant());
    System.out.printf("Time: %s, zone: %s, offset(s): %s\n", time1, zone, 
off.getTotalSeconds());
    String timestamp1 = dateFormatter.format(timeCal); //makes local zone 
timestamp when commons-lang3 v3.18.0 is used.
    Date time = Date.from(Instant.now().minusSeconds(off.getTotalSeconds()));
    String timestamp2 = dateFormatter.format(time); //GMT timestamp
    assertEquals(timestamp2, timestamp1);
}{code}
 

I think the bug is in 3.18.0 the FastDatePrinter here -
{code:java}
@Override
public <B extends Appendable> B format(Calendar calendar, final B buf) {
    // do not pass in calendar directly, this will cause TimeZone of 
FastDatePrinter to be ignored
    if (!calendar.getTimeZone().equals(timeZone)) {
        calendar = (Calendar) calendar.clone();
        calendar.setTimeZone(timeZone);
    }
    return applyRules(calendar, buf);
}{code}
 - note misleading comment, - it's calendar's timeZone is in fact there ignored 
instead of date printer's timeZone!
 - we cannot require this "do{color:#808080} not pass in calendar 
directly{color}" nor change the behavior here, not in v3.x.x (perhaps in v4.x.x 
- aye), according to Semantic versioning rules.

 

 


> FastDateFormat behavior changed between 3.0.1 and e.g. 3.18.0 and later
> -----------------------------------------------------------------------
>
>                 Key: LANG-1791
>                 URL: https://issues.apache.org/jira/browse/LANG-1791
>             Project: Commons Lang
>          Issue Type: Bug
>         Environment: Linux (Ubuntu 24.04), temurin-17.0.16 JDK, run in 
> Toronto.
>            Reporter: Igor Rodchenkov
>            Priority: Major
>
> Lately, we discovered a bug which is better demonstrate with this test case 
> (it's not perfect  because always passes in GMT zone, but e.g. in Toronto it 
> passes when commons-lang3 v3.0.1 used and fails when v3.18.0 or 3.19.0 is 
> used; and yes, in this special case, we have somewhat legacy java code using 
> Calendar etc., but that was what broke in our app by this library upgrade, 
> which supoosed to be backward compatible):
>  
>  
> {code:java}
> /*
> * org.apache.commons.lang3.time.FastDateFormat
> * In dateFormatter.format(timeCal), when time was not set explicitly, would 
> make e.g.
> * the following timestamps in Toronto at 10 am on 2025-09-17:
> * "2025091714" - GMT, as expected, when using commons-lang3 v3.0.1
> * "2025091710" - EDT, when using commons-lang3 v3.18.0 or 3.19.0 (likely some 
> other versions <3.18.0 too, but I did not test that)
> */
> @Test
> public void fastDateFormatFormatterUsingCalendarShouldMakeGmtTimestamp() {    
>  
>   Instant now = Instant.now();
>   ZoneId zoneId = ZoneId.systemDefault(); //e.g. America/Toronto
>   ZoneOffset offset = zoneId.getRules().getOffset(now);
>   System.out.printf("Current time: %s, zone: %s, offset: %sh\n", now, zoneId, 
> offset);
>   //some legacy code that should still work the same after commons-lang3 
> minor ver. upgrade but it does not:
>   FastDateFormat dateFormatter = FastDateFormat.getInstance("yyyyMMddHH");
>   TimeZone timeZone = TimeZone.getTimeZone("GMT");
>   Calendar timeCal = Calendar.getInstance(timeZone);
>   String timestamp = dateFormatter.format(timeCal); //makes local zone 
> timestamp when commons-lang3 v3.18.0, 3.19.0 is used (ignores Calendar's 
> zone).
>   //expected GMT timestamp
>   String expected = 
> DateTimeFormatter.ofPattern("yyyyMMddHH").withZone(ZoneId.of("GMT")).format(now);
>   assertEquals(expected, timestamp);
> }{code}
>  
> I think the bug is in 3.18.0 the FastDatePrinter here -
> {code:java}
> @Override
> public <B extends Appendable> B format(Calendar calendar, final B buf) {
>     // do not pass in calendar directly, this will cause TimeZone of 
> FastDatePrinter to be ignored
>     if (!calendar.getTimeZone().equals(timeZone)) {
>         calendar = (Calendar) calendar.clone();
>         calendar.setTimeZone(timeZone);
>     }
>     return applyRules(calendar, buf);
> }{code}
>  - note misleading comment, - it's calendar's timeZone is in fact there 
> ignored instead of date printer's timeZone!
>  - we cannot require this "do{color:#808080} not pass in calendar 
> directly{color}" nor change the behavior here, not in v3.x.x (perhaps in 
> v4.x.x - aye), according to Semantic versioning rules.
>  
>  



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to