• Stars
    star
    136
  • Rank 267,670 (Top 6 %)
  • Language
    Swift
  • License
    MIT License
  • Created almost 4 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

๐Ÿ“† Create dates and date components easily (e.g. "first Thursday of the next month")

DateBuilder

DateBuilder allows you to create Date and DateComponents instances with ease in a visual and declarative manner. With DateBuilder, it's very trivial to define dates from as simple as "tomorrow at 9pm" or as complex as "first fridays for the next 24 months, at random times between 3pm and 7pm".

Maintainer: @dreymonde

As of now, DateBuilder is in beta. Some APIs might be changed between releases.

DateBuilder is a stand-alone part of NiceNotifications, a framework that radically simplifies local notifications, from content to permissions.

Usage

import DateBuilder

Today()
    .at(hour: 20, minute: 15)
    .dateComponents() // year: 2021, month: 1, day: 31, hour: 20, minute: 15

NextWeek()
    .weekday(.saturday)
    .at(hour: 18, minute: 50)
    .dateComponents() // DateComponents

EveryWeek(forWeeks: 10, starting: .thisWeek)
    .weekendStartDay
    .at(hour: 9, minute: 00)
    .dates() // [Date]
    
ExactlyAt(account.createdAt)
    .addingDays(15)
    .date() // Date
    
WeekOf(account.createdAt)
    .addingWeeks(1)
    .lastDay
    .at(hour: 10, minute: 00)
    .dateComponents() // DateComponents

EveryMonth(forMonths: 12, starting: .thisMonth)
    .lastDay
    .at(hour: 23, minute: 50)
    .dateComponents() // [DateComponents]

NextYear().addingYears(2)
    .firstMonth.addingMonths(3) // April (in Gregorian)
    .first(.thursday)
    .dateComponents() // year: 2024, month: 4, day: 4

ExactDay(year: 2020, month: 10, day: 5)
    .at(hour: 10, minute: 15)
    .date() // Date

ExactYear(year: 2020)
    .lastMonth
    .lastDay
    .dateComponents()

Guide

Anatomy of a date builder

Every DateBuilder expression ends on a specific day (or a set of days if you use functions like EveryDay/EveryMonth/etc.). First you specify your expression down to a day, and then define the time of day by calling at(hour:minute:) function. For example:

NextWeek()
    .firstDay
    .at(hour: 10, minute: 15)

Once you have your at expression, your date is now fully resolved. You can get a ready-to-use Date or DateComponents instance by calling .date() or .dateComponents().

Slightly more complicated example would be:

let dateComponents = NextYear()
    .firstMonth.addingMonths(3)
    .first(.thursday)
    .at(hour: 21, minute: 00)
    .dateComponents()

So we start on the scale of years, then we notch it down to the scale of months, and then we finally get the specific day, which in this case will be the first thursday of a 4th month of the next year. After that, we finalize our query by using the at function.

Available functions

Day

// top-level
Today()
Tomorrow()
DayOf(account.createdAt)
ExactDay(year: 2021, month: 1, day: 26)
AddingDays(15, to: .today)
AddingDays(15, to: .dayOf(account.createdAt))
EveryDay(forDays: 100, starting: .tomorrow)
EveryDay(forDays: 100, starting: .dayOf(account.createdAt))

// instance
Today()
--->.addingDays(10)

Week

NOTE: the start and end of the week is determined by the currently set Calendar and its Locale. To learn how to customize the calendar object used for DateBuilder queries, see "Customizing the Calendar / Locale / Timezone" section below

// top-level
ThisWeek()
NextWeek()
WeekOf(account.createdAt)
WeekOf(Today()) // use any `DateBuilder.Day` instance here
AddingWeeks(5, to: .thisWeek)
EveryWeek(forWeeks: 10, starting: .nextWeek)

// instance
ThisWeek()
--->.addingWeeks(10) // Week
--->.firstDay // Day
--->.lastDay // Day
--->.allDays // [Day]
--->.weekday(.thursday) // Day
--->.weekendStartDay // Day
--->.weekendEndDay // Day

Month

// top-level
ThisMonth()
NextMonth()
MonthOf(account.createdAt)
MonthOf(Today()) // use any `DateBuilder.Day` instance here
ExactMonth(year: 2021, month: 03)
AddingMonths(3, to: .thisMonth)
EveryMonth(forMonths: 5, starting: .monthOf(account.createdAt))

// instance
ThisMonth()
--->.addingMonths(5) // Month
--->.firstDay // Day
--->.lastDay // Day
--->.allDays // [Day]
--->.first(.saturday) // Day
--->.weekday(.third, .friday) // Day

Year

// top-level
ThisYear()
NextYear()
YearOf(account.createdAt)
YearOf(Tomorrow()) // use any `DateBuilder.Day` instance here
YearOf(NextMonth()) // use any `DateBuilder.Month` instance here
ExactYear(year: 2022)
AddingYears(1, to: ThisYear())
EveryYear(forYears: 100, starting: .thisYear)

// instance
ThisYear()
--->.addingYears(1) // Year
--->.firstMonth // Month
--->.lastMonth // Month
--->.allMonths // [Month]

Resolving the date

Today()
--->.at(hour: 10, minute: 15)
--->.at(hour: 19, minute: 30, second: 30)
--->.at(TimeOfDay(hour: 10, minute: 30, second: 0)) // equivalent to:
--->.at(.time(hour: 10, minute: 30))
--->.at(.randomTime(from: .time(hour: 10, minute: 15), to: .time(hour: 15, minute: 30)))
Today()
    .at(hour: 9, minute: 15)
    .date() // Date
    
// or

Today()
    .at(hour: 9, minute: 15)
    .dateComponents() // DateComponents

You can also get the DateComponents (but not Date) instance by calling dateComponents() on an instance of DateBuilder.Day, without using at:

NextMonth()
    .firstDay
    .dateComponents() // year: 2021, month: 2, day: 1

Using ExactlyAt function

ExactlyAt creates a resolved date from the existing Date instance. You can then use it to perform easy date calculations (functions addingMinutes/addingHours etc.) and easily get Date or DateComponents instances.

ExactlyAt(account.createdAt)
--->.addingSeconds(30)
--->.addingMinutes(1)
--->.addingHours(5)
--->.addingDays(20)
--->.addingMonths(3)
--->.addingWeeks(14)
--->.addingYears(1)

// usge:
ExactlyAt(account.createdAt)
    .addingMinutes(15)
    .dateComponents() // DateComponents

Using Every functions

You can use EveryDay, EveryWeek, EveryMonth and EveryYear functions in the same way as you would use something like Today() or NextYear(). The only difference is that at the end you will get an array of dates instead of a single instance:

let dates = EveryMonth(forMonths: 12, starting: .thisMonth)
    .firstDay.addingDays(9)
    .at(hour: 20, minute: 00)
    .dates() // [Date]
    
// or

let dates = EveryMonth(forMonths: 12, starting: .thisMonth)
    .lastDay.addingDays(-5)
    .at(hour: 20, minute: 00)
    .dateComponents() // [DateComponents]

In case you use .at(.randomTime( ... )) function with Every functions, the exact resolved time will be different each day.

Customizing the Calendar / Locale / Timezone

By default, DateBuilder uses Calendar.current for all calculations. If you need to customize it, you can either change it globally:

var customCalendar = DateBuilder.calendar
customCalendar.firstWeekday = 6
DateBuilder.calendar = customCalendar

Or temporarily, using the DateBuilder.withCalendar function:

DateBuilder.withCalendar(customCalendar) {
    ThisWeek().firstDay.dateComponents()
}

DateBuilder will return to its global Calendar instance after evaluating the expression.

In a similar manner, you can also use DateBuilder.withTimeZone and DateBuilder.withLocale functions:

DateBuilder.withTimeZone(TimeZone(identifier: "America/Cancun")) {
    Tomorrow().at(hour: 9, minute: 15).date()
}

let nextFriday = DateBuilder.withLocale(Locale(identifier: "he_IL")) {
    NextWeek()
        .weekendStartDay
        .at(hour: 7, minute: 00)
        .date() // next friday!
}

All of these functions support returning the result of the closure (see above).

Installation

Swift Package Manager

  1. Click File โ†’ Swift Packages โ†’ Add Package Dependency.
  2. Enter http://github.com/nicephoton/DateBuilder.git.

Acknowledgments

Special thanks to:

Related materials:

  • Time by @dreymonde - Type-safe time calculations in Swift, powered by generics
  • Time by @davedelong - Building a better date/time library for Swift

More Repositories

1

Time

๐Ÿ•ฐ Type-safe time calculations in Swift
Swift
1,073
star
2

AppFolder

๐Ÿ—‚ Never use NSSearchPathForDirectoriesInDomains again
Swift
938
star
3

Delegated

๐Ÿ‘ทโ€โ™€๏ธ Closure-based delegation without memory leaks
Swift
703
star
4

Shallows

๐Ÿ›ถ Your lightweight persistence toolbox
Swift
622
star
5

NiceNotifications

๐Ÿ”” Create rich local notifications experiences on iOS with incredible ease
Swift
269
star
6

Placeholders

๐Ÿ…ฟ๏ธ Define multiple placeholders for UITextField and animate their change
Swift
199
star
7

ScheduledNotificationsViewController

See all your scheduled local notifications in one place
Swift
170
star
8

TheGreatGame

๐Ÿ† Open-source first-class iOS & watchOS app dedicated to Womenโ€™s Euro 2017
Swift
135
star
9

Paperville

๐Ÿ™ Design a city in Swift code (๏ฃฟWWDC 2018 submission, ACCEPTED)
Swift
51
star
10

TelegraphKit

๐Ÿ“œ The ultimate solution for showing ad hoc, server-editable web content (FAQs, Tutorials, Privacy Policy, etc.) in your iOS apps
Swift
51
star
11

DonateToUkraine

๐Ÿ‡บ๐Ÿ‡ฆ Implement "donate to Ukraine" inside your app, with Apple Pay
Swift
25
star
12

Alba

๐ŸŽ™ Stateful event observing engine [DEPRECATED]
Swift
19
star
13

Avenues

๐ŸŒ… [WIP] Idiomatic image fetching and caching in Swift.
Swift
13
star
14

Subviews

๐Ÿงฉ @โ€‹Subview and other ways of making UIKit more fun to use
Swift
9
star
15

Timers

โฒ๏ธ Intuitive Swift timers with automatic memory management
Swift
8
star
16

SwiftyNURE

Swift framework for NURE API (CIST)
Swift
3
star
17

Operacjas

๐Ÿ›  [DEPRECATED] Unlocking the full glory of NSOperations
Swift
2
star
18

Swift-hints

1
star
19

DynamicInstance

Swift
1
star
20

Operations

[WIP] NSOperations for 2018
Swift
1
star
21

uahelp-js-scripts

JavaScript
1
star
22

Avenues-Shallows

Making caching even better
Swift
1
star
23

SofarKit

Access Sofar admin data with Swift [WIP]
Swift
1
star
24

Light

๐Ÿ•Š Super thin networking layer built on top of Shallows
Swift
1
star