• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JohnSnowLabs / spark-nlp / 4992350528

pending completion
4992350528

Pull #13797

github

GitHub
Merge 424c7ff18 into ef7906c5e
Pull Request #13797: SPARKNLP-835: ProtectedParam and ProtectedFeature

24 of 24 new or added lines in 6 files covered. (100.0%)

8643 of 13129 relevant lines covered (65.83%)

0.66 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.5
/src/main/scala/com/johnsnowlabs/nlp/annotators/DateMatcherUtils.scala
1
/*
2
 * Copyright 2017-2022 John Snow Labs
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *    http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16

17
package com.johnsnowlabs.nlp.annotators
18

19
import com.johnsnowlabs.nlp.util.io.MatchStrategy
20
import com.johnsnowlabs.nlp.util.regex.RuleFactory
21
import org.apache.spark.ml.param._
22

23
import java.util.Calendar
24
import scala.util.matching.Regex
25
import scala.util.{Failure, Success, Try}
26

27
trait DateMatcherUtils extends Params {
28

29
  protected val EMPTY_INIT_ARRAY = Array("")
1✔
30
  protected val SPACE_CHAR = " "
1✔
31

32
  /** Container of a parsed date with identified bounds
33
    *
34
    * @param calendar
35
    *   [[Calendar]] holding parsed date
36
    * @param start
37
    *   start bound of detected match
38
    * @param end
39
    *   end bound of detected match
40
    */
41
  private[annotators] case class MatchedDateTime(calendar: Calendar, start: Int, end: Int)
42

43
  /** Standard formal dates, e.g. 05/17/2014 or 17/05/2014 or 2014/05/17 */
44
  private val formalDate = new Regex(
1✔
45
    "\\b(0?[1-9]|1[012])[-/]([0-2]?[1-9]|[1-3][0-1])[-/](\\d{2,4})\\b",
46
    "month",
47
    "day",
48
    "year")
49
  private val formalDateAlt = new Regex(
1✔
50
    "\\b([0-2]?[1-9]|[1-3][0-1])[-/](0?[1-9]|1[012])[-/](\\d{2,4})\\b",
51
    "day",
52
    "month",
53
    "year")
54
  private val formalDateAlt2 = new Regex(
1✔
55
    "\\b(\\d{2,4})[-/](0?[1-9]|1[012])[-/]([0-2]?[1-9]|[1-3][0-1])\\b",
56
    "year",
57
    "month",
58
    "day")
59
  private val formalDateShort = new Regex("\\b(0?[1-9]|1[012])[-/](\\d{2,4})\\b", "month", "year")
1✔
60

61
  protected val months = Seq(
1✔
62
    "january",
63
    "february",
64
    "march",
65
    "april",
66
    "may",
67
    "june",
68
    "july",
69
    "august",
70
    "september",
71
    "october",
72
    "november",
73
    "december")
74
  protected val shortMonths =
75
    Seq("jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec")
1✔
76

77
  /** Relaxed dates, e.g. March 2nd */
78
  private val relaxedDayNumbered = "\\b([0-2]?[1-9]|[1-3][0-1])(?:st|rd|nd|th)*\\b".r
1✔
79
  private val relaxedMonths = "(?i)" + months
1✔
80
    .zip(shortMonths)
81
    .map(m => m._1 + "|" + m._2)
82
    .mkString("|")
1✔
83
  private val relaxedYear = "\\d{4}\\b|\\B'\\d{2}\\b".r
1✔
84

85
  /** Used for past relative date matches */
86
  val relativePastPattern = " ago"
1✔
87
  val relativeFuturePattern = "in "
1✔
88

89
  /** Relative dates, e.g. tomorrow */
90
  private val relativeDate = "(?i)(next|last)\\s(week|month|year)".r
1✔
91
  private val relativeDateFuture =
92
    "(?i)(in)\\s+(\\d+)\\s+(second|seconds|minute|minutes|hour|hours|day|days|week|weeks|month|months|year|years)".r
1✔
93
  private val relativeDatePast =
94
    "(?i)(\\d+)\\s+(day|days|week|month|year|weeks|months|years)\\s+(ago)".r
