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