Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Week representation in Java 8 Time API

I would like to have a class to represent a particular week of year - for example today it's 29 November, which is exactly week number 48 of year 2014. So, one example implementation would be:

import java.time.Year;

public class WorkingWeek {
    private int weekNumber;
    private Year year;
}

Another conception is to have just a week's Monday date in a WorkingWeek class, but I feel it's less intuitive and I will frequently use week-in-year number.

Is there a class in Java 8 Time API that would fit best my requirements? Or if not, what would be recommended approach?

like image 637
kabeen Avatar asked Nov 29 '14 18:11

kabeen


1 Answers

You need to specify your definition of a week.

String Representation Of A Year-Week

One option is strings. The ISO 8601 standard defines a week as beginning on a Monday, ending on Sunday, with the first week of the year being the first to contain a Thursday, resulting in 52 or 53 weeks a year.

The standard also defines a string representation for this week-of-year span of time in the format of YYYY-Www (or omitting hypen, YYYYWww) such as 2014-W07. A day within the week is represented by a digit where Monday is 1 and Sunday is 7, in the format YYYY-Www-D (or omitting hyphen, YYYYWwwD) such as 2014-W07-2 (a Tuesday in 7th week of year). The W is important to disambiguate from a year-month such as 2014-07 being July of 2014.


java.time

The java.time package built into Java 8 and later is inspired by Joda-Time but entirely re-architected. See Tutorial.

In java.time, an Instant is a moment on the timeline in UTC. Apply a time zone (ZoneId) to get a ZonedDateTime. Use LocalDate to get a date-only value with no time-of-day and no time zone.

Note that determining the first moment of a day in java.time requires an extra step when compared to Joda-Time: We must go through the LocalDate class to call its atStartOfDay method.

ZoneId zoneId = ZoneId.of ( "America/Montreal" );
ZonedDateTime now = ZonedDateTime.now ( zoneId );
LocalDate firstDayOfThisWeek = now.toLocalDate ().with ( DayOfWeek.MONDAY );
LocalDate firstDayOfNextWeek = firstDayOfThisWeek.plusWeeks ( 1 );
ZonedDateTime thisWeekStart = firstDayOfThisWeek.atStartOfDay ( zoneId );
ZonedDateTime nextWeekStart = firstDayOfNextWeek.atStartOfDay ( zoneId );

Unfortunately, java.time lacks the equivalent of Joda-Time's Interval.

Fortunately we have ThreeTen Extra, the project that extends java.time (310 being the number of the JSR defining java.time). This library includes an Interval class that integrates with java.time. This Interval class is more limited than that of Joda-Time as it supports only Instant objects without time zones (always in UTC).

Caution: The ThreeTen-Extra project reserves the right to change its interfaces and/or implementations. While intended to be useful as-is, it also serves as an experimental proving ground for classes that may be eventually incorporated into java.time. I gladly make use of ThreeTen-Extra, but you must make your own risk-benefit decision.

// This next line requires adding the `ThreeTen Extra` library to your project.
Interval interval = Interval.of ( thisWeekStart.toInstant () , nextWeekStart.toInstant () );  // "Interval" is part of ThreeTen-Extra project, not built into Java 8.

Dump to console.

System.out.println ( "now: " + now + " thisWeekStart: " + thisWeekStart + " nextWeekStart: " + nextWeekStart + " interval: " + interval );

now: 2016-01-15T18:10:48.143-05:00[America/Montreal] thisWeekStart: 2016-01-11T00:00-05:00[America/Montreal] nextWeekStart: 2016-01-18T00:00-05:00[America/Montreal] interval: 2016-01-11T05:00:00Z/2016-01-18T05:00:00Z

You can determine the week-of-year as defined by the ISO 8601 standard. Note the "week based" terms. Near the beginning or ending of the year, a date will be in one calendar year while its ISO 8601 week’s year may be ±1.

ZoneId zoneId = ZoneId.of ( "America/Montreal" );
ZonedDateTime now = ZonedDateTime.now ( zoneId );
int weekOfYear = now.get ( IsoFields.WEEK_OF_WEEK_BASED_YEAR );
int weekBasedYear = now.get ( IsoFields.WEEK_BASED_YEAR );
System.out.println ( "weekOfYear: " + weekOfYear + " of weekBasedYear: " + weekBasedYear );

weekOfYear: 2 of weekBasedYear: 2016


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?

  • Java SE 8, Java SE 9, Java SE 10, and later
    • Built-in.
    • Part of the standard Java API with a bundled implementation.
    • Java 9 adds some minor features and fixes.
  • Java SE 6 and Java SE 7
    • Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
  • Android
    • Later versions of Android bundle implementations of the java.time classes.
    • For earlier Android (<26), the ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See How to use ThreeTenABP….

The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.


Joda-Time

UPDATE: The Joda-Time project is now in maintenance mode and advises migration to the java.time classes. I am leaving this section intact as history.

The Joda-Time 2.5 library offers the Interval class to represent a span of time as a pair of specific moments in time along the timeline of the Universe. Each moment is represented by the DateTime class.

Half-Open

Joda-Time uses the Half-Open [) approach to defining spans of time. The beginning is inclusive while the ending is exclusive. This is generally the best way to work with such spans of time. Search StackOverflow for many examples and discussions.

ISO 8601 in Joda-Time

Joda-Time uses the ISO 8601 definition of weeks. Also, Joda-Time uses ISO 8601 as its defaults for parsing and generating string representations of date-time values.

DateTimeZone zone = DateTimeZone( "America/Montreal" );
// Get first moment of a Monday. Inclusive.
DateTime start = new DateTime( 2014, 11, 24, 0, 0, 0, zone ); // Handle exception thrown if occurring during a Daylight Saving Time gap.
DateTime stop = start.plusWeeks( 1 ); // First moment of following Monday. Exclusive.
Interval week = new Interval ( start, stop );

First Monday

Search StackOverflow for many questions and answers on finding the first Monday of a week.

LocalDate (date-only)

While you might well be tempted to use LocalDate objects (date only, no time-of-day) to build an Interval. That would be sensible and useful. Unfortunately, the implementation of Interval supports only DateTime objects, not LocalDate.

like image 81
Basil Bourque Avatar answered Sep 20 '22 14:09

Basil Bourque