Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 4.0

We use class Date for simple date processing:

Code Block
def today= new Date() //represents the date and time when it is created
println today

//we can add to and subtract from a date...
def tomorrow= today + 1,
    dayAfter= today + 2,
    yesterday= today - 1,
    dayBefore= today - 2
println "\n$dayBefore\n$yesterday\n$today\n$tomorrow\n$dayAfter\n"
assert today + 7 == today.plus(7) && today - 15 == today.minus(15)
    //equivalent methods

//we can increment and decrement a date...
def d= today.clone()
d++; assert d == tomorrow
d= d.next(); assert d == dayAfter //equivalent method
d--; assert d == tomorrow
d= d.previous(); assert d == today //equivalent method

//we can compare dates...
assert tomorrow.after(today)
assert yesterday.before(today)
assert tomorrow.compareTo(today) > 0
assert tomorrow.compareTo(dayAfter) < 0
assert dayBefore.compareTo(dayBefore) == 0

def n= today.time //we can convert a Date to a Long
println n
today.time = 0 //long 0 is beginning of 1 Jan 1970 GMT
println today
def sometimeAgo= new Date(0) //we can construct a date with a Long argument
assert sometimeAgo == today

Other date and time processing can be done using the GregorianCalendar class:

Code Block
System.setProperty('user.timezone', 'GMT') //we'll look at timezones later

def c= new GregorianCalendar()
println c.time //'time' property gives a Date class
c= Calendar.instance
assert c.class == GregorianCalendar //another way to create a GregorianCalendar
println c.time
assert c.timeInMillis == c.time.time
    //we can get the time in milliseconds after 1 Jan 1970 at 0:00:00am GMT

println System.currentTimeMillis() //another way to get the current time
println System.nanoTime()
    //time in nano-seconds: good for measuring elapsed computation times

c= new GregorianCalendar(2009, Calendar.JULY, 22) //creates 22 July 2009
c= new GregorianCalendar(2009, Calendar.JULY, 22, 2, 35)
    //creates 22 July 2009 at 2:35am GMT
c= new GregorianCalendar(2009, Calendar.JULY, 22, 2, 35, 21)
    //creates 22 July 2009 at 2:35:21am GMT

c.clear() //if we clear the fields, we get...
assert c.get(Calendar.ERA) == GregorianCalendar.AD &&
       c.get(Calendar.YEAR) == 1970 &&
       c.get(Calendar.MONTH) == 0 &&
                         //dates range from 0 to 11, so this is January
       c.get(Calendar.WEEK_OF_MONTH) == 1 && //should be: 0
       c.get(Calendar.DAY_OF_MONTH) == 1 &&
       c.get(Calendar.DATE) == 1 && //same as DAY_OF_MONTH
       c.get(Calendar.DAY_OF_WEEK) == 5 &&
       c.get(Calendar.DAY_OF_WEEK_IN_MONTH) == 1 &&
       c.get(Calendar.AM_PM) == Calendar.AM &&
       c.get(Calendar.HOUR) == 0 &&
       c.get(Calendar.HOUR_OF_DAY) == 0 &&
       c.get(Calendar.MINUTE) == 0 &&
       c.get(Calendar.SECOND) == 0 &&
       c.get(Calendar.MILLISECOND) == 0 &&

       c.get(Calendar.WEEK_OF_YEAR) == 1 &&
       c.get(Calendar.DAY_OF_YEAR) == 1

def d= new GregorianCalendar()
d.timeInMillis= 0
    //we can set the 'time', here 1 Jan 1970 at 00:00:00.000 GMT (Gregorian)
d.time= new Date(0) //alternative syntax
assert d == c

GregorianCalendar supports both the Julian and Gregorian calendar systems, supporting one discontinuity, which by default is when the Gregorian calendar was first instituted in some countries, ie, 4 October 1582 (Julian) followed by 15 October, 1582 (Gregorian). The only difference between the calendars is the leap year rule: the Julian specifies leap years every four years, whereas the Gregorian omits century years which are not divisible by 400. Because dates are computed by extrapolating the current rules indefinitely far backward and forward in time, this calendar generates consistent results for all years, although dates obtained are historically accurate only from March 1, 4 AD onward, when modern Julian calendar rules were adopted. Although New Year's Day was March 25 prior to the institution of the Gregorian calendar, to avoid confusion, this calendar always uses January 1.