1✔
95
  private val relativeDay =
96
    "(?i)(today|tomorrow|yesterday|past tomorrow|[^a-zA-Z0-9]day before yesterday|[^a-zA-Z0-9]day after tomorrow|[^a-zA-Z0-9]day before|[^a-zA-Z0-9]day after)".r
1✔
97
  private val relativeExactDay = "(?i)(next|last|past)\\s(mon|tue|wed|thu|fri)".r
1✔
98

99
  /** standard time representations e.g. 05:42:16 or 5am */
100
  private val clockTime =
101
    new Regex("(?i)([0-2][0-9]):([0-5][0-9])(?::([0-5][0-9]))?", "hour", "minutes", "seconds")
1✔
102
  private val altTime =
103
    new Regex("([0-2]?[0-9])\\.([0-5][0-9])\\.?([0-5][0-9])?", "hour", "minutes", "seconds")
1✔
104
  private val coordTIme = new Regex(
1✔
105
    "([0-2]?[0-9])([0-5][0-9])?\\.?([0-5][0-9])?\\s*(?:h|a\\.?m|p\\.?m)",
106
    "hour",
107
    "minutes",
108
    "seconds")
109
  private val refTime = new Regex("at\\s+([0-9])\\s*([0-5][0-9])*\\s*([0-5][0-9])*")
1✔
110
  protected val amDefinition: Regex = "(?i)(a\\.?m)".r
1✔
111

112
  protected val defaultMonthWhenMissing = 0
1✔
113
  protected val defaultYearWhenMissing: Int = Calendar.getInstance.get(Calendar.YEAR)
1✔
114

115
  /** Date Matcher regex patterns.
116
    *
117
    * @group param
118
    */
119
  val inputFormats: StringArrayParam =
120
    new StringArrayParam(this, "inputFormats", "Date Matcher inputFormats.")
1✔
121

122
  /** @group getParam */
123
  def getInputFormats: Array[String] = $(inputFormats)
1✔
124

125
  /** @group setParam */
126
  def setInputFormats(value: Array[String]): this.type = set(inputFormats, value)
1✔
127

128
  /** Output format of parsed date (Default: `"yyyy/MM/dd"`)
129
    *
130
    * @group param
131
    */
132
  val outputFormat: Param[String] =
133
    new Param(this, "outputFormat", "Output format of parsed date")
1✔
134

135
  /** @group getParam */
136
  def getOutputFormat: String = $(outputFormat)
1✔
137

138
  /** @group setParam */
139
  def setOutputFormat(value: String): this.type = set(outputFormat, value)
1✔
140

141
  /** Add an anchor year for the relative dates such as a day after tomorrow (Default: `-1`). If
142
    * it is not set, the by default it will use the current year. Example: 2021
143
    *
144
    * @group param
145
    */
146
  val anchorDateYear: Param[Int] = new IntParam(
1✔
147
    this,
148
    "anchorDateYear",
1✔
149
    "Add an anchor year for the relative dates such as a day after tomorrow. If not set it will use the current year. Example: 2021")
1✔
150

151
  /** @group setParam */
152
  def setAnchorDateYear(value: Int): this.type = {
153
    require(value <= 9999 || value >= 999, "The year must be between 999 and 9999")
×
154
    set(anchorDateYear, value)
1✔
155
  }
156

157
  /** @group getParam */
158
  def getAnchorDateYear: Int = $(anchorDateYear)
×
159

160
  /** Add an anchor month for the relative dates such as a day after tomorrow (Default: `-1`). By
161
    * default it will use the current month. Month values start from `1`, so `1` stands for
162
    * January.
163
    *
164
    * @group param
165
    */
166
  val anchorDateMonth: Param[Int] = new IntParam(
1✔
167
    this,
168
    "anchorDateMonth",
1✔
169
    "Add an anchor month for the relative dates such as a day after tomorrow. If not set it will use the current month. Example: 1 which means January")
1✔
170

171
  /** @group setParam */
172
  def setAnchorDateMonth(value: Int): this.type = {
173
    val normalizedMonth = value - 1
1✔
174
    require(
1✔
175
      normalizedMonth <= 11 || normalizedMonth >= 0,
1✔
176
      "The month value is 1-based. e.g., 1 for January. The value must be between 1 and 12")
×
177
    set(anchorDateMonth, normalizedMonth)
1✔
178
  }
179

180
  /** @group getParam */
181
  def getAnchorDateMonth: Int = $(anchorDateMonth) + 1
×
182

183
  /** Add an anchor day for the relative dates such as a day after tomorrow (Default: `-1`). By
184
    * default it will use the current day. The first day of the month has value 1.
185
    *
186
    * @group param
187
    */
188
  val anchorDateDay: Param[Int] = new IntParam(
1✔
189
    this,
190
    "anchorDateDay",
1✔
191
    "Add an anchor day of the day for the relative dates such as a day after tomorrow. If not set it will use the current day. Example: 11")
1✔
192

193
  /** @group setParam */
194
  def setAnchorDateDay(value: Int): this.type = {
195
    require(
1✔
196
      value <= 31 || value >= 1,
1✔
197
      "The day value starts from 1. The value must be between 1 and 31")
×
198
    set(anchorDateDay, value)
1✔
199
  }
200

201
  /** @group getParam */
202
  def getAnchorDateDay: Int = $(anchorDateDay)
×
203

204
  /** Whether to interpret dates as MM/DD/YYYY instead of DD/MM/YYYY (Default: `true`)
205
    *
206
    * @group param
207
    */
208
  val readMonthFirst: BooleanParam = new BooleanParam(
1✔
209
    this,
210
    "readMonthFirst",
1✔
211
    "Whether to interpret dates as MM/DD/YYYY instead of DD/MM/YYYY")
1✔
212

213
  /** @group setParam */
214
  def setReadMonthFirst(value: Boolean): this.type = set(readMonthFirst, value)
×
215

216
  /** @group getParam */
217
  def getReadMonthFirst: Boolean = $(readMonthFirst)
×
218

219
  /** Which day to set when it is missing from parsed input (Default: `1`)
220
    *
221
    * @group param
222
    */
223
  val defaultDayWhenMissing: IntParam = new IntParam(
1✔
224
    this,
225
    "defaultDayWhenMissing",
1✔
226
    "Which day to set when it is missing from parsed input")
1✔
227

228
  /** @group setParam */
229
  def setDefaultDayWhenMissing(value: Int): this.type = set(defaultDayWhenMissing, value)
×
230

231
  /** @group getParam */
232
  def getDefaultDayWhenMissing: Int = $(defaultDayWhenMissing)
×
233

234
  /** Source language for explicit translation
235
    *
236
    * @group param
237
    */
238
  val sourceLanguage: Param[String] =
239
    new Param(this, "sourceLanguage", "source language for explicit translation")
1✔
240

241
  /** To get to use or not the multi-language translation.
242
    *
243
    * @group getParam
244
    */
245
  def getSourceLanguage: String = $(sourceLanguage)
1✔
246

247
  /** To set or not the source language for explicit translation.
248
    *
249
    * @group setParam
250
    */
251
  def setSourceLanguage(value: String): this.type = set(sourceLanguage, value)
1✔
252

253
  /** Matched strategy to search relaxed dates by ordered rules by more exhaustive to less
254
    * Strategy
255
    *
256
    * @group param
257
    */
258
  val relaxedFactoryStrategy: Param[String] =
259
    new Param(this, "relaxedFactoryStrategy", "Matched Strategy to searches relaxed dates")
1✔
260

261
  /** To set matched strategy to search relaxed dates by ordered rules by more exhaustive to less
262
    * Strategy
263
    *
264
    * @group param
265
    */
266
  def setRelaxedFactoryStrategy(
267
      matchStrategy: MatchStrategy.Format = MatchStrategy.MATCH_FIRST): this.type = {
268
    set(relaxedFactoryStrategy, matchStrategy.toString)
1✔
269
  }
270

271
  /** To get matched strategy to search relaxed dates by ordered rules by more exhaustive to less
272
    * Strategy
273
    *
274
    * @group param
275
    */
276
  def getRelaxedFactoryStrategy: String = $(relaxedFactoryStrategy)
1✔
277

