1 /***********************************************************************
2  * Copyright (c) 2013-2024 Commonwealth Computer Research, Inc.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Apache License, Version 2.0
5  * which accompanies this distribution and is available at
6  * http://www.opensource.org/licenses/apache2.0.php.
7  ***********************************************************************/
8 
9 package org.locationtech.geomesa.utils.text
10 
11 import org.locationtech.geomesa.utils.date.DateUtils.toInstant
12 
13 import java.time._
14 import java.time.format.{DateTimeFormatter, DateTimeFormatterBuilder}
15 import java.time.temporal.{ChronoField, TemporalAccessor, TemporalQuery}
16 import java.util.{Date, Locale}
17 
18 object DateParsing {
19 
20   private val format =
21     new DateTimeFormatterBuilder()
22       .parseCaseInsensitive()
23       .append(DateTimeFormatter.ISO_LOCAL_DATE)
24       .parseLenient()
25       .optionalStart()
26       .appendLiteral('T')
27       .appendValue(ChronoField.HOUR_OF_DAY, 2)
28       .appendLiteral(':')
29       .appendValue(ChronoField.MINUTE_OF_HOUR, 2)
30       .optionalStart()
31       .appendLiteral(':')
32       .appendValue(ChronoField.SECOND_OF_MINUTE, 2)
33       .optionalStart()
34       .appendFraction(ChronoField.MILLI_OF_SECOND, 3, 3, true)
35       .optionalEnd()
36       .optionalEnd()
37       .optionalEnd()
38       .optionalStart()
39       .appendOffsetId()
40       .toFormatter(Locale.US)
41       .withZone(ZoneOffset.UTC)
42 
43   val Epoch: ZonedDateTime = ZonedDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC)
44 
45   object TemporalQueries {
46     val ZonedQuery: TemporalQuery[ZonedDateTime] = new TemporalQuery[ZonedDateTime] {
47       override def queryFrom(temporal: TemporalAccessor): ZonedDateTime = ZonedDateTime.from(temporal)
48     }
49     val LocalQuery: TemporalQuery[LocalDateTime] = new TemporalQuery[LocalDateTime] {
50       override def queryFrom(temporal: TemporalAccessor): LocalDateTime = LocalDateTime.from(temporal)
51     }
52     val LocalDateQuery: TemporalQuery[LocalDate] = new TemporalQuery[LocalDate] {
53       override def queryFrom(temporal: TemporalAccessor): LocalDate = LocalDate.from(temporal)
54     }
55     val YearMonthQuery: TemporalQuery[YearMonth] = new TemporalQuery[YearMonth] {
56       override def queryFrom(temporal: TemporalAccessor): YearMonth = YearMonth.from(temporal)
57     }
58     val YearQuery: TemporalQuery[Year] = new TemporalQuery[Year] {
59       override def queryFrom(temporal: TemporalAccessor): Year = Year.from(temporal)
60     }
61   }
62 
63   /**
64     * Parses a date string, with optional time and zone
65     *
66     * @param value date string
67     * @param format date formatter, default ISO format with optional time and zone
68     * @return
69     */
70   def parse(value: String, format: DateTimeFormatter = format): ZonedDateTime = {
71     import TemporalQueries._
72     format.parseBest(value, ZonedQuery, LocalQuery, LocalDateQuery, YearMonthQuery, YearQuery) match {
73       case d: ZonedDateTime => d
74       case d: LocalDateTime => d.atZone(ZoneOffset.UTC)
75       case d: LocalDate     => d.atTime(LocalTime.MIN).atZone(ZoneOffset.UTC)
76       case d: YearMonth     => d.atDay(1).atTime(LocalTime.MIN).atZone(ZoneOffset.UTC)
77       case d: Year          => d.atMonth(1).atDay(1).atTime(LocalTime.MIN).atZone(ZoneOffset.UTC)
78     }
79   }
80 
81   /**
82     * Parses a date string, with optional time and zone
83     *
84     * @param value date string
85     * @param format date formatter, default ISO format with optional time and zone
86     * @return
87     */
88   def parseInstant(value: String, format: DateTimeFormatter = format): Instant = {
89     import TemporalQueries.{LocalDateQuery, LocalQuery, ZonedQuery}
90     format.parseBest(value, ZonedQuery, LocalQuery, LocalDateQuery) match {
91       case d: ZonedDateTime => d.toInstant
92       case d: LocalDateTime => d.toInstant(ZoneOffset.UTC)
93       case d: LocalDate     => d.atTime(LocalTime.MIN).toInstant(ZoneOffset.UTC)
94     }
95   }
96 
97   /**
98     * Parses a date string, with optional time and zone
99     *
100     * @param value date string
101     * @param format date formatter, default ISO format with optional time and zone
102     * @return
103     */
104   def parseDate(value: String, format: DateTimeFormatter = format): Date = {
105     import TemporalQueries.{LocalDateQuery, LocalQuery, ZonedQuery}
106     format.parseBest(value, ZonedQuery, LocalQuery, LocalDateQuery) match {
107       case d: ZonedDateTime => Date.from(d.toInstant)
108       case d: LocalDateTime => Date.from(d.toInstant(ZoneOffset.UTC))
109       case d: LocalDate     => Date.from(d.atTime(LocalTime.MIN).toInstant(ZoneOffset.UTC))
110     }
111   }
112 
113   /**
114     * Parses a date string, with optional time and zone
115     *
116     * @param value date string
117     * @param format date formatter, default ISO format with optional time and zone
118     * @return
119     */
120   def parseMillis(value: String, format: DateTimeFormatter = format): Long = {
121     import TemporalQueries.{LocalDateQuery, LocalQuery, ZonedQuery}
122     format.parseBest(value, ZonedQuery, LocalQuery, LocalDateQuery) match {
123       case d: ZonedDateTime => d.toInstant.toEpochMilli
124       case d: LocalDateTime => d.toInstant(ZoneOffset.UTC).toEpochMilli
125       case d: LocalDate     => d.atTime(LocalTime.MIN).toInstant(ZoneOffset.UTC).toEpochMilli
126     }
127   }
128 
129   def format(value: ZonedDateTime, format: DateTimeFormatter = format): String = value.format(format)
130 
131   def formatDate(value: Date, format: DateTimeFormatter = format): String =
132     ZonedDateTime.ofInstant(toInstant(value), ZoneOffset.UTC).format(format)
133 
134   def formatInstant(value: Instant, format: DateTimeFormatter = format): String =
135     ZonedDateTime.ofInstant(value, ZoneOffset.UTC).format(format)
136 
137   def formatMillis(value: Long, format: DateTimeFormatter = format): String =
138     ZonedDateTime.ofInstant(Instant.ofEpochMilli(value), ZoneOffset.UTC).format(format)
139 }
Line Stmt Id Pos Tree Symbol Tests Code
23 16185 893 - 925 Select java.time.format.DateTimeFormatter.ISO_LOCAL_DATE java.time.format.DateTimeFormatter.ISO_LOCAL_DATE
26 16186 993 - 996 Literal <nosymbol> 'T'
27 16187 1017 - 1040 Literal <nosymbol> HOUR_OF_DAY
27 16188 1042 - 1043 Literal <nosymbol> 2
28 16189 1066 - 1069 Literal <nosymbol> ':'
29 16190 1090 - 1116 Literal <nosymbol> MINUTE_OF_HOUR
29 16191 1118 - 1119 Literal <nosymbol> 2
31 16192 1165 - 1168 Literal <nosymbol> ':'
32 16193 1189 - 1217 Literal <nosymbol> SECOND_OF_MINUTE
32 16194 1219 - 1220 Literal <nosymbol> 2
34 16195 1267 - 1294 Literal <nosymbol> MILLI_OF_SECOND
34 16196 1296 - 1297 Literal <nosymbol> 3
34 16197 1299 - 1300 Literal <nosymbol> 3
34 16198 1302 - 1306 Literal <nosymbol> true
40 16199 1437 - 1446 Select java.util.Locale.US java.util.Locale.US
41 16200 1464 - 1478 Select java.time.ZoneOffset.UTC java.time.ZoneOffset.UTC
41 16201 818 - 1479 Apply java.time.format.DateTimeFormatter.withZone new java.time.format.DateTimeFormatterBuilder().parseCaseInsensitive().append(java.time.format.DateTimeFormatter.ISO_LOCAL_DATE).parseLenient().optionalStart().appendLiteral('T').appendValue(HOUR_OF_DAY, 2).appendLiteral(':').appendValue(MINUTE_OF_HOUR, 2).optionalStart().appendLiteral(':').appendValue(SECOND_OF_MINUTE, 2).optionalStart().appendFraction(MILLI_OF_SECOND, 3, 3, true).optionalEnd().optionalEnd().optionalEnd().optionalStart().appendOffsetId().toFormatter(java.util.Locale.US).withZone(java.time.ZoneOffset.UTC)
43 16202 1534 - 1547 Select java.time.Instant.EPOCH java.time.Instant.EPOCH
43 16203 1549 - 1563 Select java.time.ZoneOffset.UTC java.time.ZoneOffset.UTC
43 16204 1510 - 1564 Apply java.time.ZonedDateTime.ofInstant java.time.ZonedDateTime.ofInstant(java.time.Instant.EPOCH, java.time.ZoneOffset.UTC)
46 16206 1644 - 1647 Apply org.locationtech.geomesa.utils.text.DateParsing.TemporalQueries.$anon.<init> new $anon()
47 16205 1753 - 1781 Apply java.time.ZonedDateTime.from java.time.ZonedDateTime.from(temporal)
49 16208 1839 - 1842 Apply org.locationtech.geomesa.utils.text.DateParsing.TemporalQueries.$anon.<init> new $anon()
50 16207 1948 - 1976 Apply java.time.LocalDateTime.from java.time.LocalDateTime.from(temporal)
52 16210 2034 - 2037 Apply org.locationtech.geomesa.utils.text.DateParsing.TemporalQueries.$anon.<init> new $anon()
53 16209 2135 - 2159 Apply java.time.LocalDate.from java.time.LocalDate.from(temporal)
55 16212 2217 - 2220 Apply org.locationtech.geomesa.utils.text.DateParsing.TemporalQueries.$anon.<init> new $anon()
56 16211 2318 - 2342 Apply java.time.YearMonth.from java.time.YearMonth.from(temporal)
58 16214 2390 - 2393 Apply org.locationtech.geomesa.utils.text.DateParsing.TemporalQueries.$anon.<init> new $anon()
59 16213 2481 - 2500 Apply java.time.Year.from java.time.Year.from(temporal)
72 16215 2854 - 2864 Select org.locationtech.geomesa.utils.text.DateParsing.TemporalQueries.ZonedQuery DateParsing.this.TemporalQueries.ZonedQuery
72 16216 2866 - 2876 Select org.locationtech.geomesa.utils.text.DateParsing.TemporalQueries.LocalQuery DateParsing.this.TemporalQueries.LocalQuery
72 16217 2878 - 2892 Select org.locationtech.geomesa.utils.text.DateParsing.TemporalQueries.LocalDateQuery DateParsing.this.TemporalQueries.LocalDateQuery
72 16218 2894 - 2908 Select org.locationtech.geomesa.utils.text.DateParsing.TemporalQueries.YearMonthQuery DateParsing.this.TemporalQueries.YearMonthQuery
72 16219 2910 - 2919 Select org.locationtech.geomesa.utils.text.DateParsing.TemporalQueries.YearQuery DateParsing.this.TemporalQueries.YearQuery
72 16220 2830 - 2920 Apply java.time.format.DateTimeFormatter.parseBest format.parseBest(value, DateParsing.this.TemporalQueries.ZonedQuery, DateParsing.this.TemporalQueries.LocalQuery, DateParsing.this.TemporalQueries.LocalDateQuery, DateParsing.this.TemporalQueries.YearMonthQuery, DateParsing.this.TemporalQueries.YearQuery)
73 16221 2960 - 2961 Ident org.locationtech.geomesa.utils.text.DateParsing.d d
74 16222 3002 - 3016 Select java.time.ZoneOffset.UTC java.time.ZoneOffset.UTC
74 16223 2993 - 3017 Apply java.time.LocalDateTime.atZone d.atZone(java.time.ZoneOffset.UTC)
74 16224 2993 - 3017 Block java.time.LocalDateTime.atZone d.atZone(java.time.ZoneOffset.UTC)
75 16225 3058 - 3071 Select java.time.LocalTime.MIN java.time.LocalTime.MIN
75 16226 3080 - 3094 Select java.time.ZoneOffset.UTC java.time.ZoneOffset.UTC
75 16227 3049 - 3095 Apply java.time.LocalDateTime.atZone d.atTime(java.time.LocalTime.MIN).atZone(java.time.ZoneOffset.UTC)
75 16228 3049 - 3095 Block java.time.LocalDateTime.atZone d.atTime(java.time.LocalTime.MIN).atZone(java.time.ZoneOffset.UTC)
76 16229 3135 - 3136 Literal <nosymbol> 1
76 16230 3145 - 3158 Select java.time.LocalTime.MIN java.time.LocalTime.MIN
76 16231 3167 - 3181 Select java.time.ZoneOffset.UTC java.time.ZoneOffset.UTC
76 16232 3127 - 3182 Apply java.time.LocalDateTime.atZone d.atDay(1).atTime(java.time.LocalTime.MIN).atZone(java.time.ZoneOffset.UTC)
76 16233 3127 - 3182 Block java.time.LocalDateTime.atZone d.atDay(1).atTime(java.time.LocalTime.MIN).atZone(java.time.ZoneOffset.UTC)
77 16234 3224 - 3225 Literal <nosymbol> 1
77 16235 3233 - 3234 Literal <nosymbol> 1
77 16236 3243 - 3256 Select java.time.LocalTime.MIN java.time.LocalTime.MIN
77 16237 3265 - 3279 Select java.time.ZoneOffset.UTC java.time.ZoneOffset.UTC
77 16238 3214 - 3280 Apply java.time.LocalDateTime.atZone d.atMonth(1).atDay(1).atTime(java.time.LocalTime.MIN).atZone(java.time.ZoneOffset.UTC)
77 16239 3214 - 3280 Block java.time.LocalDateTime.atZone d.atMonth(1).atDay(1).atTime(java.time.LocalTime.MIN).atZone(java.time.ZoneOffset.UTC)
90 16240 3674 - 3684 Select org.locationtech.geomesa.utils.text.DateParsing.TemporalQueries.ZonedQuery DateParsing.this.TemporalQueries.ZonedQuery
90 16241 3686 - 3696 Select org.locationtech.geomesa.utils.text.DateParsing.TemporalQueries.LocalQuery DateParsing.this.TemporalQueries.LocalQuery
90 16242 3698 - 3712 Select org.locationtech.geomesa.utils.text.DateParsing.TemporalQueries.LocalDateQuery DateParsing.this.TemporalQueries.LocalDateQuery
90 16243 3650 - 3713 Apply java.time.format.DateTimeFormatter.parseBest format.parseBest(value, DateParsing.this.TemporalQueries.ZonedQuery, DateParsing.this.TemporalQueries.LocalQuery, DateParsing.this.TemporalQueries.LocalDateQuery)
91 16244 3753 - 3764 Apply java.time.chrono.ChronoZonedDateTime.toInstant d.toInstant()
91 16245 3753 - 3764 Block java.time.chrono.ChronoZonedDateTime.toInstant d.toInstant()
92 16246 3808 - 3822 Select java.time.ZoneOffset.UTC java.time.ZoneOffset.UTC
92 16247 3796 - 3823 Apply java.time.chrono.ChronoLocalDateTime.toInstant d.toInstant(java.time.ZoneOffset.UTC)
92 16248 3796 - 3823 Block java.time.chrono.ChronoLocalDateTime.toInstant d.toInstant(java.time.ZoneOffset.UTC)
93 16249 3864 - 3877 Select java.time.LocalTime.MIN java.time.LocalTime.MIN
93 16250 3889 - 3903 Select java.time.ZoneOffset.UTC java.time.ZoneOffset.UTC
93 16251 3855 - 3904 Apply java.time.chrono.ChronoLocalDateTime.toInstant d.atTime(java.time.LocalTime.MIN).toInstant(java.time.ZoneOffset.UTC)
93 16252 3855 - 3904 Block java.time.chrono.ChronoLocalDateTime.toInstant d.atTime(java.time.LocalTime.MIN).toInstant(java.time.ZoneOffset.UTC)
106 16253 4292 - 4302 Select org.locationtech.geomesa.utils.text.DateParsing.TemporalQueries.ZonedQuery DateParsing.this.TemporalQueries.ZonedQuery
106 16254 4304 - 4314 Select org.locationtech.geomesa.utils.text.DateParsing.TemporalQueries.LocalQuery DateParsing.this.TemporalQueries.LocalQuery
106 16255 4316 - 4330 Select org.locationtech.geomesa.utils.text.DateParsing.TemporalQueries.LocalDateQuery DateParsing.this.TemporalQueries.LocalDateQuery
106 16256 4268 - 4331 Apply java.time.format.DateTimeFormatter.parseBest format.parseBest(value, DateParsing.this.TemporalQueries.ZonedQuery, DateParsing.this.TemporalQueries.LocalQuery, DateParsing.this.TemporalQueries.LocalDateQuery)
107 16257 4381 - 4392 Apply java.time.chrono.ChronoZonedDateTime.toInstant d.toInstant()
107 16258 4371 - 4393 Apply java.util.Date.from java.util.Date.from(d.toInstant())
107 16259 4371 - 4393 Block java.util.Date.from java.util.Date.from(d.toInstant())
108 16260 4447 - 4461 Select java.time.ZoneOffset.UTC java.time.ZoneOffset.UTC
108 16261 4435 - 4462 Apply java.time.chrono.ChronoLocalDateTime.toInstant d.toInstant(java.time.ZoneOffset.UTC)
108 16262 4425 - 4463 Apply java.util.Date.from java.util.Date.from(d.toInstant(java.time.ZoneOffset.UTC))
108 16263 4425 - 4463 Block java.util.Date.from java.util.Date.from(d.toInstant(java.time.ZoneOffset.UTC))
109 16264 4514 - 4527 Select java.time.LocalTime.MIN java.time.LocalTime.MIN
109 16265 4539 - 4553 Select java.time.ZoneOffset.UTC java.time.ZoneOffset.UTC
109 16266 4505 - 4554 Apply java.time.chrono.ChronoLocalDateTime.toInstant d.atTime(java.time.LocalTime.MIN).toInstant(java.time.ZoneOffset.UTC)
109 16267 4495 - 4555 Apply java.util.Date.from java.util.Date.from(d.atTime(java.time.LocalTime.MIN).toInstant(java.time.ZoneOffset.UTC))
109 16268 4495 - 4555 Block java.util.Date.from java.util.Date.from(d.atTime(java.time.LocalTime.MIN).toInstant(java.time.ZoneOffset.UTC))
122 16269 4945 - 4955 Select org.locationtech.geomesa.utils.text.DateParsing.TemporalQueries.ZonedQuery DateParsing.this.TemporalQueries.ZonedQuery
122 16270 4957 - 4967 Select org.locationtech.geomesa.utils.text.DateParsing.TemporalQueries.LocalQuery DateParsing.this.TemporalQueries.LocalQuery
122 16271 4969 - 4983 Select org.locationtech.geomesa.utils.text.DateParsing.TemporalQueries.LocalDateQuery DateParsing.this.TemporalQueries.LocalDateQuery
122 16272 4921 - 4984 Apply java.time.format.DateTimeFormatter.parseBest format.parseBest(value, DateParsing.this.TemporalQueries.ZonedQuery, DateParsing.this.TemporalQueries.LocalQuery, DateParsing.this.TemporalQueries.LocalDateQuery)
123 16273 5024 - 5048 Apply java.time.Instant.toEpochMilli d.toInstant().toEpochMilli()
123 16274 5024 - 5048 Block java.time.Instant.toEpochMilli d.toInstant().toEpochMilli()
124 16275 5080 - 5120 Apply java.time.Instant.toEpochMilli d.toInstant(java.time.ZoneOffset.UTC).toEpochMilli()
124 16276 5080 - 5120 Block java.time.Instant.toEpochMilli d.toInstant(java.time.ZoneOffset.UTC).toEpochMilli()
125 16277 5152 - 5214 Apply java.time.Instant.toEpochMilli d.atTime(java.time.LocalTime.MIN).toInstant(java.time.ZoneOffset.UTC).toEpochMilli()
125 16278 5152 - 5214 Block java.time.Instant.toEpochMilli d.atTime(java.time.LocalTime.MIN).toInstant(java.time.ZoneOffset.UTC).toEpochMilli()
129 16279 5307 - 5327 Apply java.time.ZonedDateTime.format value.format(format)
132 16280 5409 - 5481 Apply java.time.ZonedDateTime.format java.time.ZonedDateTime.ofInstant(org.locationtech.geomesa.utils.date.DateUtils.toInstant(value), java.time.ZoneOffset.UTC).format(format)
135 16281 5569 - 5630 Apply java.time.ZonedDateTime.format java.time.ZonedDateTime.ofInstant(value, java.time.ZoneOffset.UTC).format(format)
138 16282 5714 - 5797 Apply java.time.ZonedDateTime.format java.time.ZonedDateTime.ofInstant(java.time.Instant.ofEpochMilli(value), java.time.ZoneOffset.UTC).format(format)