Skip to content

Commit

Permalink
Merge branch 'release/4.0.12'
Browse files Browse the repository at this point in the history
  • Loading branch information
malcommac committed Jan 30, 2017
2 parents 9c36b26 + 88ef5c3 commit 30e5198
Show file tree
Hide file tree
Showing 14 changed files with 291 additions and 80 deletions.
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

## CHANGELOG

* Version **[4.0.12](#412)**
* Version **[4.0.11](#411)**
* Version **[4.0.10](#410)**
* Version **[4.0.9](#409)**
Expand All @@ -18,6 +19,23 @@
* Version **[4.0.2](#402)**
* Version **[4.0.0](#400)**

<a name="412" />

## SwiftDate 4.0.12
---
- **Release Date**: 2017/01/30
- **Zipped Version**: [Download 4.0.12](https://github.com/malcommac/SwiftDate/releases/tag/4.0.12)

#### Fixes
- [#372](https://github.com/malcommac/SwiftDate/issues/372) Fix for Local.collatorIdentifier (Returns zh-hans-CN and zh-hant-CN)
- [#374](https://github.com/malcommac/SwiftDate/pull/374) `DateZeroBehavior` options are now public outside the library

#### New Features
- [#379](https://github.com/malcommac/SwiftDate/pull/379) Added Hebrew translation (thanks to @ilandbt)
- [#376](https://github.com/malcommac/SwiftDate/pull/376) Added Swedish translation (thanks to @traneHead)
- [#381](https://github.com/malcommac/SwiftDate/pull/381) Replaced `useImminentInterval` in `DateInRegionFormatter` with a configurable value called `imminentInterval`. With a default value of 5 it fallback to `just now` version. If `nil` fallback is disabled.
- [#380](https://github.com/malcommac/SwiftDate/pull/380) `DateInRegionFormatter` is now able to load custom localization both from `LocaleName` and custom `.strings` files (just set the `formatter.localization = Localization(path: [PATH_TO_YOUR_STRINGS_FILE]`)

<a name="411" />

## SwiftDate 4.0.11
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ You can also generate the latest documentation using [Jazzy](https://github.com/

## Current Release

Latest release is: 4.0.11 [Download here](https://github.com/malcommac/SwiftDate/releases/tag/4.0.11).
Latest release is: 4.0.12 [Download here](https://github.com/malcommac/SwiftDate/releases/tag/4.0.12).

A complete list of changes for each release is available in the [CHANGELOG](CHANGELOG.md) file.

Expand Down
1 change: 1 addition & 0 deletions Sources/SwiftDate/Commons.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ public enum DateError: Error {
case DifferentCalendar
case MissingRsrcBundle
case FailedToSetComponent(Calendar.Component)
case InvalidLocalizationFile
}


Expand Down
4 changes: 2 additions & 2 deletions Sources/SwiftDate/DateInRegion+Formatter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ public extension DateInRegion {
/// - returns: colloquial string representation
public func colloquial(toDate date: DateInRegion) throws -> (colloquial: String, time: String?) {
let formatter = DateInRegionFormatter()
formatter.locale = self.region.locale
formatter.localization = Localization(locale: self.region.locale)
return try formatter.colloquial(from: self, to: date)
}

Expand Down Expand Up @@ -178,7 +178,7 @@ public extension DateInRegion {
@available(*, deprecated: 4.0.3, message: "Use timeComponents(toDate:,options:) instead")
public func timeComponents(toDate date: DateInRegion, unitStyle: DateComponentsFormatter.UnitsStyle = .short, max: Int? = nil, zero: DateZeroBehaviour? = nil, separator: String? = nil) throws -> String {
let formatter = DateInRegionFormatter()
formatter.locale = self.region.locale
formatter.localization = Localization(locale: self.region.locale)
formatter.maxComponentCount = max
formatter.unitStyle = unitStyle
formatter.zeroBehavior = zero ?? .dropAll
Expand Down
100 changes: 28 additions & 72 deletions Sources/SwiftDate/DateInRegionFormatter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,22 +35,22 @@ public struct DateZeroBehaviour: OptionSet {
}

/// None, it does not remove components with zero values
static let none = DateZeroBehaviour(rawValue: 1)
public static let none = DateZeroBehaviour(rawValue: 1)

/// Units whose values are 0 are dropped starting at the beginning of the sequence until the
/// first non-zero component
static let dropLeading = DateZeroBehaviour(rawValue: 3)
public static let dropLeading = DateZeroBehaviour(rawValue: 3)

/// Units whose values are 0 are dropped from anywhere in the middle of a sequence.
static let dropMiddle = DateZeroBehaviour(rawValue: 4)
public static let dropMiddle = DateZeroBehaviour(rawValue: 4)

/// Units whose value is 0 are dropped starting at the end of the sequence back to the first
/// non-zero component
static let dropTrailing = DateZeroBehaviour(rawValue: 3)
public static let dropTrailing = DateZeroBehaviour(rawValue: 3)

/// This behavior drops all units whose values are 0. For example, when days, hours,
/// minutes, and seconds are allowed, the abbreviated version of one hour is displayed as “1h”.
static let dropAll: DateZeroBehaviour = [.dropLeading,.dropMiddle,.dropTrailing]
public static let dropAll: DateZeroBehaviour = [.dropLeading,.dropMiddle,.dropTrailing]

}

Expand Down Expand Up @@ -83,64 +83,28 @@ public class DateInRegionFormatter {
/// colloquial format. Default is `,`.
public var unitSeparator: String = ","

/// For interval less than 5 minutes if this value is true the equivalent of 'just now' is
/// printed in the output string. By default is `true`.
public var useImminentInterval: Bool = true
/// Imminent interval defines the smaller time interval (expressed in minutes) under which a
/// time difference fallback to `just now`.
/// Set this variable to `nil` disable automatic fallback.
/// Supported values must between `1` and `59`. Other values are ignored.
/// By default value is set to 5: if differences between two dates is less than 5 minutes it just fallback to `just now` formatters.
public var imminentInterval: Int? = 5

// If true numbers in timeComponents() function receive a padding if necessary
public var zeroPadding: Bool = true

/// Locale to use when print the date. By default is the same locale set by receiver's `DateInRegion`.
/// Locale to use when print the date.
/// By default is the same locale set by receiver's `DateInRegion`.
/// If not set default device locale is used instead.
public var locale: Locale?
/// You can also customize the source by giving a path to a custom `.strings` file (via `Localization(path: <pathtofile>)`)
public var localization = Localization(locale: nil)

// number of a days in a week
let DAYS_IN_WEEK = 7

public init() {
}

/// Return the main bundle with resources used to localize time components
private lazy var resourceBundle: Bundle? = {
var framework = Bundle(for: DateInRegion.self)
let path = NSURL(fileURLWithPath:
framework.resourcePath!).appendingPathComponent("SwiftDate.bundle")
let bundle = Bundle(url: path!)
guard let _ = bundle else {
return nil
}
return bundle!
}()


/// If you have specified a custom `Locale` this function return the localized bundle for this locale
/// If no locale is specified it return the default system locale.
///
/// - returns: bundle where current localization strings are available
private func localizedResourceBundle() -> Bundle? {
guard let locale = self.locale else {
return self.resourceBundle
}

let localeID = locale.collatorIdentifier
guard let innerLanguagePath = self.resourceBundle!.path(forResource: localeID, ofType: "lproj") else {

//fallback to language only
if let languageCode = locale.languageCode {
//example : get french traduction even though you are live in belgium
if let localOnlyPath = self.resourceBundle!.path(forResource: "\(languageCode)-\(languageCode.uppercased())" , ofType: "lproj") {
return Bundle(path: localOnlyPath)
}
}
// fallback to english if language was not found
let englishPath = self.resourceBundle!.path(forResource: "en-US", ofType: "lproj")!
return Bundle(path: englishPath)
}
return Bundle(path: innerLanguagePath)
}



/// String representation of an absolute interval expressed in seconds.
/// For example "4 days" or "33s".
///
Expand Down Expand Up @@ -262,13 +226,14 @@ public class DateInRegionFormatter {
}

if cmp.minute != nil && (cmp.minute != 0 || !hasLowerAllowedComponents(than: .minute)) {
if self.useImminentInterval && abs(cmp.minute!) < 5 {
if let value = self.imminentInterval, (value > 1 && value < 60), (abs(cmp.minute!) < value) {
// A valid `imminentInterval` should be set. Valid interval must be between 1 and 60 minutes (not inclueded)
let colloquial_date = try self.stringLocalized(identifier: "colloquial_now", arguments: [])
return (colloquial_date,nil)
} else {
let colloquial_date = try self.localized(unit: .minute, withValue: cmp.minute!, asFuture: isFuture, args: abs(cmp.minute!))
return (colloquial_date,nil)
}
// otherwise fallback to difference
let colloquial_date = try self.localized(unit: .minute, withValue: cmp.minute!, asFuture: isFuture, args: abs(cmp.minute!))
return (colloquial_date,nil)
}

if cmp.second != nil && (cmp.second != 0 || cmp.second == 0) { // Seconds difference
Expand Down Expand Up @@ -312,19 +277,17 @@ public class DateInRegionFormatter {
///
/// - returns: a localized representation of time interval in term of passed calendar component
private func colloquial_time(forUnit unit: Calendar.Component, withValue value: Int, date: DateInRegion) throws -> String? {
guard let bundle = self.localizedResourceBundle() else {
throw DateError.MissingRsrcBundle
}

let unitStr = unit.localizedKey(forValue: value)
let id_relative = "relevanttime_\(unitStr)"
let relative_localized = NSLocalizedString(id_relative,
tableName: "SwiftDate",
bundle: bundle, value: "", comment: "")
let relative_localized = self.localization.get(id_relative, default: "")
if (relative_localized as NSString).length == 0 {
return nil
}
let relevant_time = date.string(format: .custom(relative_localized))
let localeDate = DateInRegion(absoluteDate: date.absoluteDate,
in: Region(tz: date.region.timeZone,
cal: date.region.calendar,
loc: self.localization.locale ?? date.region.locale))
let relevant_time = localeDate.string(format: .custom(relative_localized))
return relevant_time
}

Expand All @@ -340,16 +303,12 @@ public class DateInRegionFormatter {
///
/// - returns: localized colloquial string with passed unit of time
private func localized(unit: Calendar.Component, withValue value: Int, asFuture: Bool, args: CVarArg...) throws -> String {
guard let bundle = self.localizedResourceBundle() else {
throw DateError.MissingRsrcBundle
}

let future_key = (value == 0 ? "n" : (asFuture ? "f" : "p"))

let unitStr = unit.localizedKey(forValue: value)
let identifier = "colloquial_\(future_key)_\(unitStr)"
let localized_date = withVaList(args) { (pointer: CVaListPointer) -> String in
let localized = NSLocalizedString(identifier, tableName: "SwiftDate", bundle: bundle, value: "", comment: "")
let localized = self.localization.get(identifier, default: "")
return NSString(format: localized, arguments: pointer) as String
}
return localized_date
Expand All @@ -364,10 +323,7 @@ public class DateInRegionFormatter {
///
/// - returns: localized string with arguments
private func stringLocalized(identifier: String, arguments: CVarArg...) throws -> String {
guard let bundle = self.localizedResourceBundle() else {
throw DateError.MissingRsrcBundle
}
var localized_str = NSLocalizedString(identifier, tableName: "SwiftDate", bundle: bundle, comment: "")
var localized_str = self.localization.get(identifier, default: "")
localized_str = String(format: localized_str, arguments: arguments)
return localized_str
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/SwiftDate/LocaleName.swift
Original file line number Diff line number Diff line change
Expand Up @@ -570,7 +570,7 @@ public enum LocaleName: String {
case polishPoland = "pl_PL"
case portuguese = "pt"
case portugueseAngola = "pt_AO"
case portugueseBrazil = "pt"
case portugueseBrazil = "pt_BR"
case portugueseCapeVerde = "pt_CV"
case portugueseGuineaBissau = "pt_GW"
case portugueseMacauSarChina = "pt_MO"
Expand Down
122 changes: 122 additions & 0 deletions Sources/SwiftDate/Localization.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
//
// SwiftDate, Full featured Swift date library for parsing, validating, manipulating, and formatting dates and timezones.
// Created by: Daniele Margutti
// Main contributors: Jeroen Houtzager
//
//
// 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

/// This class allows you to read and load a custom `.strings` file or a file generated by a gived `Locale` instance.
public class Localization {

/// Translations file loaded from a custom `.strings` file.
/// It has a valid value only if `Localization` is loaded from a file.
private var translations: NSDictionary?

/// Bundle for given locale instances
private var bundle: Bundle?
/// Locale instances loaded (only if initialized via Locale instance)
private(set) var locale: Locale?


/// Init `Localization` instance from a custom `.strings` file at given path
///
/// - Parameter path: path to a strings file
/// - Throws: throw an exception if not valid `strings` file was loaded.
public init(path: String) throws {
guard let data = NSDictionary(contentsOfFile: path) else {
throw DateError.InvalidLocalizationFile
}
translations = data
}


/// Initialize a new `Localization` from a given `LocaleName`
///
/// - Parameter name: name of locale
public convenience init(locale name: LocaleName) {
self.init(locale: name.locale)
}


/// Init `Localization` instance with a given `locale` instance
///
/// - Parameter locale: locale to load; if `nil` current `locale` is used instead.
public init(locale: Locale?) {
self.locale = locale ?? Locale.current

let resourceBundle = Localization.resourceBundle()!
let localeID = self.locale!.collatorIdentifier
guard let innerLanguagePath = resourceBundle.path(forResource: localeID, ofType: "lproj") else {

//fallback to language only
if let languageCode = self.locale!.languageCode {
//example : get french traduction even though you are live in belgium
if let localOnlyPath = resourceBundle.path(forResource: "\(languageCode)-\(languageCode.uppercased())" , ofType: "lproj") {
self.bundle = Bundle(path: localOnlyPath)
return
}
}
// fallback to english if language was not found
let englishPath = resourceBundle.path(forResource: "en-US", ofType: "lproj")!
self.bundle = Bundle(path: englishPath)
return
}
self.bundle = Bundle(path: innerLanguagePath)
}


/// Get translated value for a given key
///
/// - Parameters:
/// - key: key to search
/// - defValue: default fallback value. By default is empty string.
/// - Returns: translated value
public func get(_ key: String, default defValue: String = "") -> String {
if translations != nil {
guard let value = translations!.object(forKey: key) as? String else {
return defValue
}
return value
} else {
guard let bundle = self.bundle else {
return defValue
}
let value = NSLocalizedString(key, tableName: "SwiftDate", bundle: bundle, value: "", comment: "")
return value
}
}


/// Framework's translation bundle resource
///
/// - Returns: path
private class func resourceBundle() -> Bundle? {
let framework = Bundle(for: DateInRegion.self)
let path = NSURL(fileURLWithPath: framework.resourcePath!).appendingPathComponent("SwiftDate.bundle")
let bundle = Bundle(url: path!)
guard let _ = bundle else {
return nil
}
return bundle!
}

}
Loading

0 comments on commit 30e5198

Please sign in to comment.