<!-- --><style type="text/css">@import url(https://www.blogger.com/static/v1/v-css/navbar/3334278262-classic.css); div.b-mobile {display:none;} </style> </head><body><script type="text/javascript"> function setAttributeOnload(object, attribute, val) { if(window.addEventListener) { window.addEventListener('load', function(){ object[attribute] = val; }, false); } else { window.attachEvent('onload', function(){ object[attribute] = val; }); } } </script> <div id="navbar-iframe-container"></div> <script type="text/javascript" src="https://apis.google.com/js/plusone.js"></script> <script type="text/javascript"> gapi.load("gapi.iframes:gapi.iframes.style.bubble", function() { if (gapi.iframes && gapi.iframes.getContext) { gapi.iframes.getContext().openChild({ url: 'https://www.blogger.com/navbar.g?targetBlogID\x3d7256432\x26blogName\x3dThe+Frustrated+Programmer\x26publishMode\x3dPUBLISH_MODE_BLOGSPOT\x26navbarType\x3dBLACK\x26layoutType\x3dCLASSIC\x26searchRoot\x3dhttp://frustratedprogrammer.blogspot.com/search\x26blogLocale\x3den_US\x26v\x3d2\x26homepageUrl\x3dhttp://frustratedprogrammer.blogspot.com/\x26vt\x3d5012862196962223429', where: document.getElementById("navbar-iframe-container"), id: "navbar-iframe" }); } }); </script>
| Monday, August 09, 2004

I hit an interesting bug in java.util.GregorianCalendar today. I was writing some reporting code that needed to find the number of widgets sold for each week of the year. I found the current week of the year from Calendar using get(WEEK_OF_YEAR), then selected the sum of selected the sum of widgets sold where TO_DATE( sale_date, 'WW' ) = ?. The documentation for each week of year calculation is below:

WW
Week of year (1-53) where week 1 starts on the first
day of the year and continues to the seventh day of the year.


WEEK_OF_MONTH
Field number for get and set indicating the week number
within the current month. The first week of the month, as
defined by getFirstDayOfWeek() and
getMinimalDaysInFirstWeek(), has value 1. Subclasses
define the value of WEEK_OF_MONTH for days before the
first week of the month.


Specifically, Oracle thinks of dates as full seven day weeks. Oracle weeks always start on the same weekday as the first day of the year. In constrast, Java thinks of weeks as you traditionally see on a paper calendar. In the en_US locale, weeks always start on Sunday, except the first week of the year, which starts on 1/1 regardless.

In an attempt to have GregorianCalendar behave like Oracle, I tried Calendar.setMinimalDaysInFirstWeek(7). This didn't work as I expected. GregorianCalendar called still wanted the week change to take affect on Sundays, in addition, it reported the first 3 days of the year in 2004 as being in week 52.

My solution was to use my own computed value to match oracle's week calculation:

oracleWeek = (normal.get( Calendar.DAY_OF_YEAR ) - 1) / 7 + 1


Here is a code sample to show this:

public static final int ORACLE_WEEKS[] = { 52,1,1,1,1,1,1,1,2,2 };
public static void main( String args[] ) {
Calendar normal = new GregorianCalendar(); normal.set( Calendar.YEAR, 2004);
Calendar adjusted = new GregorianCalendar(); adjusted.set( Calendar.YEAR, 2004 );
adjusted.setMinimalDaysInFirstWeek( 7 );

for( int i = 1; i < 10; i++ ) {
normal.set( Calendar.DAY_OF_YEAR, i );
adjusted.set( Calendar.DAY_OF_YEAR, i );
System.out.println("1/" + i
+ " | " + ORACLE_WEEKS[i]
+ " | " + normal.get( Calendar.WEEK_OF_YEAR )
+ " | " + adjusted.get( Calendar.WEEK_OF_YEAR)
+ " | " + ((normal.get( Calendar.DAY_OF_YEAR ) - 1) / 7 + 1 ) );
}
}

It produces this output (headers added):

Day | Oracle Week | Calendar | adjusted | calculated
| WW | WEEK_OF_MONTH | |
1/1 | 1 | 1 | 52 | 1
1/2 | 1 | 1 | 52 | 1
1/3 | 1 | 1 | 52 | 1
1/4 | 1 | 2 | 1 | 1
1/5 | 1 | 2 | 1 | 1
1/6 | 1 | 2 | 1 | 1
1/7 | 1 | 2 | 1 | 1
1/8 | 2 | 2 | 1 | 2
1/9 | 2 | 2 | 1 | 2

I also tried to add adjusted.setFirstDayOfWeek(5). While this corrected the table for the year 2004, the 1/1/2003 was still in week 52.

A quick search of bugs produced a bunch related to WEEK_OF_YEAR, but all were either fixed or not a bug. This seems like a bug to me, maybe I'm still using things wrong. If things are working as designed, I suggest writing a wrapper around GregorianCalendar called OracleCalendar, which overrides get(int) set(int, int) and roll( int, int ).