278
  setDefault(
1✔
279
    inputFormats -> Array(""),
1✔
280
    outputFormat -> "yyyy/MM/dd",
1✔
281
    anchorDateYear -> -1,
1✔
282
    anchorDateMonth -> -1,
1✔
283
    anchorDateDay -> -1,
1✔
284
    readMonthFirst -> true,
1✔
285
    defaultDayWhenMissing -> 1,
1✔
286
    sourceLanguage -> "en",
1✔
287
    relaxedFactoryStrategy -> MatchStrategy.MATCH_FIRST.toString)
1✔
288

289
  protected val formalFactoryInputFormats = new RuleFactory(MatchStrategy.MATCH_ALL)
1✔
290

291
  protected val formalInputFormats: Map[String, Regex] = Map(
1✔
292
    "yyyy/dd/MM" -> new Regex(
1✔
293
      "\\b(\\d{2,4})[-/]([0-2]?[1-9]|[1-3][0-1])[-/](0?[1-9]|1[012])\\b",
294
      "year",
295
      "day",
296
      "month"),
297
    "dd/MM/yyyy" -> new Regex(
1✔
298
      "\\b([0-2]?[1-9]|[1-3][0-1])[-/](0?[1-9]|1[012])[-/](\\d{2,4})\\b",
299
      "day",
300
      "month",
301
      "year"),
302
    "yyyy/MM/dd" -> new Regex(
1✔
303
      "\\b(\\d{2,4})[-/](0?[1-9]|1[012])[-/]([0-2]?[1-9]|[1-3][0-1])\\b",
304
      "year",
305
      "month",
306
      "day"),
307
    "yyyy/MM" -> new Regex("\\b(\\d{2,4})[-/](0?[1-9]|1[012])\\b", "year", "month"),
1✔
308
    "dd/MM" -> new Regex("\\b([0-2]?[1-9]|[1-3][0-1])[-/](0?[1-9]|1[012])\\b", "day", "month"),
1✔
309
    "MM/dd" -> new Regex("\\b(0?[1-9]|1[012])[-/]([0-2]?[1-9]|[1-3][0-1])\\b", "month", "day"),
1✔
310
    "MM/yyyy" -> new Regex("\\s+\\b(0?[1-9]|1[012])[-/](\\d{2,4})\\b", "month", "year"),
1✔
311
    "yyyy-dd-MM" -> new Regex(
1✔
312
      "\\b(\\d{2,4})[-/]([0-2]?[1-9]|[1-3][0-1])[-/](0?[1-9]|1[012])\\b",
313
      "year",
314
      "day",
315
      "month"),
316
    "dd-MM-yyyy" -> new Regex(
1✔
317
      "\\b([0-2]?[1-9]|[1-3][0-1])[-/](0?[1-9]|1[012])[-/](\\d{2,4})\\b",
318
      "day",
319
      "month",
320
      "year"),
321
    "yyyy-MM-dd" -> new Regex(
1✔
322
      "\\b(\\d{2,4})[-/](0?[1-9]|1[012])[-/]([0-2]?[1-9]|[1-3][0-1])\\b",
323
      "year",
324
      "month",
325
      "day"),
326
    "dd-MM" -> new Regex("\\b([0-2]?[1-9]|[1-3][0-1])[-/](0?[1-9]|1[012])\\b", "day", "month"),
1✔
327
    "yyyy-MM" -> new Regex("\\b(\\d{2,4})[-/](0?[1-9]|1[012])\\b", "year", "month"),
1✔
328
    "dd-MM" -> new Regex("\\b([0-2]?[1-9]|[1-3][0-1])[-/](0?[1-9]|1[012])\\b", "day", "month"),
1✔
329
    "MM-dd" -> new Regex("\\b(0?[1-9]|1[012])[-/]([0-2]?[1-9]|[1-3][0-1])\\b", "month", "day"),
1✔
330
    "MM-yyyy" -> new Regex("\\b(0?[1-9]|1[012])[-/](\\d{2,4})\\b", "month", "year"),
1✔
331
    "yyyy" -> new Regex("\\b(\\d{4})\\b", "year"))
1✔
332

