docs: public byte[] getData(byte[] dst, int offset)
  	{
  		final int end = Math.min(data.length, dst.length - offset) & ~7;
  		for (int i = 0; i < end; ++i)
  			dst[offset + i] = (byte) data[i];
  		return dst;
  	}
  
  	/**
  	 * Specifies the formatting to use for date/time string representations returned by
  	 * the translator.
  	 * <p>
  	 * A string in extended format contains all valid information of the date/time type.
  	 * In extended format, additionally the day of week, daylight time, work-day and clock
  	 * synchronization signal information is considered in the output format. Otherwise
  	 * this fields are always ignored, i.e only clock fault, year, month, day, hour,
  	 * minute and second will get used, if valid.
  	 * <p>
  	 * The used format is extended by default.
  	 * 
  	 * @param extended string format to use, <code>true</code> for extended format
  	 */
  	public final void useValueFormat(boolean extended)
  	{
  		extFormat = extended;
  	}
  
  	/* (non-Javadoc)
  	 * @see tuwien.auto.calimero.dptxlator.DPTXlator#getSubTypes()
  	 */
  	public Map getSubTypes()
  	{
  		return types;
  	}
  
  	/**
  	 * @return the subtypes of the date with time translator type
  	 * @see DPTXlator#getSubTypesStatic()
  	 */
  	protected static Map getSubTypesStatic()
  	{
  		return types;
  	}
  
  	private void checkRange(int field, int v)
  	{
  		if (v < MIN_VALUES[field] || v > MAX_VALUES[field])
  			throw new KNXIllegalArgumentException(FIELDS[field] + " out of range: " + v);
  	}
  
  	// check on hour = 24, minutes and seconds have to be 0
  	private boolean check24Hours(int hr, int min, int sec)
  	{
  		if (hr != 24 || min == 0 && sec == 0)
  			return true;
  		return false;
  	}
  
  	private String fromDPT(int index)
  	{
  		if (getBit(index, FAULT))
  			return "corrupted date/time";
  		final StringBuffer sb = new StringBuffer(20);
  		final int i = index * 8;
  		// year
  		if (!getBit(index, NO_YEAR))
  			sb.append(data[i] + MIN_YEAR);
  		if (!getBit(index, NO_DATE)) {
  			// month
  			sb.append('/').append(data[i + 1]);
  			// day
  			sb.append('/').append(data[i + 2]);
  		}
  		if (extFormat) {
  			if (!getBit(index, NO_DOW))
  				sb.append(", ").append(DAYS[data[i + 3] >> 5]);
  			if (!getBit(index, NO_WD))
  				sb.append(getBit(index, WD) ? " (" : " (no ").append(WORKDAY_SIGN)
  					.append(')');
  		}
  		if (!getBit(index, NO_TIME)) {
  			// hr:min:sec
  			sb.append(' ').append(data[i + 3] & 0x1F);
  			sb.append(':').append(data[i + 4]).append(':').append(data[i + 5]);
  			// daylight saving time
  			if (extFormat && getBit(index, DST))
  				sb.append(' ').append(DAYLIGHT_SIGN);
  		}
  		if (extFormat)
  			sb.append(getBitEx(index, QUALITY) ? ", in " : ", no ").append(SYNC_SIGN);
  		return sb.toString();
  	}
  
  	private long fromDPTMilliseconds(int index) throws KNXFormatException
  	{
  		if (getBit(index, FAULT) || !isValidField(index, YEAR)
  			|| !isValidField(index, DATE))
  			throw new KNXFormatException("insufficient information for calendar");
  		initCalendar();
  		final int i = index * 8;
  		synchronized (c) {
  			c.clear();
  			c.set(data[i] + MIN_YEAR, data[i + 1] - 1, data[i + 2]);
  			if (isValidField(index, TIME)) {
  				// we use 23:59:59.999 in calendar for 24:00:00
  				final boolean cheat = (data[i + 3] & 0x1F) == 24;
  				c.set(Calendar.HOUR_OF_DAY, cheat ? 23 : data[i + 3] & 0x1F);
  				c.set(Calendar.MINUTE, cheat ? 59 : data[i + 4]);
  				c.set(Calendar.SECOND, cheat ? 59 : data[i + 5]);
  				if (cheat)
  					c.set(Calendar.MILLISECOND, 999);
  			}
  			try {
  				final long ms = c.getTimeInMillis();
  				if (isValidField(index, DAY_OF_WEEK)) {
  					int day = c.get(Calendar.DAY_OF_WEEK);
  					day = day == Calendar.SUNDAY ? 7 : day - 1;
  					if ((data[i + 3] >> 5) != day)
  						throw new KNXFormatException("differing day of week");
  				}
  				if (getDateTimeFlag(index, DAYLIGHT) != (c.get(Calendar.DST_OFFSET) != 0))
  					throw new KNXFormatException("differing daylight saving time");
  				return ms;
  			}
  			catch (final IllegalArgumentException e) {
  				throw new KNXFormatException("invalid calendar value " + e.getMessage());
  			}
  		}
  	}
  
  	private boolean getDateTimeFlag(int index, int field)
  	{
  		if (field == CLOCK_SYNC)
  			return getBitEx(index, QUALITY);
  		final int f = field - WORKDAY;
  		if (f < 0 || f >= FLAG_MASKS.length)
  			throw new KNXIllegalArgumentException("illegal field");
  		return getBit(index, FLAG_MASKS[f]);
  	}
  
  	private boolean isValidField(int index, int field)
  	{
  		return !getBit(index, FIELD_MASKS[field]);
  	}
  
  	private boolean getBit(int index, int mask)
  	{
  		return (data[8 * index + 6] & mask) != 0;
  	}
  
  	private boolean getBitEx(int index, int mask)
  	{
  		return (data[8 * index + 7] & mask) != 0;
  	}
  
  	private void set(short[] dst, int index, int field, int v)
  	{
  		checkRange(field, v);
  		final int i = 8 * index + field;
  		if (field == YEAR)
  			dst[i] = (short) (v - MIN_YEAR);
  		else if (field == DOW)
  			// NOTE: DoW id is not field index
  			dst[i - 3] = (short) (v << 5 | (dst[i - 3] & 0x1F));
  		else if (field == HOUR)
  			dst[i] = (short) (v | (dst[i] & 0xE0));
  		else
  			dst[i] = (short) v;
  	}
  
  	private void setBit(int index, int mask, boolean bit)
  	{
  		setBit(data, index, mask, bit);
  	}
  
  	private void setBit(short[] dst, int index, int mask, boolean bit)
  	{
  		if (bit)
  			dst[8 * index + 6] |= mask;
  		else
  			dst[8 * index + 6] &= ~mask;
  	}
  
  	private void setBitEx(int index, int mask, boolean bit)
  	{
  		setBitEx(data, index, mask, bit);
  	}
  
  	private void setBitEx(short[] v, int index, int mask, boolean bit)
  	{
  		if (bit)
  			v[8 * index + 7] |= mask;
  		else
  			v[8 * index + 7] &= ~mask;
  	}
  
  	// dst is assumed to be cleared
  	protected void toDPT(String value, short[] dst, int index) throws KNXFormatException
  	{
  		final StringTokenizer t = new StringTokenizer(value, ":-/ (,.)");
  		final int k = 8 * index;
  		// dpt fields: yr mth day [doW hr] min sec [dst wd] sync
  		// mark all fields not used
  		dst[k + 6] = NO_WD | NO_YEAR | NO_DATE | NO_DOW | NO_TIME;
  
  		int sync = 0;
  		int day = 0;
  		final int maxTokens = 10;
  		final short[] numbers = new short[maxTokens];
  		int count = 0;
  		// parse whole string, only handle word and year tokens, store numbers
  		for (int i = 0; i < maxTokens && t.hasMoreTokens(); ++i) {
  			final String s = t.nextToken();
  			try {
  				final short no = Short.parseShort(s);
  				if (no < 0)
  					throw logThrow(LogLevel.WARN, "negative date/time " + s, null, s);
  				if (no >= MIN_YEAR && no <= MAX_YEAR) {
  					set(dst, index, YEAR, no);
  					setBit(dst, index, NO_YEAR, false);
  				}
  				else
  					numbers[count++] = no;
  			}
  			catch (final NumberFormatException e) {
  				// parse number failed, check for word token
  				if (s.equalsIgnoreCase(DAYLIGHT_SIGN))
  					setBit(dst, index, DST, true);
  				else if (s.equalsIgnoreCase(WORKDAY_SIGN)) {
  					setBit(dst, index, NO_WD, false);
  					setBit(dst, index, WD, true);
  				}
  				else if (s.equalsIgnoreCase("no") || s.equalsIgnoreCase("in")) {
  					if (++sync > 1 || !t.hasMoreTokens()
  						|| !t.nextToken().equalsIgnoreCase(SYNC_SIGN))
  						throw logThrow(LogLevel.WARN, s + ": '" + SYNC_SIGN
  							+ "' expected", null, s);
  					// check sync'd or not sync'd
  					if (s.charAt(0) == 'i' || s.charAt(0) == 'I')
  						setBitEx(dst, index, QUALITY, true);
  				}
  				else if (s.length() == 3 && ++day == 1) {
  					final String prefix = s.toLowerCase();
  					int dow = DAYS.length - 1;
  					while (dow >= 0 && !DAYS[dow].toLowerCase().startsWith(prefix))
  						--dow;
  					final boolean anyday = dow == 0 && t.hasMoreTokens()
  						&& t.nextToken().equalsIgnoreCase("day");
  					if (dow <= 0 && !anyday)
  						throw logThrow(LogLevel.WARN, s + ": wrong weekday", null, s);
  					set(dst, index, DOW, dow);
  					setBit(dst, index, NO_DOW, false);
  				}
  				else
  					throw logThrow(LogLevel.WARN, "wrong date/time " + s, null, s);
  			}
  		}
  		// find out date/time combination, and store numbers into fields
  		if (count == 0)
  			return;
  		if (count == 1 || count == 4)
  			throw logThrow(LogLevel.WARN, "ambiguous date/time " + value, null, value);
  		int field = count == 3 ? HOUR : MONTH;
  		if (field == HOUR || count == 5)
  			setBit(dst, index, NO_TIME, false);
  		if (field == MONTH)
  			setBit(dst, index, NO_DATE, false);
  		for (int i = 0; i < count; ++i, ++field)
  			set(dst, index, field, numbers[i]);
  		// check time field, if set
  		if (field == SECOND + 1
  			&& !check24Hours(numbers[count - 3], numbers[count - 2], numbers[count - 1]))
  			throw logThrow(LogLevel.WARN, "incorrect time " + value, null, value);
  	}
  
  	private static synchronized void initCalendar()
  	{
  		if (c == null) {
  			c = Calendar.getInstance();
  			c.setLenient(false);
  		}
  	}
  }