From Groovy 1.5.7 / 1.6.x,  you may use Date.format() directly. Refer to GROOVY-3066 for details.

Alternatively, Dates and times can be formatted easily with String.format(). The first character is 't' or 'T' for each item:

Code Block
def c1= new GregorianCalendar(1995, Calendar.SEPTEMBER, 5, 19, 35, 30, 750)

//dates...
assert String.format('%tY/%<tm/%<td', c1) == '1995/09/05'
assert String.format('%tA %<te %<tB %<ty', c1) == 'Tuesday 5 September 95'
assert String.format('century:%tC, month:%<tb, day:%<te', c1) ==
    'century:19, month:Sep, day:5'
assert String.format('month:%th, day of year:%<tj, day of week:%<ta', c1) ==
    'month:Sep, day of year:248, day of week:Tue' //'h' same as 'b'

//times...
assert String.format('%tH:%<tM:%<tS.%<tL', c1) == '19:35:30.750'
assert String.format('%tI%<tp, %<tl%<tp, nanoseconds:%<tN', c1) ==
    '07pm, 7pm, nanoseconds:750000000'
assert String.format('%ts', c1) == '810300930'
    //seconds since start of 1-Jan-1970 GMT
assert String.format('%tQ', c1) == '810300930750'
    //milliseconds since start of 1-Jan-1970 GMT
assert String.format('%tk',
            new GregorianCalendar(1995, Calendar.SEPTEMBER, 5, 6, 35)) == '6'

//shortcut formats...
assert String.format('%tF', c1) == '1995-09-05' //date as '%tm/%td/%ty'
assert String.format('%tD', c1) == '09/05/95' //date as '%tY-%tm-%td'
assert String.format('%tT', c1) == '19:35:30' //time as '%tH:%tM:%tS'
assert String.format('%tR', c1) == '19:35' //time as '%tH:%tM'
assert String.format('%tr', c1) == '07:35:30 PM' //time as '%tI:%tM:%tS %Tp'

//additionally...
assert String.format('%tF', new Date(0)) == '1970-01-01'
    //we can supply a Date instead of a Calendar
assert String.format('%tF', 0L) == '1970-01-01' //we can also supply a long

assert String.format('...%15tF...', 0L) == '...     1970-01-01...' //width 15
assert String.format('...%-15tF...', 0L) == '...1970-01-01     ...'
    // '-' flag to left-justify


After setting fields, we must call any get(), add(), or roll() method, or access the 'timeInMillis' or 'time' properties, to cause other relevant fields to update themselves:

Code Block
System.setProperty('user.timezone', 'GMT')

def c= new GregorianCalendar()
c.set( Calendar.ERA, GregorianCalendar.AD )
c.set( Calendar.YEAR, 1949 )
c.set( Calendar.MONTH, Calendar.OCTOBER )
c.set( Calendar.DATE, 31 )
assert String.format('%tF %<ta', c) == '1949-10-31 Mon'

//properties for calculating WEEK_OF_YEAR and WEEK_OF_MONTH fields...
c.firstDayOfWeek = Calendar.SUNDAY //Sunday in most countries, Monday in others
c.minimalDaysInFirstWeek = 1

assert c.get(Calendar.ERA) == GregorianCalendar.AD &&
       c.get(Calendar.YEAR) == 1949 &&
       c.get(Calendar.MONTH) == 9 && //dates range from 0 to 11, so October
       c.get(Calendar.MONTH) == Calendar.OCTOBER && //alternatively
       c.get(Calendar.DAY_OF_MONTH) == 31 &&
       c.get(Calendar.WEEK_OF_YEAR) == 45 && //range from 1 to 53
       c.get(Calendar.WEEK_OF_MONTH) == 6 && //range from 1 to 6
       c.get(Calendar.DAY_OF_YEAR) == 304 &&
       c.get(Calendar.DAY_OF_WEEK) == 2 && //Monday
       c.get(Calendar.DAY_OF_WEEK_IN_MONTH) == 5

//changing the month uses the same year and day of month...
c.set(Calendar.MONTH, Calendar.AUGUST )
c.time //cause other fields to update themselves
assert String.format('%tF %<ta', c) == '1949-08-31 Wed'

