From 31988e74d1ff99dbf6e868358b4d2409ab4007f7 Mon Sep 17 00:00:00 2001
From: lkassianik <lyubakassianik@gmail.com>
Date: Sat, 16 May 2015 20:13:25 -0700
Subject: [PATCH] Add a setting so user can choose when Calendar weeks start

Summary: Closes T8176, Add a setting so user can choose when Calendar weeks start

Test Plan: Open account settings, go to "Date and Time", change week start day, open Calendar, observe that month view responds to new week start day.

Reviewers: #blessed_reviewers, epriestley

Reviewed By: #blessed_reviewers, epriestley

Subscribers: Korvin, epriestley

Maniphest Tasks: T8176

Differential Revision: https://secure.phabricator.com/D12884
---
 .../PhabricatorCalendarEventSearchEngine.php  | 18 ++--
 .../PhabricatorDateTimeSettingsPanel.php      | 28 ++++++-
 .../storage/PhabricatorUserPreferences.php    |  1 +
 .../phui/calendar/PHUICalendarMonthView.php   | 82 ++++++++++---------
 4 files changed, 82 insertions(+), 47 deletions(-)

diff --git a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php
index 7added162f..4f65202a2c 100644
--- a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php
+++ b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php
@@ -76,9 +76,11 @@ final class PhabricatorCalendarEventSearchEngine
       $display_start = $start_day->format('U');
       $display_end = $next->format('U');
 
-      // 0 = Sunday is always the start of the week, for now
-      $start_of_week = 0;
-      $end_of_week = 6 - $start_of_week;
+      $preferences = $viewer->loadPreferences();
+      $pref_week_day = PhabricatorUserPreferences::PREFERENCE_WEEK_START_DAY;
+
+      $start_of_week = $preferences->getPreference($pref_week_day, 0);
+      $end_of_week = ($start_of_week + 6) % 7;
 
       $first_of_month = $start_day->format('w');
       $last_of_month = id(clone $next)->modify('-1 day')->format('w');
@@ -87,9 +89,10 @@ final class PhabricatorCalendarEventSearchEngine
         $min_range = $display_start;
 
         if ($this->isMonthView($saved) &&
-          $first_of_month > $start_of_week) {
+          $first_of_month !== $start_of_week) {
+          $interim_day_num = ($first_of_month + 7 - $start_of_week) % 7;
           $min_range = id(clone $start_day)
-            ->modify('-'.$first_of_month.' days')
+            ->modify('-'.$interim_day_num.' days')
             ->format('U');
         }
       }
@@ -97,9 +100,10 @@ final class PhabricatorCalendarEventSearchEngine
         $max_range = $display_end;
 
         if ($this->isMonthView($saved) &&
-          $last_of_month < $end_of_week) {
+          $last_of_month !== $end_of_week) {
+          $interim_day_num = ($end_of_week + 7 - $last_of_month) % 7;
           $max_range = id(clone $next)
-            ->modify('+'.(6 - $first_of_month).' days')
+            ->modify('+'.$interim_day_num.' days')
             ->format('U');
         }
 
diff --git a/src/applications/settings/panel/PhabricatorDateTimeSettingsPanel.php b/src/applications/settings/panel/PhabricatorDateTimeSettingsPanel.php
index 661a1172b7..5fbf825d22 100644
--- a/src/applications/settings/panel/PhabricatorDateTimeSettingsPanel.php
+++ b/src/applications/settings/panel/PhabricatorDateTimeSettingsPanel.php
@@ -19,6 +19,7 @@ final class PhabricatorDateTimeSettingsPanel extends PhabricatorSettingsPanel {
     $username = $user->getUsername();
 
     $pref_time = PhabricatorUserPreferences::PREFERENCE_TIME_FORMAT;
+    $pref_week_start = PhabricatorUserPreferences::PREFERENCE_WEEK_START_DAY;
     $preferences = $user->loadPreferences();
 
     $errors = array();
@@ -30,7 +31,12 @@ final class PhabricatorDateTimeSettingsPanel extends PhabricatorSettingsPanel {
         $errors[] = pht('The selected timezone is not a valid timezone.');
       }
 
-      $preferences->setPreference($pref_time, $request->getStr($pref_time));
+      $preferences->setPreference(
+        $pref_time,
+        $request->getStr($pref_time));
+      $preferences->setPreference(
+        $pref_week_start,
+        $request->getStr($pref_week_start));
 
       if (!$errors) {
         $preferences->save();
@@ -72,6 +78,14 @@ final class PhabricatorDateTimeSettingsPanel extends PhabricatorSettingsPanel {
           ->setCaption(
             pht('Format used when rendering a time of day.'))
           ->setValue($preferences->getPreference($pref_time)))
+      ->appendChild(
+        id(new AphrontFormSelectControl())
+          ->setLabel(pht('Week Starts On'))
+          ->setOptions($this->getWeekDays())
+          ->setName($pref_week_start)
+          ->setCaption(
+            pht('Calendar weeks will start with this day.'))
+          ->setValue($preferences->getPreference($pref_week_start, 0)))
       ->appendChild(
         id(new AphrontFormSubmitControl())
           ->setValue(pht('Save Account Settings')));
@@ -86,4 +100,16 @@ final class PhabricatorDateTimeSettingsPanel extends PhabricatorSettingsPanel {
       $form_box,
     );
   }
+
+  private function getWeekDays() {
+    return array(
+      pht('Sunday'),
+      pht('Monday'),
+      pht('Tuesday'),
+      pht('Wednesday'),
+      pht('Thursday'),
+      pht('Friday'),
+      pht('Saturday'),
+    );
+  }
 }
