Skip to content

Instantly share code, notes, and snippets.

@nbasham
Last active January 21, 2024 14:57
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save nbasham/c219d8c8c773d2c146c526dfccb4353b to your computer and use it in GitHub Desktop.
Save nbasham/c219d8c8c773d2c146c526dfccb4353b to your computer and use it in GitHub Desktop.
Get a random date between two values. Swift 4.2+ uses Random(in:).
import Foundation
extension Date {
static func randomBetween(start: String, end: String, format: String = "yyyy-MM-dd") -> String {
let date1 = Date.parse(start, format: format)
let date2 = Date.parse(end, format: format)
return Date.randomBetween(start: date1, end: date2).dateString(format)
}
static func randomBetween(start: Date, end: Date) -> Date {
var date1 = start
var date2 = end
if date2 < date1 {
let temp = date1
date1 = date2
date2 = temp
}
let span = TimeInterval.random(in: date1.timeIntervalSinceNow...date2.timeIntervalSinceNow)
return Date(timeIntervalSinceNow: span)
}
func dateString(_ format: String = "yyyy-MM-dd") -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = format
return dateFormatter.string(from: self)
}
static func parse(_ string: String, format: String = "yyyy-MM-dd") -> Date {
let dateFormatter = DateFormatter()
dateFormatter.timeZone = NSTimeZone.default
dateFormatter.dateFormat = format
let date = dateFormatter.date(from: string)!
return date
}
}
@nbasham
Copy link
Author

nbasham commented Apr 21, 2019

Example usage using Date

        let date1 = Date.parse("2018-01-01")
        let date2 = Date.parse("2019-01-01")
        print(Date.randomBetween(start: date1, end: date2)) // 2018-08-10 01:28:42 +0000

Example usage using String

        let date1 = "2018-01-01"
        let date2 = "2019-01-01"
        print(Date.randomBetween(start: date1, end: date2)) //    2018-03-24

Example usage using String and formatting

        let date1 = "1/1/2018"
        let date2 = "1/1/2019"
        print(Date.randomBetween(start: date1, end: date2, format: "d/M/YYYY")) //    10/7/2018

@mdavalos1993
Copy link

thanks!

@simonbromberg
Copy link

simonbromberg commented Nov 26, 2021

Thanks for this!

Can simplify using Range

extension Date {
    static func random(in range: Range<Date>) -> Date {
        Date(
            timeIntervalSinceNow: .random(
                in: range.lowerBound.timeIntervalSinceNow...range.upperBound.timeIntervalSinceNow
            )
        )
    }
}

@nbasham
Copy link
Author

nbasham commented Nov 26, 2021

@simonbromberg I like it, much better from the call site. Thank you!

@BrightScreenTV
Copy link

BrightScreenTV commented Jan 1, 2022

Thanks! This is just what I was looking for. Small Swifty suggestion - if I may - to replace the checking logic for the passed in start and end dates:

let date1 = min(start, end)
let date2 = max(start, end)

@nbasham
Copy link
Author

nbasham commented Jan 1, 2022

@BrightScreenTV That reads much better, thank you!

@its-all-waves
Copy link

Hey! This was super helpful! I did find a small bug, though, and I think I fixed it. (Please keep in mind that I've been coding since Jan 2022, and started learning Swift 2 weeks ago.)

I incorporated @BrightScreenTV's slick change, and corrected for the rare case where date1 == date2 by making date2 = date1 + 2 minutes. I started with 1 minute, but in case that's the smallest division in some cases, and thus wouldn't be split-able, I added the next smallest integer that is.

Found the bug by calling the method repeatedly. (Testing a little UI I'm making for a course.)

Thank you @nbasham!

extension Date {
    
    static func randomBetween(start: String, end: String, format: String = "yyyy-MM-dd") -> String {
        let date1 = Date.parse(start, format: format)
        let date2 = Date.parse(end, format: format)
        return Date.randomBetween(start: date1, end: date2).dateString(format)
    }

    static func randomBetween(start: Date, end: Date) -> Date {

    // - - - - - - - - - - - - CHANGES START HERE - - - - - - - - - - - - //
        let date1 = min(start, end)
        var date2 = max(start, end)
        
        if date1 == date2 {
            date2 = date1.addingTimeInterval(120)
        }
    // - - - - - - - - - - - - CHANGES END HERE - - - - - - - - - - - - //

        let span = TimeInterval.random(in: date1.timeIntervalSinceNow...date2.timeIntervalSinceNow)
        return Date(timeIntervalSinceNow: span)
    }

    func dateString(_ format: String = "yyyy-MM-dd") -> String {
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = format
        return dateFormatter.string(from: self)
    }

    static func parse(_ string: String, format: String = "yyyy-MM-dd") -> Date {
        let dateFormatter = DateFormatter()
        dateFormatter.timeZone = NSTimeZone.default
        dateFormatter.dateFormat = format

        let date = dateFormatter.date(from: string)!
        return date
    }
}

@nbasham
Copy link
Author

nbasham commented Oct 30, 2022

@its-all-waves I like your use of min/max it's an improvement. As the caller is responsible for the logic behind the dates passed in I would lean towards respecting their choices. If the dates are equal, perhaps a guard statement could just return that date? Your velocity at picking up the language is impressive. I hope you really enjoy Swift, and that I get to see more of your amazing trajectory.

@its-all-waves
Copy link

its-all-waves commented Nov 7, 2022

@nbasham, that is so nice to hear! It's awfully rare to find encouraging words in these places (here and StackExchange, namely). Thank you for taking the time to say that! You truly made my day!

Unfortunately, I can't take credit for the min/max bit, as that was @BrightScreenTV's idea.

I hear you about respecting the user's choices. The guard statement is a better / cleaner / more respectful way to accomplish what I was trying to. Thank you for sharing your code and your wisdom, sir!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment