1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.bluetooth.avrcpcontroller; 18 19 import java.util.Calendar; 20 import java.util.Date; 21 import java.util.Locale; 22 import java.util.Objects; 23 import java.util.TimeZone; 24 import java.util.regex.Matcher; 25 import java.util.regex.Pattern; 26 27 /** 28 * An object representing a DateTime sent over the Basic Imaging Profile 29 * 30 * Date-time format is as follows: 31 * 32 * YYYYMMDDTHHMMSSZ, where 33 * Y/M/D/H/M/S - years, months, days, hours, minutes, seconds 34 * T - A delimiter 35 * Z - An optional, but recommended, character indicating the time is in UTC. If UTC 36 * is not used then we're to assume "local timezone" instead. 37 * 38 * Example date-time values: 39 * 20000101T000000Z 40 * 20000101T235959Z 41 * 20000101T000000 42 */ 43 public class BipDateTime { 44 private static final String TAG = "avrcpcontroller.BipDateTime"; 45 46 private Date mDate = null; 47 private boolean mIsUtc = false; 48 BipDateTime(String time)49 public BipDateTime(String time) { 50 try { 51 /* 52 * Match groups for the timestamp are numbered as follows: 53 * 54 * YYYY MM DD T HH MM SS Z 55 * ^^^^ ^^ ^^ ^^ ^^ ^^ ^ 56 * 1 2 3 4 5 6 7 57 */ 58 Pattern p = Pattern.compile("(\\d{4})(\\d{2})(\\d{2})T(\\d{2})(\\d{2})(\\d{2})([Z])?"); 59 Matcher m = p.matcher(time); 60 61 if (m.matches()) { 62 /* Default to system default and assume it knows best what our local timezone is */ 63 Calendar.Builder builder = new Calendar.Builder(); 64 65 /* Throw exceptions when given bad values */ 66 builder.setLenient(false); 67 68 /* Note that Calendar months are zero-based in Java framework */ 69 builder.setDate(Integer.parseInt(m.group(1)), /* year */ 70 Integer.parseInt(m.group(2)) - 1, /* month */ 71 Integer.parseInt(m.group(3))); /* day of month */ 72 73 /* Note the timestamp doesn't have milliseconds and we're explicitly setting to 0 */ 74 builder.setTimeOfDay(Integer.parseInt(m.group(4)), /* hours */ 75 Integer.parseInt(m.group(5)), /* minutes */ 76 Integer.parseInt(m.group(6)), /* seconds */ 77 0); /* milliseconds */ 78 79 /* If the 7th group is matched then we have UTC based timestamp */ 80 if (m.group(7) != null) { 81 TimeZone tz = TimeZone.getTimeZone("UTC"); 82 tz.setRawOffset(0); 83 builder.setTimeZone(tz); 84 mIsUtc = true; 85 } else { 86 mIsUtc = false; 87 } 88 89 /* Note: Java dates are UTC and any date generated will be offset by the timezone */ 90 mDate = builder.build().getTime(); 91 return; 92 } 93 } catch (IllegalArgumentException e) { 94 // Let calendar bad values be caught and fall through 95 } catch (NullPointerException e) { 96 // Let null strings while matching fall through 97 } 98 99 // If we reach here then we've failed to parse the input string into a time 100 throw new ParseException("Failed to parse time '" + time + "'"); 101 } 102 BipDateTime(Date date)103 public BipDateTime(Date date) { 104 mDate = Objects.requireNonNull(date, "Date cannot be null"); 105 mIsUtc = true; // All Java Date objects store timestamps as UTC 106 } 107 isUtc()108 public boolean isUtc() { 109 return mIsUtc; 110 } 111 getTime()112 public Date getTime() { 113 return mDate; 114 } 115 116 @Override equals(Object o)117 public boolean equals(Object o) { 118 if (o == this) return true; 119 if (!(o instanceof BipDateTime)) return false; 120 121 BipDateTime d = (BipDateTime) o; 122 return d.isUtc() == isUtc() && d.getTime() == getTime(); 123 } 124 125 @Override toString()126 public String toString() { 127 Date d = getTime(); 128 if (d == null) { 129 return null; 130 } 131 132 Calendar cal = Calendar.getInstance(); 133 cal.setTime(d); 134 135 /* Note that months are numbered stating from 0 */ 136 if (isUtc()) { 137 TimeZone utc = TimeZone.getTimeZone("UTC"); 138 utc.setRawOffset(0); 139 cal.setTimeZone(utc); 140 return String.format(Locale.US, "%04d%02d%02dT%02d%02d%02dZ", cal.get(Calendar.YEAR), 141 cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DATE), 142 cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE), 143 cal.get(Calendar.SECOND)); 144 } else { 145 cal.setTimeZone(TimeZone.getDefault()); 146 return String.format(Locale.US, "%04d%02d%02dT%02d%02d%02d", cal.get(Calendar.YEAR), 147 cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DATE), 148 cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE), 149 cal.get(Calendar.SECOND)); 150 } 151 } 152 } 153