Setting up a two week timetable in Swift - swift

NOTE: I am a new Swift programmer, a NOOB if you will.
I am creating a school timetable app just for personal use to practise my coding. However, our school operates on a two week time table system, with 10 days, labeled 1 through to ten. I am wondering if anyone had some ideas as to how I could work out whether the current date is day one or day nine or day 4. I know I could use if statements for the dates, but the would take a long time, and require manual input of the dates. How could I have the app keep count of what day it is, skipping weekends?
EDIT - I could maybe have 14 days, with days 6,7,13 and 14 empty.
FOR EXAMPLE:
The current date is OCT 4, this is day one. I would like the app to be able to work out what day of the timetable the current date is. This would then load the appropriate day (e.g. Subject, Teacher, Classroom). Day One is Monday, Two is Tuesday, Five is Friday, Six is Monday, 10 is Friday. Could I have some sort of rostering system?
I am sorry if the question is vague, please tell me if I need to clarify.
I have been working on a fix for weeks now, so I have decided to turn to help. Any guidance whatsoever would be much appreciated, as I am at a dead end!
Many thanks

The numbers that I'm plugging into this example probably don't match your requirements but consider this as a strategy. (In this case, using a 1-to-14 cycle. If you'd rather get 1-to-10 you can put in a subtraction and a different error to throw on the "bad" days.)
class CyclicDay {
enum CyclicDayError: ErrorType {
case InvalidStartDate
}
lazy var baseline: NSDate? = {
// Set up some start date that matches day 1
var components = NSDateComponents()
components.day = 6
components.month = 9
components.year = 2015
return NSCalendar.currentCalendar().dateFromComponents(components)
}()
func dayOfCycle(testDate: NSDate) throws -> Int {
if let start = baseline {
// Convert difference to days
let interval = testDate.timeIntervalSinceDate(start)
let days = interval / (60 * 60 * 24)
// Convert to value 1..14 to position in a 2-week cycle
return Int(days % 14) + 1
}
throw CyclicDayError.InvalidStartDate
}
}
// Test today
let cd = CyclicDay()
let day = try cd.dayOfCycle(NSDate())

Related

Is there an elegant way of determining if two dates are separated by a week of year in Swift?

If I have two dates, and I want to know if one of them falls on a week of year (as defined here) just prior to the other, how can I figure this out in Swift?
Assuming I don't care about time, one approach could be:
let calendar = Calendar.current
let startOfDate1 = calendar.startOfDay(for: date1)
let startOfDate2 = calendar.startOfDay(for: date2)
let date1WeekOfYear = calendar.dateComponents([.weekOfYear], from: startOfDate1).weekOfYear!
let date2WeekOfYear = calendar.dateComponents([.weekOfYear], from: startOfDate2).weekOfYear!
if (date1WeekOfYear - date2WeekOfYear) == 1 {
// do some stuff
}
This works except in the case where date1 falls within the first week of the year, and date2 falls within the last week of the prior year. Do I really have to also add in other logic to check the situation where the year components are different, and account for varying lengths of years in weeks (most have 52 weeks, some have 53), or is there a more elegant way to handle this?
Please note that I'm not interested in checking if the two dates are within 7 days of each other. It's possible that the two days are within 2 days of each other, but fall within different weeks of the year.
Thanks in advance.
Here's a way I found:
You do need to get the year, but Calendar can still do the calculation for you. You just need to call a different overload of the method from the one in Rob Napier's answer. You need the overload that accepts DateComponents:
let calendar = Calendar.current
let start = Date(timeIntervalSince1970: 1577375330) // 2019-12-26
let end = Date(timeIntervalSince1970: 1577893730) // 2020-01-01
// remember it's yearForWeekOfYear, not just "year"
let startDateComponents = calendar.dateComponents([.yearForWeekOfYear, .weekOfYear], from: start)
let endDateComponents = calendar.dateComponents([.yearForWeekOfYear, .weekOfYear], from: end)
let interval = calendar.dateComponents([.weekOfYear], from: startDateComponents, to: endDateComponents).weekOfYear!
print(interval) // 1
My speculation of why this works but the overload taking Dates doesn't:
The overload that takes Dates will first get the difference between the two dates, which represent instants, and then convert that time interval to the specified set of DateComponents. Note that it's probably converting a TimeInterval to DateComponents, which is why it can't calculate week boundaries and such.
The overload that takes DateComponents can calculate week boundaries because that information is given as its parameters.
You can add 7 days to one date (allowing for year roll-over), and determine the difference in week-numbers between the two dates to be zero weeks rather then one week.
Have a look at the 2019 end of year
> var cal = Calendar.current
> let df = DateFormatter(); df.dateFormat = "yyyy-MM-dd"
> var d1 = df.date(from: "2019-12-28")!;
> var w1=cal.component(.weekOfYear, from:d1)
w1: Int = 52
This tells us Dec 28 still fell in week 52.
> var w2=cal.component(.weekOfYear, from: df.date(from: "2019-12-29")!)
w2: Int = 1
And this tells us Dec 29 fell in week 1 of 2020. These two dates are "1 week apart", as humans can easily tell. Determining this by calculation can be a bit harder, even if you use modulo arithmetic: the wrap-around is irregular, at 52 for some years, and 53 for others, as the OP hinted at. (2006, 2012, 2017 and 2023 are all 53-week years)
To determine that they are 1 week apart by calculation, first move up the earlier date by 7 days, i.e. 2019-12-28 plus 7 days:
> var d1_7 = cal.date(byAdding: .day, value:7, to:d1)!;
d1_7: Date = 2020-01-04 08:00:00 UTC
> var w1_7 = cal.component(.weekOfYear,from:d1_7)
w1_7: Int = 1
Its week number is 1, equalling that of 2019-12-29. We conclude that 2019-12-28 and 2019-12-29 are 1 week apart.

Get the difference between two dates in calendar days

can anyone explain how can I get difference between two dates in calendar days, not in whole 24-hour periods. There is a good solution here: Getting the difference between two NSDates in (months/days/hours/minutes/seconds) -- but it doesn't work for me as, for example, it gives the difference between 23:00 today and 1:30 tomorrow as 0 days despite of calendar dates differ already by 1.
Use the normal way to calculate the difference in days with one change - convert both of your dates to midnight.
let d1 = ... // your first date
let d2 = ... // your second date
let cal = Calendar.current
let days = cal.dateComponents([.day], from: cal.startOfDay(for: d1), to: cal.startOfDay(for: d2)).day!
This will give an answer of 1 for "yesterday at 23:00" and "today at 1:30", for example.

Swift - Get number of years between two dates in double format similar to YEARFRAC function is MS Excel [duplicate]

How can I get the exact difference (in decimal) between 2 values of NSDate.
Eg. Jan 15 2016 to Jul 15 2017 = 1.5 Years.
I can use something like:
NSCalendar.currentCalendar().components(NSCalendarUnit.CalendarUnitYear, fromDate: date1, toDate: date1, options: nil).year
but this gives me absolute values. i.e. for above example it would give me 1 Year. Is it possible to get exact values correct to at least a few decimal places?
The terms you've used here are misleading. When you say "absolute" you mean "integral." And when you say "exact" you mean "within some desired precision."
Let's say the precision you wanted was 2 decimal places, so we'd need to measure a year to 1%. That's larger than a day, so tracking days is sufficient. If you needed more precision, then you could expand this technique, but if you push it too far, "year" gets more tricky, and you have to start asking what you mean by "a year."
Avoid asking this question when you can. Many answers here say things like "there are 365.25 days in a year." But try adding "365.25 * 24 hours" to "right now" and see if you get "the same date and time next year." While it may seem correct "on average," it is actually wrong 100% of the time for calendar dates. (It works out here because it's within 1%, but so would 365, 366, or even 363.)
We avoid this madness by saying "1% is close enough for this problem."
// What calendar do you *really* mean here? The user's current calendar,
// or the Gregorian calendar? The below code should work for any calendar,
// because every calendar's year is made up of some number of days, but it's
// worth considering if you really mean (and are testing) arbitrary calendars.
// If you mean "Gregorian," then use NSCalendar(identifier: NSCalendarIdentifierGregorian)!
let calendar = NSCalendar.currentCalendar()
// Determine how many integral days are between the dates
let diff = calendar.components(.Day, fromDate: date1, toDate: date2, options: [])
// Determine how many days are in a year. If you really meant "Gregorian" above, and
// so used calendarWithIdentifer rather than currentCalendar, you can estimate 365 here.
// Being within one day is inside the noise floor of 1%.
// Yes, this is harder than you'd think. This is based on MartinR's code: http://stackoverflow.com/a/16812482/97337
var startOfYear: NSDate? = nil
var lengthOfYear = NSTimeInterval(0)
calendar.rangeOfUnit(.Year, startDate: &startOfYear, interval: &lengthOfYear, forDate: date1)
let endOfYear = startOfYear!.dateByAddingTimeInterval(lengthOfYear)
let daysInYear = calendar.components(.Day, fromDate: startOfYear!, toDate: endOfYear, options: []).day
// Divide
let fracDiff = Double(diff.day) / Double(daysInYear)
That said, in most cases you shouldn't be doing this. Since iOS 8, the preferred tool is NSDateComponentsFormatter. You won't get this precise format (i.e. fractional years), but you'll get a nicely localized result that takes most issues into account across different cultures.
let formatter = NSDateComponentsFormatter()
formatter.unitsStyle = .Full
formatter.includesApproximationPhrase = true
formatter.allowedUnits = [.Year, .Month]
formatter.allowsFractionalUnits = true
formatter.stringFromDate(date1, toDate: date2)
// About 1 year, 6 months
Since you mentioned that your goal is something you can display to users as a meaningful indication of the time between two dates, you might find it easier to use NSDateComponentsFormatter. For example:
let dateStr1 = "Jan 15 2016"
let dateStr2 = "Jul 15 2017"
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "MMM dd yyyy"
if let date1 = dateFormatter.dateFromString(dateStr1),
let date2 = dateFormatter.dateFromString(dateStr2) {
let dateComponentsFormatter = NSDateComponentsFormatter()
dateComponentsFormatter.allowedUnits = [.Year, .Month]
dateComponentsFormatter.unitsStyle = .Full
let difference = dateComponentsFormatter.stringFromDate(date1, toDate: date2)
}
This gives you a string that reads "1 year, 6 months". It's not exactly what you specified as your goal, but it's a clear indication for users and avoids a lot of complexity. There's a property on NSDateComponentsFormatter called allowsFractionalUnits that's supposed to lead to results like "1.5 years", but it doesn't seem to work right now. (Even if you limit the allowedUnits to only .Year, you still don't get a fractional year. I'm off to file a bug with Apple...). You can tweak allowedUnits to get whatever granularity you like, and use includesApproximationPhrase to have the class add a localized version of "About..." to the resulting string if it's not precise. If you have some flexibility in your final format, this would be a really good solution.
There isn't a perfect answer to this question. Different years are slightly different lengths. You have to make some assumptions.
If you assume 365.2425 days per year, with each day having 24 hours, then the calculation is trivial:
let secondsPerYear: NSTimeInterval = NSTimeInterval(365.2425 * 24 * 60 * 60)
let secondsBetweenDates =
date2.timeIntervalSinceReferenceDate - date1.timeIntervalSinceReferenceDate;
let yearsBetweenDates = secondsBetweenDates / secondPerYear
But there are lots of edge cases and weirdness to deal with. Because of leap years, some years have 365 days, and some have 366. Then there's leap seconds.
If you get rid of months in #CodeDifferent's answer then you'll get an answer that allows for leap days between the dates.
But, as Code Different pointed out, his answer as written actually gives answers that seem more accurate, even though they are not. (A difference of 3 months will always yield .25 years, and will ignore longer/shorter months. Is that the right thing to do? Depends on your goal and your assumptions.)
According to NASA, there are 365.2422 days per year on average. Here, I round that up to 365.25 days per year:
let components = NSCalendar.currentCalendar().components([.Year, .Month, .Day], fromDate: fromDate, toDate: toDate, options: [])
var totalYears = Double(components.year)
totalYears += Double(components.month) / 12.0
totalYears += Double(components.day) / 365.25
Obviously, this depends on your assumptions. If you want to count of leap days between fromDate and toDate, it will be more complicated.
Some sample outputs:
From date To date Total Years
------------ ------------ ------------
Jan 15, 2016 Jul 15, 2017 1.5
Jan 15, 2016 Apr 14, 2016 0.25
Jan 15, 2016 Aug 15, 2017 1.5833
Jan 15, 2016 Jan 14, 2018 1.9988

Codename One days left in current week or month

I am building a calendar application that needs to calculate the remaining days of (1) the current week and (2) the current month.
What I have tried was using the java.util.Calendar API, but it seems not to be fully supported.
int days = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
How would I do this in CN1?
Many thanks in advance.
We would love to add support for JSR310 in the future which would probably solve this in a more elegant way. This is something that can be done as a cn1lib without modifying Codename One but would require some work...
A bit of a hack to do this is:
int month = cal.get(Calendar.MONTH);
cal.set(Calendar.DAY_OF_MONTH, 1);
if(month == cal.DECEMBER) {
cal.set(Calendar.YEAR, cal.get(Calendar.YEAR) + 1);
cal.set(Calendar.MONTH, Calendar.JANUARY);
} else {
cal.set(Calendar.MONTH, month + 1);
}
int day = 24 * 60 * 60000;
cal.setTime(new Date(cal.getTime().getTime() - day));
I don't know CN1, but you can calculate the values using a subtraction between the max and the current value:
(1) cal.getActualMaximum(Calendar.DAY_OF_WEEK) - cal.get(Calendar.DAY_OF_WEEK)
(2) cal.getActualMaximum(Calendar.DAY_OF_MONTH) - cal.get(Calendar.DAY_OF_MONTH)
For your notification: The first day of the week is sunday in the api of calendar. When your week should start with monday you have to reduce the offset by 1.

java.time.Instant.plus(long amountToAdd, TemporalUnit unit) Unsupported unit

I trying to add few years to current time. My code looks like:
// ten yeas ago
int backYears = 10;
Instant instant = ChronoUnit.YEARS.addTo(Instant.now(), -backYears);
But I got an exception:
java.time.temporal.UnsupportedTemporalTypeException: Unsupported unit: Years
at java.time.Instant.plus(Instant.java:862)
When I opened the method Instant.plus I see the following:
#Override
public Instant plus(long amountToAdd, TemporalUnit unit) {
if (unit instanceof ChronoUnit) {
switch ((ChronoUnit) unit) {
case NANOS: return plusNanos(amountToAdd);
case MICROS: return plus(amountToAdd / 1000_000, (amountToAdd % 1000_000) * 1000);
case MILLIS: return plusMillis(amountToAdd);
case SECONDS: return plusSeconds(amountToAdd);
case MINUTES: return plusSeconds(Math.multiplyExact(amountToAdd, SECONDS_PER_MINUTE));
case HOURS: return plusSeconds(Math.multiplyExact(amountToAdd, SECONDS_PER_HOUR));
case HALF_DAYS: return plusSeconds(Math.multiplyExact(amountToAdd, SECONDS_PER_DAY / 2));
case DAYS: return plusSeconds(Math.multiplyExact(amountToAdd, SECONDS_PER_DAY));
}
throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit);
}
return unit.addTo(this, amountToAdd);
}
As you can see MONTHS and YEARS are unsupported. But why?
With an old java.util.Calendar I can do that easily:
Calendar c = Calendar.getInstance();
c.setTime(date);
c.add(Calendar.YEAR, amount);
return c.getTime();
The only one reason what I guess is that we don't know how many days in a month and year because of leap day 29 Feb.
But to be honest we also have a leap second.
Thus I think that this is a bug and all ChronoUnits should be supported.
The only one question is: do we need to take in account leap second and leap day.
As for my needs it's okay just to assume that month has 30 days and year 365.
I don't need to make something like Calendar.roll() but this can satisfy me too.
Let’s try something out. I am taking an instant as ZonedDateTime and subtracting 10 years in different time zones.
OffsetDateTime origin = OffsetDateTime.of(2018, 3, 1, 0, 0, 0, 0, ZoneOffset.UTC);
Instant originInstant = origin.toInstant();
Instant tenYearsBackKyiv = origin.atZoneSameInstant(ZoneId.of("Europe/Kiev"))
.minusYears(10)
.toInstant();
long hoursSubtractedKyiv = ChronoUnit.HOURS.between(tenYearsBackKyiv, originInstant);
System.out.println("Hours subtracted in Київ: " + hoursSubtractedKyiv);
Instant tenYearsBackSaoPaulo = origin.atZoneSameInstant(ZoneId.of("America/Sao_Paulo"))
.minusYears(10)
.toInstant();
long hoursSubtractedSaoPaulo = ChronoUnit.HOURS.between(tenYearsBackSaoPaulo, originInstant);
System.out.println("Hours subtracted in São Paulo: " + hoursSubtractedSaoPaulo);
The output is:
Hours subtracted in Київ: 87648
Hours subtracted in São Paulo: 87672
As you can see, 24 hours more (1 day more) is subtracted in São Paulo compared to Київ (Kyiv, Kiev). You may already have figured out that it’s because there we pass from 1 March to 29 February three times in leap years, in Київ only twice.
The old and now outdated Calendar class always had a time zone in it, so knew in which time zone to subtract years (another thing is it was happy to give you a result even in situations where it was unclear which result you wanted). The modern classes ZonedDateTime, OffsetDateTime and LocalDateTime can do the same. So use them. An Instant conceptually doesn’t have a time zone, so refuses to do operations that depend on time zone (I know it’s implemented using UTC, but we should regard this as an irrelevant implementation detail, not as a part of the specification of the interface to the class).
Neither the old nor the modern classes take leap seoncds into account, and you are right, only therefore can an Instant add and subtract days, hours and minutes.