c.set(Calendar.MONTH, Calendar.APRIL )
    //...but may cause adjustment to roll into following month
c.time
assert String.format('%tF %<ta', c) == '1949-05-01 Sun'

c.set(Calendar.DATE, 31 )
c.set(Calendar.MONTH, Calendar.FEBRUARY )
c.set(Calendar.MONTH, Calendar.SEPTEMBER )

//rolling into following month only occurs when other fields update themselves,
//call this method to trigger it...
c.time

assert String.format('%tF %<ta', c) == '1949-10-01 Sat'
    //...so Feb-28 DIDN'T roll into Mar-03

//changing the day of month uses the same month and year...
c.set( Calendar.DATE, 1 ); c.time
assert String.format('%tF %<ta', c) == '1949-10-01 Sat'

//changing the day of year adjusts the month, day, and other date fields...
c.set(Calendar.DAY_OF_YEAR, c.get(Calendar.DAY_OF_YEAR) + 2 ); c.time
assert String.format('%tF %<ta', c) == '1949-10-03 Mon'

//changing the week of year keeps the same day of week, but adjusts
//the other date fields...
c.set(Calendar.WEEK_OF_YEAR, c.get(Calendar.WEEK_OF_YEAR) + 3 ); c.time
assert String.format('%tF %<ta', c) == '1949-10-24 Mon'

//changing the week of month keeps both the same month and day of week...
c.set(Calendar.WEEK_OF_MONTH, c.get(Calendar.WEEK_OF_MONTH) - 2 ); c.time
assert String.format('%tF %<ta', c) == '1949-10-10 Mon'

//changing the day of week in month also keeps both the
//same month and day of week...
c.set(Calendar.DAY_OF_WEEK_IN_MONTH, c.get(Calendar.DAY_OF_WEEK_IN_MONTH) - 1 )
c.time
assert String.format('%tF %<ta', c) == '1949-10-03 Mon'

//changing the day of week keeps the same week in year...
c.set( Calendar.DAY_OF_WEEK, Calendar.SATURDAY ); c.time
assert String.format('%tF %<ta', c) == '1949-10-08 Sat'
c.set( Calendar.DAY_OF_WEEK, Calendar.SUNDAY ); c.time
assert String.format('%tF %<ta', c) == '1949-10-02 Sun'

We can also set the time in this way:

Code Block
System.setProperty('user.timezone', 'GMT')
def c= new GregorianCalendar( 1949, Calendar.OCTOBER, 2 )

c.set( Calendar.AM_PM, Calendar.AM )
c.set( Calendar.HOUR, 6 ) //set the AM_PM and HOUR fields...
c.set( Calendar.MINUTE, 30 )
c.set( Calendar.SECOND, 15 ); c.time
assert String.format('%tF %<tT', c) == '1949-10-02 06:30:15'
assert c.get( Calendar.HOUR_OF_DAY ) == 6
    //...and the HOUR_OF_DAY field is updated...

c.set( Calendar.HOUR_OF_DAY, 19 ); c.time
assert String.format('%tF %<tT', c) == '1949-10-02 19:30:15'
assert c.get( Calendar.HOUR ) == 7 && c.get( Calendar.AM_PM ) == Calendar.PM
                                                   //...and vice versa

c.set( Calendar.AM_PM, Calendar.AM ); c.time
assert String.format('%tF %<tT', c) == '1949-10-02 07:30:15' &&
    c.get( Calendar.AM_PM ) == Calendar.AM

c.set( Calendar.HOUR, 18 ); c.time
    //if we set the HOUR with a 24-hr value, it self-adjusts
assert c.get( Calendar.HOUR ) == 6 && c.get( Calendar.AM_PM ) == Calendar.PM

//there's no 24:00, only 00:00 which is 'am', on the following day...
c= new GregorianCalendar(1950, Calendar.JANUARY, 26, 23, 59)
assert String.format('%tF %<tT %<tp', c) == '1950-01-26 23:59:00 pm'
c.add( Calendar.MINUTE, 1 )
assert String.format('%tF %<tT %<tp', c) == '1950-01-27 00:00:00 am'

//12:00 noon is 'pm'...
c= new GregorianCalendar(1950, Calendar.JANUARY, 27, 12, 00)
assert String.format('%tF %<tT %<tp', c) == '1950-01-27 12:00:00 pm'

