View Javadoc
1   /*
2    *
3    * The DbUnit Database Testing Framework
4    * Copyright (C)2002-2004, DbUnit.org
5    *
6    * This library is free software; you can redistribute it and/or
7    * modify it under the terms of the GNU Lesser General Public
8    * License as published by the Free Software Foundation; either
9    * version 2.1 of the License, or (at your option) any later version.
10   *
11   * This library is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14   * Lesser General Public License for more details.
15   *
16   * You should have received a copy of the GNU Lesser General Public
17   * License along with this library; if not, write to the Free Software
18   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19   *
20   */
21  
22  package org.dbunit.util;
23  
24  import java.time.Clock;
25  import java.time.Instant;
26  import java.time.LocalDateTime;
27  import java.time.LocalTime;
28  import java.time.ZoneId;
29  import java.time.temporal.ChronoUnit;
30  import java.time.temporal.TemporalUnit;
31  import java.util.regex.Matcher;
32  import java.util.regex.Pattern;
33  
34  /**
35   * <p>
36   * A parser for relative date time string.<br>
37   * The basic format is <code>[now{diff...}{time}]</code>.<br>
38   * 'diff' consists of two parts 1) a number with a leading plus or minus sign
39   * and 2) a character represents temporal unit. See the table below for the
40   * supported units. There can be multiple 'diff's and they can be specified in
41   * any order.<br>
42   * 'time' is a string that can be parsed by
43   * <code>LocalTime#parse()</cde>. If specified, it is used instead of the current time.<br>
44   * Both 'diff' and 'time' are optional.<br>
45   * Whitespaces are allowed before and after each 'diff'.
46   * </p>
47   * <h3>Unit</h3>
48   * <ul>
49   * <li>y : years</li>
50   * <li>M : months</li>
51   * <li>d : days</li>
52   * <li>h : hours</li>
53   * <li>m : minutes</li>
54   * <li>s : seconds</li>
55   * </ul>
56   * <p>
57   * Here are some examples.
58   * </p>
59   * <ul>
60   * <li><code>[now]</code> : current date time.</li>
61   * <li><code>[now-1d]</code> : the same time yesterday.</li>
62   * <li><code>[now+1y+1M-2h]</code> : a year and a month from today, two hours
63   * earlier.</li>
64   * <li><code>[now+1d 10:00]</code> : 10 o'clock tomorrow.</li>
65   * </ul>
66   */
67  public class RelativeDateTimeParser
68  {
69      private static final Pattern inputPattern = Pattern.compile(
70              "^\\[[nN][oO][wW]\\s*(([-+][0-9]+[yMdhms]\\s*)*)([0-9:]*)?\\]$");
71      private static final int GROUP_DIFFS = 1;
72      private static final int GROUP_TIME = 3;
73      private static final Pattern diffPattern =
74              Pattern.compile("([+-][0-9]+[yMdhms])");
75  
76      private Clock clock;
77      private LocalDateTime now;
78  
79      public RelativeDateTimeParser()
80      {
81          // Use fixed clock to provide consistent 'now' values.
82          this(Clock.fixed(Instant.now(), ZoneId.systemDefault()));
83      }
84  
85      public RelativeDateTimeParser(Clock clock)
86      {
87          this.clock = clock;
88          cacheLocalDateTime(clock);
89      }
90  
91      public LocalDateTime parse(String input)
92      {
93          if (input == null || input.isEmpty())
94          {
95              throw new IllegalArgumentException(
96                      "Relative datetime input must not be null or empty.");
97          }
98  
99          Matcher matcher = inputPattern.matcher(input);
100         if (!matcher.matches())
101         {
102             throw new IllegalArgumentException("'" + input
103                     + "' does not match the expected pattern [now{diff}{time}]. "
104                     + "Please see the data types documentation for the details. "
105                     + "http://dbunit.sourceforge.net/datatypes.html#relativedatetime");
106         }
107 
108         LocalDateTime datetime = initLocalDateTime(matcher);
109 
110         String diffStr = matcher.group(GROUP_DIFFS);
111         if (diffStr.isEmpty())
112         {
113             return datetime;
114         }
115 
116         Matcher diffMatcher = diffPattern.matcher(diffStr);
117         while (diffMatcher.find())
118         {
119             String diff = diffMatcher.group();
120             int amountLength = diff.length() - 1;
121             TemporalUnit unit = resolveUnit(diff.charAt(amountLength));
122             long amount = Long.parseLong(diff.substring(0, amountLength));
123             datetime = datetime.plus(amount, unit);
124         }
125         return datetime;
126     }
127 
128     public Clock getClock()
129     {
130         return clock;
131     }
132 
133     public void setClock(Clock clock)
134     {
135         this.clock = clock;
136         cacheLocalDateTime(clock);
137     }
138 
139     private LocalDateTime initLocalDateTime(Matcher matcher)
140     {
141         String timeStr = matcher.group(GROUP_TIME);
142         if (timeStr.isEmpty())
143         {
144             return now;
145         } else
146         {
147             LocalTime time = LocalTime.parse(timeStr);
148             return LocalDateTime.of(now.toLocalDate(), time);
149         }
150     }
151 
152     private static TemporalUnit resolveUnit(char c)
153     {
154         switch (c)
155         {
156         case 'y':
157             return ChronoUnit.YEARS;
158         case 'M':
159             return ChronoUnit.MONTHS;
160         case 'd':
161             return ChronoUnit.DAYS;
162         case 'h':
163             return ChronoUnit.HOURS;
164         case 'm':
165             return ChronoUnit.MINUTES;
166         case 's':
167             return ChronoUnit.SECONDS;
168         default:
169             throw new IllegalArgumentException("'" + c
170                     + "' is not a valid unit. It has to be one of 'yMdhms'.");
171         }
172     }
173 
174     private void cacheLocalDateTime(Clock clock)
175     {
176         this.now = LocalDateTime.now(clock);
177     }
178 }