2777. Date Range Generator 🔒
Problem Description
This problem asks you to create a generator function that produces a sequence of dates within a given range.
You're given three parameters:
start
: A string representing the starting date in format "YYYY-MM-DD"end
: A string representing the ending date in format "YYYY-MM-DD"step
: A positive integer indicating how many days to skip between each generated date
The generator should yield dates starting from start
and continuing up to and including end
, with each successive date being step
days after the previous one.
For example, if you have:
- Start date: "2023-04-01"
- End date: "2023-04-04"
- Step: 1
The generator would yield: "2023-04-01", "2023-04-02", "2023-04-03", "2023-04-04"
If the step were 2 instead, it would yield: "2023-04-01", "2023-04-03"
The key requirements are:
- Use a generator function (function* syntax in TypeScript/JavaScript)
- All yielded dates must be strings in "YYYY-MM-DD" format
- Include both the start and end dates in the range (if the end date is reachable with the given step)
- The generator stops when the next date would exceed the end date
Intuition
The core idea is to treat dates as objects that we can increment by a certain number of days, then convert them back to the required string format for output.
Since we need to produce values one at a time (lazily) rather than all at once, a generator function is the natural choice. Generators allow us to pause execution after each yield
and resume when the next value is requested.
The approach follows these logical steps:
-
Convert strings to Date objects: JavaScript's
Date
objects have built-in methods for date arithmetic, making it easy to add days. We convert bothstart
andend
strings to Date objects for easier manipulation. -
Use a loop with a moving pointer: We maintain a
currentDate
variable that starts at the beginning and moves forward bystep
days each iteration. This is similar to iterating through an array withi += step
instead ofi++
. -
Yield formatted dates: At each valid position (where
currentDate <= endDate
), we convert the current Date object back to the "YYYY-MM-DD" format and yield it. ThetoISOString()
method gives us a format like "2023-04-01T00:00:00.000Z", and we can slice the first 10 characters to get just the date portion. -
Increment and check bounds: After yielding, we add
step
days to the current date usingsetDate(currentDate.getDate() + step)
. The loop continues as long as we haven't exceeded the end date.
This approach leverages JavaScript's date handling capabilities while using a generator to provide memory-efficient, on-demand date generation.
Solution Approach
The implementation uses a generator function to create an iterator that yields dates on demand. Let's walk through the solution step by step:
1. Function Signature
function* dateRangeGenerator(start: string, end: string, step: number): Generator<string>
The function*
syntax defines a generator function that returns a Generator<string>
object.
2. Date Initialization
const startDate = new Date(start);
const endDate = new Date(end);
let currentDate = startDate;
- Convert the input strings to
Date
objects for easier manipulation - Initialize
currentDate
as the starting point for iteration
3. Main Loop Logic
while (currentDate <= endDate) { yield currentDate.toISOString().slice(0, 10); currentDate.setDate(currentDate.getDate() + step); }
The loop continues as long as currentDate
hasn't exceeded endDate
. In each iteration:
-
Yield the formatted date:
currentDate.toISOString()
produces a string like"2023-04-01T00:00:00.000Z"
. We use.slice(0, 10)
to extract just the date portion"2023-04-01"
. -
Increment the date:
currentDate.getDate()
returns the day of the month (1-31). Addingstep
to it and passing tosetDate()
moves the date forward. JavaScript'sDate
object automatically handles month and year rollovers (e.g., adding 5 days to March 29 correctly gives April 3).
Key Implementation Details:
- The comparison
currentDate <= endDate
works because JavaScriptDate
objects can be compared directly using relational operators - The generator automatically stops when the loop ends (no more values to yield)
- Each call to
.next()
on the generator resumes execution from where it last yielded - The generator maintains its state between calls, remembering the current position in the date range
Time Complexity: O(n) where n is the number of dates yielded ((end - start) / step + 1
)
Space Complexity: O(1) as the generator only maintains a single current date in memory at any time
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example with:
start = "2023-04-10"
end = "2023-04-15"
step = 2
Step 1: Initialize the dates
startDate = new Date("2023-04-10") // April 10, 2023
endDate = new Date("2023-04-15") // April 15, 2023
currentDate = startDate // April 10, 2023
Step 2: First iteration
- Check:
currentDate (Apr 10) <= endDate (Apr 15)
? ✓ Yes - Yield:
"2023-04-10"
(fromtoISOString().slice(0, 10)
) - Update:
currentDate.setDate(10 + 2)
→ currentDate becomes April 12, 2023
Step 3: Second iteration
- Check:
currentDate (Apr 12) <= endDate (Apr 15)
? ✓ Yes - Yield:
"2023-04-12"
- Update:
currentDate.setDate(12 + 2)
→ currentDate becomes April 14, 2023
Step 4: Third iteration
- Check:
currentDate (Apr 14) <= endDate (Apr 15)
? ✓ Yes - Yield:
"2023-04-14"
- Update:
currentDate.setDate(14 + 2)
→ currentDate becomes April 16, 2023
Step 5: Fourth iteration
- Check:
currentDate (Apr 16) <= endDate (Apr 15)
? ✗ No - Loop ends, generator completes
Final output sequence: "2023-04-10"
, "2023-04-12"
, "2023-04-14"
Notice how April 15 (the end date) is not included because our step of 2 days from April 14 would take us to April 16, which exceeds the end date. The generator correctly stops before yielding any dates beyond the specified range.
Solution Implementation
1def date_range_generator(start, end, step):
2 """
3 Generator function that yields dates in a range with a specified step
4
5 Args:
6 start (str): The start date in YYYY-MM-DD format
7 end (str): The end date in YYYY-MM-DD format
8 step (int): The number of days to increment for each iteration
9
10 Yields:
11 str: Dates in YYYY-MM-DD format within the specified range
12 """
13 from datetime import datetime, timedelta
14
15 # Parse the start and end date strings into datetime objects
16 start_date = datetime.strptime(start, '%Y-%m-%d')
17 end_date = datetime.strptime(end, '%Y-%m-%d')
18
19 # Initialize current date to start from the beginning of the range
20 current_date = start_date
21
22 # Iterate through dates until we exceed the end date
23 while current_date <= end_date:
24 # Convert current date to YYYY-MM-DD string format
25 yield current_date.strftime('%Y-%m-%d')
26
27 # Increment current date by the specified step (in days)
28 current_date += timedelta(days=step)
29
30
31# Example usage:
32# g = date_range_generator('2023-04-01', '2023-04-04', 1)
33# next(g) # '2023-04-01'
34# next(g) # '2023-04-02'
35# next(g) # '2023-04-03'
36# next(g) # '2023-04-04'
37# next(g) # StopIteration exception
38
1import java.time.LocalDate;
2import java.time.format.DateTimeFormatter;
3import java.util.Iterator;
4import java.util.NoSuchElementException;
5
6/**
7 * Generator class that yields dates in a range with a specified step
8 */
9public class DateRangeGenerator implements Iterator<String>, Iterable<String> {
10 private LocalDate currentDate;
11 private final LocalDate endDate;
12 private final int step;
13 private static final DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE;
14
15 /**
16 * Constructor for DateRangeGenerator
17 * @param start - The start date in YYYY-MM-DD format
18 * @param end - The end date in YYYY-MM-DD format
19 * @param step - The number of days to increment for each iteration
20 */
21 public DateRangeGenerator(String start, String end, int step) {
22 // Parse the start and end date strings into LocalDate objects
23 this.currentDate = LocalDate.parse(start, formatter);
24 this.endDate = LocalDate.parse(end, formatter);
25 this.step = step;
26 }
27
28 /**
29 * Check if there are more dates in the range
30 * @return true if there are more dates, false otherwise
31 */
32 @Override
33 public boolean hasNext() {
34 // Check if current date is within the range (inclusive)
35 return !currentDate.isAfter(endDate);
36 }
37
38 /**
39 * Get the next date in the range
40 * @return The next date in YYYY-MM-DD format
41 * @throws NoSuchElementException if no more dates are available
42 */
43 @Override
44 public String next() {
45 // Throw exception if we've exceeded the end date
46 if (!hasNext()) {
47 throw new NoSuchElementException("No more dates in range");
48 }
49
50 // Store current date value to return
51 String dateString = currentDate.format(formatter);
52
53 // Increment current date by the specified step (in days)
54 currentDate = currentDate.plusDays(step);
55
56 // Return the date string in YYYY-MM-DD format
57 return dateString;
58 }
59
60 /**
61 * Returns an iterator over the date range
62 * @return Iterator for this date range generator
63 */
64 @Override
65 public Iterator<String> iterator() {
66 return this;
67 }
68
69 /**
70 * Static factory method to create a new DateRangeGenerator
71 * @param start - The start date in YYYY-MM-DD format
72 * @param end - The end date in YYYY-MM-DD format
73 * @param step - The number of days to increment for each iteration
74 * @return A new DateRangeGenerator instance
75 */
76 public static DateRangeGenerator dateRangeGenerator(String start, String end, int step) {
77 return new DateRangeGenerator(start, end, step);
78 }
79
80 /**
81 * Example usage:
82 * DateRangeGenerator g = DateRangeGenerator.dateRangeGenerator("2023-04-01", "2023-04-04", 1);
83 * g.next(); // "2023-04-01"
84 * g.next(); // "2023-04-02"
85 * g.next(); // "2023-04-03"
86 * g.next(); // "2023-04-04"
87 * g.hasNext(); // false
88 */
89}
90
1#include <string>
2#include <sstream>
3#include <iomanip>
4#include <ctime>
5#include <vector>
6#include <memory>
7
8class DateRangeGenerator {
9private:
10 std::tm start_date;
11 std::tm end_date;
12 std::tm current_date;
13 int step_days;
14 bool is_done;
15
16 /**
17 * Parse a date string in YYYY-MM-DD format into a tm structure
18 * @param date_str - The date string to parse
19 * @return tm structure representing the date
20 */
21 std::tm parseDate(const std::string& date_str) {
22 std::tm tm = {};
23 std::istringstream ss(date_str);
24 ss >> std::get_time(&tm, "%Y-%m-%d");
25 // Set time to noon to avoid DST issues
26 tm.tm_hour = 12;
27 return tm;
28 }
29
30 /**
31 * Format a tm structure into YYYY-MM-DD string format
32 * @param date - The tm structure to format
33 * @return String in YYYY-MM-DD format
34 */
35 std::string formatDate(const std::tm& date) {
36 std::ostringstream oss;
37 oss << std::put_time(&date, "%Y-%m-%d");
38 return oss.str();
39 }
40
41 /**
42 * Compare two tm structures
43 * @param date1 - First date
44 * @param date2 - Second date
45 * @return true if date1 <= date2, false otherwise
46 */
47 bool compareDates(const std::tm& date1, const std::tm& date2) {
48 std::time_t time1 = std::mktime(const_cast<std::tm*>(&date1));
49 std::time_t time2 = std::mktime(const_cast<std::tm*>(&date2));
50 return time1 <= time2;
51 }
52
53 /**
54 * Add days to a tm structure
55 * @param date - The date to modify
56 * @param days - Number of days to add
57 */
58 void addDays(std::tm& date, int days) {
59 std::time_t time = std::mktime(&date);
60 time += days * 24 * 60 * 60; // Convert days to seconds
61 date = *std::localtime(&time);
62 }
63
64public:
65 /**
66 * Constructor for DateRangeGenerator
67 * @param start - The start date in YYYY-MM-DD format
68 * @param end - The end date in YYYY-MM-DD format
69 * @param step - The number of days to increment for each iteration
70 */
71 DateRangeGenerator(const std::string& start, const std::string& end, int step)
72 : step_days(step), is_done(false) {
73 // Parse the start and end date strings into tm structures
74 start_date = parseDate(start);
75 end_date = parseDate(end);
76
77 // Initialize current date to start from the beginning of the range
78 current_date = start_date;
79
80 // Check if range is already exceeded
81 if (!compareDates(current_date, end_date)) {
82 is_done = true;
83 }
84 }
85
86 /**
87 * Check if generator has more values
88 * @return true if no more values, false otherwise
89 */
90 bool done() const {
91 return is_done;
92 }
93
94 /**
95 * Get the next date in the range
96 * @return Next date in YYYY-MM-DD format, or empty string if done
97 */
98 std::string next() {
99 // Check if we've exceeded the range
100 if (is_done) {
101 return "";
102 }
103
104 // Store current date value to return
105 std::string result = formatDate(current_date);
106
107 // Increment current date by the specified step (in days)
108 addDays(current_date, step_days);
109
110 // Check if we've now exceeded the end date
111 if (!compareDates(current_date, end_date)) {
112 is_done = true;
113 }
114
115 return result;
116 }
117};
118
119/**
120 * Example usage:
121 * DateRangeGenerator g("2023-04-01", "2023-04-04", 1);
122 * g.next(); // "2023-04-01"
123 * g.next(); // "2023-04-02"
124 * g.next(); // "2023-04-03"
125 * g.next(); // "2023-04-04"
126 * g.done(); // true
127 */
128
1/**
2 * Generator function that yields dates in a range with a specified step
3 * @param start - The start date in YYYY-MM-DD format
4 * @param end - The end date in YYYY-MM-DD format
5 * @param step - The number of days to increment for each iteration
6 * @yields {string} Dates in YYYY-MM-DD format within the specified range
7 */
8function* dateRangeGenerator(start: string, end: string, step: number): Generator<string> {
9 // Parse the start and end date strings into Date objects
10 const startDate: Date = new Date(start);
11 const endDate: Date = new Date(end);
12
13 // Initialize current date to start from the beginning of the range
14 const currentDate: Date = new Date(startDate);
15
16 // Iterate through dates until we exceed the end date
17 while (currentDate <= endDate) {
18 // Convert current date to ISO string format and extract YYYY-MM-DD portion
19 yield currentDate.toISOString().slice(0, 10);
20
21 // Increment current date by the specified step (in days)
22 currentDate.setDate(currentDate.getDate() + step);
23 }
24}
25
26/**
27 * Example usage:
28 * const g = dateRangeGenerator('2023-04-01', '2023-04-04', 1);
29 * g.next().value; // '2023-04-01'
30 * g.next().value; // '2023-04-02'
31 * g.next().value; // '2023-04-03'
32 * g.next().value; // '2023-04-04'
33 * g.next().done; // true
34 */
35
Time and Space Complexity
Time Complexity: O(n)
where n
is the number of dates in the range, calculated as ⌈(endDate - startDate) / step⌉ + 1
.
Each iteration of the generator performs constant time operations:
yield
statement withtoISOString().slice()
:O(1)
- Date comparison
currentDate <= endDate
:O(1)
- Date increment with
setDate()
andgetDate()
:O(1)
Since the generator yields exactly n
values (one for each date in the range with the given step), the overall time complexity is O(n)
.
Space Complexity: O(1)
The generator uses constant extra space regardless of the date range size:
- Three Date objects (
startDate
,endDate
,currentDate
):O(1)
- No additional data structures that grow with input size
- The generator yields values one at a time rather than storing all dates in memory
The space complexity remains constant because generators are lazy and don't pre-compute or store all values. Each value is computed on-demand when next()
is called.
Common Pitfalls
1. Date Object Mutation in JavaScript/TypeScript
In the JavaScript/TypeScript implementation, a critical pitfall is that Date
objects are mutable, and the line currentDate.setDate(currentDate.getDate() + step)
modifies the original object. If someone tries to store or reference the yielded dates, they might encounter unexpected behavior:
// Problematic approach - storing references
function* dateRangeGenerator(start, end, step) {
const startDate = new Date(start);
const endDate = new Date(end);
let currentDate = startDate; // This creates a reference, not a copy!
while (currentDate <= endDate) {
yield currentDate; // Yielding the object directly
currentDate.setDate(currentDate.getDate() + step);
}
}
// This would cause all collected dates to show the same final value
const dates = [...dateRangeGenerator('2023-04-01', '2023-04-04', 1)];
// All elements in dates array would reference the same mutated object
Solution: Always create a new Date object or yield the string representation immediately:
function* dateRangeGenerator(start, end, step) {
const startDate = new Date(start);
const endDate = new Date(end);
let currentDate = new Date(startDate); // Create a copy
while (currentDate <= endDate) {
yield currentDate.toISOString().slice(0, 10); // Yield string immediately
currentDate.setDate(currentDate.getDate() + step);
}
}
2. Timezone and DST Issues
Both implementations can face issues with timezone handling and Daylight Saving Time (DST) transitions:
# Problem: Using local timezone can cause unexpected date skips during DST from datetime import datetime, timedelta start_date = datetime.strptime('2023-03-11', '%Y-%m-%d') # DST transition date in US # Adding days might not work as expected around DST boundaries
Solution: Use date-only operations or UTC to avoid timezone complications:
from datetime import date, timedelta
def date_range_generator(start, end, step):
# Use date objects instead of datetime to avoid time components
start_date = date.fromisoformat(start)
end_date = date.fromisoformat(end)
current_date = start_date
while current_date <= end_date:
yield current_date.isoformat() # Returns YYYY-MM-DD format
current_date += timedelta(days=step)
3. Large Step Values Causing Month/Year Overflow
When using large step values, the JavaScript setDate()
method handles overflow automatically, but this behavior might not be immediately obvious:
// If currentDate is January 30 and step is 5 currentDate.setDate(30 + 5); // Becomes February 4 (or 5 depending on leap year)
While this is generally correct behavior, developers might not expect automatic month/year rollovers.
Solution: Document this behavior clearly or use a more explicit date arithmetic library:
// Using explicit date arithmetic for clarity
function* dateRangeGenerator(start, end, step) {
let currentDate = new Date(start);
const endDate = new Date(end);
while (currentDate <= endDate) {
yield currentDate.toISOString().slice(0, 10);
// More explicit: create new date with added milliseconds
currentDate = new Date(currentDate.getTime() + step * 24 * 60 * 60 * 1000);
}
}
4. Invalid Date String Format
Both implementations assume the input strings are valid and in the correct format. Invalid inputs will cause runtime errors:
# This will raise ValueError date_range_generator('2023/04/01', '2023-04-04', 1) # Wrong date format date_range_generator('2023-13-01', '2023-04-04', 1) # Invalid month
Solution: Add input validation:
def date_range_generator(start, end, step):
from datetime import datetime, timedelta
try:
start_date = datetime.strptime(start, '%Y-%m-%d')
end_date = datetime.strptime(end, '%Y-%m-%d')
except ValueError:
raise ValueError(f"Invalid date format. Expected YYYY-MM-DD")
if start_date > end_date:
raise ValueError("Start date must be before or equal to end date")
if step <= 0:
raise ValueError("Step must be a positive integer")
current_date = start_date
while current_date <= end_date:
yield current_date.strftime('%Y-%m-%d')
current_date += timedelta(days=step)
What is the best way of checking if an element exists in a sorted array once in terms of time complexity? Select the best that applies.
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!