More field manipulations:

Code Block
System.setProperty('user.timezone', 'GMT')

//we can set common fields using terser syntax...
def c= new GregorianCalendar()
c.set( 1947, Calendar.AUGUST, 11 ); c.time
assert String.format('%tF %<ta', c) == '1947-08-11 Mon'
c.set( 1947, Calendar.AUGUST, 12, 6, 30 ); c.time
assert String.format('%tF %<ta', c) == '1947-08-12 Tue'
c.set( 1947, Calendar.AUGUST, 15, 6, 30, 45 ); c.time
assert String.format('%tF %<ta', c) == '1947-08-15 Fri'

//we can clear individual fields, and check if they're set...
assert c.isSet( Calendar.YEAR ) && c.isSet( Calendar.MONTH )
c.clear( Calendar.YEAR )
assert ! c.isSet( Calendar.YEAR ) && c.isSet( Calendar.MONTH )

//we can check different maximums and minimums of a field...
c.set( 1947, Calendar.APRIL, 11 ); c.time
assert c.getMinimum( Calendar.DATE ) == 1 &&
    c.getMaximum( Calendar.DATE ) == 31
assert c.getActualMinimum( Calendar.DATE ) == 1 &&
    c.getActualMaximum( Calendar.DATE ) == 30
assert c.getGreatestMinimum( Calendar.DATE ) == 1 &&
    c.getLeastMaximum( Calendar.DATE ) == 28

//the first week in a year may be numbered as part of the previous year,
//and in a month as 0...
c.firstDayOfWeek = Calendar.SUNDAY
c.minimalDaysInFirstWeek = 1
c.set( 1954, Calendar.JANUARY, 1 ); c.time
assert String.format('%tF %<ta', c) == '1954-01-01 Fri'
assert c.get(Calendar.WEEK_OF_YEAR) == 1
assert c.get(Calendar.WEEK_OF_MONTH) == 1

assert c.firstDayOfWeek == Calendar.SUNDAY &&
    c.minimalDaysInFirstWeek == 1
c.firstDayOfWeek = Calendar.MONDAY
c.minimalDaysInFirstWeek = 4 //trigger different week numbering
assert c.get(Calendar.WEEK_OF_YEAR) == 53
assert c.get(Calendar.WEEK_OF_MONTH) == 0

c.set( 1956, Calendar.DECEMBER, 31 ); c.time
assert String.format('%tF %<ta', c) == '1956-12-31 Mon'
assert c.get(Calendar.WEEK_OF_YEAR) == 1
    //last week of year may be numbered as first of next

We can compare dates:

Code Block
c1= new GregorianCalendar(2008, Calendar.AUGUST, 8)
c2= new GregorianCalendar(2009, Calendar.JULY, 22)
assert c1.before( c2 ) && c2.after( c1 )
assert c1.compareTo( c2 ) < 0 &&
       c2.compareTo( c1 ) > 0 &&
       c1.compareTo( c1 ) == 0

As well as using set(), calendar fields can be changed using add() and roll(), both of which force all fields to update themselves:

Code Block
def c= new GregorianCalendar(1999, Calendar.AUGUST, 31)
assert String.format('%tF %<ta', c) == '1999-08-31 Tue'
c.add(Calendar.MONTH, 13)
assert String.format('%tF %<ta', c) == '2000-09-30 Sat'
                                          //we DON'T roll to Oct-01

c= new GregorianCalendar(1999, Calendar.AUGUST, 31)
c.roll(Calendar.MONTH, 13) //rolls a field without changing larger fields
assert String.format('%tF %<ta', c) == '1999-09-30 Thu'
c.roll(Calendar.MONTH, true) //rolls +1
assert String.format('%tF %<ta', c) == '1999-10-30 Sat'
c.roll(Calendar.MONTH, false) //rolls -1
assert String.format('%tF %<ta', c) == '1999-09-30 Thu'

We can turn off the lenient mode for field updates to force us to give calendars precisely correct values:

Code Block
System.setProperty('user.timezone', 'GMT')
def c= new GregorianCalendar( 2002, Calendar.JUNE, 30 )
assert c.lenient
c.set( Calendar.DATE, 31 ); c.time
assert String.format('%tF %<ta', c) == '2002-07-01 Mon'

