/********************************************************************************** * $URL: https://source.sakaiproject.org/svn/kernel/trunk/kernel-impl/src/main/java/org/sakaiproject/time/impl/BasicTimeService.java $ * $Id: BasicTimeService.java 105077 2012-02-24 22:54:29Z ottenhoff@longsight.com $ *********************************************************************************** * * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008 Sakai Foundation * * Licensed under the Educational Community License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.opensource.org/licenses/ECL-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * **********************************************************************************/ package org.sakaiproject.time.impl; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.GregorianCalendar; import java.util.Hashtable; import java.util.Locale; import java.util.StringTokenizer; import java.util.TimeZone; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.sakaiproject.entity.api.ResourceProperties; import org.sakaiproject.memory.api.Cache; import org.sakaiproject.memory.api.MemoryService; import org.sakaiproject.time.api.Time; import org.sakaiproject.time.api.TimeBreakdown; import org.sakaiproject.time.api.TimeRange; import org.sakaiproject.time.api.TimeService; import org.sakaiproject.tool.api.SessionManager; import org.sakaiproject.user.api.Preferences; import org.sakaiproject.user.api.PreferencesService; import org.sakaiproject.util.ResourceLoader; /** * <p> * BasicTimeService implements the Sakai TimeService * </p> */ public class BasicTimeService implements TimeService { /** Our log (commons). */ private static Log M_log = LogFactory.getLog(TimeService.class); /** The time zone for our GMT times. */ protected TimeZone M_tz = null; /** * a calendar to clone for GMT time construction */ protected GregorianCalendar M_GCal = null; /** * The formatter for our special GMT format(s) */ protected DateFormat M_fmtA = null; protected DateFormat M_fmtB = null; protected DateFormat M_fmtC = null; protected DateFormat M_fmtD = null; protected DateFormat M_fmtE = null; protected DateFormat M_fmtG = null; // Map of Timezone/Locales to LocalTzFormat objects private Hashtable<String, LocalTzFormat> M_localeTzMap = new Hashtable<String, LocalTzFormat>(); // Cache of userIds to Timezone/Locales private Cache M_userTzCache; // Default Timezone/Locale protected String[] M_tz_locale_default = new String[] { TimeZone.getDefault().getID(), Locale.getDefault().toString() }; // Used for fetching user's default language locale ResourceLoader rl = new ResourceLoader(); /********************************************************************************************************************************************************************************************************************************************************** * Dependencies and their setter methods *********************************************************************************************************************************************************************************************************************************************************/ private SessionManager sessionManager; public void setSessionManager(SessionManager sessionManager) { this.sessionManager = sessionManager; } private PreferencesService preferencesService; public void setPreferencesService(PreferencesService preferencesService) { this.preferencesService = preferencesService; } private MemoryService memoryService; public void setMemoryService(MemoryService memoryService) { this.memoryService = memoryService; } /********************************************************************************************************************************************************************************************************************************************************** * Init and Destroy *********************************************************************************************************************************************************************************************************************************************************/ /** * Final initialization, once all dependencies are set. */ public void init() { /** The time zone for our GMT times. */ M_tz = TimeZone.getTimeZone("GMT"); M_log.info("init()"); /** * a calendar to clone for GMT time construction */ M_GCal = getCalendar(M_tz, 0, 0, 0, 0, 0, 0, 0); // Note: formatting for GMT time representations M_fmtA = (DateFormat)(new SimpleDateFormat("yyyyMMddHHmmssSSS")); M_fmtB = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT); M_fmtC = DateFormat.getTimeInstance(DateFormat.SHORT); M_fmtD = DateFormat.getDateInstance(DateFormat.MEDIUM); M_fmtE = (DateFormat)(new SimpleDateFormat("yyyyMMddHHmmss")); M_fmtG = (DateFormat)(new SimpleDateFormat("yyyy/DDD/HH/")); // that's year, day of year, hour M_fmtA.setTimeZone(M_tz); M_fmtB.setTimeZone(M_tz); M_fmtC.setTimeZone(M_tz); M_fmtD.setTimeZone(M_tz); M_fmtE.setTimeZone(M_tz); M_fmtG.setTimeZone(M_tz); //register the Cache M_userTzCache = memoryService.newCache("org.sakaiproject.time.impl.BasicTimeService.userTimezoneCache"); } /** * Final cleanup. */ public void destroy() { M_log.info("destroy()"); } /** Return string with user's prefered timezone _and_ preferred locale ** (dates are formatted according to the locale) **/ protected String[] getUserTimezoneLocale() { // Check if we already cached this user's timezone String userId = sessionManager.getCurrentSessionUserId(); if (userId == null) return M_tz_locale_default; String[] timeZoneLocale = (String[]) M_userTzCache.get(userId); if (timeZoneLocale != null) return timeZoneLocale; // Otherwise, get the user's preferred time zone Preferences prefs = preferencesService.getPreferences(userId); ResourceProperties tzProps = prefs.getProperties(TimeService.APPLICATION_ID); String timeZone = tzProps.getProperty(TimeService.TIMEZONE_KEY); if (timeZone == null || timeZone.equals("")) timeZone = TimeZone.getDefault().getID(); // Now, get user's prefered locale String localeId = rl.getLocale().toString(); timeZoneLocale = new String[] {timeZone, localeId}; M_userTzCache.put(userId, timeZoneLocale); return timeZoneLocale; } protected LocalTzFormat getLocalTzFormat(String[] timeZoneLocale) { //we need to convert the String[] to a string key String tzLocaleString = stringAraytoKeyString(timeZoneLocale); LocalTzFormat tzFormat = (LocalTzFormat) M_localeTzMap.get(tzLocaleString); if (M_log.isDebugEnabled()) { M_log.debug("M_localeTzMap contains: " + M_localeTzMap.size() + " members"); } if (tzFormat == null) { tzFormat = new LocalTzFormat(timeZoneLocale[0], timeZoneLocale[1]); M_localeTzMap.put(tzLocaleString, tzFormat); } return tzFormat; } private String stringAraytoKeyString(String[] timeZoneLocale) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < timeZoneLocale.length; i++ ) { if (i > 0) { sb.append("_"); } sb.append(timeZoneLocale[i]); } if (M_log.isDebugEnabled()) { M_log.debug("returing key: " + sb.toString()); } return sb.toString(); } /********************************************************************************************************************************************************************************************************************************************************** * Work interface methods: org.sakai.service.time.TimeService *********************************************************************************************************************************************************************************************************************************************************/ /** * no arg constructor */ public BasicTimeService() { } /** * {@inheritDoc} */ public Time newTime() { return new MyTime(this); } /** * {@inheritDoc} */ public Time newTimeGmt(String value) { return new MyTime(this,value); } /** * {@inheritDoc} */ public Time newTime(long value) { return new MyTime(this,value); } /** * {@inheritDoc} */ public Time newTime(GregorianCalendar cal) { return new MyTime(this,cal.getTimeInMillis()); } /** * {@inheritDoc} */ public Time newTimeGmt(int year, int month, int day, int hour, int minute, int second, int millisecond) { return new MyTime(this,M_tz, year, month, day, hour, minute, second, millisecond); } /** * {@inheritDoc} */ public Time newTimeGmt(TimeBreakdown breakdown) { return new MyTime(this,M_tz, breakdown); } /** * {@inheritDoc} */ public Time newTimeLocal(int year, int month, int day, int hour, int minute, int second, int millisecond) { TimeZone tz_local = getLocalTzFormat(getUserTimezoneLocale()).M_tz_local; return new MyTime(this,tz_local, year, month, day, hour, minute, second, millisecond); } /** * {@inheritDoc} */ public Time newTimeLocal(TimeBreakdown breakdown) { TimeZone tz_local = getLocalTzFormat(getUserTimezoneLocale()).M_tz_local; return new MyTime(this,tz_local, breakdown); } /** * {@inheritDoc} */ public TimeBreakdown newTimeBreakdown(int year, int month, int day, int hour, int minute, int second, int millisecond) { return new MyTimeBreakdown(year, month, day, hour, minute, second, millisecond); } /** * {@inheritDoc} */ public TimeRange newTimeRange(Time start, Time end, boolean startIncluded, boolean endIncluded) { return new MyTimeRange(start, end, startIncluded, endIncluded); } /** * {@inheritDoc} */ public TimeRange newTimeRange(String value) { return new MyTimeRange(value); } /** * {@inheritDoc} */ public TimeRange newTimeRange(Time startAndEnd) { return new MyTimeRange(startAndEnd); } /** * {@inheritDoc} */ public TimeRange newTimeRange(long start, long duration) { return new MyTimeRange(start, duration); } /** * {@inheritDoc} */ public TimeRange newTimeRange(Time start, Time end) { return new MyTimeRange(start, end); } /** * {@inheritDoc} */ public TimeZone getLocalTimeZone() { return getLocalTzFormat(getUserTimezoneLocale()).M_tz_local; } /** * {@inheritDoc} */ public boolean clearLocalTimeZone(String userId) { M_userTzCache.remove(userId); return true; } /** * {@inheritDoc} */ public GregorianCalendar getCalendar(TimeZone zone, int year, int month, int day, int hour, int min, int sec, int ms) { GregorianCalendar rv = new GregorianCalendar(year, month, day, hour, min, sec); rv.setTimeZone(zone); rv.set(GregorianCalendar.MILLISECOND, ms); return rv; } /** * Compare two Time for differences, either may be null * * @param a * One Time. * @param b * The other Time. * @return true if the Times are different, false if they are the same. */ public boolean different(Time a, Time b) { // if both null, they are the same if ((a == null) && (b == null)) return false; // if either are null (they both are not), they are different if ((a == null) || (b == null)) return true; // now we know neither are null, so compare return (!a.equals(b)); } /********************************************************************************************************************************************************************************************************************************************************** * LocalTzFormat -- maintains a local timezone, locale and DateFormats *********************************************************************************************************************************************************************************************************************************************************/ protected class LocalTzFormat { // The time zone for our local times public TimeZone M_tz_local = null; // The Locale for our local date/time formatting public Locale M_locale = null; // a calendar to clone for GMT time construction public GregorianCalendar M_GCall = null; // The formatter for our special local timezone format(s) public DateFormat M_fmtAl = null; public DateFormat M_fmtBl = null; public DateFormat M_fmtBlz = null; public DateFormat M_fmtCl = null; public DateFormat M_fmtClz = null; public DateFormat M_fmtDl = null; public DateFormat M_fmtD2 = null; public DateFormat M_fmtFl = null; private LocalTzFormat() { }; // disable default constructor public LocalTzFormat(String timeZoneId, String localeId ) { M_tz_local = TimeZone.getTimeZone(timeZoneId); Locale M_locale = null; String langLoc[] = localeId.split("_"); if ( langLoc.length >= 2 ) if (langLoc[0].equals("en") && langLoc[1].equals("ZA")) M_locale = new Locale("en", "GB"); else M_locale = new Locale(langLoc[0], langLoc[1]); else M_locale = new Locale(langLoc[0]); M_fmtAl = (DateFormat)(new SimpleDateFormat("yyyyMMddHHmmssSSS")); M_fmtBl = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT, M_locale); M_fmtBlz = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG, M_locale); M_fmtCl = DateFormat.getTimeInstance(DateFormat.SHORT, M_locale); M_fmtClz = DateFormat.getTimeInstance(DateFormat.LONG, M_locale); M_fmtDl = DateFormat.getDateInstance(DateFormat.MEDIUM, M_locale); M_fmtD2 = DateFormat.getDateInstance(DateFormat.SHORT, M_locale); M_fmtFl = (DateFormat)(new SimpleDateFormat("HH:mm:ss")); // Strip the seconds from the Blz and Clz (default) formats try { SimpleDateFormat sdf = ((SimpleDateFormat)M_fmtBlz); String pattern = sdf.toLocalizedPattern(); pattern = pattern.replaceAll(":ss",""); sdf.applyLocalizedPattern( pattern ); sdf = ((SimpleDateFormat)M_fmtClz); pattern = sdf.toLocalizedPattern(); pattern = pattern.replaceAll(":ss",""); sdf.applyLocalizedPattern( pattern ); } catch (ClassCastException e) { // ignore -- not all locales support this } M_fmtAl.setTimeZone(M_tz_local); M_fmtBl.setTimeZone(M_tz_local); M_fmtBlz.setTimeZone(M_tz_local); M_fmtCl.setTimeZone(M_tz_local); M_fmtClz.setTimeZone(M_tz_local); M_fmtDl.setTimeZone(M_tz_local); M_fmtD2.setTimeZone(M_tz_local); M_fmtFl.setTimeZone(M_tz_local); M_GCall = getCalendar(M_tz_local, 0, 0, 0, 0, 0, 0, 0); } } /********************************************************************************************************************************************************************************************************************************************************** * TimeRange implementation *********************************************************************************************************************************************************************************************************************************************************/ public class MyTimeRange implements TimeRange { // ends included? protected boolean m_startIncluded = true; protected boolean m_endIncluded = true; // start and end times protected Time m_startTime = null; protected Time m_endTime = null; public Object clone() { TimeRange obj = newTimeRange((Time) m_startTime.clone(), (Time) m_endTime.clone(), m_startIncluded, m_endIncluded); return obj; } // clone /** * construct from a two times, and start and end inclusion booleans * * @param start: * start time * @param end: * end time * @param startIncluded: * true if start is part of the range * @param endIncluded: * true of end is part of the range */ public MyTimeRange(Time start, Time end, boolean startIncluded, boolean endIncluded) { m_startTime = start; m_endTime = end; m_startIncluded = startIncluded; m_endIncluded = endIncluded; // start time must be <= end time if (m_startTime.getTime() > m_endTime.getTime()) { // reverse them to fix Time t = m_startTime; m_startTime = m_endTime; m_endTime = t; } } // TimeRange /** * construct from a string, in our format * * @param str * the time range string */ public MyTimeRange(String str) { parse(str, null, null); } // TimeRange /** * construct from a single time * * @param startAndEnd: * the single time value for the range */ public MyTimeRange(Time startAndEnd) { this(startAndEnd, startAndEnd, true, true); } // TimeRange /** * construct from a time long and a duration long in ms * * @param start * time value * @param duration * ms duration */ public MyTimeRange(long start, long duration) { m_startTime = newTime(start); m_endTime = newTime(start + duration); m_startIncluded = true; m_endIncluded = true; // start time must be <= end time if (m_startTime.getTime() > m_endTime.getTime()) { // reverse them to fix Time t = m_startTime; m_startTime = m_endTime; m_endTime = t; } } // TimeRange /** * construct from a two times - inclusive * * @param start: * the start time * @param end: * the end time */ public MyTimeRange(Time start, Time end) { this(start, end, true, true); } // TimeRange /** * is this time in my range? * * @param time: * the time to check for inclusion * @return true if the time is in the range, false if not */ public boolean contains(Time time) { // assume in range, unless proven otherwise boolean inRange = true; // if out of the range... if (time.before(m_startTime) || time.after(m_endTime)) { inRange = false; } // if at begin and begin not in range else if ((!m_startIncluded) && time.equals(m_startTime)) { inRange = false; } // if at the end and end not in range else if ((!m_endIncluded) && time.equals(m_endTime)) { inRange = false; } return inRange; } // contains /** * do I overlap this other range at all? * * @param range: * the time range to check for overlap * @return true if any time in range is in my range is in the other range, false if not */ public boolean overlaps(TimeRange range) { boolean overlaps = false; // null range? // if my start is in the other range if (range.contains(firstTime())) { overlaps = true; } // if my end is in the other range else if (range.contains(lastTime())) { overlaps = true; } // if I contain the other range else if (contains(range)) { overlaps = true; } return overlaps; } // overlaps /** * do I completely contain this other range? * * @param range: * the time range to check for containment * @return true if range is within my time ramge */ public boolean contains(TimeRange range) { // I must contain both is start and end return (contains(range.firstTime()) && contains(range.lastTime())); } // contains /** * what is the first time range included? * * @return the first time actually in the range */ public Time firstTime() { return firstTime(1); } // firstTime /** * what is the last time range included? * * @return the last time actually in the range */ public Time lastTime() { return lastTime(1); } // lastTime /** * what is the first time range included? * * @param fudge * How many ms to advance if the first is not included. * @return the first time actually in the range */ public Time firstTime(long fudge) { // if the start is included, return this if (m_startIncluded) { return m_startTime; } // if not, return a time one ms after start Time fudgeStartTime = (Time) m_startTime.clone(); fudgeStartTime.setTime(m_startTime.getTime() + fudge); return fudgeStartTime; } // firstTime /** * what is the last time range included? * * @param fudge * How many ms to decrease if the first is not included. * @return the last time actually in the range */ public Time lastTime(long fudge) { // if the end is included, return this if (m_endIncluded) { return m_endTime; } // if not, return a time one ms before end Time fudgeEndTime = (Time) m_endTime.clone(); fudgeEndTime.setTime(m_endTime.getTime() - fudge); return fudgeEndTime; } // lastTime /** * format the range * * @return a string representation of the time range */ public String toString() { // a place to build the string (slightly larger) StringBuilder buf = new StringBuilder(64); // start with the start value, always used buf.append(m_startTime); // more that single value? if (!m_startTime.equals(m_endTime)) { // what separator to use? if (m_startIncluded && m_endIncluded) { buf.append('-'); } else if ((!m_startIncluded) && (!m_endIncluded)) { buf.append('~'); } else if (!m_startIncluded) { buf.append('['); } else { buf.append(']'); } // add the end buf.append(m_endTime); } // return the answer as a string return buf.toString(); } // toString /** * format the range - human readable * * @return a string representation of the time range, human readable */ public String toStringHR() { // a place to build the string (slightly larger) StringBuilder buf = new StringBuilder(64); // start with the start value, always used buf.append(m_startTime.toStringGmtFull()); // more that single value? if (!m_startTime.equals(m_endTime)) { // what separator to use? if (m_startIncluded && m_endIncluded) { buf.append(" - "); } else if ((!m_startIncluded) && (!m_endIncluded)) { buf.append(" ~ "); } else if (!m_startIncluded) { buf.append(" [ "); } else { buf.append(" ] "); } // add the end buf.append(m_endTime.toStringGmtFull()); } // return the answer as a string return buf.toString(); } // toStringHR /** * equals to another time range */ public boolean equals(Object obj) { boolean equals = false; if (obj instanceof MyTimeRange) { equals = ((((MyTimeRange) obj).m_startIncluded == m_startIncluded) && (((MyTimeRange) obj).m_endIncluded == m_endIncluded) && (((MyTimeRange) obj).m_startTime.equals(m_startTime)) && (((MyTimeRange) obj).m_endTime .equals(m_endTime))); } return equals; } // equals /** * Objects that are equal must have the same hashCode */ public int hashCode() { String hash = Boolean.toString(m_startIncluded) + Boolean.toString(m_endIncluded) + m_startTime.getDisplay() + m_endTime.getDisplay(); return hash.hashCode(); } /** * compute the duration, in ms, of the time range */ public long duration() { return (lastTime().getTime() - firstTime().getTime()); } // duration /** * parse from a string - resolve fully earliest ('!') and latest ('*') and durations ('=') * * @param str * the string to parse * @param earliest * Time to substitute for any 'earliest' values * @param latest * the Time to use for 'latest' */ protected void parse(String str, Time earliest, Time latest) { try { // separate the string by '[]~-' // (we do want the delimiters as tokens, thus the true param) StringTokenizer tokenizer = new StringTokenizer(str, "[]~-", true); int tokenCount = 0; long startMs = -1; long endMs = -1; m_startTime = null; m_endTime = null; while (tokenizer.hasMoreTokens()) { tokenCount++; String next = tokenizer.nextToken(); switch (tokenCount) { case 1: { if (next.charAt(0) == '=') { // use the rest as a duration in ms startMs = Long.parseLong(next.substring(1)); } else { m_startTime = newTimeGmt(next); } } break; case 2: { // set the inclusions switch (next.charAt(0)) { // start not included case '[': { m_startIncluded = false; m_endIncluded = true; } break; // end not included case ']': { m_startIncluded = true; m_endIncluded = false; } break; // neither included case '~': { m_startIncluded = false; m_endIncluded = false; } break; // both included case '-': { m_startIncluded = true; m_endIncluded = true; } break; // trouble! default: { throw new Exception(next.charAt(0) + " invalid"); } } // switch (next[0]) } break; case 3: { if (next.charAt(0) == '=') { // use the rest as a duration in ms endMs = Long.parseLong(next.substring(1)); } else { m_endTime = newTimeGmt(next); } } break; // trouble! default: { throw new Exception(">3 tokens"); } } // switch (tokenCount) } // while (tokenizer.hasMoreTokens()) // if either start or end was in duration, adjust (but not both!) if ((startMs != -1) && (endMs != -1)) { throw new Exception("=="); } if (startMs != -1) { if (m_endTime == null) { throw new Exception("=, * null"); } m_startTime = newTime(m_endTime.getTime() - startMs); } else if (endMs != -1) { if (m_startTime == null) { throw new Exception("=, ! null"); } m_endTime = newTime(m_startTime.getTime() + endMs); } // if there is only one token if (tokenCount == 1) { // end is start, both included m_endTime = m_startTime; m_startIncluded = true; m_endIncluded = true; } // start time must be <= end time if (m_startTime.getTime() > m_endTime.getTime()) { // reverse them to fix Time t = m_startTime; m_startTime = m_endTime; m_endTime = t; } } catch (Exception e) { M_log.warn("parse: exception parsing: " + str + " : " + e.toString()); // set a now range, just to have something m_startTime = newTime(); m_endTime = m_startTime; m_startIncluded = true; m_endIncluded = true; } } /** * Shift the time range back an intervel * * @param i * time intervel in ms */ public void shiftBackward(long i) { m_startTime.setTime(m_startTime.getTime() - i); m_endTime.setTime(m_endTime.getTime() - i); } /** * Shift the time range forward an intervel * * @param i * time intervel in ms */ public void shiftForward(long i) { m_startTime.setTime(m_startTime.getTime() + i); m_endTime.setTime(m_endTime.getTime() + i); } /** * Enlarge or shrink the time range by multiplying a zooming factor * * @param f * zooming factor */ public void zoom(double f) { long oldRange = m_endTime.getTime() - m_startTime.getTime(); long center = m_startTime.getTime() + oldRange / 2; long newRange = (long) ((double) oldRange * f); m_startTime.setTime(center - newRange / 2); m_endTime.setTime(center + newRange / 2); } /** * Adjust this time range based on the difference between the origRange and the modRange, if any * * @param original * the original time range. * @param modified * the modified time range. */ public void adjust(TimeRange original, TimeRange modified) { if (original.equals(modified)) return; // adjust for the change in the start time m_startTime.setTime(m_startTime.getTime() + (((MyTimeRange) modified).m_startTime.getTime() - ((MyTimeRange) original).m_startTime.getTime())); // adjust for the change in the end time m_endTime.setTime(m_endTime.getTime() + (((MyTimeRange) modified).m_endTime.getTime() - ((MyTimeRange) original).m_endTime.getTime())); } // adjust /** * check if the time range is really just a single time * * @return true if the time range is a single time, false if it is not */ public boolean isSingleTime() { return (m_startTime.equals(m_endTime) && m_startIncluded && m_endIncluded); } // isSingleTime } // class TimeRange /********************************************************************************************************************************************************************************************************************************************************** * TimeBreakdown implementation *********************************************************************************************************************************************************************************************************************************************************/ public class MyTimeBreakdown implements TimeBreakdown { /** The parts. */ protected int year; protected int month; protected int day; protected int hour; protected int min; protected int sec; protected int ms; public MyTimeBreakdown(int y, int m, int d, int h, int minutes, int s, int milliseconds) { year = y; month = m; day = d; hour = h; min = minutes; sec = s; ms = milliseconds; } public MyTimeBreakdown(TimeBreakdown other) { year = ((MyTimeBreakdown) other).year; month = ((MyTimeBreakdown) other).month; day = ((MyTimeBreakdown) other).day; hour = ((MyTimeBreakdown) other).hour; min = ((MyTimeBreakdown) other).min; sec = ((MyTimeBreakdown) other).sec; ms = ((MyTimeBreakdown) other).ms; } public String toString() { return "year: " + year + " month: " + month + " day: " + day + " hour: " + hour + " min: " + min + " sec: " + sec + " ms: " + ms; } public int getYear() { return year; } public int getMonth() { return month; } public int getDay() { return day; } public int getHour() { return hour; } public int getMin() { return min; } public int getSec() { return sec; } public int getMs() { return ms; } /** * @param i */ public void setDay(int i) { day = i; } /** * @param i */ public void setHour(int i) { hour = i; } /** * @param i */ public void setMin(int i) { min = i; } /** * @param i */ public void setMonth(int i) { month = i; } /** * @param i */ public void setMs(int i) { ms = i; } /** * @param i */ public void setSec(int i) { sec = i; } /** * @param i */ public void setYear(int i) { year = i; } } }