333
  /** Searches formal date by ordered rules Matching strategy is to find first match only, ignore
334
    * additional matches from then Any 4 digit year will be assumed a year, any 2 digit year will
335
    * be as part of XX Century e.g. 1954
336
    */
337
  protected val formalFactory = new RuleFactory(MatchStrategy.MATCH_ALL)
1✔
338

339
  if ($(readMonthFirst))
1✔
340
    formalFactory
341
      .addRule(formalDate, "formal date with month at first")
1✔
342
      .addRule(formalDateAlt, "formal date with day at first")
1✔
343
      .addRule(formalDateAlt2, "formal date with year at beginning")
1✔
344
      .addRule(formalDateShort, "formal date short version")
1✔
345
  else
346
    formalFactory
347
      .addRule(formalDateAlt, "formal date with day at first")
×
348
      .addRule(formalDate, "formal date with month at first")
×
349
      .addRule(formalDateAlt2, "formal date with year at beginning")
×
350
      .addRule(formalDateShort, "formal date short version")
×
351

352
  /** Searches relaxed dates by ordered rules by more exhaustive to less Strategy. Auto completes
353
    * short versions of months. Any two digit year is considered to be XX century
354
    */
355
  protected lazy val relaxedFactory: RuleFactory = new RuleFactory(getRelaxedFactoryStrategy)
356
    .addRule(relaxedDayNumbered, "relaxed days")
357
    .addRule(relaxedMonths.r, "relaxed months exclusive")
358
    .addRule(relaxedYear, "relaxed year")
359

360
  /** extracts relative dates. Strategy is to get only first match. Will always assume relative
361
    * day from current time at processing ToDo: Support relative dates from input date
362
    */
363
  protected val relativeFactory: RuleFactory = new RuleFactory(MatchStrategy.MATCH_FIRST)
1✔
364
    .addRule(relativeDate, "relative dates")
1✔
365

366
  protected val relativePastFactory: RuleFactory = new RuleFactory(MatchStrategy.MATCH_FIRST)
1✔
367
    .addRule(relativeDatePast, "relative dates in the past")
1✔
368

369
  protected val relativeFutureFactory: RuleFactory = new RuleFactory(MatchStrategy.MATCH_FIRST)
1✔
370
    .addRule(relativeDateFuture, "relative dates in the future")
1✔
371

372
  /** Searches for relative informal dates such as today or the day after tomorrow */
373
  protected val tyFactory: RuleFactory = new RuleFactory(MatchStrategy.MATCH_FIRST)
1✔
374
    .addRule(relativeDay, "relative days")
1✔
375

376
  /** Searches for exactly provided days of the week. Always relative from current time at
377
    * processing
378
    */
379
  protected val relativeExactFactory: RuleFactory = new RuleFactory(MatchStrategy.MATCH_ALL)
1✔
380
    .addRule(relativeExactDay, "relative precise dates")
1✔
381

382
  /** Searches for times of the day dateTime If any dates found previously, keep it as part of the
383
    * final result text target document
384
    *
385
    * @return
386
    *   a final possible date if any found
387
    */
388
  protected val timeFactory: RuleFactory = new RuleFactory(MatchStrategy.MATCH_FIRST)
1✔
389
    .addRule(clockTime, "standard time extraction")
1✔
390
    .addRule(altTime, "alternative time format")
1✔
391
    .addRule(coordTIme, "coordinate like time")
1✔
392
    .addRule(refTime, "referred time")
1✔
393

394
  protected def calculateAnchorCalendar(): Calendar = {
395
    val calendar = Calendar.getInstance()
1✔
396

397
    val anchorYear =
398
      if ($(anchorDateYear) != -1) $(anchorDateYear) else calendar.get(Calendar.YEAR)
1✔
399
    val anchorMonth =
400
      if ($(anchorDateMonth) != -1) $(anchorDateMonth) else calendar.get(Calendar.MONTH)
1✔
401
    val anchorDay =
402
      if ($(anchorDateDay) != -1) $(anchorDateDay) else calendar.get(Calendar.DAY_OF_MONTH)
1✔
403

404
    calendar.set(anchorYear, anchorMonth, anchorDay)
1✔
405
    calendar
406
  }
407

