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

JohnSnowLabs / spark-nlp / 12342163763

15 Dec 2024 08:51PM CUT coverage: 60.373% (-0.01%) from 60.386%
12342163763

Pull #14481

github

web-flow
Merge bad778d6c into 24824367e
Pull Request #14481: ignore html as linguist-vendored

9126 of 15116 relevant lines covered (60.37%)

0.6 hits per line

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

87.84
/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")
1✔
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,
×
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)
1✔
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
  /** Whether to aggressively attempt to find date matches, even in ambiguous or less common
279
    * formats (Default: `false`)
280
    *
281
    * @group param
282
    */
283
  val aggressiveMatching: BooleanParam = new BooleanParam(
1✔
284
    this,
285
    "aggressiveMatching",
1✔
286
    "Whether to aggressively attempt to find date matches, even in ambiguous or less common formats")
1✔
287

288
  /** To set aggressive matching Strategy
289
    *
290
    * @group param
291
    */
292
  def setAggressiveMatching(value: Boolean): this.type = set(aggressiveMatching, value)
1✔
293

294
  /** To get aggressive matching Strategy
295
    *
296
    * @group param
297
    */
298
  def getAggressiveMatching: Boolean = $(aggressiveMatching)
1✔
299

300
  setDefault(
1✔
301
    inputFormats -> Array(""),
1✔
302
    outputFormat -> "yyyy/MM/dd",
1✔
303
    anchorDateYear -> -1,
1✔
304
    anchorDateMonth -> -1,
1✔
305
    anchorDateDay -> -1,
1✔
306
    readMonthFirst -> true,
1✔
307
    defaultDayWhenMissing -> 1,
1✔
308
    sourceLanguage -> "en",
1✔
309
    relaxedFactoryStrategy -> MatchStrategy.MATCH_FIRST.toString,
1✔
310
    aggressiveMatching -> false)
1✔
311

312
  protected val formalFactoryInputFormats = new RuleFactory(MatchStrategy.MATCH_ALL)
1✔
313

314
  protected val formalInputFormats: Map[String, Regex] = Map(
1✔
315
    "yyyy/dd/MM" -> new Regex(
1✔
316
      "\\b(\\d{2,4})[-/]([0-2]?[1-9]|[1-3][0-1])[-/](0?[1-9]|1[012])\\b",
317
      "year",
318
      "day",
319
      "month"),
320
    "dd/MM/yyyy" -> new Regex(
1✔
321
      "\\b([0-2]?[1-9]|[1-3][0-1])[-/](0?[1-9]|1[012])[-/](\\d{2,4})\\b",
322
      "day",
323
      "month",
324
      "year"),
325
    "yyyy/MM/dd" -> new Regex(
1✔
326
      "\\b(\\d{2,4})[-/](0?[1-9]|1[012])[-/]([0-2]?[1-9]|[1-3][0-1])\\b",
327
      "year",
328
      "month",
329
      "day"),
330
    "yyyy/MM" -> new Regex("\\b(\\d{2,4})[-/](0?[1-9]|1[012])\\b", "year", "month"),
1✔
331
    "dd/MM" -> new Regex("\\b([0-2]?[1-9]|[1-3][0-1])[-/](0?[1-9]|1[012])\\b", "day", "month"),
1✔
332
    "MM/dd" -> new Regex("\\b(0?[1-9]|1[012])[-/]([0-2]?[1-9]|[1-3][0-1])\\b", "month", "day"),
1✔
333
    "MM/yyyy" -> new Regex("\\s+\\b(0?[1-9]|1[012])[-/](\\d{2,4})\\b", "month", "year"),
1✔
334
    "yyyy-dd-MM" -> new Regex(
1✔
335
      "\\b(\\d{2,4})[-/]([0-2]?[1-9]|[1-3][0-1])[-/](0?[1-9]|1[012])\\b",
336
      "year",
337
      "day",
338
      "month"),
339
    "dd-MM-yyyy" -> new Regex(
1✔
340
      "\\b([0-2]?[1-9]|[1-3][0-1])[-/](0?[1-9]|1[012])[-/](\\d{2,4})\\b",
341
      "day",
342
      "month",
343
      "year"),
344
    "yyyy-MM-dd" -> new Regex(
1✔
345
      "\\b(\\d{2,4})[-/](0?[1-9]|1[012])[-/]([0-2]?[1-9]|[1-3][0-1])\\b",
346
      "year",
347
      "month",
348
      "day"),
349
    "dd-MM" -> new Regex("\\b([0-2]?[1-9]|[1-3][0-1])[-/](0?[1-9]|1[012])\\b", "day", "month"),
1✔
350
    "yyyy-MM" -> new Regex("\\b(\\d{2,4})[-/](0?[1-9]|1[012])\\b", "year", "month"),
1✔
351
    "dd-MM" -> new Regex("\\b([0-2]?[1-9]|[1-3][0-1])[-/](0?[1-9]|1[012])\\b", "day", "month"),
1✔
352
    "MM-dd" -> new Regex("\\b(0?[1-9]|1[012])[-/]([0-2]?[1-9]|[1-3][0-1])\\b", "month", "day"),
1✔
353
    "MM-yyyy" -> new Regex("\\b(0?[1-9]|1[012])[-/](\\d{2,4})\\b", "month", "year"),
1✔
354
    "yyyy" -> new Regex("\\b(\\d{4})\\b", "year"))
