|
12 | 12 | // See the License for the specific language governing permissions and |
13 | 13 | // limitations under the License. |
14 | 14 |
|
| 15 | +import Foundation |
| 16 | + |
15 | 17 | /// Builds programs. |
16 | 18 | /// |
17 | 19 | /// This provides methods for constructing and appending random |
@@ -4435,6 +4437,24 @@ public class ProgramBuilder { |
4435 | 4437 | return createObject(with: properties) |
4436 | 4438 | } |
4437 | 4439 |
|
| 4440 | + fileprivate static let allTimeZoneAbbreviations: [String] = Array(TimeZone.abbreviationDictionary.keys) |
| 4441 | + |
| 4442 | + // Generate a random time zone identifier |
| 4443 | + @discardableResult |
| 4444 | + func randomTimeZone() -> Variable { |
| 4445 | + // Bias towards knownTimeZoneIdentifiers since it's a larger array |
| 4446 | + if probability(0.7) { |
| 4447 | + return loadString(chooseUniform(from: TimeZone.knownTimeZoneIdentifiers)) |
| 4448 | + } else { |
| 4449 | + return loadString(chooseUniform(from: ProgramBuilder.allTimeZoneAbbreviations)) |
| 4450 | + } |
| 4451 | + } |
| 4452 | + |
| 4453 | + @discardableResult |
| 4454 | + func randomUTCOffset() -> Variable { |
| 4455 | + return loadString(randomUTCOffsetString(mayHaveSeconds: true)) |
| 4456 | + } |
| 4457 | + |
4438 | 4458 | // Generate an object with fields from |
4439 | 4459 | // https://tc39.es/proposal-temporal/#table-temporal-calendar-fields-record-fields |
4440 | 4460 | // |
@@ -4549,32 +4569,37 @@ public class ProgramBuilder { |
4549 | 4569 | } |
4550 | 4570 | // timeZone |
4551 | 4571 | if zonedFields { |
4552 | | - // TODO: write generator for timezone strings |
| 4572 | + var generatedOffset: Variable? = nil |
| 4573 | + // ZonedDateTime must be constructed with a timeZone property. |
| 4574 | + // but ZonedDateTime.with will always reject a timeZone. |
| 4575 | + // |
| 4576 | + // This is because ZonedDateTime.with is about partially replacing |
| 4577 | + // calendar fields: and operations like "change the day and also the |
| 4578 | + // time zone" are ambiguous based on whether you change the day first |
| 4579 | + // or the time zone first (and it's not a *useful* ambiguity). |
| 4580 | + // Instead, you are expected to use `.with()` and `.withTimeZone()` |
| 4581 | + // in whatever order you need. |
4553 | 4582 | if (!forWith) { |
4554 | | - // ZonedDateTime needs a timeZone property |
4555 | | - properties["timeZone"] = randomVariable(forUseAs: .string) |
| 4583 | + if Bool.random() { |
| 4584 | + // Time zones can be offsets, but cannot have seconds |
| 4585 | + generatedOffset = loadString(randomUTCOffsetString(mayHaveSeconds: false)) |
| 4586 | + properties["timeZone"] = generatedOffset |
| 4587 | + } else { |
| 4588 | + properties["timeZone"] = randomTimeZone() |
| 4589 | + } |
4556 | 4590 | } |
4557 | 4591 |
|
4558 | | - // Most of the time this will cause uninteresting errors |
4559 | | - // (it needs to match with the offset), so |
4560 | | - // we generate this with a lower probability |
4561 | | - if probability(0.3) { |
4562 | | - let hours = Int.random(in: 0..<24) |
4563 | | - let minutes = Int.random(in: 0..<60) |
4564 | | - let plusminus = Bool.random() ? "+" : "-"; |
4565 | | - var offset = String(format: "%s%02d:%02d", plusminus, hours, minutes) |
4566 | | - if probability(0.3) { |
4567 | | - offset += ":" |
4568 | | - let seconds = Int.random(in: 0..<60) |
4569 | | - offset += "\(seconds)" |
4570 | | - if probability(0.3) { |
4571 | | - offset += "." |
4572 | | - offset += String(format: "%09d", Int.random(in: 0...999999999)) |
4573 | | - } |
4574 | | - |
4575 | | - offset += "" |
4576 | | - } |
4577 | | - properties["offset"] = loadString(offset) |
| 4592 | + // Most of the time mixing a random offset and timezone |
| 4593 | + // will cause uninteresting errors since they need to match. |
| 4594 | + // If our timezone was a UTC offset, then there's not much harm |
| 4595 | + // in setting the field (it matches!), but otherwise we should generate offsets |
| 4596 | + // with a low probability. |
| 4597 | + if generatedOffset != nil && Bool.random() { |
| 4598 | + // Half the time when we've generated an offset, set the offset field too, it won't clash. |
| 4599 | + properties["offset"] = generatedOffset! |
| 4600 | + } else if probability(0.3) { |
| 4601 | + // Otherwise, with a low probability, generate a random offset. |
| 4602 | + properties["offset"] = loadString(randomUTCOffsetString(mayHaveSeconds: true)) |
4578 | 4603 | } |
4579 | 4604 | } |
4580 | 4605 | return createObject(with: properties) |
@@ -4725,3 +4750,21 @@ public class ProgramBuilder { |
4725 | 4750 |
|
4726 | 4751 | } |
4727 | 4752 |
|
| 4753 | + |
| 4754 | + |
| 4755 | +fileprivate func randomUTCOffsetString(mayHaveSeconds: Bool) -> String { |
| 4756 | + let hours = Int.random(in: 0..<24) |
| 4757 | + // Bias towards zero minutes since that's what most time zones do. |
| 4758 | + let zeroMinutes = probability(0.8) |
| 4759 | + let minutes = zeroMinutes ? 0 : Int.random(in: 0..<60) |
| 4760 | + let plusminus = Bool.random() ? "+" : "-"; |
| 4761 | + var offset = String(format: "%@%02d:%02d", plusminus, hours, minutes) |
| 4762 | + if !zeroMinutes && mayHaveSeconds && probability(0.3) { |
| 4763 | + let seconds = Int.random(in: 0..<60) |
| 4764 | + offset = String(format: "%@:%02d", offset, seconds) |
| 4765 | + if probability(0.3) { |
| 4766 | + offset = String(format: "%@:.%09d", offset, Int.random(in: 0...999999999)) |
| 4767 | + } |
| 4768 | + } |
| 4769 | + return offset |
| 4770 | +} |
0 commit comments