c= new GregorianCalendar( 2002, Calendar.JUNE, 30 )
c.lenient= false
c.set( Calendar.DATE, 31 )
try{ c.time; assert 0 }catch(e){ assert e in IllegalArgumentException }

Durations

We can use durations:

Code Block
import groovy.time.*

class Extras{
  static toString(BaseDuration it){
    def list= []
    if(it.years != 0) list<< "$it.years yrs"
    if(it.months != 0) list<< "$it.months mths"
    if(it.days != 0) list<< "$it.days days"
    if(it.hours != 0) list<< "$it.hours hrs"
    if(it.minutes != 0) list<< "$it.minutes mins"
    if(it.seconds != 0 || it.millis != 0) list<< "$it.seconds.$it.millis secs"
    list.join(', ')
  }
}

//enable utility methods for duration classes using 'category' syntax,
//introduced in a later tutorial...
use(Extras){
  [ {new TimeDuration( 12, 30, 0, 0 )}:   '12 hrs, 30 mins',
    {new TimeDuration( 4, 12, 30, 0, 0 )}:'4 days, 12 hrs, 30 mins',
    {new Duration( 4, 12, 30, 0, 500 )}:  '4 days, 12 hrs, 30 mins, 0.500 secs',
    {new DatumDependentDuration( 7, 6, 0, 12, 30, 0, 0 )}:
                                          '7 yrs, 6 mths, 12 hrs, 30 mins',
  ].each{
    assert it.key().toString() == it.value
  }
}

def convertToMilliseconds= { yr, mth, day, hr, min, sec, mil->
  mil + 1000g*( sec + 60g*( min + 60g*( hr + 24g*(
    day + 30g*( mth + 12g*yr )
  ))))
}

assert new TimeDuration( 12, 30, 0, 0 ).toMilliseconds() ==
    convertToMilliseconds( 0, 0, 0, 12, 30, 0, 0 )
    //ignores 61-second leap minutes
assert new Duration( 114, 12, 30, 0, 0 ).toMilliseconds() ==
    convertToMilliseconds( 0, 0, 114, 12, 30, 0, 0 )
    //ignores 25-hour daylight-saving days
assert new DatumDependentDuration( 5, 1, 14, 12, 30, 0, 0 ).toMilliseconds() !=
    convertToMilliseconds( 5, 1, 14, 12, 30, 0, 0 )
    //considers 31-day months and leap-years

These durations can be created more easily within the TimeCategory:

Code Block
import groovy.time.*

//reuse Extras category from a previous example...
use( [Extras, groovy.time.TimeCategory] ){
  assert 10.years.class == DatumDependentDuration

  assert 10.years.toString() ==
      new DatumDependentDuration( 10, 0, 0, 0, 0, 0, 0 ).toString()
  assert 4.months.toString() ==
      new DatumDependentDuration( 0, 4, 0, 0, 0, 0, 0 ).toString()
  assert 7.weeks.toString() == new Duration( 49, 0, 0, 0, 0 ).toString()
  assert 5.days.toString() == new Duration( 5, 0, 0, 0, 0 ).toString()
  assert 12.hours.toString() == new TimeDuration( 12, 0, 0, 0 ).toString()
  assert 15.minutes.toString() == new TimeDuration( 0, 15, 0, 0).toString()
  assert 13.seconds.toString() == new TimeDuration( 0, 0, 13, 0 ).toString()
  assert 750.milliseconds.toString() ==
      new TimeDuration( 0, 0, 0, 750 ).toString()

  assert 1.day.toString() == new Duration( 1, 0, 0, 0, 0 ).toString()
      //we can use the singular name for any of these...
  assert 25.minute.toString() == new TimeDuration( 0, 25, 0, 0 ).toString()
      //...even when not grammatical in English
}

We can add and subtract durations of different types together:

Code Block
import groovy.time.*

