Как рассчитать полосу дней в основных данных?

Мне нужно подсчитать последнюю полосу дней, но не могу понять, как это сделать. Например, я получил основные данные следующим образом:

|id| isPresent(Bool)| date(NSDate)|
|==|================|=============|
| 1|               0| 2016-02-11  |
| 2|               1| 2016-02-11  |
| 3|               1| 2016-02-12  |
| 4|               0| 2016-02-14  |
| 5|               1| 2016-02-15  |
| 6|               1| 2016-02-16  |
| 7|               1| 2016-02-16  |

Я пытаюсь проверить последние не представленные (isPresent = 0) дату до сегодняшнего дня и получить 2016-02-14 - так что я могу считать дни, это легко.

Но если я отмечаю 2016-02-14 как isPresented = 1 (как в таблице ниже), я получу последний не представленный 2016-02-11 - но это неверно, нет данных для 2016-02-13, поэтому isPresented для этой даты должно быть 0, и строка должна отсчитываться с этой даты

|id| isPresent(Bool)| date(NSDate)|
|==|================|=============|
| 1|               0| 2016-02-11  |
| 2|               1| 2016-02-11  |
| 3|               1| 2016-02-12  |
| 4|               1| 2016-02-14  |
| 5|               1| 2016-02-15  |
| 6|               1| 2016-02-16  |
| 7|               1| 2016-02-16  |

Я искал разные алгоритмы для строк или sql reuests для отсутствующих дат (sql-сервер, отображающий отсутствующие даты), но не могу понять, как использовать его в основных данных.

Я думаю о других данных, которые будут содержать полосу и обновления каждый раз, когда пользователь будет открывать приложение, но получил такую ​​же проблему, что, если пользователь не открыл приложение.

Вывод: Мне нужно найти только количество дней подряд или дату, когда он сломается, поэтому для

Для первой таблицы: streak = 2 или breakDate = 2016-02-14 - я пробую это, но мое решение неверно, потому что вторая таблица

Для второй таблицы: streak = 3 или breakDate = 2016-02-13 - не может понять, как получить отсутствующую дату

Важное обновление: Там будут данные синхронизации облака, поэтому я не вижу никакого решения внутри приложения, действительно нужно найти отсутствующую дату или isPresented = 0 в coredata​​p >

p.s. Я использую swift, если вы можете помочь мне через быстрый, было бы здорово, но я также понимаю Obj-C. И извините за мой плохой английский

Ответы

Ответ 1

Из вашего вопроса, я думаю, у вас есть объект объекта с объектом NSDate. Вот какой код вы можете использовать для этого.

let userDefaults = NSUserDefaults.standardUserDefaults()
var moc: NSManagedObjectContext!

var lastStreakEndDate: NSDate!
var streakTotal: Int!



override func viewDidLoad() {
    super.viewDidLoad()


    // checks for object if nil creates one (used for first run)
    if userDefaults.objectForKey("lastStreakEndDate") == nil {
        userDefaults.setObject(NSDate(), forKey: "lastStreakEndDate")
    }

    lastStreakEndDate = userDefaults.objectForKey("lastStreakEndDate") as! NSDate

    streakTotal = calculateStreak(lastStreakEndDate)
}


// fetches dates since last streak
func fetchLatestDates(moc: NSManagedObjectContext, lastDate: NSDate) -> [NSDate] {
    var dates = [NSDate]()

    let fetchRequest = NSFetchRequest(entityName: "YourEntity")
    let datePredicate = NSPredicate(format: "date < %@", lastDate)

    fetchRequest.predicate = datePredicate

    do {
        let result = try moc.executeFetchRequest(fetchRequest)
        let allDates = result as! [NSDate]
        if allDates.count > 0 {
            for date in allDates {
                dates.append(date)
            }
        }
    } catch {
        fatalError()
    }
    return dates
}


// set date time to the end of the day so the user has 24hrs to add to the streak
func changeDateTime(userDate: NSDate) -> NSDate {
    let dateComponents = NSDateComponents()
    let currentCalendar = NSCalendar.currentCalendar()
    let year = Int(currentCalendar.component(NSCalendarUnit.Year, fromDate: userDate))
    let month = Int(currentCalendar.component(NSCalendarUnit.Month, fromDate: userDate))
    let day = Int(currentCalendar.component(NSCalendarUnit.Day, fromDate: userDate))

    dateComponents.year = year
    dateComponents.month = month
    dateComponents.day = day
    dateComponents.hour = 23
    dateComponents.minute = 59
    dateComponents.second = 59

    guard let returnDate = currentCalendar.dateFromComponents(dateComponents) else {
        return userDate
    }
    return returnDate
}


// adds a day to the date
func addDay(today: NSDate) -> NSDate {
    let tomorrow = NSCalendar.currentCalendar().dateByAddingUnit(.Day, value: 1, toDate: today, options: NSCalendarOptions(rawValue: 0))

    return tomorrow!
}

// this method returns the total of the streak and sets the ending date of the last streak
func calculateStreak(lastDate: NSDate) -> Int {
    let dateList = fetchLatestDates(moc, lastDate: lastDate)
    let compareDate = changeDateTime(lastDate)
    var streakDateList = [NSDate]()
    var tomorrow = addDay(compareDate)

    for date in dateList {
        changeDateTime(date)
        if date == tomorrow {
           streakDateList.append(date)
        }
        tomorrow = addDay(tomorrow)
    }

    userDefaults.setObject(streakDateList.last, forKey: "lastStreakEndDate")
    return streakDateList.count
}

Я поместил вызов в viewDidLoad, но вы можете добавить его к кнопке, если хотите.

Ответ 2

ВСЕ-решение SQL

Обратите внимание, что это предполагает, что "Сегодня" считается днем ​​в строке для расчета. SQL возвращает как полосу с точки зрения дней, так и дату, предшествующую началу строки.

with max_zero_dt (zero_dt) as
(
  select max(dt)
    from ( 
         select max(isPresent) as isPresent, dt from check_tab group by dt
         union select 0, min(dt) from check_tab
         )
   where isPresent = 0
),
days_to_check (isPresent, dt) as
(
  select isPresent, dt
    from check_tab ct
    join max_zero_dt on ( ct.dt >= zero_dt )
),
missing_days (isPresent, dt) as
(
  select 0, date(dt, '-1 day') from days_to_check
  UNION
  select 0, date('now')
),
all_days_dups (isPresent, dt) as
(
  select isPresent, dt from days_to_check
  union
  select isPresent, dt from missing_days
),
all_days (isPresent, dt) as
(
  select max(isPresent) as isPresent, dt
  from all_days_dups
  group by dt
)
select cast(min(julianday('now') - julianday(dt)) as int) as day_streak
     , max(dt) as dt
  from all_days
 where isPresent = 0

Вот sqlfiddle для первого сценария: http://sqlfiddle.com/#!7/0f781/2

Вот sqlfiddle для второго сценария: http://sqlfiddle.com/#!7/155bb/2

ПРИМЕЧАНИЕ. О FIDDLES: они меняют даты по отношению к "Сегодня", чтобы они точно проверили полосу.

Вот как это работает:

  • АННОТАЦИЯ: Мы собираемся "заполнить" недостающие дни нулями, а затем проделаем простую проверку, чтобы увидеть, когда указана максимальная дата. Но мы должны учитывать, если на сегодня нет строк, и если строк с нулями нет.
  • max_zero_dt: содержит одну строку, содержащую последнюю явную дату 0 или самую раннюю дату минус 1 день. Это сокращение количества строк для последующих запросов.
  • days_to_check: это сокращенное количество строк для проверки на основе того, когда последнее 0.
  • missing_days: нам нужно заполнить недостающие дни, поэтому мы получаем список всех дней минус 1 день. Мы также добавляем сегодня, если для этого нет строк.
  • all_days_dups: Мы просто комбинируем days_to_check и missing_days.
  • all_days: Здесь мы получаем "max" isPresent для каждого дня, так что теперь у нас есть истинный список дней для поиска, зная, что не будет пробелов с 0 для isPresent.
  • Заключительный запрос: это простой расчет, обеспечивающий последовательность и дату начала.

Предположения:

  • ИМЯ ТАБЛИЦЫ: check_tab
  • Текущая дата должна быть в таблице с номером 1. В противном случае строка равна 0. Запрос может быть изменен, если это не так.
  • Если у одного дня есть как 0, так и 1 для isPresent, приоритет 1, а полоса может продолжаться до предыдущих дней.
  • В этих основных данных используется SQLite, и вышеупомянутый SQL, который работает в SQLite, также будет работать с базой данных Core Data.