1a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson/* 2a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson * Copyright (C) 2012 The Android Open Source Project 3a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson * 4a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson * Licensed under the Apache License, Version 2.0 (the "License"); 5a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson * you may not use this file except in compliance with the License. 6a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson * You may obtain a copy of the License at 7a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson * 8a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson * http://www.apache.org/licenses/LICENSE-2.0 9a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson * 10a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson * Unless required by applicable law or agreed to in writing, software 11a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson * distributed under the License is distributed on an "AS IS" BASIS, 12a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson * See the License for the specific language governing permissions and 14a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson * limitations under the License. 15a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson */ 16a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson 17a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelsonpackage com.android.calendar.month; 18a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson 19a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelsonimport android.content.Context; 20a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelsonimport android.graphics.Rect; 21a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelsonimport android.os.SystemClock; 22a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelsonimport android.text.format.Time; 23a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelsonimport android.util.AttributeSet; 24a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelsonimport android.view.MotionEvent; 25a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelsonimport android.view.VelocityTracker; 26a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelsonimport android.view.View; 27a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelsonimport android.widget.ListView; 28a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson 29a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelsonimport com.android.calendar.Utils; 30a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson 31a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelsonpublic class MonthListView extends ListView { 32a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson 33a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson private static final String TAG = "MonthListView"; 34a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson VelocityTracker mTracker; 35a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson private static float mScale = 0; 36a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson 37a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson // These define the behavior of the fling. Below MIN_VELOCITY_FOR_FLING, do the system fling 38a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson // behavior. Between MIN_VELOCITY_FOR_FLING and MULTIPLE_MONTH_VELOCITY_THRESHOLD, do one month 39a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson // fling. Above MULTIPLE_MONTH_VELOCITY_THRESHOLD, do multiple month flings according to the 40a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson // fling strength. When doing multiple month fling, the velocity is reduced by this threshold 41a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson // to prevent moving from one month fling to 4 months and above flings. 42a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson private static int MIN_VELOCITY_FOR_FLING = 1500; 43a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson private static int MULTIPLE_MONTH_VELOCITY_THRESHOLD = 2000; 44a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson private static int FLING_VELOCITY_DIVIDER = 500; 45a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson private static int FLING_TIME = 1000; 46a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson 47a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson // disposable variable used for time calculations 48a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson protected Time mTempTime; 49a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson private long mDownActionTime; 50a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson private final Rect mFirstViewRect = new Rect(); 51a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson 52a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson Context mListContext; 53a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson 54a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson // Updates the time zone when it changes 55a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson private final Runnable mTimezoneUpdater = new Runnable() { 56a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson @Override 57a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson public void run() { 58a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson if (mTempTime != null && mListContext != null) { 59a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson mTempTime.timezone = 60a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson Utils.getTimeZone(mListContext, mTimezoneUpdater); 61a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson } 62a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson } 63a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson }; 64a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson 65a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson public MonthListView(Context context) { 66a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson super(context); 67a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson init(context); 68a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson } 69a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson 70a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson public MonthListView(Context context, AttributeSet attrs, int defStyle) { 71a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson super(context, attrs, defStyle); 72a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson init(context); 73a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson } 74a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson 75a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson public MonthListView(Context context, AttributeSet attrs) { 76a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson super(context, attrs); 77a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson init(context); 78a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson } 79a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson 80a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson private void init(Context c) { 81a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson mListContext = c; 82a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson mTracker = VelocityTracker.obtain(); 83a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson mTempTime = new Time(Utils.getTimeZone(c,mTimezoneUpdater)); 84a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson if (mScale == 0) { 85a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson mScale = c.getResources().getDisplayMetrics().density; 86a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson if (mScale != 1) { 87a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson MIN_VELOCITY_FOR_FLING *= mScale; 88a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson MULTIPLE_MONTH_VELOCITY_THRESHOLD *= mScale; 89a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson FLING_VELOCITY_DIVIDER *= mScale; 90a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson } 91a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson } 92a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson } 93a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson 94a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson @Override 95a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson public boolean onTouchEvent(MotionEvent ev) { 96a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson return processEvent(ev) || super.onTouchEvent(ev); 97a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson } 98a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson 99a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson @Override 100a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson public boolean onInterceptTouchEvent(MotionEvent ev) { 101a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson return processEvent(ev) || super.onInterceptTouchEvent(ev); 102a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson } 103a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson 104a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson private boolean processEvent (MotionEvent ev) { 105a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson switch (ev.getAction() & MotionEvent.ACTION_MASK) { 106a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson // Since doFling sends a cancel, make sure not to process it. 107a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson case MotionEvent.ACTION_CANCEL: 108a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson return false; 109a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson // Start tracking movement velocity 110a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson case MotionEvent.ACTION_DOWN: 111a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson mTracker.clear(); 112a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson mDownActionTime = SystemClock.uptimeMillis(); 113a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson break; 114a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson // Accumulate velocity and do a custom fling when above threshold 115a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson case MotionEvent.ACTION_UP: 116a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson mTracker.addMovement(ev); 117a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson mTracker.computeCurrentVelocity(1000); // in pixels per second 118a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson float vel = mTracker.getYVelocity (); 119a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson if (Math.abs(vel) > MIN_VELOCITY_FOR_FLING) { 120a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson doFling(vel); 121a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson return true; 122a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson } 123a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson break; 124a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson default: 125a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson mTracker.addMovement(ev); 126a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson break; 127a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson } 128a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson return false; 129a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson } 130a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson 131a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson // Do a "snap to start of month" fling 132a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson private void doFling(float velocityY) { 133a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson 134a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson // Stop the list-view movement and take over 135a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson MotionEvent cancelEvent = MotionEvent.obtain(mDownActionTime, SystemClock.uptimeMillis(), 136a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson MotionEvent.ACTION_CANCEL, 0, 0, 0); 137a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson onTouchEvent(cancelEvent); 138a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson 139a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson // Below the threshold, fling one month. Above the threshold , fling 140a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson // according to the speed of the fling. 141a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson int monthsToJump; 142a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson if (Math.abs(velocityY) < MULTIPLE_MONTH_VELOCITY_THRESHOLD) { 143a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson if (velocityY < 0) { 144a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson monthsToJump = 1; 145a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson } else { 146a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson // value here is zero and not -1 since by the time the fling is 147a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson // detected the list moved back one month. 148a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson monthsToJump = 0; 149a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson } 150a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson } else { 151a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson if (velocityY < 0) { 152a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson monthsToJump = 1 - (int) ((velocityY + MULTIPLE_MONTH_VELOCITY_THRESHOLD) 153a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson / FLING_VELOCITY_DIVIDER); 154a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson } else { 155a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson monthsToJump = -(int) ((velocityY - MULTIPLE_MONTH_VELOCITY_THRESHOLD) 156a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson / FLING_VELOCITY_DIVIDER); 157a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson } 158a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson } 159a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson 160a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson // Get the day at the top right corner 161a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson int day = getUpperRightJulianDay(); 162a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson // Get the day of the first day of the next/previous month 163a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson // (according to scroll direction) 164a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson mTempTime.setJulianDay(day); 165a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson mTempTime.monthDay = 1; 166a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson mTempTime.month += monthsToJump; 167a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson long timeInMillis = mTempTime.normalize(false); 168a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson // Since each view is 7 days, round the target day up to make sure the 169a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson // scroll will be at least one view. 170a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson int scrollToDay = Time.getJulianDay(timeInMillis, mTempTime.gmtoff) 171a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson + ((monthsToJump > 0) ? 6 : 0); 172a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson 173a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson // Since all views have the same height, scroll by pixels instead of 174a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson // "to position". 175a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson // Compensate for the top view offset from the top. 176a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson View firstView = getChildAt(0); 177a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson int firstViewHeight = firstView.getHeight(); 178a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson // Get visible part length 179a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson firstView.getLocalVisibleRect(mFirstViewRect); 180a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson int topViewVisiblePart = mFirstViewRect.bottom - mFirstViewRect.top; 181a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson int viewsToFling = (scrollToDay - day) / 7 - ((monthsToJump <= 0) ? 1 : 0); 182a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson int offset = (viewsToFling > 0) ? -(firstViewHeight - topViewVisiblePart 183a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson + SimpleDayPickerFragment.LIST_TOP_OFFSET) : (topViewVisiblePart 184a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson - SimpleDayPickerFragment.LIST_TOP_OFFSET); 185a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson // Fling 186a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson smoothScrollBy(viewsToFling * firstViewHeight + offset, FLING_TIME); 187a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson } 188a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson 189a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson // Returns the julian day of the day in the upper right corner 190a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson private int getUpperRightJulianDay() { 191a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson SimpleWeekView child = (SimpleWeekView) getChildAt(0); 192a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson if (child == null) { 193a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson return -1; 194a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson } 195a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson return child.getFirstJulianDay() + SimpleDayPickerFragment.DAYS_PER_WEEK - 1; 196a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson } 197a8133aacf45345c83fc02e2a6ddc86e62153eca8Isaac Katzenelson} 198