diff --git a/src/applications/settings/storage/PhabricatorUserPreferences.php b/src/applications/settings/storage/PhabricatorUserPreferences.php
index 3248dc8b2a..7732df5568 100644
--- a/src/applications/settings/storage/PhabricatorUserPreferences.php
+++ b/src/applications/settings/storage/PhabricatorUserPreferences.php
@@ -9,6 +9,7 @@ final class PhabricatorUserPreferences extends PhabricatorUserDAO {
   const PREFERENCE_TITLES               = 'titles';
   const PREFERENCE_MONOSPACED_TEXTAREAS = 'monospaced-textareas';
   const PREFERENCE_TIME_FORMAT          = 'time-format';
+  const PREFERENCE_WEEK_START_DAY       = 'week-start-day';
 
   const PREFERENCE_RE_PREFIX            = 're-prefix';
   const PREFERENCE_NO_SELF_MAIL         = 'self-mail';
diff --git a/src/view/phui/calendar/PHUICalendarMonthView.php b/src/view/phui/calendar/PHUICalendarMonthView.php
index fd2ca4e458..d6bb7d1a32 100644
--- a/src/view/phui/calendar/PHUICalendarMonthView.php
+++ b/src/view/phui/calendar/PHUICalendarMonthView.php
@@ -59,24 +59,10 @@ final class PHUICalendarMonthView extends AphrontView {
     $days = $this->getDatesInMonth();
 
     $cell_lists = array();
-    $empty_cell = array(
-        'list' => null,
-        'date' => null,
-        'uri' => null,
-        'count' => 0,
-        'class' => null,
-      );
 
     require_celerity_resource('phui-calendar-month-css');
 
     $first = reset($days);
-    $start_of_week = 0;
-
-    $empty = $first->format('w');
-
-    for ($ii = 0; $ii < $empty; $ii++) {
-      $cell_lists[] = $empty_cell;
-    }
 
     foreach ($days as $day) {
       $day_number = $day->format('j');
@@ -133,9 +119,6 @@ final class PHUICalendarMonthView extends AphrontView {
 
     foreach ($cell_lists_by_week as $week_of_cell_lists) {
       $cells = array();
-      while (count($week_of_cell_lists) < 7) {
-        $week_of_cell_lists[] = $empty_cell;
-      }
       foreach ($week_of_cell_lists as $cell_list) {
         $cells[] = $this->getEventListCell($cell_list);
       }
@@ -309,18 +292,28 @@ final class PHUICalendarMonthView extends AphrontView {
   }
 
   private function getDayNamesHeader() {
+    list($week_start, $week_end) = $this->getWeekStartAndEnd();
+
+    $weekday_names = array(
+      $this->getDayHeader(pht('Sun'), pht('Sunday'), true),
+      $this->getDayHeader(pht('Mon'), pht('Monday')),
+      $this->getDayHeader(pht('Tue'), pht('Tuesday')),
+      $this->getDayHeader(pht('Wed'), pht('Wednesday')),
+      $this->getDayHeader(pht('Thu'), pht('Thursday')),
+      $this->getDayHeader(pht('Fri'), pht('Friday')),
+      $this->getDayHeader(pht('Sat'), pht('Saturday'), true),
+    );
+
+    $sorted_weekday_names = array();
+
+    for ($i = $week_start; $i < ($week_start + 7); $i++) {
+      $sorted_weekday_names[] = $weekday_names[$i % 7];
+    }
+
     return phutil_tag(
       'tr',
       array('class' => 'phui-calendar-day-of-week-header'),
-      array(
-        $this->getDayHeader(pht('Sun'), pht('Sunday'), true),
-        $this->getDayHeader(pht('Mon'), pht('Monday')),
-        $this->getDayHeader(pht('Tue'), pht('Tuesday')),
-        $this->getDayHeader(pht('Wed'), pht('Wednesday')),
-        $this->getDayHeader(pht('Thu'), pht('Thursday')),
-        $this->getDayHeader(pht('Fri'), pht('Friday')),
-        $this->getDayHeader(pht('Sat'), pht('Saturday'), true),
-      ));
+      $sorted_weekday_names);
   }
 
   private function getDayHeader($short, $long, $is_weekend = false) {
@@ -466,8 +459,8 @@ final class PHUICalendarMonthView extends AphrontView {
     list($next_year, $next_month) = $this->getNextYearAndMonth();
     $end_date = new DateTime("{$next_year}-{$next_month}-01", $timezone);
 
-    $start_of_week = 0;
-    $end_of_week = 6 - $start_of_week;
+    list($start_of_week, $end_of_week) = $this->getWeekStartAndEnd();
+
     $days_in_month = id(clone $end_date)->modify('-1 day')->format('d');
 
     $first_month_day_date = new DateTime("{$year}-{$month}-01", $timezone);
@@ -477,17 +470,19 @@ final class PHUICalendarMonthView extends AphrontView {
     $last_weekday_of_month = $last_month_day_date->format('w');
 
     $num_days_display = $days_in_month;
-    if ($start_of_week < $first_weekday_of_month) {
-      $num_days_display += $first_weekday_of_month;
+    if ($start_of_week !== $first_weekday_of_month) {
+      $interim_start_num = ($first_weekday_of_month + 7 - $start_of_week) % 7;
+      $num_days_display += $interim_start_num;
+      $day_date = id(clone $first_month_day_date)
+        ->modify('-'.$interim_start_num.' days');
     }
-    if ($end_of_week > $last_weekday_of_month) {
-      $num_days_display += (6 - $last_weekday_of_month);
-      $end_date->modify('+'.(6 - $last_weekday_of_month).' days');
+    if ($end_of_week !== $last_weekday_of_month) {
+      $interim_end_day_num = ($end_of_week - $last_weekday_of_month + 7) % 7;
+      $num_days_display += $interim_end_day_num;
+      $end_date->modify('+'.$interim_end_day_num.' days');
     }
 
     $days = array();
-    $day_date = id(clone $first_month_day_date)
-      ->modify('-'.$first_weekday_of_month.' days');
 
     for ($day = 1; $day <= $num_days_display; $day++) {
       $day_epoch = $day_date->format('U');
@@ -513,14 +508,13 @@ final class PHUICalendarMonthView extends AphrontView {
   }
 
   private function getThisWeekRange() {
-    $week_start = 0;
-    $week_end = 6;
+    list($week_start, $week_end) = $this->getWeekStartAndEnd();
 
     $today = $this->getTodayMidnight();
     $date_weekday = $today->format('w');
 
-    $days_from_week_start = $date_weekday - $week_start;
-    $days_to_week_end = $week_end - $date_weekday + 1;
+    $days_from_week_start = ($date_weekday + 7 - $week_start) % 7;
+    $days_to_week_end = 7 - $days_from_week_start;
 
     $modify = '-'.$days_from_week_start.' days';
     $week_start_date = id(clone $today)->modify($modify);
@@ -531,6 +525,16 @@ final class PHUICalendarMonthView extends AphrontView {
     return array($week_start_date, $week_end_date);
   }
 
+  private function getWeekStartAndEnd() {
+    $preferences = $this->user->loadPreferences();
+    $pref_week_start = PhabricatorUserPreferences::PREFERENCE_WEEK_START_DAY;
+
+    $week_start = $preferences->getPreference($pref_week_start, 0);
+    $week_end = ($week_start + 6) % 7;
+
+    return array($week_start, $week_end);
+  }
+
   private function getDateTime() {
     $user = $this->user;
     $timezone = new DateTimeZone($user->getTimezoneIdentifier());