Cannot create a Date object representing December 29, 2019 - java.util.date

I hope it's not a JDK bug, or I am tempted to cash all my bank accounts and throw it in a mattress...
Here is the code. Please note that setting the date to Dec.29 immediately rolls the year to 2020. Also, the toString() output reveals that Calendar seems to think the week number is "1".
import java.util.*; // headers MUST be above the first class
import java.text.*;
public class JDKCalendarBug {
public static void main(String[] args) {
SimpleDateFormat sdf = new SimpleDateFormat("MM-dd-YYYY kk:mm");
Calendar cal = java.util.Calendar.getInstance(TimeZone
.getTimeZone("EST"));
cal.clear();
cal.set(Calendar.YEAR, 2019);
cal.set(Calendar.MONTH, Calendar.DECEMBER);
cal.set(Calendar.DAY_OF_MONTH, 26);
Date t = cal.getTime();
System.out.print("\n"+sdf.format(t));
System.out.print("\n"+cal.toString());
cal.set(Calendar.DAY_OF_MONTH, 27);
t = cal.getTime();
System.out.print("\n"+sdf.format(t));
System.out.print("\n"+cal.toString());
cal.set(Calendar.DAY_OF_MONTH, 28);
t = cal.getTime();
System.out.print("\n"+sdf.format(t));
System.out.print("\n"+cal.toString());
cal.set(Calendar.DAY_OF_MONTH, 29);
t = cal.getTime();
System.out.print("\n"+sdf.format(t));
System.out.print("\n"+cal.toString());
}
}
Output:
12-26-2019 24:00
java.util.GregorianCalendar[time=1577336400000,areFieldsSet=true,areAllFieldsSet=false,lenient=true,zone=sun.util.calendar.ZoneInfo[id="EST",offset=-18000000,dstSavings=0,useDaylight=false,transitions=0,lastRule=null],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=?,YEAR=2019,MONTH=11,WEEK_OF_YEAR=?,WEEK_OF_MONTH=?,DAY_OF_MONTH=26,DAY_OF_YEAR=?,DAY_OF_WEEK=?,DAY_OF_WEEK_IN_MONTH=?,AM_PM=?,HOUR=?,HOUR_OF_DAY=?,MINUTE=?,SECOND=?,MILLISECOND=?,ZONE_OFFSET=?,DST_OFFSET=?]
12-27-2019 24:00
java.util.GregorianCalendar[time=1577422800000,areFieldsSet=true,areAllFieldsSet=false,lenient=true,zone=sun.util.calendar.ZoneInfo[id="EST",offset=-18000000,dstSavings=0,useDaylight=false,transitions=0,lastRule=null],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2019,MONTH=11,WEEK_OF_YEAR=52,WEEK_OF_MONTH=4,DAY_OF_MONTH=27,DAY_OF_YEAR=361,DAY_OF_WEEK=6,DAY_OF_WEEK_IN_MONTH=4,AM_PM=0,HOUR=0,HOUR_OF_DAY=0,MINUTE=0,SECOND=0,MILLISECOND=0,ZONE_OFFSET=-18000000,DST_OFFSET=0] 12-28-2019 24:00
java.util.GregorianCalendar[time=1577509200000,areFieldsSet=true,areAllFieldsSet=false,lenient=true,zone=sun.util.calendar.ZoneInfo[id="EST",offset=-18000000,dstSavings=0,useDaylight=false,transitions=0,lastRule=null],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2019,MONTH=11,WEEK_OF_YEAR=52,WEEK_OF_MONTH=4,DAY_OF_MONTH=28,DAY_OF_YEAR=362,DAY_OF_WEEK=7,DAY_OF_WEEK_IN_MONTH=4,AM_PM=0,HOUR=0,HOUR_OF_DAY=0,MINUTE=0,SECOND=0,MILLISECOND=0,ZONE_OFFSET=-18000000,DST_OFFSET=0]
12-29-2020 24:00
java.util.GregorianCalendar[time=1577595600000,areFieldsSet=true,areAllFieldsSet=false,lenient=true,zone=sun.util.calendar.ZoneInfo[id="EST",offset=-18000000,dstSavings=0,useDaylight=false,transitions=0,lastRule=null],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2019,MONTH=11,WEEK_OF_YEAR=1,WEEK_OF_MONTH=5,DAY_OF_MONTH=29,DAY_OF_YEAR=363,DAY_OF_WEEK=1,DAY_OF_WEEK_IN_MONTH=5,AM_PM=0,HOUR=0,HOUR_OF_DAY=0,MINUTE=0,SECOND=0,MILLISECOND=0,ZONE_OFFSET=-18000000,DST_OFFSET=0]

The problem is with SimpleDateFormat. If you change your format string to lowercase ys the date formats correctly as 12-29-2019 24:00
new SimpleDateFormat("MM-dd-yyyy kk:mm")
The Y corresponds to the "Week Year", which is different than "Year" and not always supported in all calendars. According to the JavaDocs:
If week year 'Y' is specified and the calendar doesn't support any
week years, the calendar year ('y') is used instead. The support of
week years can be tested with a call to
getCalendar().isWeekDateSupported().
In this case, "week year" is supported as the underlying Calendar implementation is GregorianCalendar. According to the its JavaDocs:
A week year is in sync with a WEEK_OF_YEAR cycle. All weeks between
the first and last weeks (inclusive) have the same week year value.
Therefore, the first and last days of a week year may have different
calendar year values.

Related

Adding Period to startDate doesn't produce endDate

I have two LocalDates declared as following:
val startDate = LocalDate.of(2019, 10, 31) // 2019-10-31
val endDate = LocalDate.of(2019, 9, 30) // 2019-09-30
Then I calculate the period between them using Period.between function:
val period = Period.between(startDate, endDate) // P-1M-1D
Here the period has the negative amount of months and days, which is expected given that endDate is earlier than startDate.
However when I add that period back to the startDate, the result I'm getting is not the endDate, but the date one day earlier:
val endDate1 = startDate.plus(period) // 2019-09-29
So the question is, why doesn't the invariant
startDate.plus(Period.between(startDate, endDate)) == endDate
hold for these two dates?
Is it Period.between who returns an incorrect period, or LocalDate.plus who adds it incorrectly?
If you look how plus is implemented for LocalDate
#Override
public LocalDate plus(TemporalAmount amountToAdd) {
if (amountToAdd instanceof Period) {
Period periodToAdd = (Period) amountToAdd;
return plusMonths(periodToAdd.toTotalMonths()).plusDays(periodToAdd.getDays());
}
...
}
you'll see plusMonths(...) and plusDays(...) there.
plusMonths handles cases when one month has 31 days, and the other has 30. So the following code will print 2019-09-30 instead of non-existent 2019-09-31
println(startDate.plusMonths(period.months.toLong()))
After that, subtracting one day results in 2019-09-29. This is the correct result, since 2019-09-29 and 2019-10-31 are 1 month 1 day apart
The Period.between calculation is weird and in this case boils down to
LocalDate end = LocalDate.from(endDateExclusive);
long totalMonths = end.getProlepticMonth() - this.getProlepticMonth();
int days = end.day - this.day;
long years = totalMonths / 12;
int months = (int) (totalMonths % 12); // safe
return Period.of(Math.toIntExact(years), months, days);
where getProlepticMonth is total number of months from 00-00-00. In this case, it's 1 month and 1 day.
From my understanding, it's a bug in a Period.between and LocalDate#plus for negative periods interaction, since the following code has the same meaning
val startDate = LocalDate.of(2019, 10, 31)
val endDate = LocalDate.of(2019, 9, 30)
val period = Period.between(endDate, startDate)
println(endDate.plus(period))
but it prints the correct 2019-10-31.
The problem is that LocalDate#plusMonths normalises date to be always "correct". In the following code, you can see that after subtracting 1 month from 2019-10-31 the result is 2019-09-31 that is then normalised to 2019-10-30
public LocalDate plusMonths(long monthsToAdd) {
...
return resolvePreviousValid(newYear, newMonth, day);
}
private static LocalDate resolvePreviousValid(int year, int month, int day) {
switch (month) {
...
case 9:
case 11:
day = Math.min(day, 30);
break;
}
return new LocalDate(year, month, day);
}
I believe that you are simply out of luck. The invariant that you have invented sounds reasonable, but doesn’t hold in java.time.
It seems that the between method just subtracts the month numbers and the days of month and since the results have the same sign, is content with this result. I think I agree that probably a better decision could have been taken here, but as #Meno Hochschild has correctly stated, math involving the 29, 30 or 31 of months can hardly be clearcut, and I dare not suggest what the better rule would have been.
I bet they are not going to change it now. Not even if you file a bug report (which you can always try). Too much code is already relying on how it’s been working for more than five and a half years.
Adding P-1M-1D back into the start date works the way I would have expected. Subtracting 1 month from (really adding –1 month to) October 31 yeilds September 30, and subtracting 1 day yields September 29. Again, it’s not clear-cut, you could argue in favour of September 30 instead.
Analyzing your expectation (in pseudo code)
startDate.plus(Period.between(startDate, endDate)) == endDate
we have to discuss several topics:
how to handle separate units like months or days?
how is the addition of a duration (or "period") defined?
how to determine the temporal distance (duration) between two dates?
how is the subtraction of a duration (or "period") defined?
Let's first look at the units. Days are no problem because they are the smallest possible calendar unit and every calendar date differs by any other date in full integers of days. So we always have in pseudo code equal if positive or negative:
startDate.plus(ChronoUnit.Days.between(startDate, endDate)) == endDate
Months however are tricky because the gregorian calendar defines calendar months with different lengths. So the situation can arise that the addition of any integer of months to a date can cause an invalid date:
[2019-08-31] + P1M = [2019-09-31]
The decision of java.time to reduce the end date to a valid one - here [2019-09-30] - is reasonable and corresponds to the expectations of most users because the final date still preserves the calculated month. However, this addition including an end-of-month-correction is NOT reversible, see the reverted operation called subtraction:
[2019-09-30] - P1M = [2019-08-30]
The result is also reasonable because a) the basic rule of month addition is to keep the day-of-month as much as possible and b) [2019-08-30] + P1M = [2019-09-30].
What is the addition of a duration (period) exactly?
In java.time, a Period is a composition of items consisting of years, months and days with any integer partial amounts. So the addition of a Period can be resolved to the addition of the partial amounts to the starting date. Since years are always convertible to 12-multiples of months, we can first combine years and months and then add the total in one step in order to avoid strange side effects in leap years. The days can be added in the last step. A reasonable design as done in java.time.
How to determine the right Period between two dates?
Let's first discuss the case when the duration is positive, meaning the starting date is before the ending date. Then we can always define the duration by first determining the difference in months and then in days. This order is important to achieve a month component because otherwise every duration between two dates would only consist of days. Using your example dates:
[2019-09-30] + P1M1D = [2019-10-31]
Technically, the starting date is first moved forward by the calculated difference in months between start and end. Then the day delta as difference between the moved start date and the end date is added to the moved start date. This way we can calculate the duration as P1M1D in the example. So far so reasonable.
How to subtract a duration?
Most interesting point in the previous addition example is, there is by accident NO end-of-month-correction. Nevertheless java.time fails to do the reverse subtraction.
It first subtracts the months and then the days:
[2019-10-31] - P1M1D = [2019-09-29]
If java.time had instead tried to reverse the steps in the addition before then the natural choice would have been to first subtract the days and then the months. With this changed order, we would get [2019-09-30]. The changed order in the subtraction would help as long as there was no end-of-month-correction in the corresponding addition step. This is especially true if the day-of-month of any starting or ending date is not bigger than 28 (the minimum possible month length). Unfortunately java.time has defined another design for the subtraction of Period which leads to less consistent results.
Is the addition of a duration reversible in the subtraction?
First we have to understand that the suggested changed order in the subtraction of a duration from a given calendar date does not guarantee the reversibility of the addition. Counter example which has an end-of-month-correction in the addition:
[2011-03-31] + P3M1D = [2011-06-30] + P1D = [2011-07-01] (ok)
[2011-07-01] - P3M1D = [2011-06-30] - P3M = [2011-03-30] :-(
Changing the order is not bad because it yields more consistent results. But
how to cure the remaining deficiencies? The only way left is to change the calculation of the duration, too. Instead of using P3M1D, we can see that the duration P2M31D will work in both directions:
[2011-03-31] + P2M31D = [2011-05-31] + P31D = [2011-07-01] (ok)
[2011-07-01] - P2M31D = [2011-05-31] - P2M = [2011-03-31] (ok)
So the idea is to change the normalization of the computed duration. This can be done by looking if the addition of the computed month delta is reversible in a subtraction step - i.e. avoids the need for an end-of-month-correction. java.time does unfortunately not offer such a solution. It is not a bug, but can be considered as a design limitation.
Alternatives?
I have enhanced my time library Time4J by reversible metrics which deploy the ideas given above. See following example:
PlainDate d1 = PlainDate.of(2011, 3, 31);
PlainDate d2 = PlainDate.of(2011, 7, 1);
TimeMetric<CalendarUnit, Duration<CalendarUnit>> metric =
Duration.inYearsMonthsDays().reversible();
Duration<CalendarUnit> duration =
metric.between(d1, d2); // P2M31D
Duration<CalendarUnit> invDur =
metric.between(d2, d1); // -P2M31D
assertThat(d1.plus(duration), is(d2)); // first invariance
assertThat(invDur, is(duration.inverse())); // second invariance
assertThat(d2.minus(duration), is(d1)); // third invariance

VB.NET DatetimePicker - Wrong week number

I have an issue with my vbnet extented datetime picker.
When the element pass to new year (2016), the week number displayed on the left is wrong.
I have a "datetimepicker" which is not the default component, it was downloaded here :
http://www.codeproject.com/Articles/17063/ExtendedDateTimePicker-control-with-week-numbers
I don't understand why the calendar pass from 53 to 2 and not 53 to 1.
Maybe one of you has the same error.
Thanks for your time.
I don't understand why the calendar pass from 53 to 2 and not 53 to 1
It is pretty much working as expected. The way it is counting weeks, those first 3 days of 2016 count as the first week of 2016.
Note that the control doesnt do anything calendar or display related. It is simply changing the display style of the calendar window provided by Windows. The code seen on the CP page is all there is and mainly it just sets a style flag to tell Windows to add the week numbers:
style = style | MCS_WEEKNUMBERS;
The MSDN entry for it indicates:
Week 1 is defined as the first week that contains at least four days.
Since Jan 1-3 is not 4 days, it would seem that there is either an error, a different calendar being used or MSDN is out of date.
From comments:
From what i understood, what's wrong is "date format". Maybe it's not a 8601
No, it is more than that: ISO8601 is a different calendar which neither Windows nor NET implements. Wikipedia notes:
The first week of a year is the week that contains the first Thursday of the year (and, hence, always contains 4 January). ISO week year numbering therefore slightly deviates from the Gregorian for some days close to 1 January.
This is what you see in the calendar drop down.
Alternative
But the ISO8601 Week Of Year is easy to calculate:
Start with the code for GetISOWeekOfYear() from my answer to a very similar question. You can use that to display the ISO8601 week of year for the selected date in a label or something next to the DTP.
Print the first and last week numbers for 2011 To 2021:
Dim cal As Calendar = CultureInfo.CurrentCulture.DateTimeFormat.Calendar
For n As Int32 = 2011 To 2017 '2021
dt = New DateTime(n, 12, 21)
Console.WriteLine(" ***** {0} *****", n)
For j = 0 To 3
Dim NetWk = cal.GetWeekOfYear(dt, CalendarWeekRule.FirstDay, firstD)
Console.WriteLine("Invariant Date: {0} ISO #:{1:00} NET #:{2:00}",
dt.ToString("MM/dd/yyyy"), GetISOWeekOfYear(dt), NetWk)
dt = dt.AddDays(7)
Next
Next
The result for 2015/2016 portion:
***** 2015 *****
Invariant Date: 12/21/2015 ISO #:52 NET #:52
Invariant Date: 12/28/2015 ISO #:53 NET #:53
Invariant Date: 01/04/2016 ISO #:01 NET #:02
Invariant Date: 01/11/2016 ISO #:02 NET #:03
***** 2016 *****
Invariant Date: 12/21/2016 ISO #:51 NET #:52
Invariant Date: 12/28/2016 ISO #:52 NET #:53
Invariant Date: 01/04/2017 ISO #:01 NET #:01
Invariant Date: 01/11/2017 ISO #:02 NET #:02
Unless you are willing to write your own control from scratch or license one which can be set to a different calendar (and has a definition for ISO8601), that may be the best you can do.
The Bottomline: The Week number is not wrong. It using a different calendar than you expect/want.
References:
Get or convert NET GetWeekOfYear() to ISO week
MSDN: Month Calendar Control Styles
DateTimePicker in Reference Source
The control is working fine.
When the year changes over the final few days of the year are in week 53 - but it's not a full week. Similarly the first few days of the year are in week 1, but the control takes the system's "first day of the week" setting to determine when week 2 begins - so it is possible for the first week of the year to have any where from 1 to 7 days in it.
This means that the image you've shown is showing Week 53 because you're in December and Week 2 because the 2nd week of January does start on the 4th.
If you navigate to January it would display week 1 for the row starting on December 28.
The bottom-line is that the first week in January only has 3 days in it.
This is just the normal and correct behaviour of this control.

Check if the current date string is within or outside the range of two other dates

I have 2 date pickers. One is for selecting a start date and the other is for selecting an expiry date. When a selection has been made I convert the date to string format and store the results in a two text fields.
This is how I save to the database:
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "MMMM d" // e.g. September 15
let sDate = dateFormatter.dateFromString(startDateField.text) as NSDate!
let eDate = dateFormatter.dateFromString(expiryDateField.text) as NSDate!
activity["startDate"] = sDate
activity["expiryDate"] = eDate
activity.saveInBackgroundWithBlock({ (success: Bool, error: NSError!) -> Void in
What I intend to do is have a table view display 2 sections: one showing activities that are active, and the other showing activities that are expired/not in date range.
I figured I could do this by taking the current date and checking it was within the start date and expiry date range.
My saved date from above is showing up in parse like this:
The thing is, I don't want to take the year or time into account. The only thing that matters is the month and day. So for example an activity could run once a year between September 15th and December 20th.
I need a way to check that the current date is within those two dates taking the day (e.g. 15th) of the month into account. So today's date is October 19 and an activity would be in range if its start date was September 15 and its expiry date December 20, but would be out of range/operation if the expiry date was October 18 or start date was October 25.
So to summarise:
I will be displaying two sections in a table view. One with active activities and another with non-active out of operation activities.
How do take my stored start date and expiry date and easily check the current date is within their range?
For the occasions when users pick either a start date or expiry date I need to check if the current date is greater/less than the start date (to be able to decide if the activity should show up or not) or if the current date is greater/less than the expiry date (to be able to decide if the activity has passed or not passed its expiry date.
It would be nice if I could roll 1 and 2 into one function and use it in the table view.
Would be interested to learn how the more experienced programmer would achieve this.
Instead of the date format "MMMM d" (e.g. "September 15"), you can convert the dates
to a string with the date format "MMdd" (e.g. "0915"). Then you can do a simple
string comparison between the current date, the start date and the expiry date.
For example,
"0915" < "1019" < "1220"
so October 19 is in the range from September 15 to December 20.

SQL-Automatically Bucketize all dates into that weeks monday date-

all- I'm looking to automatically standardize a certain date field into the monday date of that week. Essentially, if an entry came in with a date in this field of Tuesday, July 30th, 2013- I would want to standardize it to Monday, July 30th, 2013. I'd like to be able to apply this to only dates in a certain column where entries may have more than one date in seperate columns.
Thank you!!
If you want Monday of the current week you can use the following in SQL Server:
SELECT DateAdd (Day, 2 - DatePart (dw, YourDate), YourDate)
Depending on desired behavior, you may have to use SET DATEFIRST 2 to adjust the behavior.
List<List<myRecord>> recordList = new List<<myRecord>>();
List<DateTime> mondays = new List<DateTime>();
DateTime first = new DateTime(2013, 1, 30);
DateTime last = new DateTime(2013, 7, 29);
for (Date mon = first; mon <= last; mon = mon.AddDays(7)) {
recordList.Add(new List<myRecord>([SELECT * FROM myRecord WHERE myRecord.Date >= mon AND myRecord.Date < mon.AddDays(7)] ))
}
foreach (List<myRecord> monList : recordList) {
//do stuff with each monday bucket
}
dunno what technology youre using but maybe something like this would work?

NSDateFormatter "w"

In my app, I used "w" to format date:
With "w", does a new week start on Sundays?
The date is local right?
NSDateFormatter will use your location settings (See NSLocal).
So If canadian is your local and canadian calendars starts on Saturday this is your week.
For your question about the week element,
The following is from HERE, your NSDateFormatter uses the ISO Standard.
Week date is an alternative date representation used in many
commercial and industrial applications. It is: YYYY-Www-D
where YYYY is the Year in the Gregorian calendar, ww is the week of
the year between 01 (the first week) and 52 or 53 (the last week), and
D is the day in the week between 1 (Monday) and 7 (Sunday).
Example: 2003-W14-2 represents the second day of the fourteenth week
of 2003.
This means that for the Gregorian calendar, the weeks start on Mondays.
In additions to PascalTurbo's post, if you need to, you can explicitly set set the timezone for your date formatter like the following example:
NSDateFormatter *dateFormatter.timeZone = [NSTimeZone timeZoneWithAbbreviation:#"GMT"];