Skip to content

Latest commit

 

History

History
568 lines (397 loc) · 30.4 KB

3.Manipulate_Date.md

File metadata and controls

568 lines (397 loc) · 30.4 KB

Manipulate & Derivate Dates

Dates can be manipulated as you need by using classic math operators and readable time units.

SwiftDate allows you to use numbers to work with time components in dates. By extending the Int type it defines a list of time units:

  • nanoseconds
  • seconds
  • minutes
  • days
  • weeks
  • months
  • quarters
  • years

You can use a value followed by one of these unit specifications to add or remove the component from a date.

So, for example, you can produce a date which can be a mix of intuitive math operations:

let oneYearAhead = DateInRegion() + 1.years
let someMinutesAgo = date1 - 2.minutes
let fancyDate = date1 + 3.hours - 5.minutes + 1.weeks

IMPORTANT NOTE: These values are converted automatically to DateComponents evaluated in the same context of the target Date or DateInRegion's calendar.

Another way to add time components to a date is to use the dateByAdding() function:

func dateByAdding(_ count: Int, _ component: Calendar.Component) -> DateInRegion

takes two arguments:

  • count | Int value to add (maybe negative)
  • component | Calendar.Component the time unit components to add.

IMPORTANT NOTE: New date is evaluated in the same context of the target Date or DateInRegion's calendar.

let nextDate = dateA.dateByAdding(5, .years) // 5 years from dateA

^ Top

With SwiftDate you have several convenience properties to inspect each datetime unit of a date, both for Date and DateInRegion.

These properties are strictly correlated to the date's calendar (and some also with locale): if you are manipulating a DateInRegion remember these properties return values in the context of the associated region attributes (Locale, TimeZone and Calendar).

IMPORTANT NOTE: If you are working with plain Date properties uses as reference the currently set SwiftDate.defaultRegion which, unless you modify it, is set to Gregorian/UTC/Device's Language.

This a complete list of the properties you can inspect for a date object:

PROPERTY DESCRIPTION
year current year number
month current month number (1 is January)
monthName(_ style: SymbolFormatStyle) name of the current month with given style (uses region's locale)
monthDays number of the days into the current month
day day number in the current month
dayOfYear day of the year
ordinalDay The number of day in ordinal style format for the receiver in current locale.
hour current hour
nearestHour nearest rounded hour
minute current minute
second current second
nanosecond current nanosecond
msInDay "Milliseconds in day of the receiver. This field behaves exactly like a composite of all time-related fields, not including the zone fields.As such, it also reflects discontinuities of those fields on DST transition days.
On a day of DST onset, it will jump forward. On a day of DST cessation, it will jump backward.
This reflects the fact that is must be combined with the offset field to obtain a unique local time value."
weekday Weekday unit of the receiver. The weekday units are the numbers 1-N (where for the Gregorian calendar N=7 and 1 is Sunday).
weekdayName(_ style: SymbolFormatStyle) Name of the weekday expressed in given format style.
weekOfYear Week of a year of the receiver.
weekOfMonth Week of a month of the receiver.
weekdayOrdinal Ordinal position within the month unit of the corresponding weekday unit.
firstDayOfWeek Return the first day number of the week where the receiver date is located
lastDayOfWeek Return the last day number of the week where the receiver date is located.
yearForWeekOfYear Relative year for a week within a year calendar unit.
quarter Quarter value of the receiver.
quarterName(_ style: SymbolFormatStyle) Quarter name expressed in given format style
era Era value of the receiver.
eraName(_ style: SymbolFormatStyle) Name of the era expressed in given format style
DSTOffset The current daylight saving time offset of the represented date

Several other properties defines additional attributes of the date:

  • date | Date: return the absolute date instance (for Date it just return itself)
  • region | Region: return the region associated with date (for Date it return SwiftDate.defaultRegion).
  • calendar | Calendar: return the associated calendar
  • dateComponents | DateComponents: return all the date components of the date in the context of its associated region.

^ Top

You can get the interval between two dates and express it in form of time units easily with SwiftDate.

The .getInterval(toDate:component:) function allows you to express the difference between to dates in form of a passed time component.

func getInterval(toDate: DateInRegion?, component: Calendar.Component) -> Int64

Takes two arguments:

  • toDate | DateInRegion: reference date to compare against
  • component | Calendar.Component: the component in which the difference must be returned.

Examples:

let dateA = DateInRegion("2017-07-22 00:00:00", format: format, region: rome)!
let dateB = DateInRegion("2017-07-23 12:00:00", format: format, region: rome)!

let hours = dateA.getInterval(toDate: dateB, component: .hour) // 36 hours
let days = dateA.getInterval(toDate: dateB, component: .day) // 1 day

^ Top

DateInRegion can be converted easily to another region just using .convertTo(region:) or .convertTo(calendar: timezone:locale:) functions.

  • convertTo(region:) convert the receiver date to another region. Region may include a different time zone for example, or a locale.
  • convertTo(calendar:timezone:locale:) allows to convert the receiver date instance to a specific calendar/timezone/locale. All parameters are optional and only non-nil parameters alter the final region. For a nil param the current receiver's region attribute is kept.

Examples:

// Create a date in NY: "2001-09-11 12:00:05"
let regionNY = Region(calendar: Calendars.gregorian, zone: Zones.americaNewYork, locale: Locales.english)
let dateInNY = DateInRegion(components: {
	$0.year = 2001
	$0.month = 9
	$0.day = 11
	$0.hour = 12
	$0.minute = 0
	$0.second = 5
}, region: regionNY)

// Convert to GMT timezone (and locale)
let inGMT = dateInNY.convertTo(region: Region.UTC) // date is now: "2001-09-11 16:00:05"

^ Top

.dateRoundedAt() function allows to round a given date time to the passed style: off, up or down.

func dateRoundedAt(at style: RoundDateMode) -> DateInRegion

takes a single argument:

  • style | RoundDateMode: define the style of rounding: it can be off, ceil (up) or floor (down). RoundDateMode defines a list of convenience rounding to 5,10,30 minutes but you can use .toMins(), .toCeilMins() or .toFloorMins() and pass your own rounding values expressed in minutes.

Example:

let rome = Region(...)
let format = "yyyy-MM-dd HH:mm:ss"

// Round down 10mins
let date =  DateInRegion("2017-07-22 00:03:50", format: format, region: rome)
let r10min = date.dateRoundedAt(.to10Mins) // 2017-07-22T00:00:00+02:00

// Round up 30min
let r30min = "2015-01-24 15:07:20".toDate(format: format, region: rome).dateRoundedAt(.toCeil30Mins) // 2015-01-24T15:30:00+01:00

^ Top

Sometimes you may need to truncate a date by zeroing all values below certain time unit. .dateTruncated(from:) and .dateTruncated(to:) functions can be used for this scope.

Truncating From

It creates a new instance by truncating the components starting from given components down the granurality.

func dateTruncated(from component: Calendar.Component) -> DateInRegion?

Truncated At

It creates a new instance by truncating all passed components.

func dateTruncated(at components: [Calendar.Component]) -> DateInRegion?

Examples:

let rome = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.italian)
let date = "2017-07-22 15:03:50".toDate("yyyy-MM-dd HH:mm:ss", region: rome)