408
  protected def formalDateContentParse(date: RuleFactory.RuleMatch): MatchedDateTime = {
409
    val formalDate = date.content
1✔
410
    val calendar = new Calendar.Builder()
1✔
411

412
    def processYear = {
413
      Try(formalDate.group("year")) match {
1✔
414
        case Success(_) =>
415
          if (formalDate.group("year").toInt > 999)
1✔
416
            formalDate.group("year").toInt
1✔
417

418
          /** If year found is greater than <10> years from now, assume text is talking about 20th
419
            * century
420
            */
421
          else if (formalDate.group("year").toInt > Calendar.getInstance
1✔
422
              .get(Calendar.YEAR)
423
              .toString
424
              .takeRight(2)
425
              .toInt + 10)
1✔
426
            formalDate.group("year").toInt + 1900
1✔
427
          else
428
            formalDate.group("year").toInt + 2000
1✔
429
        case Failure(_) => Calendar.getInstance().get(Calendar.YEAR)
×
430
      }
431
    }
432

433
    def processMonth = {
434
      Try(formalDate.group("month")) match {
1✔
435
        case Success(_) => formalDate.group("month").toInt - 1
1✔
436
        case Failure(_) => 0
1✔
437
      }
438
    }
439

440
    def processDay = {
441
      Try(formalDate.group("day")) match {
1✔
442
        case Success(_) =>
443
          if (formalDate.groupCount == 3) formalDate.group("day").toInt
1✔
444
          else $(defaultDayWhenMissing)
×
445
        case Failure(_) => 1
1✔
446
      }
447
    }
448

449
    MatchedDateTime(
1✔
450
      calendar.setDate(processYear, processMonth, processDay).build(),
1✔
451
      formalDate.start,
1✔
452
      formalDate.end)
1✔
453
  }
454

455
  protected def relativeDateFutureContentParse(date: RuleFactory.RuleMatch): MatchedDateTime = {
456

457
    val relativeDateFuture = date.content
1✔
458

459
    val calendar = calculateAnchorCalendar()
1✔
460
    val amount = relativeDateFuture.group(2).toInt
1✔
461

462
    relativeDateFuture.group(3) match {
1✔
463
      case "hour" | "hours" => calendar.add(Calendar.HOUR_OF_DAY, amount)
1✔
464
      case "day" | "days" => calendar.add(Calendar.DAY_OF_MONTH, amount)
1✔
465
      case "week" | "weeks" => calendar.add(Calendar.WEEK_OF_MONTH, amount)
1✔
466
      case "month" | "months" => calendar.add(Calendar.MONTH, amount)
1✔
467
      case "year" | "years" => calendar.add(Calendar.YEAR, amount)
1✔
468
      case _ =>
×
469
    }
470
    MatchedDateTime(calendar, relativeDateFuture.start, relativeDateFuture.end)
1✔
471
  }
472

473
  protected def relativeDatePastContentParse(date: RuleFactory.RuleMatch): MatchedDateTime = {
474

475
    val relativeDatePast = date.content
1✔
476
    val calendar = calculateAnchorCalendar()
1✔
477
    val amount = -relativeDatePast.group(1).toInt
1✔
478

479
    relativeDatePast.group(2) match {
1✔
480
      case "day" | "days" => calendar.add(Calendar.DAY_OF_MONTH, amount)
1✔
481
      case "week" | "weeks" => calendar.add(Calendar.WEEK_OF_MONTH, amount)
1✔
482
      case "month" | "months" => calendar.add(Calendar.MONTH, amount)
×
483
      case "year" | "years" => calendar.add(Calendar.YEAR, amount)
1✔
484
      case _ =>
×
485
    }
486
    MatchedDateTime(calendar, relativeDatePast.start, relativeDatePast.end)
1✔
487
  }
488

489
  protected def relativeDateContentParse(date: RuleFactory.RuleMatch): MatchedDateTime = {
490
    val relativeDate = date.content
1✔
491
    val calendar = calculateAnchorCalendar()
1✔
492
    val amount = if (relativeDate.group(1) == "next") 1 else -1
×
493
    relativeDate.group(2) match {
1✔
494
      case "week" => calendar.add(Calendar.WEEK_OF_MONTH, amount)
1✔
495
      case "month" => calendar.add(Calendar.MONTH, amount)
1✔
496
      case "year" => calendar.add(Calendar.YEAR, amount)
1✔
497
      case _ =>
×
498
    }
499
    MatchedDateTime(calendar, relativeDate.start, relativeDate.end)
1✔
500
  }
