1 /* 2 * Copyright (C) 2014 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.tv.settings.system; 18 19 import com.android.tv.settings.ActionBehavior; 20 import com.android.tv.settings.ActionKey; 21 import com.android.tv.settings.BaseSettingsActivity; 22 import com.android.tv.settings.R; 23 import com.android.tv.settings.util.SettingsHelper; 24 import com.android.tv.settings.dialog.old.Action; 25 import com.android.tv.settings.dialog.old.ActionAdapter; 26 import com.android.tv.settings.dialog.old.ActionFragment; 27 import com.android.tv.settings.dialog.old.ContentFragment; 28 import com.android.tv.settings.widget.picker.DatePicker; 29 import com.android.tv.settings.widget.picker.TimePicker; 30 import com.android.tv.settings.widget.picker.Picker; 31 import com.android.tv.settings.widget.picker.PickerConstant; 32 33 import org.xmlpull.v1.XmlPullParserException; 34 35 import android.app.AlarmManager; 36 import android.content.BroadcastReceiver; 37 import android.content.Context; 38 import android.content.Intent; 39 import android.content.IntentFilter; 40 import android.content.res.XmlResourceParser; 41 import android.os.Bundle; 42 import android.provider.Settings; 43 import android.provider.Settings.SettingNotFoundException; 44 import android.text.format.DateFormat; 45 import android.util.Log; 46 import android.view.View; 47 48 import java.text.SimpleDateFormat; 49 import java.util.ArrayList; 50 import java.util.Calendar; 51 import java.util.Collections; 52 import java.util.Date; 53 import java.util.List; 54 import java.util.TimeZone; 55 56 public class DateTimeActivity extends BaseSettingsActivity implements ActionAdapter.Listener { 57 58 private static final String TAG = "DateTimeActivity"; 59 private static final boolean DEBUG = false; 60 61 private static final String HOURS_12 = "12"; 62 private static final String HOURS_24 = "24"; 63 64 private static final int HOURS_IN_HALF_DAY = 12; 65 66 private static final String XMLTAG_TIMEZONE = "timezone"; 67 68 private Calendar mDummyDate; 69 private boolean mIsResumed; 70 71 private IntentFilter mIntentFilter; 72 73 private String mNowDate; 74 private String mNowTime; 75 76 private ArrayList<Action> mTimeZoneActions; 77 78 private SettingsHelper mHelper; 79 80 /** 81 * Flag indicating whether this UpdateView call is from onCreate. 82 */ 83 private boolean mOnCreateUpdateView = false; 84 85 /** 86 * Flag indicating whether this UpdateView call is from onResume. 87 */ 88 private boolean mOnResumeUpdateView = false; 89 90 private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { 91 @Override 92 public void onReceive(Context context, Intent intent) { 93 if (mIsResumed) { 94 switch ((ActionType) mState) { 95 case DATE_TIME_OVERVIEW: 96 case DATE: 97 case TIME: 98 updateTimeAndDateDisplay(); 99 } 100 } 101 } 102 }; 103 104 @Override 105 public void onCreate(Bundle savedInstanceState) { 106 mDummyDate = Calendar.getInstance(); 107 mIntentFilter = new IntentFilter(); 108 mIntentFilter.addAction(Intent.ACTION_TIME_TICK); 109 mIntentFilter.addAction(Intent.ACTION_TIME_CHANGED); 110 mIntentFilter.addAction(Intent.ACTION_TIMEZONE_CHANGED); 111 mIntentFilter.addAction(Intent.ACTION_DATE_CHANGED); 112 registerReceiver(mIntentReceiver, mIntentFilter); 113 114 mHelper = new SettingsHelper(getApplicationContext()); 115 116 setSampleDate(); 117 118 updateTimeAndDateStrings(); 119 mOnCreateUpdateView = true; 120 121 super.onCreate(savedInstanceState); 122 } 123 124 private void setSampleDate() { 125 Calendar now = Calendar.getInstance(); 126 mDummyDate.setTimeZone(now.getTimeZone()); 127 // We use December 31st because it's unambiguous when demonstrating the date format. 128 // We use 15:14 so we can demonstrate the 12/24 hour options. 129 mDummyDate.set(now.get(Calendar.YEAR), 11, 31, 15, 14, 0); 130 } 131 132 private boolean getAutoState(String name) { 133 try { 134 return Settings.Global.getInt(getContentResolver(), name) > 0; 135 } catch (SettingNotFoundException snfe) { 136 return false; 137 } 138 } 139 140 static void setDate(Context context, int year, int month, int day) { 141 Calendar c = Calendar.getInstance(); 142 143 c.set(Calendar.YEAR, year); 144 c.set(Calendar.MONTH, month); 145 c.set(Calendar.DAY_OF_MONTH, day); 146 long when = c.getTimeInMillis(); 147 148 if (when / 1000 < Integer.MAX_VALUE) { 149 ((AlarmManager) context.getSystemService(Context.ALARM_SERVICE)).setTime(when); 150 } 151 } 152 153 static void setTime(Context context, int hourOfDay, int minute) { 154 Calendar c = Calendar.getInstance(); 155 156 c.set(Calendar.HOUR_OF_DAY, hourOfDay); 157 c.set(Calendar.MINUTE, minute); 158 c.set(Calendar.SECOND, 0); 159 c.set(Calendar.MILLISECOND, 0); 160 long when = c.getTimeInMillis(); 161 162 if (when / 1000 < Integer.MAX_VALUE) { 163 ((AlarmManager) context.getSystemService(Context.ALARM_SERVICE)).setTime(when); 164 } 165 } 166 167 private String[] getFormattedDates() { 168 String[] dateFormats = getResources().getStringArray(R.array.date_format_values); 169 String[] formattedDates = new String[dateFormats.length]; 170 171 for (int i = 0; i < formattedDates.length; i++) { 172 String formatted = DateFormat.getDateFormatForSetting(this, dateFormats[i]) 173 .format(mDummyDate.getTime()); 174 175 if (dateFormats[i].length() == 0) { 176 formattedDates[i] = getString(R.string.normal_date_format, formatted); 177 } else { 178 formattedDates[i] = formatted; 179 } 180 } 181 return formattedDates; 182 } 183 184 private boolean isTimeFormat24h() { 185 return DateFormat.is24HourFormat(this); 186 } 187 188 private String getDateFormat() { 189 return Settings.System.getString(getContentResolver(), 190 Settings.System.DATE_FORMAT); 191 } 192 193 private void setDateFormat(String s) { 194 Settings.System.putString(getContentResolver(), Settings.System.DATE_FORMAT, s); 195 } 196 197 private void setTime24Hour(boolean is24Hour) { 198 Settings.System.putString(getContentResolver(), 199 Settings.System.TIME_12_24, 200 is24Hour ? HOURS_24 : HOURS_12); 201 } 202 203 private void setAutoDateTime(boolean on) { 204 Settings.Global.putInt(getContentResolver(), Settings.Global.AUTO_TIME, on ? 1 : 0); 205 } 206 207 @Override 208 protected void onResume() { 209 super.onResume(); 210 mIsResumed = true; 211 registerReceiver(mIntentReceiver, mIntentFilter); 212 213 mOnResumeUpdateView = true; 214 215 updateTimeAndDateDisplay(); 216 } 217 218 @Override 219 protected void onPause() { 220 super.onPause(); 221 mIsResumed = false; 222 unregisterReceiver(mIntentReceiver); 223 } 224 225 // Updates the member strings to reflect the current date and time. 226 private void updateTimeAndDateStrings() { 227 final Calendar now = Calendar.getInstance(); 228 java.text.DateFormat dateFormat = DateFormat.getDateFormat(this); 229 mNowDate = dateFormat.format(now.getTime()); 230 java.text.DateFormat timeFormat = DateFormat.getTimeFormat(this); 231 mNowTime = timeFormat.format(now.getTime()); 232 } 233 234 @Override 235 public void onActionClicked(Action action) { 236 /* 237 * For list preferences 238 */ 239 final String key = action.getKey(); 240 switch ((ActionType) mState) { 241 case DATE_CHOOSE_FORMAT: 242 setDateFormat(key); 243 updateTimeAndDateStrings(); 244 goBack(); 245 return; 246 case TIME_SET_TIME_ZONE: 247 setTimeZone(key); 248 updateTimeAndDateStrings(); 249 goBack(); 250 return; 251 } 252 /* 253 * For other preferences 254 */ 255 ActionKey<ActionType, ActionBehavior> actionKey = new ActionKey<ActionType, ActionBehavior>( 256 ActionType.class, ActionBehavior.class, key); 257 final ActionType type = actionKey.getType(); 258 final ActionBehavior behavior = actionKey.getBehavior(); 259 if (type == null || behavior == null) { 260 // Possible race condition manifested by monkey test. 261 Log.e(TAG, "type or behavior is null - exiting b/17404946"); 262 return; 263 } 264 switch (type) { 265 case DATE: 266 case TIME: 267 case DATE_CHOOSE_FORMAT: 268 case TIME_CHOOSE_FORMAT: 269 case DATE_SET_DATE: 270 case TIME_SET_TIME: 271 case TIME_SET_TIME_ZONE: 272 case AUTO_DATE_TIME: 273 if (behavior == ActionBehavior.INIT) { 274 setState(type, true); 275 } 276 break; 277 } 278 switch (behavior) { 279 case ON: 280 if (mState == ActionType.TIME_CHOOSE_FORMAT) { 281 setTime24Hour(true); 282 updateTimeAndDateStrings(); 283 } else if (mState == ActionType.AUTO_DATE_TIME) { 284 setAutoDateTime(true); 285 } 286 goBack(); 287 break; 288 case OFF: 289 if (mState == ActionType.TIME_CHOOSE_FORMAT) { 290 setTime24Hour(false); 291 updateTimeAndDateStrings(); 292 } else if (mState == ActionType.AUTO_DATE_TIME) { 293 setAutoDateTime(false); 294 } 295 goBack(); 296 break; 297 } 298 } 299 300 @Override 301 protected Object getInitialState() { 302 return ActionType.DATE_TIME_OVERVIEW; 303 } 304 305 private String getDateFormatExampleString() { 306 // Display a default example date. 307 // TODO: Check with UX on exactly what we should display here, a sample date 308 // or a standard (but localized) format descriptor. 309 String setFormat = getDateFormat(); 310 String formatted = DateFormat.getDateFormatForSetting(this, getDateFormat()) 311 .format(mDummyDate.getTime()); 312 String displayText; 313 if (setFormat == null || setFormat.isEmpty()) { 314 displayText = getString(R.string.normal_date_format, formatted); 315 } else { 316 displayText = formatted; 317 } 318 return displayText; 319 } 320 321 // Updates the Date and Time entries in the current view, without resetting the 322 // Action fragment, so we don't trigger an animation. 323 protected void updateTimeAndDateDisplay() { 324 updateTimeAndDateStrings(); 325 326 if (mActionFragment instanceof ActionFragment) { 327 ActionAdapter adapter = (ActionAdapter) ((ActionFragment)mActionFragment).getAdapter(); 328 329 if (adapter != null) { 330 mActions.clear(); 331 332 switch ((ActionType) mState) { 333 case DATE_TIME_OVERVIEW: 334 mActions.add(ActionType.AUTO_DATE_TIME.toAction(mResources, 335 mHelper.getStatusStringFromBoolean(getAutoState( 336 Settings.Global.AUTO_TIME)))); 337 mActions.add(ActionType.DATE.toAction(mResources, mNowDate)); 338 mActions.add(ActionType.TIME.toAction(mResources, mNowTime)); 339 break; 340 case DATE: 341 mActions.add(ActionType.DATE_SET_DATE.toAction(mResources, mNowDate)); 342 mActions.add(ActionType.DATE_CHOOSE_FORMAT.toAction(mResources, 343 getDateFormatExampleString())); 344 break; 345 case TIME: 346 mActions.add(ActionType.TIME_SET_TIME.toAction(mResources, mNowTime)); 347 mActions.add(ActionType.TIME_SET_TIME_ZONE.toAction(mResources, 348 getCurrentTimeZoneName())); 349 mActions.add(ActionType.TIME_CHOOSE_FORMAT.toAction( 350 mResources, getTimeFormatDescription())); 351 break; 352 } 353 adapter.setActions(mActions); 354 return; 355 } 356 } 357 358 // If we don't have an ActionFragment or adapter, fall back to the regular updateView 359 updateView(); 360 } 361 362 @Override 363 protected void refreshActionList() { 364 mActions.clear(); 365 boolean autoTime = getAutoState(Settings.Global.AUTO_TIME); 366 switch ((ActionType)mState) { 367 case DATE_TIME_OVERVIEW: 368 mActions.add(ActionType.AUTO_DATE_TIME.toAction(mResources, 369 mHelper.getStatusStringFromBoolean(autoTime))); 370 mActions.add(ActionType.DATE.toAction(mResources, mNowDate)); 371 mActions.add(ActionType.TIME.toAction(mResources, mNowTime)); 372 break; 373 case DATE: 374 mActions.add(ActionType.DATE_SET_DATE.toAction(mResources, mNowDate, !autoTime)); 375 376 mActions.add(ActionType.DATE_CHOOSE_FORMAT.toAction(mResources, 377 getDateFormatExampleString())); 378 break; 379 case TIME: 380 mActions.add(ActionType.TIME_SET_TIME.toAction(mResources, mNowTime, !autoTime)); 381 mActions.add(ActionType.TIME_SET_TIME_ZONE.toAction( 382 mResources, getCurrentTimeZoneName())); 383 mActions.add(ActionType.TIME_CHOOSE_FORMAT.toAction( 384 mResources, getTimeFormatDescription())); 385 break; 386 case DATE_CHOOSE_FORMAT: 387 String[] formats = mResources.getStringArray(R.array.date_format_values); 388 String currentFormat = getDateFormat(); 389 mActions = Action.createActionsFromArrays(formats, getFormattedDates()); 390 for (Action action : mActions) { 391 action.setChecked(action.getKey().equalsIgnoreCase(currentFormat)); 392 } 393 break; 394 case TIME_CHOOSE_FORMAT: 395 mActions.add(ActionBehavior.ON.toAction(ActionBehavior.getOnKey( 396 ActionType.TIME_CHOOSE_FORMAT.name()), mResources, isTimeFormat24h())); 397 mActions.add(ActionBehavior.OFF.toAction(ActionBehavior.getOffKey( 398 ActionType.TIME_CHOOSE_FORMAT.name()), mResources, !isTimeFormat24h())); 399 break; 400 case TIME_SET_TIME_ZONE: 401 mActions = getZoneActions(this); 402 break; 403 case AUTO_DATE_TIME: 404 mActions.add(ActionBehavior.ON.toAction(ActionBehavior.getOnKey( 405 ActionType.AUTO_DATE_TIME.name()), mResources, autoTime)); 406 mActions.add(ActionBehavior.OFF.toAction(ActionBehavior.getOffKey( 407 ActionType.AUTO_DATE_TIME.name()), mResources, !autoTime)); 408 break; 409 default: 410 break; 411 } 412 } 413 414 private String getTimeFormatDescription() { 415 String status = mHelper.getStatusStringFromBoolean(isTimeFormat24h()); 416 String desc = String.format("%s (%s)", status, 417 DateFormat.getTimeFormat(this).format(mDummyDate.getTime())); 418 return desc; 419 } 420 421 @Override 422 protected void updateView() { 423 refreshActionList(); 424 425 switch ((ActionType) mState) { 426 case DATE_TIME_OVERVIEW: 427 if (mOnCreateUpdateView && mOnResumeUpdateView) { 428 // If current updateView call is due to onResume following onCreate, 429 // avoid duplicate setView, which will lead to broken animation. 430 mOnCreateUpdateView = false; 431 mOnResumeUpdateView = false; 432 433 return; 434 } else { 435 mOnResumeUpdateView = false; 436 } 437 mActionFragment = ActionFragment.newInstance(mActions); 438 break; 439 case DATE_SET_DATE: 440 DatePicker datePicker = 441 DatePicker.newInstance(new String(DateFormat.getDateFormatOrder(this))); 442 datePicker.setResultListener(new Picker.ResultListener() { 443 444 @Override 445 public void onCommitResult(List<String> result) { 446 String formatOrder = new String( 447 DateFormat.getDateFormatOrder(DateTimeActivity.this)).toUpperCase(); 448 int yIndex = formatOrder.indexOf('Y'); 449 int mIndex = formatOrder.indexOf('M'); 450 int dIndex = formatOrder.indexOf('D'); 451 if (yIndex < 0 || mIndex < 0 || dIndex < 0 || 452 yIndex > 2 || mIndex > 2 || dIndex > 2) { 453 // Badly formatted input. Use default order. 454 mIndex = 0; 455 dIndex = 1; 456 yIndex = 2; 457 } 458 String month = result.get(mIndex); 459 int day = Integer.parseInt(result.get(dIndex)); 460 int year = Integer.parseInt(result.get(yIndex)); 461 int monthInt = 0; 462 String[] months = PickerConstant.getInstance(mResources).months; 463 int totalMonths = months.length; 464 for (int i = 0; i < totalMonths; i++) { 465 if (months[i].equals(month)) { 466 monthInt = i; 467 } 468 } 469 470 // turn off Auto date/time 471 setAutoDateTime(false); 472 473 setDate(DateTimeActivity.this, year, monthInt, day); 474 goBack(); 475 } 476 }); 477 mActionFragment = datePicker; 478 break; 479 case TIME_SET_TIME: 480 Picker timePicker = TimePicker.newInstance(isTimeFormat24h(), true); 481 timePicker.setResultListener(new Picker.ResultListener() { 482 483 @Override 484 public void onCommitResult(List<String> result) { 485 boolean is24hFormat = isTimeFormat24h(); 486 int hour = Integer.parseInt(result.get(0)); 487 int minute = Integer.parseInt(result.get(1)); 488 if (!is24hFormat) { 489 String ampm = result.get(2); 490 if (ampm.equals(getResources().getStringArray(R.array.ampm)[1])) { 491 // PM case, valid hours: 12-23 492 hour = (hour % HOURS_IN_HALF_DAY) + HOURS_IN_HALF_DAY; 493 } else { 494 // AM case, valid hours: 0-11 495 hour = hour % HOURS_IN_HALF_DAY; 496 } 497 } 498 499 // turn off Auto date/time 500 setAutoDateTime(false); 501 502 setTime(DateTimeActivity.this, hour, minute); 503 goBack(); 504 } 505 }); 506 mActionFragment = timePicker; 507 break; 508 default: 509 mActionFragment = ActionFragment.newInstance(mActions); 510 break; 511 } 512 513 setViewWithActionFragment( 514 ((ActionType) mState).getTitle(mResources), getPrevState() != null ? 515 ((ActionType) getPrevState()).getTitle(mResources) 516 : getString(R.string.settings_app_name), 517 ((ActionType) mState).getDescription(mResources), R.drawable.ic_settings_datetime); 518 } 519 520 protected void setViewWithActionFragment(String title, String breadcrumb, String description, 521 int iconResId) { 522 mContentFragment = ContentFragment.newInstance(title, breadcrumb, description, iconResId, 523 getResources().getColor(R.color.icon_background)); 524 setContentAndActionFragments(mContentFragment, mActionFragment); 525 } 526 527 @Override 528 protected void setProperty(boolean enable) { 529 } 530 531 /** 532 * Returns a string representing the current time zone set in the system. 533 */ 534 private String getCurrentTimeZoneName() { 535 final Calendar now = Calendar.getInstance(); 536 TimeZone tz = now.getTimeZone(); 537 538 Date date = new Date(); 539 return formatOffset(new StringBuilder(), tz.getOffset(date.getTime())). 540 append(", "). 541 append(tz.getDisplayName(tz.inDaylightTime(date), TimeZone.LONG)).toString(); 542 } 543 544 /** 545 * Formats the provided timezone offset into a string of the form GMT+XX:XX 546 */ 547 private static StringBuilder formatOffset(StringBuilder sb, long offset) { 548 long off = offset / 1000 / 60; 549 550 sb.append("GMT"); 551 if (off < 0) { 552 sb.append('-'); 553 off = -off; 554 } else { 555 sb.append('+'); 556 } 557 558 int hours = (int) (off / 60); 559 int minutes = (int) (off % 60); 560 561 sb.append((char) ('0' + hours / 10)); 562 sb.append((char) ('0' + hours % 10)); 563 564 sb.append(':'); 565 566 sb.append((char) ('0' + minutes / 10)); 567 sb.append((char) ('0' + minutes % 10)); 568 569 return sb; 570 } 571 572 /** 573 * Helper class to hold the time zone data parsed from the Time Zones XML 574 * file. 575 */ 576 private class TimeZoneInfo implements Comparable<TimeZoneInfo> { 577 public String tzId; 578 public String tzName; 579 public long tzOffset; 580 581 public TimeZoneInfo(String id, String name, long offset) { 582 tzId = id; 583 tzName = name; 584 tzOffset = offset; 585 } 586 587 @Override 588 public int compareTo(TimeZoneInfo another) { 589 return (int) (tzOffset - another.tzOffset); 590 } 591 } 592 593 /** 594 * Parse the Time Zones information from the XML file and creates Action 595 * objects for each time zone. 596 */ 597 private ArrayList<Action> getZoneActions(Context context) { 598 if (mTimeZoneActions != null && mTimeZoneActions.size() != 0) { 599 return mTimeZoneActions; 600 } 601 602 ArrayList<TimeZoneInfo> timeZones = getTimeZones(context); 603 604 mTimeZoneActions = new ArrayList<Action>(); 605 606 // Sort the Time Zones list in ascending offset order 607 Collections.sort(timeZones); 608 609 TimeZone currentTz = TimeZone.getDefault(); 610 611 for (TimeZoneInfo tz : timeZones) { 612 StringBuilder name = new StringBuilder(); 613 boolean checked = currentTz.getID().equals(tz.tzId); 614 mTimeZoneActions.add(getTimeZoneAction(tz.tzId, tz.tzName, 615 formatOffset(name, tz.tzOffset).toString(), checked)); 616 } 617 618 return mTimeZoneActions; 619 } 620 621 /** 622 * Parses the XML time zone information into an array of TimeZoneInfo 623 * objects. 624 */ 625 private ArrayList<TimeZoneInfo> getTimeZones(Context context) { 626 ArrayList<TimeZoneInfo> timeZones = new ArrayList<TimeZoneInfo>(); 627 final long date = Calendar.getInstance().getTimeInMillis(); 628 try { 629 XmlResourceParser xrp = context.getResources().getXml(R.xml.timezones); 630 while (xrp.next() != XmlResourceParser.START_TAG) 631 continue; 632 xrp.next(); 633 while (xrp.getEventType() != XmlResourceParser.END_TAG) { 634 while (xrp.getEventType() != XmlResourceParser.START_TAG && 635 xrp.getEventType() != XmlResourceParser.END_DOCUMENT) { 636 xrp.next(); 637 } 638 639 if (xrp.getEventType() == XmlResourceParser.END_DOCUMENT) { 640 break; 641 } 642 643 if (xrp.getName().equals(XMLTAG_TIMEZONE)) { 644 String id = xrp.getAttributeValue(0); 645 String displayName = xrp.nextText(); 646 TimeZone tz = TimeZone.getTimeZone(id); 647 long offset; 648 if (tz != null) { 649 offset = tz.getOffset(date); 650 timeZones.add(new TimeZoneInfo(id, displayName, offset)); 651 } else { 652 continue; 653 } 654 } 655 while (xrp.getEventType() != XmlResourceParser.END_TAG) { 656 xrp.next(); 657 } 658 xrp.next(); 659 } 660 xrp.close(); 661 } catch (XmlPullParserException xppe) { 662 Log.e(TAG, "Ill-formatted timezones.xml file"); 663 } catch (java.io.IOException ioe) { 664 Log.e(TAG, "Unable to read timezones.xml file"); 665 } 666 return timeZones; 667 } 668 669 private static Action getTimeZoneAction(String tzId, String displayName, String gmt, 670 boolean setChecked) { 671 return new Action.Builder().key(tzId).title(displayName).description(gmt). 672 checked(setChecked).build(); 673 } 674 675 private void setTimeZone(String tzId) { 676 // Update the system timezone value 677 final AlarmManager alarm = (AlarmManager) getSystemService(Context.ALARM_SERVICE); 678 alarm.setTimeZone(tzId); 679 680 setSampleDate(); 681 } 682 } 683