Skip to content

Commit

Permalink
Add date and time functions
Browse files Browse the repository at this point in the history
Closes #142
  • Loading branch information
jberkel committed Sep 28, 2017
1 parent 1826097 commit 364a240
Show file tree
Hide file tree
Showing 7 changed files with 249 additions and 4 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
0.11.4 (xx-09-2017), [diff][diff-0.11.4]
========================================

* Added Date and Time functions ([#142][])
* Preliminary Linux support ([#315][], [#681][])
* Add `RowIterator` for more safety ([#647][], [#726][])
* Make Row.get throw instead of crash ([#649][])
Expand Down Expand Up @@ -47,6 +48,7 @@
[diff-0.11.3]: https://github.com/stephencelis/SQLite.swift/compare/0.11.2...0.11.3
[diff-0.11.4]: https://github.com/stephencelis/SQLite.swift/compare/0.11.3...0.11.4

[#142]: https://github.com/stephencelis/SQLit1e.swift/issues/142
[#315]: https://github.com/stephencelis/SQLit1e.swift/issues/315
[#481]: https://github.com/stephencelis/SQLit1e.swift/pull/481
[#532]: https://github.com/stephencelis/SQLit1e.swift/issues/532
Expand Down
13 changes: 13 additions & 0 deletions Documentation/Index.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
- [Other Operators](#other-operators)
- [Core SQLite Functions](#core-sqlite-functions)
- [Aggregate SQLite Functions](#aggregate-sqlite-functions)
- [Date and Time Functions](#date-and-time-functions)
- [Custom SQL Functions](#custom-sql-functions)
- [Custom Collations](#custom-collations)
- [Full-text Search](#full-text-search)
Expand Down Expand Up @@ -1430,6 +1431,18 @@ Many of SQLite’s [core functions](https://www.sqlite.org/lang_corefunc.html) h
Most of SQLite’s [aggregate functions](https://www.sqlite.org/lang_aggfunc.html) have been surfaced in and type-audited for SQLite.swift.
## Date and Time functions
SQLite's [date and time](https://www.sqlite.org/lang_datefunc.html) are available:
```swift
DateFunctions.date("now")
// date('now')
Date().date
// date('2007-01-09T09:41:00.000')
Expression<Date>("date").date
// date("date")
```
## Custom SQL Functions
Expand Down
18 changes: 18 additions & 0 deletions SQLite.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,22 @@
03A65E951C6BB3030062603F /* TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B161C3F127200AE3E12 /* TestHelpers.swift */; };
03A65E971C6BB3210062603F /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 03A65E961C6BB3210062603F /* libsqlite3.tbd */; };
19A1709C3E7A406E62293B2A /* Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B93B48B5560E6E51791 /* Fixtures.swift */; };
19A17152E32A9585831E3FE0 /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; };
19A1717B10CC941ACB5533D6 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; };
19A171967CC511C4F6F773C9 /* RowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */; };
19A171E6FA242F72A308C594 /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; };
19A171F12AB8B07F2FD7201A /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; };
19A1720B67ED13E6150C6A3D /* RowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */; };
19A17254FBA7894891F7297B /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; };
19A172EB202970561E5C4245 /* DateAndTimeFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */; };
19A173668D948AD4DF1F5352 /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; };
19A1737286A74F3CF7412906 /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; };
19A17408007B182F884E3A53 /* Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B93B48B5560E6E51791 /* Fixtures.swift */; };
19A17490543609FCED53CACC /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; };
19A174D78559CD30679BCCCB /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; };
19A1750CEE9B05267995CF3D /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; };
19A175DFF47B84757E547C62 /* fixtures in Resources */ = {isa = PBXBuildFile; fileRef = 19A17E2695737FAB5D6086E3 /* fixtures */; };
19A1769C1F3A7542BECF50FF /* DateAndTimeFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */; };
19A177CC33F2E6A24AF90B02 /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; };
19A178072B371489E6A1E839 /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; };
19A17835FD5886FDC5A3228F /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; };
Expand All @@ -66,8 +71,10 @@
19A179CCF9671E345E5A9811 /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; };
19A179E76EA6207669B60C1B /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; };
19A17C4B951CB054EE48AB1C /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; };
19A17C80076860CF7751A056 /* DateAndTimeFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */; };
19A17DC282E36C4F41AA440B /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; };
19A17E04C4C0956715C5676A /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; };
19A17E29278A12BC4F542506 /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; };
19A17EC0D68BA8C03288ADF7 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; };
19A17F3E1F7ACA33BD43E138 /* fixtures in Resources */ = {isa = PBXBuildFile; fileRef = 19A17E2695737FAB5D6086E3 /* fixtures */; };
19A17F60B685636D1F83C2DD /* Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B93B48B5560E6E51791 /* Fixtures.swift */; };
Expand Down Expand Up @@ -209,12 +216,14 @@
03A65E961C6BB3210062603F /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; };
19A1710E73A46D5AC721CDA9 /* Errors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = "<group>"; };
19A1721B8984686B9963B45D /* FTS5Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS5Tests.swift; sourceTree = "<group>"; };
19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateAndTimeFunctionTests.swift; sourceTree = "<group>"; };
19A1730E4390C775C25677D1 /* FTS5.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS5.swift; sourceTree = "<group>"; };
19A17399EA9E61235D5D77BF /* CipherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CipherTests.swift; sourceTree = "<group>"; };
19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RowTests.swift; sourceTree = "<group>"; };
19A178A39ACA9667A62663CC /* Cipher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cipher.swift; sourceTree = "<group>"; };
19A1794CC4D7827E997E32A7 /* FoundationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FoundationTests.swift; sourceTree = "<group>"; };
19A17B93B48B5560E6E51791 /* Fixtures.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Fixtures.swift; sourceTree = "<group>"; };
19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateAndTimeFunctions.swift; sourceTree = "<group>"; };
19A17E2695737FAB5D6086E3 /* fixtures */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = folder; path = fixtures; sourceTree = "<group>"; };
3D67B3E51DB2469200A4F4C6 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS3.0.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; };
49EB68C31F7B3CB400D89D40 /* Coding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coding.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -408,6 +417,7 @@
19A17399EA9E61235D5D77BF /* CipherTests.swift */,
19A17B93B48B5560E6E51791 /* Fixtures.swift */,
19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */,
19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */,
);
name = SQLiteTests;
path = Tests/SQLiteTests;
Expand Down Expand Up @@ -452,6 +462,7 @@
EE247B011C3F06E900AE3E12 /* Schema.swift */,
EE247B021C3F06E900AE3E12 /* Setter.swift */,
49EB68C31F7B3CB400D89D40 /* Coding.swift */,
19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */,
);
path = Typed;
sourceTree = "<group>";
Expand Down Expand Up @@ -806,6 +817,7 @@
19A17EC0D68BA8C03288ADF7 /* FTS5.swift in Sources */,
19A179E76EA6207669B60C1B /* Cipher.swift in Sources */,
19A17FF4A10B44D3937C8CAC /* Errors.swift in Sources */,
19A1737286A74F3CF7412906 /* DateAndTimeFunctions.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -833,6 +845,7 @@
19A179A0C45377CB09BB358C /* CipherTests.swift in Sources */,
19A17F60B685636D1F83C2DD /* Fixtures.swift in Sources */,
19A1785195182AF8731A8BDA /* RowTests.swift in Sources */,
19A1769C1F3A7542BECF50FF /* DateAndTimeFunctionTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -862,6 +875,7 @@
3D67B3E81DB246BA00A4F4C6 /* Connection.swift in Sources */,
19A179CCF9671E345E5A9811 /* Cipher.swift in Sources */,
19A17DC282E36C4F41AA440B /* Errors.swift in Sources */,
19A173668D948AD4DF1F5352 /* DateAndTimeFunctions.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -891,6 +905,7 @@
19A1717B10CC941ACB5533D6 /* FTS5.swift in Sources */,
19A171F12AB8B07F2FD7201A /* Cipher.swift in Sources */,
19A1792C0520D4E83C2EB075 /* Errors.swift in Sources */,
19A17E29278A12BC4F542506 /* DateAndTimeFunctions.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -918,6 +933,7 @@
19A177CC33F2E6A24AF90B02 /* CipherTests.swift in Sources */,
19A17408007B182F884E3A53 /* Fixtures.swift in Sources */,
19A1720B67ED13E6150C6A3D /* RowTests.swift in Sources */,
19A17C80076860CF7751A056 /* DateAndTimeFunctionTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -947,6 +963,7 @@
19A1750CEE9B05267995CF3D /* FTS5.swift in Sources */,
19A17835FD5886FDC5A3228F /* Cipher.swift in Sources */,
19A17490543609FCED53CACC /* Errors.swift in Sources */,
19A17152E32A9585831E3FE0 /* DateAndTimeFunctions.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -974,6 +991,7 @@
19A17C4B951CB054EE48AB1C /* CipherTests.swift in Sources */,
19A1709C3E7A406E62293B2A /* Fixtures.swift in Sources */,
19A171967CC511C4F6F773C9 /* RowTests.swift in Sources */,
19A172EB202970561E5C4245 /* DateAndTimeFunctionTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
106 changes: 106 additions & 0 deletions Sources/SQLite/Typed/DateAndTimeFunctions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
//
// SQLite.swift
// https://github.com/stephencelis/SQLite.swift
// Copyright © 2014-2015 Stephen Celis.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//

import Foundation

/// All five date and time functions take a time string as an argument.
/// The time string is followed by zero or more modifiers.
/// The strftime() function also takes a format string as its first argument.
///
/// https://www.sqlite.org/lang_datefunc.html
public class DateFunctions {
/// The date() function returns the date in this format: YYYY-MM-DD.
public static func date(_ timestring: String, _ modifiers: String...) -> Expression<Date?> {
return timefunction("date", timestring: timestring, modifiers: modifiers)
}

/// The time() function returns the time as HH:MM:SS.
public static func time(_ timestring: String, _ modifiers: String...) -> Expression<Date?> {
return timefunction("time", timestring: timestring, modifiers: modifiers)
}

/// The datetime() function returns "YYYY-MM-DD HH:MM:SS".
public static func datetime(_ timestring: String, _ modifiers: String...) -> Expression<Date?> {
return timefunction("datetime", timestring: timestring, modifiers: modifiers)
}

/// The julianday() function returns the Julian day -
/// the number of days since noon in Greenwich on November 24, 4714 B.C.
public static func julianday(_ timestring: String, _ modifiers: String...) -> Expression<Date?> {
return timefunction("julianday", timestring: timestring, modifiers: modifiers)
}

/// The strftime() routine returns the date formatted according to the format string specified as the first argument.
public static func strftime(_ format: String, _ timestring: String, _ modifiers: String...) -> Expression<Date?> {
if !modifiers.isEmpty {
let templates = [String](repeating: "?", count: modifiers.count).joined(separator: ", ")
return Expression("strftime(?, ?, \(templates))", [format, timestring] + modifiers)
}
return Expression("strftime(?, ?)", [format, timestring])
}

private static func timefunction(_ name: String, timestring: String, modifiers: [String]) -> Expression<Date?> {
if !modifiers.isEmpty {
let templates = [String](repeating: "?", count: modifiers.count).joined(separator: ", ")
return Expression("\(name)(?, \(templates))", [timestring] + modifiers)
}
return Expression("\(name)(?)", [timestring])
}
}

extension Date {
public var date: Expression<Date?> {
return DateFunctions.date(dateFormatter.string(from: self))
}

public var time: Expression<Date?> {
return DateFunctions.time(dateFormatter.string(from: self))
}

public var datetime: Expression<Date?> {
return DateFunctions.datetime(dateFormatter.string(from: self))
}

public var julianday: Expression<Date?> {
return DateFunctions.julianday(dateFormatter.string(from: self))
}
}

extension Expression where UnderlyingType == Date {
public var date: Expression<Date> {
return Expression<Date>("date(\(template))", bindings)
}

public var time: Expression<Date> {
return Expression<Date>("time(\(template))", bindings)
}

public var datetime: Expression<Date> {
return Expression<Date>("datetime(\(template))", bindings)
}

public var julianday: Expression<Date> {
return Expression<Date>("julianday(\(template))", bindings)
}
}
17 changes: 13 additions & 4 deletions Sources/SQLite/Typed/Operators.swift
Original file line number Diff line number Diff line change
Expand Up @@ -474,11 +474,20 @@ public func <=<V : Value>(lhs: V, rhs: Expression<V?>) -> Expression<Bool?> wher
return infix(lhs, rhs)
}

public func ~=<V : Value>(lhs: ClosedRange<V>, rhs: Expression<V>) -> Expression<Bool> where V.Datatype : Comparable {
return Expression("\(rhs.template) BETWEEN ? AND ?", rhs.bindings + [lhs.lowerBound as? Binding, lhs.upperBound as? Binding])
public func ~=<V : Value>(lhs: ClosedRange<V>, rhs: Expression<V>) -> Expression<Bool> where V.Datatype : Comparable & Value {
return Expression("\(rhs.template) BETWEEN ? AND ?", rhs.bindings + [lhs.lowerBound.datatypeValue, lhs.upperBound.datatypeValue])
}
public func ~=<V : Value>(lhs: ClosedRange<V>, rhs: Expression<V?>) -> Expression<Bool?> where V.Datatype : Comparable {
return Expression("\(rhs.template) BETWEEN ? AND ?", rhs.bindings + [lhs.lowerBound as? Binding, lhs.upperBound as? Binding])

public func ~=<V : Value>(lhs: ClosedRange<V>, rhs: Expression<V?>) -> Expression<Bool?> where V.Datatype : Comparable & Value {
return Expression("\(rhs.template) BETWEEN ? AND ?", rhs.bindings + [lhs.lowerBound.datatypeValue, lhs.upperBound.datatypeValue])
}

public func ~=<V : Value>(lhs: Range<V>, rhs: Expression<V>) -> Expression<Bool> where V.Datatype : Comparable & Value {
return Expression("\(rhs.template) >= ? AND \(rhs.template) < ?", rhs.bindings + [lhs.lowerBound.datatypeValue] + rhs.bindings + [lhs.upperBound.datatypeValue])
}

public func ~=<V : Value>(lhs: Range<V>, rhs: Expression<V?>) -> Expression<Bool?> where V.Datatype : Comparable & Value {
return Expression("\(rhs.template) >= ? AND \(rhs.template) < ?", rhs.bindings + [lhs.lowerBound.datatypeValue] + rhs.bindings + [lhs.upperBound.datatypeValue])
}

// MARK: -
Expand Down
Loading

0 comments on commit 364a240

Please sign in to comment.