1✔
355

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

362
  if ($(readMonthFirst))
1✔
363
    formalFactory
364
      .addRule(formalDate, "formal date with month at first")
1✔
365
      .addRule(formalDateAlt, "formal date with day at first")
1✔
366
      .addRule(formalDateAlt2, "formal date with year at beginning")
1✔
367
      .addRule(formalDateShort, "formal date short version")
1✔
368
  else
369
    formalFactory
370
      .addRule(formalDateAlt, "formal date with day at first")
×
371
      .addRule(formalDate, "formal date with month at first")
×
372
      .addRule(formalDateAlt2, "formal date with year at beginning")
×
373
      .addRule(formalDateShort, "formal date short version")
×
374

375
  /** Searches relaxed dates by ordered rules by more exhaustive to less Strategy. Auto completes
376
    * short versions of months. Any two digit year is considered to be XX century
377
    */
378
  protected lazy val relaxedFactory: RuleFactory = new RuleFactory(getRelaxedFactoryStrategy)
379
    .addRule(relaxedDayNumbered, "relaxed days")
380
    .addRule(relaxedMonths.r, "relaxed months exclusive")
381
    .addRule(relaxedYear, "relaxed year")
382

383
  /** extracts relative dates. Strategy is to get only first match. Will always assume relative
384
    * day from current time at processing ToDo: Support relative dates from input date
385
    */
386
  protected val relativeFactory: RuleFactory = new RuleFactory(MatchStrategy.MATCH_FIRST)
1✔
387
    .addRule(relativeDate, "relative dates")
1✔
388

389
  protected val relativePastFactory: RuleFactory = new RuleFactory(MatchStrategy.MATCH_FIRST)
1✔
390
    .addRule(relativeDatePast, "relative dates in the past")
1✔
391

392
  protected val relativeFutureFactory: RuleFactory = new RuleFactory(MatchStrategy.MATCH_FIRST)
1✔
393
    .addRule(relativeDateFuture, "relative dates in the future")
1✔
394

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

399
  /** Searches for exactly provided days of the week. Always relative from current time at
400
    * processing
401
    */
402
  protected val relativeExactFactory: RuleFactory = new RuleFactory(MatchStrategy.MATCH_ALL)
1✔
403
    .addRule(relativeExactDay, "relative precise dates")
1✔
404

405
  /** Searches for times of the day dateTime If any dates found previously, keep it as part of the
406
    * final result text target document
407
    *
408
    * @return
409
    *   a final possible date if any found
410
    */
411
  protected val timeFactory: RuleFactory = new RuleFactory(MatchStrategy.MATCH_FIRST)
1✔
412
    .addRule(clockTime, "standard time extraction")
1✔
413
    .addRule(altTime, "alternative time format")
1✔
414
    .addRule(coordTIme, "coordinate like time")
1✔
415
    .addRule(refTime, "referred time")
1✔
416

417
  protected def calculateAnchorCalendar(): Calendar = {
418
    val calendar = Calendar.getInstance()
1✔
419

420
    val anchorYear =
421
      if ($(anchorDateYear) != -1) $(anchorDateYear) else calendar.get(Calendar.YEAR)
1✔
422
    val anchorMonth =
423
      if ($(anchorDateMonth) != -1) $(anchorDateMonth) else calendar.get(Calendar.MONTH)
1✔
424
    val anchorDay =
425
      if ($(anchorDateDay) != -1) $(anchorDateDay) else calendar.get(Calendar.DAY_OF_MONTH)
1✔
426

427
    calendar.set(anchorYear, anchorMonth, anchorDay)
1✔
428
    calendar
429
  }
430

431
  protected def formalDateContentParse(date: RuleFactory.RuleMatch): MatchedDateTime = {
432
    val formalDate = date.content
1✔
433
    val calendar = new Calendar.Builder()
1✔
434

435
    def processYear = {
436
      Try(formalDate.group("year")) match {
1✔
437
        case Success(_) =>
438
          if (formalDate.group("year").toInt > 999)
1✔
439
            formalDate.group("year").toInt
1✔
440

441
          /** If year found is greater than <10> years from now, assume text is talking about 20th
442
            * century
443
            */
444
          else if (formalDate.group("year").toInt > Calendar.getInstance
1✔
445
              .get(Calendar.YEAR)
446
              .toString
447
              .takeRight(2)
448
              .toInt + 10)
1✔
449
            formalDate.group("year").toInt + 1900
1✔
450
          else
451
            formalDate.group("year").toInt + 2000
1✔
452
        case Failure(_) => Calendar.getInstance().get(Calendar.YEAR)
×
453
      }
454
    }
455

456
    def processMonth = {
457
      Try(formalDate.group("month")) match {
1✔
458
        case Success(_) => formalDate.group("month").toInt - 1
1✔
459
        case Failure(_) => 0
1✔
460
      }
461
    }
462

463
    def processDay = {
464
      Try(formalDate.group("day")) match {
1✔
465
        case Success(_) =>
466
          if (formalDate.groupCount == 3) formalDate.group("day").toInt
1✔
467
          else $(defaultDayWhenMissing)
×
468
        case Failure(_) => 1
1✔
469
      }
470
    }
471

472
    MatchedDateTime(
1✔
473
      calendar.setDate(processYear, processMonth, processDay).build(),
1✔
474
      formalDate.start,
1✔
475
      formalDate.end)
1✔
476
  }
477

478
  protected def relativeDateFutureContentParse(date: RuleFactory.RuleMatch): MatchedDateTime = {
479

480
    val relativeDateFuture = date.content
1✔
481

482
    val calendar = calculateAnchorCalendar()
1✔
483
    val amount = relativeDateFuture.group(2).toInt
1✔
484

485
    relativeDateFuture.group(3) match {
1✔
486
      case "hour" | "hours" => calendar.add(Calendar.HOUR_OF_DAY, amount)
1✔
487
      case "day" | "days" => calendar.add(Calendar.DAY_OF_MONTH, amount)
1✔
488
      case "week" | "weeks" => calendar.add(Calendar.WEEK_OF_MONTH, amount)
1✔
489
      case "month" | "months" => calendar.add(Calendar.MONTH, amount)
1✔
490
      case "year" | "years" => calendar.add(Calendar.YEAR, amount)
1✔
491
      case _ =>
×
492
    }
493
    MatchedDateTime(calendar, relativeDateFuture.start, relativeDateFuture.end)
1✔
494
  }
495

496
  protected def relativeDatePastContentParse(date: RuleFactory.RuleMatch): MatchedDateTime = {
497

498
    val relativeDatePast = date.content
1✔
499
    val calendar = calculateAnchorCalendar()
1✔
500
    val amount = -relativeDatePast.group(1).toInt
1✔
501

502
    relativeDatePast.group(2) match {
1✔
503
      case "day" | "days" => calendar.add(Calendar.DAY_OF_MONTH, amount)
1✔
504
      case "week" | "weeks" => calendar.add(Calendar.WEEK_OF_MONTH, amount)
1✔
505
      case "month" | "months" => calendar.add(Calendar.MONTH, amount)
×
506
      case "year" | "years" => calendar.add(Calendar.YEAR, amount)
1✔
507
      case _ =>
×
508
    }
509
    MatchedDateTime(calendar, relativeDatePast.start, relativeDatePast.end)
1✔
510
  }
511