//reuse Extras category from a previous example...
use( [Extras, groovy.time.TimeCategory] ){

  assert (10.years + 4.months).class == DatumDependentDuration
  assert (10.years + 4.months).toString() ==
      new DatumDependentDuration( 10, 4, 0, 0, 0, 0, 0 ).toString()
  assert (10.years.plus(4.months) ).toString() ==
     (10.years + 4.months).toString() //alternative method name
  assert (4.months + 10.years).toString() == (10.years + 4.months).toString()
      //all duration operations are commutative

  assert (10.years + 4.weeks).class == DatumDependentDuration
  assert (5.days + 7.weeks).class == Duration
  assert (5.days + 17.hours).class == TimeDuration
  assert (10.minutes + 5.seconds).class == TimeDuration

  //adding a DatumDependentDuration and a TimeDuration gives a
  //specially-defined TimeDatumDependentDuration...
  assert (10.years + 12.hours).toString() ==
      new TimeDatumDependentDuration( 10, 0, 0, 12, 0, 0, 0 ).toString()
  assert (10.years + 12.hours).class == TimeDatumDependentDuration

  assert ( 10.years + new TimeDatumDependentDuration( 0, 0, 0, 12, 0, 0, 0 )
               ).class == TimeDatumDependentDuration
  assert ( 10.days + new TimeDatumDependentDuration( 0, 0, 0, 12, 0, 0, 0 )
               ).class == TimeDatumDependentDuration
  assert ( 10.minutes + new TimeDatumDependentDuration( 0, 0, 0, 12, 0, 0, 0 )
               ).class == TimeDatumDependentDuration
  assert ( new TimeDatumDependentDuration( 0, 0, 0, 12, 0, 0, 0 ) +
           new TimeDatumDependentDuration( 0, 0, 0, 0, 10, 0, 0 )
               ).class == TimeDatumDependentDuration

  //subtracting durations...
  assert (10.years - 4.months).class == DatumDependentDuration
  assert (10.years - 4.months).toString() ==
      new DatumDependentDuration( 10, -4, 0, 0, 0, 0, 0 ).toString()
  assert (10.years.minus(4.months) ).toString() ==
      (10.years - 4.months).toString() //alternative method name

  assert (10.years - 12.hours).class == DatumDependentDuration
  assert (5.days - 7.weeks).class == Duration
  assert (5.days - 17.hours).class == TimeDuration
  assert (10.minutes - 5.seconds).class == TimeDuration
  assert (10.years - 4.weeks).class == DatumDependentDuration
}

We can add a Date to a duration to give another Date. A TimeDuration takes leap minutes into account, a Duration also takes daylight saving into account, and a DatumDependentDuration considers 31-day months and leap-years:

Code Block
import groovy.time.*

//reuse Extras category from a previous example...
use( [Extras, groovy.time.TimeCategory] ){

  def today= new Date(),
      tomorrow= today + 1,
      dayAfter= today + 2,
      nextWeek= today + 7 //days-only Date arithmetic
  assert ( today + 7.days ).toString() == nextWeek.toString()
      //use Date and duration together
  assert ( today.plus(7.days) ).toString() == ( today + 7.days ).toString()
      //alternative method name
  assert ( 7.days + today ).toString() == nextWeek.toString()
      //commutative
  assert ( nextWeek - 6.days ).toString() == tomorrow.toString()
  assert ( nextWeek.minus(6.days) ).toString() == tomorrow.toString()
      //alternative method name
  assert ( nextWeek - dayAfter ).toString() == 5.days.toString()
      //subtract two dates to get a duration

  //some handy operations...
  [2.days.ago, 3.days.from.now, 3.days.from.today].each{
    assert it.class == java.sql.Date
  }
}

Time Zones

We can retrieve lists of all time zones on a system:

Code Block
//we can get all available time zone ID's, and get the time zone for an ID...
TimeZone.availableIDs.toList().groupBy{ TimeZone.getTimeZone(it).rawOffset }.
  entrySet().sort{it.key}.reverse().each{
    println String.format('%6.2f hrs: %2d',
                          it.key / (60*60*1000), it.value.size())
    it.value.each{
      def tz= TimeZone.getTimeZone(it)
      println "${' '*8}$tz.displayName ($tz.ID): " +
              "${tz.DSTSavings / (60*60*1000)}, ${tz.useDaylightTime()}"
    }
  }

//we can get all the available time zone ID's for a specific offset...
TimeZone.getAvailableIDs( 12 * (60*60*1000) ).toList().each{
  def tz= TimeZone.getTimeZone(it)
  println "$tz.displayName ($tz.ID): " +
          "${tz.DSTSavings / (60*60*1000)}, ${tz.useDaylightTime()}"
}