let truncatedTime = date.dateTruncated(from: .hour) // 2017-07-22T00:00:00+02:00
let truncatedCmps = date.dateTruncated(at: [.month, .day, .minute]) // 2017-01-01T15:00:50+01:00

^ Top

Sometimes you may need to alter time in a specified date. SwiftDate allows you to perform this using .dateBySet(hour:min:secs:options:) function.

func dateBySet(hour: Int?, min: Int?, secs: Int?, ms: Int?, options: TimeAlterOptions = TimeAlterOptions()) -> DateInRegion?

takes five arguments:

  • hour | Int?: set a non nil value to change the hour value
  • min | Int?: set a non nil value to change the minute value
  • secs | Int?: set a non nil value to change the seconds value
  • ms | Int?: set a non nil value to change the milliseconds value
  • options: TimeCalculationOptions: allows to specify calculation options attributes (usually you don't need to set it).

Example:

let rome = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.italian)
let date = DateInRegion("2010-01-01 00:00:00", format: "yyyy-MM-dd HH:mm:ss", region: rome)!

let alteredDate = date.dateBySet(hour: 20, min: 13, secs: 15) // 2010-01-01 20:13:15

^ Top

SwiftDate allows you to return a new date representing the date calculated by setting specific components of a given date to given values, while trying to keep lower components the same (altering more components at the same time may result in different-than-expected results, this is because lower components maybe need to be recalculated).

dateBySet(_ components: [Calendar.Component: Int]) -> DateInRegion?

Takes one argument:

  • components | [Calendar.Component: Int] a dictionary of key/values for each component you want to alter.

NOTE: While the algorithm try to keep lower compoments the same, the resulting date may differ from what expected when you alter more than a component a time.

Example:

let _ = date.dateBySet([.month: 1, .day: 1, .hour: 9, .minute: 26, .second: 0])

^ Top

Sometimes you may need to generate a related date from a specified instance; maybe the next sunday, the first day of the next week or the start datetime of a date. SwiftDate includes 20+ different "interesting" dates you can obtain by calling .dateAt() function from any Date or DateInRegion instance.

func dateAt(_ type: DateRelatedType) -> DateInRegion

takes just an argument which defines the type of date you want to obtain starting from the receiver date. DateRelatedType is an enum which has the following options:

  • startOfDay
  • endOfDay
  • startOfWeek
  • endOfWeek
  • startOfMonth
  • endOfMonth
  • tomorrow
  • tomorrowAtStart
  • yesterday
  • yesterdayAtStart
  • nearestMinute(minute:Int)
  • nearestHour(hour:Int)
  • nextWeekday(_: WeekDay)
  • nextDSTDate
  • prevMonth
  • nextMonth
  • prevWeek
  • nextWeek
  • nextYear
  • prevYear

CONTRIBUTE! Have you a new related date you want to be part of this list? Create a new PR with the code and unit tests and we'll be happy to add it to the list!

Examples:

// Return today's datetime at 00:00:00
let _ = DateInRegion().dateAt(.startOfDay)
// Return today's datetime at 23:59:59
let _ = DateInRegion().dateAt(.endOfDay)
// Return the date at the start of this week
let _ = DateInRegion().dateAt(.startOfWeek)
// Return current time tomorrow
let _ = DateInRegion().dateAt(.tomorrow)
// Return the next sunday from specified date
let _ = date.dateAt(.nextWeekday(.sunday))
// and so on...

^ Top

Two functions called .dateAtStartOf() and .dateAtEndOf() allows you to get the related date from a Date/DateInRegion instance moved at the start or end of the specified component.

You can, for example, get the date at the start of the week, or the year, or a the end of the quarter.

  • func dateAtStartOf(_ unit: Calendar.Component) -> DateInRegion: return the date at the start of the specified time component.
  • func dateAtEndOf(_ unit: Calendar.Component) -> DateInRegion: return the date at the end of the specified time component.

Examples:

// Return today's date at 23:59:59
let _ = DateInRegion().dateAtEndOf(.day)
// Return the first day's date of the month described in date1
let _ = date1.dateAtStartOf(.month)
// Return the first day of this year at 00:00:00
let _ = DateInRegion().dateAtStartOf(.year)

^ Top

3.10 - Enumerate Dates

Dates enumeration function allows you to generate a list of dates in a closed date intervals incrementing date components by a fixed or variable interval at each new date.

  • VARIABLE INCREMENT static func enumerateDates(from startDate: DateInRegion, to endDate: DateInRegion, increment: ((DateInRegion) -> (DateComponents))) -> [DateInRegion]
  • FIXED INCREMENT static func enumerateDates(from startDate: DateInRegion, to endDate: DateInRegion, increment: DateComponents) -> [DateInRegion]