501

502
  def tomorrowYesterdayContentParse(date: RuleFactory.RuleMatch): MatchedDateTime = {
503
    val tyDate = date.content
1✔
504
    val calendar = calculateAnchorCalendar()
1✔
505
    tyDate.matched.toLowerCase match {
1✔
506
      case "today" =>
507
        MatchedDateTime(calendar, tyDate.start, tyDate.end)
1✔
508
      case "tomorrow" =>
509
        calendar.add(Calendar.DAY_OF_MONTH, 1)
1✔
510
        MatchedDateTime(calendar, tyDate.start, tyDate.end)
1✔
511
      case "past tomorrow" =>
512
        calendar.add(Calendar.DAY_OF_MONTH, 2)
×
513
        MatchedDateTime(calendar, tyDate.start, tyDate.end)
×
514
      case "yesterday" =>
515
        calendar.add(Calendar.DAY_OF_MONTH, -1)
1✔
516
        MatchedDateTime(calendar, tyDate.start, tyDate.end)
1✔
517
      case " day after" =>
518
        calendar.add(Calendar.DAY_OF_MONTH, 1)
1✔
519
        MatchedDateTime(calendar, tyDate.start, tyDate.end)
1✔
520
      case " day before" =>
521
        calendar.add(Calendar.DAY_OF_MONTH, -1)
1✔
522
        MatchedDateTime(calendar, tyDate.start, tyDate.end)
1✔
523
      case " day after tomorrow" =>
524
        calendar.add(Calendar.DAY_OF_MONTH, 2)
1✔
525
        MatchedDateTime(calendar, tyDate.start, tyDate.end)
1✔
526
      case " day before yesterday" =>
527
        calendar.add(Calendar.DAY_OF_MONTH, -2)
1✔
528
        MatchedDateTime(calendar, tyDate.start, tyDate.end)
1✔
529
      case _ => MatchedDateTime(calendar, tyDate.start, tyDate.end)
×
530
    }
531
  }
532

533
  def relativeExactContentParse(possibleDate: RuleFactory.RuleMatch): MatchedDateTime = {
534
    val relativeDate = possibleDate.content
1✔
535
    val calendar = calculateAnchorCalendar()
1✔
536
    val amount = if (relativeDate.group(1) == "next") 1 else -1
1✔
537
    calendar.add(Calendar.DAY_OF_MONTH, amount)
1✔
538
    relativeDate.group(2) match {
1✔
539
      case "mon" =>
540
        while (calendar.get(Calendar.DAY_OF_WEEK) != Calendar.MONDAY) {
1✔
541
          calendar.add(Calendar.DAY_OF_MONTH, amount)
1✔
542
        }
543
      case "tue" =>
544
        while (calendar.get(Calendar.DAY_OF_WEEK) != Calendar.TUESDAY) {
×
545
          calendar.add(Calendar.DAY_OF_MONTH, amount)
×
546
        }
547
      case "wed" =>
548
        while (calendar.get(Calendar.DAY_OF_WEEK) != Calendar.WEDNESDAY) {
1✔
549
          calendar.add(Calendar.DAY_OF_MONTH, amount)
1✔
550
        }
551
      case "thu" =>
552
        while (calendar.get(Calendar.DAY_OF_WEEK) != Calendar.THURSDAY) {
1✔
553
          calendar.add(Calendar.DAY_OF_MONTH, amount)
1✔
554
        }
555
      case "fri" =>
556
        while (calendar.get(Calendar.DAY_OF_WEEK) != Calendar.FRIDAY) {
1✔
557
          calendar.add(Calendar.DAY_OF_MONTH, amount)
1✔
558
        }
559
      case _ =>
×
560
    }
561
    MatchedDateTime(calendar, relativeDate.start, relativeDate.end)
1✔
562
  }
563

564
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc