Как рассчитать полосу дней в основных данных?
Мне нужно подсчитать последнюю полосу дней, но не могу понять, как это сделать.
Например, я получил основные данные следующим образом:
|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 в coredatap >
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.