Both of these functions are pretty similar; it takes:

  • startDate | DateInRegion: the initial date of the enumeration (it will be item #0 of the final array)
  • endDate | DateInRegion: the upper bound limit date. The last item of the array is evaluated automatically and maybe not equal to endDate.
  • increment | DateComponents or Function: for fixed increment it takes a DateComponents instance which define the increment of time components at each new date; for variable it provides the latest date generated and require as return object of the closure the increment as DateComponents.

Examples:

let increment = DateComponents.create {
  $0.hour = 1
  $0.minute = 30
}
// Generate an array of dates where the first item is fromDate
// and each new date is incremented by 1h30m from the previous.
// Latest date is < endDate (but maybe not the same).
let dates = DateInRegion.enumerateDates(from: fromDate, to: toDate, increment: increment)

^ Top

The following function allows you to enumerate all dates of a particular weekdays which are in a determinated date range. Both Date and DateInRegion implements this static function:

public static func datesForWeekday(_ weekday: WeekDay, from startDate: Date, to endDate: Date, region: Region = SwiftDate.defaultRegion) -> [Date]

Another shortcut method allows you to get weekdays in a particular month of a year:

public static func datesForWeekday(_ weekday: WeekDay, inMonth month: Int, ofYear year: Int, region: Region = SwiftDate.defaultRegion) -> [Date]

Examples:

// Get all mondays in Jan 2019
let mondaysInJan2019 = Date.datesForWeekday(.monday, inMonth: 1, ofYear: 2019)

// You will get 4 results
// - 2019-01-07T00:00:00Z
// - 2019-01-14T00:00:00Z
// - 2019-01-21T00:00:00Z
// - 2019-01-28T00:00:00Z

// Get all fridays between May 27, 2019 and June 8, 2019
let fromDate = Date(year: 2019, month: 5, day: 27, hour: 0, minute: 0)
let toDate = Date(year: 2019, month: 6, day: 8, hour: 0, minute: 0)
let fridaysInJunePartial = Date.datesForWeekday(.friday, from: fromDate, to: toDate, region: Region.UTC)

// You will get 2 results:
// - 2019-05-31T00:00:00Z
// - 2019-06-07T00:00:00Z

SwiftDate exposes a set of functions to generate a random date or array of random dates in a bounds. There are several functions to perform this operation:

Single Random Date

  • randomDate(region:) generate a random date into the specified region.
  • randomDate(withinDaysBeforeToday:region:) generate a random date between now and a specified amount days ealier into the specified region.
  • randomDate(between:and:region:) generate a random date into the specified region between two given date bounds.

Array of Random Dates

  • randomDates(count:between:and:region:) return count random generated dates into the specified region between two given date bounds.

IMPORTANT: For all of thes function if you don't specify the region, SwiftDate.defaultRegion is used instead.

Examples:

// Generate random dates array in limit
let now = DateInRegion()
let someYearsAgo = (upperBound - 3.years)
// generate 40 random dates between 3 years ago today and today
let randomDates = DateInRegion.randomDates(count: 40, between: someYearsAgo, and: now)

// Generate a random date between now and 7 days ago
let rome: Region = ...
let aDate = DateInRegion.randomDate(withinDaysBeforeToday: 7, region: rome) 

^ Top

Two conveniences function allows you to sort an array of dates by newest or oldest. Naming is pretty simple:

  • sortedByOldest(): sort dates by the oldest
  • sortedByNewest(): sort dates by the newest

Two other functions allows you to get the oldest/newest date in array:

  • oldestIn(): return the oldest date in array
  • newestIn(): return the newest date in array

Examples:

let arrayOfDates: [DateInRegion] = [...]
// ordered array with the newest on top
let orderedByNewest = DateInRegion.sortedByNewest(list: datesArray)
// get the oldest date of the list
let oldestDate = DateInRegion.oldestIn(list: arrayOfDates)

^ Top

In order to get the next weekday preserving smaller components (hour, minute, seconds) you can use the nextWeekday() function:

let date1 = DateInRegion("2019-05-11 00:00:00", format: dateFormat, region: regionRome)!
let nextFriday = date1.nextWeekday(.friday) // 2019-05-17T00:00:00+02:00

^ Top

To returns the date at the given week number and week day preserving smaller components (hour, minute, seconds) you can use the dateAt(weekdayOrdinal:weekday:monthNumber:yearNumber:) function:

let date = DateInRegion("2019-05-11 00:00:00", format: dateFormat, region: regionRome)!
let _ = date1.dateAt(weekdayOrdinal: 3, weekday: .friday, monthNumber: date1.month + 1) // 2019-06-21T00:00:00+02:00

To get differences between two dates using given time components you can choose between two methods:

  • difference(in:from:): Returns the difference in the calendar component given (like day, month or year) with respect to the other date as a positive integer.
  • differences(in:from:): Returns the differences in the calendar components given (like day, month and year) with respect to the other date as dictionary with the calendar component as the key and the diffrence as a positive integer as the value.

Example:

let format = "yyyy-MM-dd HH:mm"
let d1 = "2019-01-01 00:00".toDate(format, region: Region.current)
let d2 = "2019-01-10 14:20".toDate(format, region: Region.current)
let diff = d1!.differences(in: [.day, .hour], from: d2!) // 9 days, 14 hours

^ Top

You can create a derivated date from a receiver by preserving small components (hour, minute and second) by using the: dateAt(dayOfMonth:monthNumber:yearNumber:) method:

let date = "2019-01-01 00:00".toDate("yyyy-MM-dd HH:mm", region: Region.current)! // Rome zone
let onMarch = date.dateAt(dayOfMonth: 31, monthNumber: 3) // 2019-03-31T00:00:00+01:00

^ Top

Returns the date after given number of weeks on the given day of week using dateAfter(weeks:on:) function.

let d = "2019-09-14 00:00".toDate("yyyy-MM-dd HH:mm", region: Region.current)! // Rome Region
let nextTwoMondays = d.dateAfter(weeks: 2, on: .monday) // 2019-09-23T00:00:00+02:00

^ Top

There are 3 functions to get the next date starting from a receiver date:

  • nextWeekday(): Returns the next weekday preserving smaller components
  • nextWeekday(:withWeekOfMonth:andMonthNumber): Returns next date with the given weekday and the given week number
  • next(dayOfMonth:monthOfYear:): Returns the next day of month preserving smaller components (hour, minute, seconds)

Next Chapter: Compare Dates