We can access various time zones individually:

Code Block
def tz= TimeZone.'default' //look at the default time zone
println "$tz.displayName ($tz.ID): offset $tz.rawOffset, " +
        "dstSaving $tz.DSTSavings, useDST ${tz.useDaylightTime()}"

TimeZone.'default'= TimeZone.getTimeZone('GMT') //set the default time zone

//get a specific time zone from the system...
tz = TimeZone.getTimeZone('America/Los_Angeles')
assert tz.displayName == 'Pacific Standard Time' &&
       tz.rawOffset == -8 * (60*60*1000) &&
       tz.useDaylightTime() &&
       tz.DSTSavings == 1 * (60*60*1000)

//we can fetch a custom time zone, without any daylight saving, by
//supplying a string...
[ 'GMT-8': 'GMT-08:00',
  'GMT+11': 'GMT+11:00', //hours must be less than 24
  'GMT+0300': 'GMT+03:00',
  'GMT-3:15': 'GMT-03:15',
  'moo': 'GMT', //syntax errors give GMT
].each{ assert TimeZone.getTimeZone( it.key ).ID == it.value }

We can create a time zone with custom daylight-saving time rules:

Code Block
//in the constructor, we can encode the rules for starting or ending
//Daylight Saving time...
def stz= new SimpleTimeZone(-8*(60*60*1000), //base GMT offset: -8:00
  "America/Death_Valley",
  Calendar.MARCH, 1, 0,    //DST starts on 1 March exactly
  2*(60*60*1000), SimpleTimeZone.STANDARD_TIME,
                           //...at 2:00am in standard time (wall time)
  Calendar.OCTOBER, 1, -Calendar.SUNDAY,
                           //ends first Sun on/after 1 Oct (first Sun in Oct)...
  2*(60*60*1000), SimpleTimeZone.WALL_TIME,
                           //...at 2:00am in daylight time (wall time)
  1*(60*60*1000) ) // save 1 hour

//leave out last parameter which defaults to 'save 1 hour', ie, 1*(60*60*1000)
stz= new SimpleTimeZone(15*(60*60*1000), //base GMT offset: +15:00
  "Pacific/Happy_Isle",
  Calendar.AUGUST, -21, -Calendar.FRIDAY,
                           //starts on last Friday on or before 21 August...
  2*(60*60*1000),      //...at 2:00am in standard time (wall time, the default)
  Calendar.APRIL,    1, -Calendar.SUNDAY,
                           //ends first Sun on/after 1 Apr (first Sun in Apr)...
  2*(60*60*1000) )     //...at 2:00am in daylight time (wall time, the default)

//two extra optional parameters (if present, both must be)...
stz= new SimpleTimeZone( 1*(60*60*1000), //base GMT offset: +1:00
  "Europe/Alps",
  Calendar.JUNE,     8, -Calendar.MONDAY,
                       //starts first Mon on/after 8 Jun (second Mon in Jun)...
  1*(60*60*1000), SimpleTimeZone.UTC_TIME, //...at 1:00am in UTC time
  Calendar.OCTOBER, -1, Calendar.SUNDAY,
                       //ends on the last Sunday in October...
  1*(60*60*1000), SimpleTimeZone.UTC_TIME, //...at 1:00am in UTC time
  1*(60*60*1000) ) // save 1 hour

//we can instead encode the rules in the same way using methods...
stz= new SimpleTimeZone( -8*(60*60*1000), //base GMT offset: -8:00
  "America/Death_Valley" ) //no daylight-saving schedule in constructor
stz.setStartRule(Calendar.APRIL, 1, -Calendar.SUNDAY, 2 * 60*60*1000)
                                                            //first Sun in Apr
stz.setEndRule(Calendar.OCTOBER, -1, Calendar.SUNDAY, 2 * 60*60*1000)
                                                            //last Sun in Oct

assert stz.dSTSavings == 60*60*1000 //the default
stz.dSTSavings= 2 * 60*60*1000
assert stz.dSTSavings == 2 * 60*60*1000
assert stz.getDSTSavings() == 2 * 60*60*1000
    //unusually-cased property name 'dSTSavings' has equivalent method names
    //'getDSTSavings()' and 'setDSTSavings()'