512
  protected def relativeDateContentParse(date: RuleFactory.RuleMatch): MatchedDateTime = {
513
    val relativeDate = date.content
1✔
514
    val calendar = calculateAnchorCalendar()
1✔
515
    val amount = if (relativeDate.group(1) == "next") 1 else -1
1✔
516
    relativeDate.group(2) match {
1✔
517
      case "week" => calendar.add(Calendar.WEEK_OF_MONTH, amount)
1✔
518
      case "month" => calendar.add(Calendar.MONTH, amount)
1✔
519
      case "year" => calendar.add(Calendar.YEAR, amount)
1✔
520
      case _ =>
×
521
    }
522
    MatchedDateTime(calendar, relativeDate.start, relativeDate.end)
1✔
523
  }
524

525
  def tomorrowYesterdayContentParse(date: RuleFactory.RuleMatch): MatchedDateTime = {
526
    val tyDate = date.content
1✔
527
    val calendar = calculateAnchorCalendar()
1✔
528
    tyDate.matched.toLowerCase match {
1✔
529
      case "today" =>
530
        MatchedDateTime(calendar, tyDate.start, tyDate.end)
1✔
531
      case "tomorrow" =>
532
        calendar.add(Calendar.DAY_OF_MONTH, 1)
1✔
533
        MatchedDateTime(calendar, tyDate.start, tyDate.end)
1✔
534
      case "past tomorrow" =>
535
        calendar.add(Calendar.DAY_OF_MONTH, 2)
×
536
        MatchedDateTime(calendar, tyDate.start, tyDate.end)
×
537
      case "yesterday" =>
538
        calendar.add(Calendar.DAY_OF_MONTH, -1)
1✔
539
        MatchedDateTime(calendar, tyDate.start, tyDate.end)
1✔
540
      case " day after" =>
541
        calendar.add(Calendar.DAY_OF_MONTH, 1)
1✔
542
        MatchedDateTime(calendar, tyDate.start, tyDate.end)
1✔
543
      case " day before" =>
544
        calendar.add(Calendar.DAY_OF_MONTH, -1)
1✔
545
        MatchedDateTime(calendar, tyDate.start, tyDate.end)
1✔
546
      case " day after tomorrow" =>
547
        calendar.add(Calendar.DAY_OF_MONTH, 2)
1✔
548
        MatchedDateTime(calendar, tyDate.start, tyDate.end)
1✔
549
      case " day before yesterday" =>
550
        calendar.add(Calendar.DAY_OF_MONTH, -2)
1✔
551
        MatchedDateTime(calendar, tyDate.start, tyDate.end)
1✔
552
      case _ => MatchedDateTime(calendar, tyDate.start, tyDate.end)
×
553
    }
554
  }
555

556
  def relativeExactContentParse(possibleDate: RuleFactory.RuleMatch): MatchedDateTime = {
557
    val relativeDate = possibleDate.content
1✔
558
    val calendar = calculateAnchorCalendar()
1✔
559
    val amount = if (relativeDate.group(1) == "next") 1 else -1
1✔
560
    calendar.add(Calendar.DAY_OF_MONTH, amount)
1✔
561
    relativeDate.group(2) match {
1✔
562
      case "mon" =>
563
        while (calendar.get(Calendar.DAY_OF_WEEK) != Calendar.MONDAY) {
×
564
          calendar.add(Calendar.DAY_OF_MONTH, amount)
×
565
        }
566
      case "tue" =>
567
        while (calendar.get(Calendar.DAY_OF_WEEK) != Calendar.TUESDAY) {
×
568
          calendar.add(Calendar.DAY_OF_MONTH, amount)
×
569
        }
570
      case "wed" =>
571
        while (calendar.get(Calendar.DAY_OF_WEEK) != Calendar.WEDNESDAY) {
1✔
572
          calendar.add(Calendar.DAY_OF_MONTH, amount)
1✔
573
        }
574
      case "thu" =>
575
        while (calendar.get(Calendar.DAY_OF_WEEK) != Calendar.THURSDAY) {
1✔
576
          calendar.add(Calendar.DAY_OF_MONTH, amount)
1✔
577
        }
578
      case "fri" =>
579
        while (calendar.get(Calendar.DAY_OF_WEEK) != Calendar.FRIDAY) {
1✔
580
          calendar.add(Calendar.DAY_OF_MONTH, amount)
1✔
581
        }
582
      case _ =>
×
583
    }
584
    MatchedDateTime(calendar, relativeDate.start, relativeDate.end)
1✔
585
  }
586

587
}
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