4

To manage dates I'm using the class Calendar. I'm saving timestamps in a DB, using

Calendar timestamp = Calendar.getInstance();

I'm able to save and retrieve the dates from the DB using long values to store dates.

This is my problem: when I have a timestamp to store, I need to know if in the DB there is already a timestamp belonging to the same week. To do this I thought I could use the couple of methods:

timestamp.get(Calendar.YEAR) and timestamp.get(Calendar.WEEK_OF_YEAR)

to uniquely identify a week, but this isn't true for weeks having days belonging to two consecutive years.

This is an example of what I get for the last week of the year 2014.

Saturday, December 27, 2014 year: 2014 week: 52

Sunday, December 28, 2014 year: 2014 week: 1

Monday, December 29, 2014 year: 2014 week: 1

Tuesday, December 30, 2014 year: 2014 week: 1

Wednesday, December 31, 2014 year: 2014 week: 1

Thursday, January 1, 2015 year: 2015 week: 1

Friday, January 2, 2015 year: 2015 week: 1

Saturday, January 3, 2015 year: 2015 week: 1

Sunday, January 4, 2015 year: 2015 week: 2

The days from 28/12/2014 to 3/1/2015 belong to the same week (in java the week starts on Sunday), but they got a different year, so the couple (YEAR, WEEK_OF_YEAR) doesn't give me the info that they belong to the same week.

How can I solve?

sixian
  • 55
  • 10
  • The Calendar class is notoriously troublesome and should be avoided (ditto java.util.Date). In this case especially this class is a poor choice: The definition of a week is [Locale dependent](http://stackoverflow.com/q/10893443/642706)! So the behavior of your app will change if moved to a computer with a different default locale. The locale can even be changed at runtime by any thread in the JVM. I strongly suggest you look at using the Joda-Time library. It uses the standard ISO 8601 definition of a week and a week number. – Basil Bourque Aug 31 '15 at 06:32
  • @basil. I added the Joda-Time library to the project and it seems to work. Now I have to convert tha Date and Time classes I used so far with the new ones and find a good way to get want I want. I'm going to study the methods I can use. If you have any suggestion, I would appreciate. Thanks – sixian Aug 31 '15 at 14:46
  • My suggestion: Search StackOverflow for "joda" to find hundreds of examples with explanations. – Basil Bourque Aug 31 '15 at 15:00
  • Also, there are one or two other builds of Joda-Time from alternate people meant for use with Android to get around some issue with a initial slowness when first loading. No issue in real Java, just Android. – Basil Bourque Aug 31 '15 at 15:04
  • May be you're referring to this: [dlew/joda-time-android](https://github.com/dlew/joda-time-android). Ok, but I'm sorry I don't understand how to install this library. Do I have to remove the joda-time library before? And then? I'm looking for a step by step tutorial, but so far I didn't find anything. – sixian Aug 31 '15 at 16:33
  • Update: The [Joda-Time](http://www.joda.org/joda-time/) project is now in [maintenance mode](https://en.wikipedia.org/wiki/Maintenance_mode), with the team advising migration to the [java.time](http://docs.oracle.com/javase/8/docs/api/java/time/package-summary.html) classes. See [Tutorial by Oracle](https://docs.oracle.com/javase/tutorial/datetime/TOC.html). Created by the same team led by the same man (Stephen Colebourne) as did Joda-Time. So similar concepts. For Java 6/7 see the *ThreeTen-Backport* project, further adapted for Android in the *ThreeTenABP* project. – Basil Bourque Jul 11 '17 at 04:23

3 Answers3

1

As suggested by @basil, I tried the Joda-Time library and I found there the solution to my problem. To get the week of the year I can use the method getWeekOfWeekyear() and to get the year I can use the method getWeekyear()

In this way, the couple of values uniquely identify the day belonging to the same week, with the week starting in Monday and ending in Sunday (another added value compared to the Java calendar class).

Following the same example of the question.

DateTime[] timestamp = new DateTime[10];

for(int i=0; i<10; i++){
   //set the start of the period
   DateTime time = new DateTime(2014, 12, 27, 18, 30, 50);
   //add i days to the start date
   timestamp[i] = time.plus(Period.days(i));

   int weekYear = timestamp[i].getWeekyear();
   int weekOfWeekyear = timestamp[i].getWeekOfWeekyear();

   Log.d(Constants.LOG_TAG, timestamp[i].toString("EEEE, MMMM d, yyyy") + " | year.week = " + weekYear + "." + weekOfWeekyear);
}

The result is:

Saturday, December 27, 2014 | year.week = 2014.52

Sunday, December 28, 2014 | year.week = 2014.52

Monday, December 29, 2014 | year.week = 2015.1

Tuesday, December 30, 2014 | year.week = 2015.1

Wednesday, December 31, 2014 | year.week = 2015.1

Thursday, January 1, 2015 | year.week = 2015.1

Saturday, January 3, 2015 | year.week = 2015.1

Sunday, January 4, 2015 | year.week = 2015.1

Monday, January 5, 2015 | year.week = 2015.2

It looks like there are a lot of other advantages in using this library and it will be useful for other calculations I have to perform.

Thank you @basil.

sixian
  • 55
  • 10
  • Can you edit your answer to include some example code? – Basil Bourque Sep 01 '15 at 01:11
  • I suggest you pass a time zone (`DateTimeZone`) to that constructor. If omitted your JVM’s current default is applied automatically. That means your results may vary. Better practice is to always specify your expected/desired time zone. – Basil Bourque Sep 01 '15 at 14:33
0

To get the first and last day of the week relative to a date, do this:

Calendar cal = Calendar.getInstance();
cal.setTime(date); // date to check
cal.set(Calendar.DAY_OF_WEEK, cal.getFirstDayOfWeek());
Date startOfWeek = cal.getTime();
cal.add(Calendar.DAY_OF_MONTH, 6);
Date endOfWeek = cal.getTime();

Disclaimer: I stole part of that from How to get the first day of the current week and month?

Then use a where clause that check the date range.

SELECT * FROM mytable WHERE mydate >= ? AND mydate <= ?

If your stored dates include time, you'd want the start of next week as the upper-exclusive boundary, by adding 7 instead of 6 days above.

SELECT * FROM mytable WHERE mydate >= ? AND mydate < ?

To show the date logic, here's some test code:

// Start on 12/27/2014
Calendar cal = Calendar.getInstance();
cal.clear();
cal.set(2014, Calendar.DECEMBER, 27);

// Collect 10 dates to test (start date + next 9 days)
Date[] dates = new Date[10];
dates[0] = cal.getTime();
for (int i = 1; i < dates.length; i++) {
    cal.add(Calendar.DAY_OF_MONTH, 1);
    dates[i] = cal.getTime();
}

// Test loop    
SimpleDateFormat fmt = new SimpleDateFormat("EEE d/M/yyyy");
for (Date date : dates) {

    // Code to test
    cal.setTime(date);
    cal.set(Calendar.DAY_OF_WEEK, cal.getFirstDayOfWeek());
    Date startOfWeek = cal.getTime();
    cal.add(Calendar.DAY_OF_MONTH, 6);
    Date endOfWeek = cal.getTime();

    // Show result
    System.out.println(fmt.format(date) + "  ->  " + fmt.format(startOfWeek) +
                                             " - " + fmt.format(endOfWeek));
}

Output

Sat 27/12/2014  ->  Sun 21/12/2014 - Sat 27/12/2014
Sun 28/12/2014  ->  Sun 28/12/2014 - Sat 3/1/2015
Mon 29/12/2014  ->  Sun 28/12/2014 - Sat 3/1/2015
Tue 30/12/2014  ->  Sun 28/12/2014 - Sat 3/1/2015
Wed 31/12/2014  ->  Sun 28/12/2014 - Sat 3/1/2015
Thu 1/1/2015  ->  Sun 28/12/2014 - Sat 3/1/2015
Fri 2/1/2015  ->  Sun 28/12/2014 - Sat 3/1/2015
Sat 3/1/2015  ->  Sun 28/12/2014 - Sat 3/1/2015
Sun 4/1/2015  ->  Sun 4/1/2015 - Sat 10/1/2015
Mon 5/1/2015  ->  Sun 4/1/2015 - Sat 10/1/2015
Community
  • 1
  • 1
Andreas
  • 154,647
  • 11
  • 152
  • 247
  • Thank you for the answer. So I need to add some logic each time I have to manage a timestamp. I thought there would be some methods doing the job. Because I have to do other date and time calculations, I think I'll have a go with the Joda-Time library, as suggested by @Basil Bourque. – sixian Aug 31 '15 at 09:30
0

tl;dr

You must understand the difference between a “normal” calendar year and a standard week-based year. The first/last few days of a calendar year may appear in the previous/next week-based year.

screenshot of December 2014 month of calendar-year with indications of standard week-based year weeks

The calendar year of 2014 ends on December 31, 2014 of course. But that last day of 2014 lands in the next week-based-year, 2015.

The last day of week-based year 2014 ends on December 28, 2014 (calendar year). The first day of 2015 week-based-year is December 29, 2014 (calendar year). The calendar graphic above should make these facts clear.

2014-12-28 = 2014-W52-7

2014-12-29 = 2015-W01-1

2015-01-01 = 2015-W01-4

Ask for week-based week and year number:

myZonedDateTime.get( IsoFields.WEEK_OF_WEEK_BASED_YEAR )

…and…

myZonedDateTime.get( IsoFields.WEEK_BASED_YEAR )

Simply put: For the last/first few days of the year, calling myZonedDateTime.getYear() and myZonedDateTime.get( IsoFields.WEEK_BASED_YEAR ) may differ in results.

java.time

The modern approach uses the java.time classes that supplanted the troublesome old legacy date-time classes.

To manage dates I'm using the class Calendar. I'm saving timestamps in a DB, using Calendar timestamp = Calendar.getInstance();

Instead, use ZoneDateTime to capture the current moment with a wall-clock time used by people of a certain region (time zone).

ZoneId z = ZoneId.of( "America/Montreal" ) ;
ZonedDateTime zdt = ZonedDateTime.now( z ) ;

I'm able to save and retrieve the dates from the DB using long values to store dates.

No, use an approriate data type, both in defining your database column and your Java code.

To store a moment, a specific moment on the timeline, in a SQL standard compliant database, define your column as TIMESTAMP WITH TIME ZONE.

With JDBC 4.2 and later, you can directly exchange java.time objects with your database.

myPreparedStatement.setObject( … , zdt ) ;

Retrieve as an Instant, a moment in UTC, may be the best route in the other direction. Then adjust into your desired time zone.

Instant instant = myResultSet.getObject( … , Instant.class ) ;
ZonedDateTime zdt = instant.atZone( z ) ;

This is my problem: when I have a timestamp to store, I need to know if in the DB there is already a timestamp belonging to the same week. To do this I thought I could use the couple of methods: timestamp.get(Calendar.YEAR) and timestamp.get(Calendar.WEEK_OF_YEAR)

Unfortunately, the meaning of a week in the confusing Calendar class varies by locale.

Perhaps your own definition agrees with the standard ISO 8601 definition of a week.

  • The first day is Monday, running through Sunday.
  • Week number one of a week-based year contains the first Thursday of the calendar year.
  • A week-based year has either 52 or 53 weeks.
  • The first/last few days of a calendar year may appear in the previous/next week-based year.

Note that determining a week requires determining a date. And determining a date requires a time zone. For any given moment, the date varies around the globe by zone. So be clear on your use of time zone (ZoneId) as seen above. In other words, a week number is contextual; it depends on a date in a particular zone.

Access a TemporalField in ZonedDateTime::get. The IsoFields class defines the constants you need.

int week = zdt.get( IsoFields.WEEK_OF_WEEK_BASED_YEAR ) ;
int weekBasedYear = zdt.get( IsoFields.WEEK_BASED_YEAR ) ;

Even better, add the ThreeTen-Extra library to your project to use YearWeek class.

org.threeten.extra.YearWeek yw = YearWeek.from( zdt ) ;

to uniquely identify a week, but this isn't true for weeks having days belonging to two consecutive years.

This is an example of what I get for the last week of the year 2014.

Saturday, December 27, 2014 year: 2014 week: 52

In a standard week, 2014-12-27 is in week 52.

YearWeek.from( LocalDate.parse( "2014-12-27" )  ).toString()

2014-W52

The end of 2014 happens to land in the following week-based year (2015).

YearWeek.from( LocalDate.parse( "2014-12-31" ) ).toString()

2015-W01

You can compare YearWeek. Call equals, isBefore, or isAfter methods.

boolean sameYearWeek = YearWeek.from( someZdt ).equals( YearWeek.from( otherZdt ) ) 

Terminology

So, be sure that in your documentation, code comments, and discussions, you always distinguish between a calendar year and week-based year. Never say "the year" as that is terribly ambiguous.

Add in financial years for even more confusion. And some industries have their own definitions of years and seasons and so on. So be careful with your terms.

Beware of calendaring software settings

Never assume the definition of a week number. Be sure the source of such a number has the same definition of week as you, such as ISO 8601 definition.

For example, the Calendar app supplied by Apple with macOS defaults to a "Gregorian" calendar definition of week. As for what that means, I do not know as I could not find any documentation as to their intent/definition. For ISO 8601 weeks, you must change a setting away from default.


About java.time

The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.

You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes.

Where to obtain the java.time classes?

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154