stz.setStartRule(Calendar.MAY, 1, 2 * 60*60*1000)
    //shortcut method for fixed date in month
stz.setStartRule(Calendar.MAY, 10, Calendar.SUNDAY, 2 * 60*60*1000, true)
    //shortcut for first Sunday on or after 10 May; true means 'after'
stz.setEndRule(Calendar.OCTOBER, 20, Calendar.SATURDAY, 2 * 60*60*1000, false)
    //shortcut for first Saturday on or before 20 October; false means 'before'

(Coordinated universal time, UTC, being based on an atomic clock, enables an extra second, a "leap second", to be added as the last second of the day on December 31 or June 30.)

We can use time zones in many various ways:

Code Block
System.setProperty('user.timezone', 'GMT') //we can set the default time zone

def tz= new SimpleTimeZone( -8*(60*60*1000), 'Somewhere',
  Calendar.MARCH, 1, 0, 2*(60*60*1000),
  Calendar.OCTOBER, 31, 0, 2*(60*60*1000) )
def cal= new GregorianCalendar( tz )
    //create a calendar with today's date in a specified time zone
cal= Calendar.getInstance( tz ) //another way

cal= new GregorianCalendar(2009, Calendar.JULY, 22)
    //we can create a calendar with the default time zone...
cal.timeZone= tz //...then set the time zone
assert cal.timeZone == tz
assert cal.get(Calendar.ZONE_OFFSET) == -8*(60*60*1000)
assert cal.get(Calendar.DST_OFFSET) == (60*60*1000)
assert Calendar.FIELD_COUNT == 17
    //the number of fields such as DAY_OF_YEAR and ZONE_OFFSET in Calendar

//we can test whether two time zones have the same rules...
assert tz.hasSameRules(
  new SimpleTimeZone( -8*(60*60*1000), 'Somewhere Else',
    Calendar.MARCH, 1, 0, 2*(60*60*1000),
    Calendar.OCTOBER, 31, 0, 2*(60*60*1000)
) )
assert ! tz.hasSameRules(
  new SimpleTimeZone( -8*(60*60*1000), 'Somewhere Else',
    Calendar.APRIL, 1, 0, 2*(60*60*1000),
    Calendar.OCTOBER, 31, 0, 2*(60*60*1000)
) )

//some methods available within TimeCategory...
use(groovy.time.TimeCategory){
  cal= new GregorianCalendar( tz )
  def today= cal.time
  println today.timeZone
  println today.daylightSavingsOffset //returns a duration
  def nextWeek= today + 7
  println( (nextWeek - today).daylightSavingsOffset )
      //a duration also has a daylight savings time offset
  println( nextWeek.getRelativeDaylightSavingsOffset( today ) )
}

//we can test if a certain date is in daylight saving time for a time zone...
assert tz.inDaylightTime( new GregorianCalendar(1990, Calendar.MAY, 5).time )
assert ! tz.inDaylightTime(
    new GregorianCalendar(1990, Calendar.NOVEMBER, 5).time )

//we can set the first year daylight savings time operates...
tz.startYear= 1973
assert ! tz.inDaylightTime( new GregorianCalendar(1971, Calendar.MAY, 5).time )

//some extra format codes for dates...
println String.format('%tZ', cal)
    //to see a string representing the time zone, eg, GMT-07:00
println String.format('%tz', cal) //numeric offset from GMT, eg, -0800
assert String.format('%tc', cal) ==
    String.format('%ta %<tb %<td %<tT %<tZ %<tY', cal)

//we can view the Gregorian changeover date...
assert String.format( '%ta %<td %<tb %<tY', cal.gregorianChange ) ==
    'Fri 15 Oct 1582' //default for GMT time zone
cal= new GregorianCalendar()
cal.set(1582, Calendar.OCTOBER, 15)
cal.time
assert String.format( '%ta %<td %<tb %<tY', cal.time - 1 ) ==
    'Thu 04 Oct 1582' //the day before the big change

//check for leap years (this instance method acts like a static method)...
[1999, 1998, 1997, 1900, 1800, 1700].each{ assert ! cal.isLeapYear(it) }
[2000, 1996, 1992, 1600, 1500, 1400].each{ assert cal.isLeapYear(it) }
    //1500 and before use Julian calendar rules