*/
package org.sonar.server.es;
+import java.time.ZoneId;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.TimeZone;
import javax.annotation.CheckForNull;
import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
private static final java.lang.String NO_DATA_PREFIX = "no_data_";
private final LinkedHashMap<String, LinkedHashMap<String, Long>> facetsByName;
- private final TimeZone timeZone;
+ private final ZoneId timeZone;
- public Facets(LinkedHashMap<String, LinkedHashMap<String, Long>> facetsByName, TimeZone timeZone) {
+ public Facets(LinkedHashMap<String, LinkedHashMap<String, Long>> facetsByName, ZoneId timeZone) {
this.facetsByName = facetsByName;
this.timeZone = timeZone;
}
- public Facets(SearchResponse response, TimeZone timeZone) {
+ public Facets(SearchResponse response, ZoneId timeZone) {
this.facetsByName = new LinkedHashMap<>();
this.timeZone = timeZone;
Aggregations aggregations = response.getAggregations();
}
}
- private static String dateTimeToDate(String timestamp, TimeZone timeZone) {
+ private static String dateTimeToDate(String timestamp, ZoneId timeZone) {
Date date = parseDateTime(timestamp);
- return date.toInstant().atZone(timeZone.toZoneId()).toLocalDate().toString();
+ return date.toInstant().atZone(timeZone).toLocalDate().toString();
}
private void processSum(Sum aggregation) {
package org.sonar.server.es;
import com.google.common.base.Function;
+import java.time.ZoneId;
import java.util.ArrayList;
import java.util.List;
-import java.util.TimeZone;
import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.search.SearchHit;
private final Facets facets;
private final long total;
- public SearchIdResult(SearchResponse response, Function<String, ID> converter, TimeZone timeZone) {
+ public SearchIdResult(SearchResponse response, Function<String, ID> converter, ZoneId timeZone) {
this.facets = new Facets(response, timeZone);
this.total = response.getHits().getTotalHits().value;
this.uuids = convertToIds(response.getHits(), converter);
*/
package org.sonar.server.es;
+import java.time.ZoneId;
import java.util.List;
import java.util.Map;
-import java.util.TimeZone;
import java.util.function.Function;
import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.elasticsearch.action.search.SearchResponse;
private final Facets facets;
private final long total;
- public SearchResult(SearchResponse response, Function<Map<String, Object>, DOC> converter, TimeZone timeZone) {
+ public SearchResult(SearchResponse response, Function<Map<String, Object>, DOC> converter, ZoneId timeZone) {
this.facets = new Facets(response, timeZone);
this.total = response.getHits().getTotalHits().value;
this.docs = EsUtils.convertToDocs(response.getHits(), converter);
private List<String> sansTop25;
private List<String> sonarsourceSecurity;
private List<String> cwe;
+ private String timeZone;
public SearchRequest() {
// nothing to do here
this.pullRequest = pullRequest;
return this;
}
+
+ @CheckForNull
+ public String getTimeZone() {
+ return timeZone;
+ }
+
+ public SearchRequest setTimeZone(@Nullable String timeZone) {
+ this.timeZone = timeZone;
+ return this;
+ }
}
SearchRequest esSearch = EsClient.prepareSearch(TYPE_RULE)
.source(sourceBuilder);
- return new SearchIdResult<>(client.search(esSearch), input -> input, system2.getDefaultTimeZone());
+ return new SearchIdResult<>(client.search(esSearch), input -> input, system2.getDefaultTimeZone().toZoneId());
}
/**
SearchRequest request = EsClient.prepareSearch(UserIndexDefinition.TYPE_USER)
.source(searchSourceBuilder.query(boolQuery().must(esQuery).filter(filter)));
- return new SearchResult<>(esClient.search(request), UserDoc::new, system2.getDefaultTimeZone());
+ return new SearchResult<>(esClient.search(request), UserDoc::new, system2.getDefaultTimeZone().toZoneId());
}
}
getState: () => Store
) => {
const organizationsEnabled = areThereCustomOrganizations(getState());
- return searchIssues({ ...query, additionalFields: '_all' })
+ return searchIssues({
+ ...query,
+ additionalFields: '_all',
+ timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone
+ })
.then(response => {
const parsedIssues = response.issues.map(issue =>
parseIssueFromResponse(issue, response.components, response.users, response.rules)
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
+import org.sonar.api.impl.utils.TestSystem2;
import org.sonar.api.resources.Language;
import org.sonar.api.resources.Languages;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.server.debt.DebtRemediationFunction;
import org.sonar.api.server.rule.RulesDefinition;
import org.sonar.api.utils.DateUtils;
-import org.sonar.api.utils.System2;
import org.sonar.api.utils.log.LogTester;
import org.sonar.core.util.UuidFactory;
import org.sonar.core.util.UuidFactoryFast;
private static final RuleKey RULE_KEY3 = RuleKey.of("fake", "rule3");
private static final RuleKey HOTSPOT_RULE_KEY = RuleKey.of("fake", "hotspot");
- private System2 system = mock(System2.class);
+ private TestSystem2 system = new TestSystem2().setNow(DATE1.getTime());
@org.junit.Rule
public ExpectedException expectedException = ExpectedException.none();
@Before
public void before() {
- when(system.now()).thenReturn(DATE1.getTime());
ruleIndexer = new RuleIndexer(es.client(), dbClient);
ruleIndex = new RuleIndex(es.client(), system);
activeRuleIndexer = new ActiveRuleIndexer(dbClient, es.client());
dbClient.ruleDao().insertOrUpdate(db.getSession(), rule1.getMetadata());
db.getSession().commit();
- when(system.now()).thenReturn(DATE2.getTime());
+ system.setNow(DATE2.getTime());
execute(new FakeRepositoryV2());
verifyIndicesNotMarkedAsInitialized();
@Test
public void update_only_rule_name() {
- when(system.now()).thenReturn(DATE1.getTime());
+ system.setNow(DATE1.getTime());
execute(context -> {
NewRepository repo = context.createRepository("fake", "java");
repo.createRule("rule")
repo.done();
});
- when(system.now()).thenReturn(DATE2.getTime());
+ system.setNow(DATE2.getTime());
execute(context -> {
NewRepository repo = context.createRepository("fake", "java");
repo.createRule("rule")
@Test
public void update_template_rule_key_should_also_update_custom_rules() {
- when(system.now()).thenReturn(DATE1.getTime());
+ system.setNow(DATE1.getTime());
execute(context -> {
NewRepository repo = context.createRepository("squid", "java");
repo.createRule("rule")
String ruleKey2 = "rule2";
String repository = "fake";
- when(system.now()).thenReturn(DATE1.getTime());
+ system.setNow(DATE1.getTime());
execute(context -> {
NewRepository repo = context.createRepository(repository, "java");
repo.createRule(ruleKey1)
assertThat(searchRule1.getUuids()).containsOnly(rule1.getUuid());
assertThat(searchRule1.getTotal()).isEqualTo(1);
- when(system.now()).thenReturn(DATE2.getTime());
+ system.setNow(DATE2.getTime());
execute(context -> {
NewRepository repo = context.createRepository(repository, "java");
repo.createRule(ruleKey2)
String repository1 = "fake1";
String repository2 = "fake2";
- when(system.now()).thenReturn(DATE1.getTime());
+ system.setNow(DATE1.getTime());
execute(context -> {
NewRepository repo = context.createRepository(repository1, "java");
repo.createRule(ruleKey)
assertThat(searchRule1.getUuids()).containsOnly(rule1.getUuid());
assertThat(searchRule1.getTotal()).isEqualTo(1);
- when(system.now()).thenReturn(DATE2.getTime());
+ system.setNow(DATE2.getTime());
execute(context -> {
NewRepository repo = context.createRepository(repository2, "java");
repo.createRule(ruleKey)
public void update_if_only_renamed_and_deprecated_key_declared(String ruleKey1, String repo1, String ruleKey2, String repo2) {
String name = "Name1";
String description = "Description";
- when(system.now()).thenReturn(DATE1.getTime());
+ system.setNow(DATE1.getTime());
execute(context -> {
NewRepository repo = context.createRepository(repo1, "java");
repo.createRule(ruleKey1)
assertThat(ruleIndex.search(new RuleQuery().setQueryText(name), new SearchOptions()).getUuids())
.containsOnly(rule1.getUuid());
- when(system.now()).thenReturn(DATE2.getTime());
+ system.setNow(DATE2.getTime());
execute(context -> {
NewRepository repo = context.createRepository(repo2, "java");
repo.createRule(ruleKey2)
String repository1 = "fake1";
String repository2 = "fake2";
- when(system.now()).thenReturn(DATE1.getTime());
+ system.setNow(DATE1.getTime());
execute(context -> {
NewRepository repo = context.createRepository(repository1, "java");
repo.createRule(ruleKey1)
assertThat(ruleIndex.search(new RuleQuery().setQueryText("Name1"), new SearchOptions()).getUuids())
.containsOnly(rule1.getUuid());
- when(system.now()).thenReturn(DATE2.getTime());
+ system.setNow(DATE2.getTime());
execute(context -> {
NewRepository repo = context.createRepository(repository2, "java");
repo.createRule(ruleKey2)
@Test
public void update_only_rule_description() {
- when(system.now()).thenReturn(DATE1.getTime());
+ system.setNow(DATE1.getTime());
execute(context -> {
NewRepository repo = context.createRepository("fake", "java");
repo.createRule("rule")
repo.done();
});
- when(system.now()).thenReturn(DATE2.getTime());
+ system.setNow(DATE2.getTime());
execute(context -> {
NewRepository repo = context.createRepository("fake", "java");
repo.createRule("rule")
@Test
public void rule_previously_created_as_adhoc_becomes_none_adhoc() {
RuleDefinitionDto rule = db.rules().insert(r -> r.setRepositoryKey("external_fake").setIsExternal(true).setIsAdHoc(true));
- when(system.now()).thenReturn(DATE2.getTime());
+ system.setNow(DATE2.getTime());
execute(context -> {
NewRepository repo = context.createExternalRepository("fake", rule.getLanguage());
repo.createRule(rule.getRuleKey())
@Test
public void disable_then_enable_rule() {
// Install rule
- when(system.now()).thenReturn(DATE1.getTime());
+ system.setNow(DATE1.getTime());
execute(new FakeRepositoryV1());
// Uninstall rule
- when(system.now()).thenReturn(DATE2.getTime());
+ system.setNow(DATE2.getTime());
execute();
RuleDto rule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
assertThat(ruleIndex.search(new RuleQuery().setKey(RULE_KEY1.toString()), new SearchOptions()).getTotal()).isEqualTo(0);
// Re-install rule
- when(system.now()).thenReturn(DATE3.getTime());
+ system.setNow(DATE3.getTime());
execute(new FakeRepositoryV1());
rule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
execute(new FakeRepositoryV1());
assertThat(dbClient.ruleDao().selectAllDefinitions(db.getSession())).hasSize(3);
- when(system.now()).thenReturn(DATE2.getTime());
+ system.setNow(DATE2.getTime());
execute(new FakeRepositoryV1());
RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
assertThat(rule2.getStatus()).isEqualTo(READY);
- when(system.now()).thenReturn(DATE2.getTime());
+ system.setNow(DATE2.getTime());
execute(new FakeRepositoryV2());
// On MySQL, need to update a rule otherwise rule2 will be seen as READY, but why ???
assertThat(ruleIndex.search(new RuleQuery(), new SearchOptions()).getUuids()).containsOnly(rule1.getUuid(), rule3.getUuid());
- when(system.now()).thenReturn(DATE3.getTime());
+ system.setNow(DATE3.getTime());
execute(new FakeRepositoryV2());
db.getSession().commit();
SearchRequest request = EsClient.prepareSearch(TYPE_COMPONENT.getMainType())
.source(source);
- return new SearchIdResult<>(client.search(request), id -> id, system2.getDefaultTimeZone());
+ return new SearchIdResult<>(client.search(request), id -> id, system2.getDefaultTimeZone().toZoneId());
}
public ComponentIndexResults searchSuggestions(SuggestionQuery query) {
.dateHistogramInterval(bucketSize)
.minDocCount(0L)
.format(DateUtils.DATETIME_FORMAT)
- .timeZone(system.getDefaultTimeZone().toZoneId())
+ .timeZone(Optional.ofNullable(query.timeZone()).orElse(system.getDefaultTimeZone().toZoneId()))
// ES dateHistogram bounds are inclusive while createdBefore parameter is exclusive
.extendedBounds(new ExtendedBounds(startInclusive ? startTime : (startTime + 1), endTime - 1L));
addEffortAggregationIfNeeded(query, dateHistogram);
package org.sonar.server.issue.index;
import com.google.common.collect.ImmutableSet;
+import java.time.ZoneId;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
private final String organizationUuid;
private final String branchUuid;
private final boolean mainBranch;
+ private final ZoneId timeZone;
private IssueQuery(Builder builder) {
this.issueKeys = defaultCollection(builder.issueKeys);
this.organizationUuid = builder.organizationUuid;
this.branchUuid = builder.branchUuid;
this.mainBranch = builder.mainBranch;
+ this.timeZone = builder.timeZone;
}
public Collection<String> issueKeys() {
return new Builder();
}
+ @CheckForNull
+ public ZoneId timeZone() {
+ return timeZone;
+ }
+
public static class Builder {
private Collection<String> issueKeys;
private Collection<String> severities;
private String organizationUuid;
private String branchUuid;
private boolean mainBranch = true;
+ private ZoneId timeZone;
private Builder() {
this.mainBranch = mainBranch;
return this;
}
+
+ public Builder timeZone(ZoneId timeZone) {
+ this.timeZone = timeZone;
+ return this;
+ }
}
private static <T> Collection<T> defaultCollection(@Nullable Collection<T> c) {
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import java.time.Clock;
+import java.time.DateTimeException;
import java.time.OffsetDateTime;
import java.time.Period;
+import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import static org.sonar.api.issue.Issue.STATUS_REVIEWED;
import static org.sonar.api.issue.Issue.STATUS_TO_REVIEW;
import static org.sonar.api.utils.DateUtils.longToDate;
-import static org.sonar.api.utils.DateUtils.parseDateOrDateTime;
import static org.sonar.api.utils.DateUtils.parseEndingDateOrDateTime;
import static org.sonar.api.utils.DateUtils.parseStartingDateOrDateTime;
import static org.sonar.core.util.stream.MoreCollectors.toHashSet;
public IssueQuery create(SearchRequest request) {
try (DbSession dbSession = dbClient.openSession(false)) {
+ final ZoneId timeZone = parseTimeZone(request.getTimeZone()).orElse(clock.getZone());
IssueQuery.Builder builder = IssueQuery.builder()
.issueKeys(request.getIssues())
.severities(request.getSeverities())
.cwe(request.getCwe())
.sonarsourceSecurity(request.getSonarsourceSecurity())
.assigned(request.getAssigned())
- .createdAt(parseDateOrDateTime(request.getCreatedAt()))
- .createdBefore(parseEndingDateOrDateTime(request.getCreatedBefore()))
+ .createdAt(parseStartingDateOrDateTime(request.getCreatedAt(), timeZone))
+ .createdBefore(parseEndingDateOrDateTime(request.getCreatedBefore(), timeZone))
.facetMode(request.getFacetMode())
- .organizationUuid(convertOrganizationKeyToUuid(dbSession, request.getOrganization()));
+ .organizationUuid(convertOrganizationKeyToUuid(dbSession, request.getOrganization()))
+ .timeZone(timeZone);
List<ComponentDto> allComponents = new ArrayList<>();
boolean effectiveOnComponentOnly = mergeDeprecatedComponentParameters(dbSession, request, allComponents);
addComponentParameters(builder, dbSession, effectiveOnComponentOnly, allComponents, request);
- setCreatedAfterFromRequest(dbSession, builder, request, allComponents);
+ setCreatedAfterFromRequest(dbSession, builder, request, allComponents, timeZone);
String sort = request.getSort();
if (!Strings.isNullOrEmpty(sort)) {
builder.sort(sort);
}
}
+ private Optional<ZoneId> parseTimeZone(@Nullable String timeZone) {
+ if (timeZone == null) {
+ return Optional.empty();
+ }
+ try {
+ return Optional.of(ZoneId.of(timeZone));
+ } catch (DateTimeException e) {
+ throw new IllegalArgumentException("TimeZone '" + timeZone + "' cannot be parsed as a valid zone ID");
+ }
+ }
+
private void setCreatedAfterFromDates(IssueQuery.Builder builder, @Nullable Date createdAfter, @Nullable String createdInLast, boolean createdAfterInclusive) {
Date actualCreatedAfter = createdAfter;
if (createdInLast != null) {
return organization.map(OrganizationDto::getUuid).orElse(UNKNOWN);
}
- private void setCreatedAfterFromRequest(DbSession dbSession, IssueQuery.Builder builder, SearchRequest request, List<ComponentDto> componentUuids) {
- Date createdAfter = parseStartingDateOrDateTime(request.getCreatedAfter());
+ private void setCreatedAfterFromRequest(DbSession dbSession, IssueQuery.Builder builder, SearchRequest request, List<ComponentDto> componentUuids, ZoneId timeZone) {
+ Date createdAfter = parseStartingDateOrDateTime(request.getCreatedAfter(), timeZone);
String createdInLast = request.getCreatedInLast();
if (request.getSinceLeakPeriod() == null || !request.getSinceLeakPeriod()) {
filtersComputer.getPostFilters().ifPresent(searchSourceBuilder::postFilter);
SearchResponse response = client.search(EsClient.prepareSearch(TYPE_PROJECT_MEASURES.getMainType())
.source(searchSourceBuilder));
- return new SearchIdResult<>(response, id -> id, system2.getDefaultTimeZone());
+ return new SearchIdResult<>(response, id -> id, system2.getDefaultTimeZone().toZoneId());
}
private static RequestFiltersComputer createFiltersComputer(SearchOptions searchOptions, AllFilters allFilters) {
IssueDocTesting.newDoc("I3", file).setAssigneeUuid("uuid-simon").setEffort(10L),
IssueDocTesting.newDoc("I4", file).setAssigneeUuid(null).setEffort(10L));
- Facets facets = new Facets(underTest.search(newQueryBuilder().build(), new SearchOptions().addFacets(asList("assignees"))), system2.getDefaultTimeZone());
+ Facets facets = new Facets(underTest.search(newQueryBuilder().build(), new SearchOptions().addFacets(asList("assignees"))), system2.getDefaultTimeZone().toZoneId());
assertThat(facets.getNames()).containsOnly("assignees", FACET_MODE_EFFORT);
assertThat(facets.get("assignees")).containsOnly(entry("uuid-steph", 10L), entry("uuid-simon", 20L), entry("", 10L));
assertThat(facets.get(FACET_MODE_EFFORT)).containsOnly(entry("total", 40L));
IssueDocTesting.newDoc("I3", file).setAuthorLogin("simon").setEffort(10L),
IssueDocTesting.newDoc("I4", file).setAuthorLogin(null).setEffort(10L));
- Facets facets = new Facets(underTest.search(newQueryBuilder().build(), new SearchOptions().addFacets(asList("authors"))), system2.getDefaultTimeZone());
+ Facets facets = new Facets(underTest.search(newQueryBuilder().build(), new SearchOptions().addFacets(asList("authors"))), system2.getDefaultTimeZone().toZoneId());
assertThat(facets.getNames()).containsOnly("authors", FACET_MODE_EFFORT);
assertThat(facets.get("authors")).containsOnly(entry("steph", 10L), entry("simon", 20L));
assertThat(facets.get(FACET_MODE_EFFORT)).containsOnly(entry("total", 40L));
SearchOptions searchOptions = fixtureForCreatedAtFacet();
Builder query = newQueryBuilder().createdBefore(parseDateTime("2016-01-01T00:00:00+0100"));
- Map<String, Long> createdAt = new Facets(underTest.search(query.build(), searchOptions), system2.getDefaultTimeZone()).get("createdAt");
+ Map<String, Long> createdAt = new Facets(underTest.search(query.build(), searchOptions), system2.getDefaultTimeZone().toZoneId()).get("createdAt");
assertThat(createdAt).containsOnly(
entry("2011-01-01", 10L),
entry("2012-01-01", 0L),
}
private Facets search(String additionalFacet) {
- return new Facets(underTest.search(newQueryBuilder().build(), new SearchOptions().addFacets(singletonList(additionalFacet))), system2.getDefaultTimeZone());
+ return new Facets(underTest.search(newQueryBuilder().build(), new SearchOptions().addFacets(singletonList(additionalFacet))), system2.getDefaultTimeZone().toZoneId());
}
private Builder newQueryBuilder() {
*/
package org.sonar.server.issue.index;
+import java.time.ZoneId;
+import java.util.Date;
import java.util.Map;
+import java.util.TimeZone;
import org.elasticsearch.action.search.SearchResponse;
import org.junit.Rule;
import org.junit.Test;
public UserSessionRule userSessionRule = UserSessionRule.standalone();
@Rule
public ExpectedException expectedException = none();
- private System2 system2 = new TestSystem2().setNow(1_500_000_000_000L).setDefaultTimeZone(getTimeZone("GMT-01:00"));
+ private final TimeZone defaultTimezone = getTimeZone("GMT-01:00");
+ private System2 system2 = new TestSystem2().setNow(1_500_000_000_000L).setDefaultTimeZone(defaultTimezone);
@Rule
public DbTester db = DbTester.create(system2);
}
@Test
- public void facet_on_created_at_with_less_than_20_days() {
+ public void facet_on_created_at_with_less_than_20_days_use_system_timezone_by_default() {
SearchOptions options = fixtureForCreatedAtFacet();
IssueQuery query = IssueQuery.builder()
.createdBefore(parseDateTime("2014-09-08T00:00:00+0100"))
.build();
SearchResponse result = underTest.search(query, options);
- Map<String, Long> buckets = new Facets(result, system2.getDefaultTimeZone()).get("createdAt");
+ Map<String, Long> buckets = new Facets(result, system2.getDefaultTimeZone().toZoneId()).get("createdAt");
assertThat(buckets).containsOnly(
entry("2014-08-31", 0L),
entry("2014-09-01", 2L),
entry("2014-09-07", 0L));
}
+ @Test
+ public void facet_on_created_at_with_less_than_20_days_use_user_timezone_if_provided() {
+ // Use timezones very far from each other in order to see some issues moving to a different calendar day
+ final ZoneId plus14 = ZoneId.of("Pacific/Kiritimati");
+ final ZoneId minus11 = ZoneId.of("Pacific/Pago_Pago");
+
+
+ SearchOptions options = fixtureForCreatedAtFacet();
+
+ final Date startDate = parseDateTime("2014-09-01T00:00:00+0000");
+ final Date endDate = parseDateTime("2014-09-08T00:00:00+0000");
+
+ IssueQuery queryPlus14 = IssueQuery.builder()
+ .createdAfter(startDate)
+ .createdBefore(endDate)
+ .timeZone(plus14)
+ .build();
+ SearchResponse resultPlus14 = underTest.search(queryPlus14, options);
+ Map<String, Long> bucketsPlus14 = new Facets(resultPlus14, plus14).get("createdAt");
+ assertThat(bucketsPlus14).containsOnly(
+ entry("2014-09-01", 0L),
+ entry("2014-09-02", 2L),
+ entry("2014-09-03", 1L),
+ entry("2014-09-04", 0L),
+ entry("2014-09-05", 0L),
+ entry("2014-09-06", 1L),
+ entry("2014-09-07", 0L),
+ entry("2014-09-08", 0L));
+
+ IssueQuery queryMinus11 = IssueQuery.builder()
+ .createdAfter(startDate)
+ .createdBefore(endDate)
+ .timeZone(minus11)
+ .build();
+ SearchResponse resultMinus11 = underTest.search(queryMinus11, options);
+ Map<String, Long> bucketsMinus11 = new Facets(resultMinus11, minus11).get("createdAt");
+ assertThat(bucketsMinus11).containsOnly(
+ entry("2014-08-31", 1L),
+ entry("2014-09-01", 1L),
+ entry("2014-09-02", 1L),
+ entry("2014-09-03", 0L),
+ entry("2014-09-04", 0L),
+ entry("2014-09-05", 1L),
+ entry("2014-09-06", 0L),
+ entry("2014-09-07", 0L));
+ }
+
@Test
public void facet_on_created_at_with_less_than_20_weeks() {
SearchOptions options = fixtureForCreatedAtFacet();
.createdAfter(parseDateTime("2014-09-01T00:00:00+0100"))
.createdBefore(parseDateTime("2014-09-21T00:00:00+0100")).build(),
options);
- Map<String, Long> createdAt = new Facets(result, system2.getDefaultTimeZone()).get("createdAt");
+ Map<String, Long> createdAt = new Facets(result, system2.getDefaultTimeZone().toZoneId()).get("createdAt");
assertThat(createdAt).containsOnly(
entry("2014-08-25", 0L),
entry("2014-09-01", 4L),
.createdAfter(parseDateTime("2014-09-01T00:00:00+0100"))
.createdBefore(parseDateTime("2015-01-19T00:00:00+0100")).build(),
options);
- Map<String, Long> createdAt = new Facets(result, system2.getDefaultTimeZone()).get("createdAt");
+ Map<String, Long> createdAt = new Facets(result, system2.getDefaultTimeZone().toZoneId()).get("createdAt");
assertThat(createdAt).containsOnly(
entry("2014-08-01", 0L),
entry("2014-09-01", 5L),
.createdAfter(parseDateTime("2011-01-01T00:00:00+0100"))
.createdBefore(parseDateTime("2016-01-01T00:00:00+0100")).build(),
options);
- Map<String, Long> createdAt = new Facets(result, system2.getDefaultTimeZone()).get("createdAt");
+ Map<String, Long> createdAt = new Facets(result, system2.getDefaultTimeZone().toZoneId()).get("createdAt");
assertThat(createdAt).containsOnly(
entry("2010-01-01", 0L),
entry("2011-01-01", 1L),
.createdAfter(parseDateTime("2014-09-01T00:00:00-0100"))
.createdBefore(parseDateTime("2014-09-02T00:00:00-0100")).build(),
options);
- Map<String, Long> createdAt = new Facets(result, system2.getDefaultTimeZone()).get("createdAt");
+ Map<String, Long> createdAt = new Facets(result, system2.getDefaultTimeZone().toZoneId()).get("createdAt");
assertThat(createdAt).containsOnly(
entry("2014-09-01", 2L));
}
.createdAfter(parseDateTime("2009-01-01T00:00:00+0100"))
.createdBefore(parseDateTime("2016-01-01T00:00:00+0100"))
.build(), options);
- Map<String, Long> createdAt = new Facets(result, system2.getDefaultTimeZone()).get("createdAt");
+ Map<String, Long> createdAt = new Facets(result, system2.getDefaultTimeZone().toZoneId()).get("createdAt");
assertThat(createdAt).containsOnly(
entry("2008-01-01", 0L),
entry("2009-01-01", 0L),
SearchResponse result = underTest.search(IssueQuery.builder()
.createdBefore(parseDateTime("2016-01-01T00:00:00+0100")).build(),
searchOptions);
- Map<String, Long> createdAt = new Facets(result, system2.getDefaultTimeZone()).get("createdAt");
+ Map<String, Long> createdAt = new Facets(result, system2.getDefaultTimeZone().toZoneId()).get("createdAt");
assertThat(createdAt).containsOnly(
entry("2011-01-01", 1L),
entry("2012-01-01", 0L),
SearchOptions searchOptions = new SearchOptions().addFacets("createdAt");
SearchResponse result = underTest.search(IssueQuery.builder().build(), searchOptions);
- Map<String, Long> createdAt = new Facets(result, system2.getDefaultTimeZone()).get("createdAt");
+ Map<String, Long> createdAt = new Facets(result, system2.getDefaultTimeZone().toZoneId()).get("createdAt");
assertThat(createdAt).isNull();
}
ComponentDto file = newFileDto(project, null);
IssueDoc issue0 = newDoc("ISSUE0", file).setFuncCreationDate(parseDateTime("2011-04-25T00:05:13+0000"));
- IssueDoc issue1 = newDoc("I1", file).setFuncCreationDate(parseDateTime("2014-09-01T12:34:56+0100"));
- IssueDoc issue2 = newDoc("I2", file).setFuncCreationDate(parseDateTime("2014-09-01T10:46:00-1200"));
- IssueDoc issue3 = newDoc("I3", file).setFuncCreationDate(parseDateTime("2014-09-02T23:34:56+1200"));
- IssueDoc issue4 = newDoc("I4", file).setFuncCreationDate(parseDateTime("2014-09-05T12:34:56+0100"));
- IssueDoc issue5 = newDoc("I5", file).setFuncCreationDate(parseDateTime("2014-09-20T12:34:56+0100"));
- IssueDoc issue6 = newDoc("I6", file).setFuncCreationDate(parseDateTime("2015-01-18T12:34:56+0100"));
+ IssueDoc issue1 = newDoc("I1", file).setFuncCreationDate(parseDateTime("2014-09-01T10:34:56+0000"));
+ IssueDoc issue2 = newDoc("I2", file).setFuncCreationDate(parseDateTime("2014-09-01T22:46:00+0000"));
+ IssueDoc issue3 = newDoc("I3", file).setFuncCreationDate(parseDateTime("2014-09-02T11:34:56+0000"));
+ IssueDoc issue4 = newDoc("I4", file).setFuncCreationDate(parseDateTime("2014-09-05T11:34:56+0000"));
+ IssueDoc issue5 = newDoc("I5", file).setFuncCreationDate(parseDateTime("2014-09-20T11:34:56+0000"));
+ IssueDoc issue6 = newDoc("I6", file).setFuncCreationDate(parseDateTime("2015-01-18T11:34:56+0000"));
indexIssues(issue0, issue1, issue2, issue3, issue4, issue5, issue6);
@SafeVarargs
private final void assertThatFacetHasExactly(IssueQuery.Builder query, String facet, Map.Entry<String, Long>... expectedEntries) {
SearchResponse result = underTest.search(query.build(), new SearchOptions().addFacets(singletonList(facet)));
- Facets facets = new Facets(result, system2.getDefaultTimeZone());
+ Facets facets = new Facets(result, system2.getDefaultTimeZone().toZoneId());
assertThat(facets.getNames()).containsOnly(facet, "effort");
assertThat(facets.get(facet)).containsExactly(expectedEntries);
}
@SafeVarargs
private final void assertThatFacetHasOnly(IssueQuery.Builder query, String facet, Map.Entry<String, Long>... expectedEntries) {
SearchResponse result = underTest.search(query.build(), new SearchOptions().addFacets(singletonList(facet)));
- Facets facets = new Facets(result, system2.getDefaultTimeZone());
+ Facets facets = new Facets(result, system2.getDefaultTimeZone().toZoneId());
assertThat(facets.getNames()).containsOnly(facet, "effort");
assertThat(facets.get(facet)).containsOnly(expectedEntries);
}
private void assertThatFacetHasSize(IssueQuery issueQuery, String facet, int expectedSize) {
SearchResponse result = underTest.search(issueQuery, new SearchOptions().addFacets(singletonList(facet)));
- Facets facets = new Facets(result, system2.getDefaultTimeZone());
+ Facets facets = new Facets(result, system2.getDefaultTimeZone().toZoneId());
assertThat(facets.get(facet)).hasSize(expectedSize);
}
}
@SafeVarargs
private final void assertThatFacetHasOnly(IssueQuery.Builder query, String facet, Map.Entry<String, Long>... expectedEntries) {
SearchResponse result = underTest.search(query.build(), new SearchOptions().addFacets(singletonList(facet)));
- Facets facets = new Facets(result, system2.getDefaultTimeZone());
+ Facets facets = new Facets(result, system2.getDefaultTimeZone().toZoneId());
assertThat(facets.getNames()).containsOnly(facet, "effort");
assertThat(facets.get(facet)).containsOnly(expectedEntries);
}
package org.sonar.server.issue.index;
import java.time.Clock;
+import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Collections;
import org.junit.rules.ExpectedException;
import org.sonar.api.resources.Qualifiers;
import org.sonar.api.rule.RuleKey;
-import org.sonar.api.utils.DateUtils;
import org.sonar.db.DbTester;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.component.SnapshotDto;
import static org.mockito.Mockito.when;
import static org.sonar.api.resources.Qualifiers.APP;
import static org.sonar.api.utils.DateUtils.addDays;
+import static org.sonar.api.utils.DateUtils.parseDateTime;
import static org.sonar.api.web.UserRole.USER;
import static org.sonar.db.component.ComponentTesting.newDirectory;
import static org.sonar.db.component.ComponentTesting.newFileDto;
assertThat(query.assigned()).isTrue();
assertThat(query.rules()).hasSize(2);
assertThat(query.directories()).containsOnly("aDirPath");
- assertThat(query.createdAfter().date()).isEqualTo(DateUtils.parseDateTime("2013-04-16T09:08:24+0200"));
+ assertThat(query.createdAfter().date()).isEqualTo(parseDateTime("2013-04-16T09:08:24+0200"));
assertThat(query.createdAfter().inclusive()).isTrue();
- assertThat(query.createdBefore()).isEqualTo(DateUtils.parseDateTime("2013-04-17T09:08:24+0200"));
+ assertThat(query.createdBefore()).isEqualTo(parseDateTime("2013-04-17T09:08:24+0200"));
assertThat(query.sort()).isEqualTo(IssueQuery.SORT_BY_CREATION_DATE);
assertThat(query.asc()).isTrue();
}
@Test
public void dates_are_inclusive() {
+ when(clock.getZone()).thenReturn(ZoneId.of("Europe/Paris"));
SearchRequest request = new SearchRequest()
.setCreatedAfter("2013-04-16")
.setCreatedBefore("2013-04-17");
IssueQuery query = underTest.create(request);
- assertThat(query.createdAfter().date()).isEqualTo(DateUtils.parseDate("2013-04-16"));
+ assertThat(query.createdAfter().date()).isEqualTo(parseDateTime("2013-04-16T00:00:00+0200"));
assertThat(query.createdAfter().inclusive()).isTrue();
- assertThat(query.createdBefore()).isEqualTo(DateUtils.parseDate("2013-04-18"));
+ assertThat(query.createdBefore()).isEqualTo(parseDateTime("2013-04-18T00:00:00+0200"));
}
@Test
public void creation_date_support_localdate() {
+ when(clock.getZone()).thenReturn(ZoneId.of("Europe/Paris"));
SearchRequest request = new SearchRequest()
.setCreatedAt("2013-04-16");
IssueQuery query = underTest.create(request);
- assertThat(query.createdAt()).isEqualTo(DateUtils.parseDate("2013-04-16"));
+ assertThat(query.createdAt()).isEqualTo(parseDateTime("2013-04-16T00:00:00+0200"));
+ }
+
+ @Test
+ public void use_provided_timezone_to_parse_createdAfter() {
+ SearchRequest request = new SearchRequest()
+ .setCreatedAfter("2020-04-16")
+ .setTimeZone("Europe/Volgograd");
+
+ IssueQuery query = underTest.create(request);
+
+ assertThat(query.createdAfter().date()).isEqualTo(parseDateTime("2020-04-16T00:00:00+0400"));
+ }
+
+ @Test
+ public void use_provided_timezone_to_parse_createdBefore() {
+ SearchRequest request = new SearchRequest()
+ .setCreatedBefore("2020-04-16")
+ .setTimeZone("Europe/Moscow");
+
+ IssueQuery query = underTest.create(request);
+
+ assertThat(query.createdBefore()).isEqualTo(parseDateTime("2020-04-17T00:00:00+0300"));
}
@Test
IssueQuery query = underTest.create(request);
- assertThat(query.createdAt()).isEqualTo(DateUtils.parseDateTime("2013-04-16T09:08:24+0200"));
+ assertThat(query.createdAt()).isEqualTo(parseDateTime("2013-04-16T09:08:24+0200"));
}
@Test
underTest.create(request);
}
+ @Test
+ public void fail_if_invalid_timezone() {
+ SearchRequest request = new SearchRequest()
+ .setTimeZone("Poitou-Charentes");
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("TimeZone 'Poitou-Charentes' cannot be parsed as a valid zone ID");
+
+ underTest.create(request);
+ }
+
@Test
public void param_componentUuids_enables_search_in_view_tree_if_user_has_permission_on_view() {
ComponentDto view = db.components().insertView();
@Test
public void set_created_after_from_created_since() {
- Date now = DateUtils.parseDateTime("2013-07-25T07:35:00+0100");
+ Date now = parseDateTime("2013-07-25T07:35:00+0100");
when(clock.instant()).thenReturn(now.toInstant());
when(clock.getZone()).thenReturn(ZoneOffset.UTC);
SearchRequest request = new SearchRequest()
.setCreatedInLast("1y2m3w4d");
- assertThat(underTest.create(request).createdAfter().date()).isEqualTo(DateUtils.parseDateTime("2012-04-30T07:35:00+0100"));
+ assertThat(underTest.create(request).createdAfter().date()).isEqualTo(parseDateTime("2012-04-30T07:35:00+0100"));
assertThat(underTest.create(request).createdAfter().inclusive()).isTrue();
}
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SONARSOURCE_SECURITY;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_STATUSES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_TAGS;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_TIMEZONE;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_TYPES;
public class SearchAction implements IssuesWsAction {
private final DbClient dbClient;
public SearchAction(UserSession userSession, IssueIndex issueIndex, IssueQueryFactory issueQueryFactory, IssueIndexSyncProgressChecker issueIndexSyncProgressChecker,
- SearchResponseLoader searchResponseLoader, SearchResponseFormat searchResponseFormat, System2 system2, DbClient dbClient) {
+ SearchResponseLoader searchResponseLoader, SearchResponseFormat searchResponseFormat, System2 system2, DbClient dbClient) {
this.userSession = userSession;
this.issueIndex = issueIndex;
this.issueQueryFactory = issueQueryFactory;
+ "<br/>When issue indexation is in progress returns 503 service unavailable HTTP code.")
.setSince("3.6")
.setChangelog(
+ new Change("8.6", "Parameter 'timeZone' added"),
new Change("8.5", "Facet 'fileUuids' is dropped in favour of the new facet 'files'" +
- "Note that they are not strictly identical, the latter returns the file paths."),
+ "Note that they are not strictly identical, the latter returns the file paths."),
new Change("8.5", "Internal parameter 'fileUuids' has been dropped"),
new Change("8.4", "parameters 'componentUuids', 'projectKeys' has been dropped."),
new Change("8.2", "'REVIEWED', 'TO_REVIEW' status param values are no longer supported"),
.setExampleValue("2017-10-19T13:00:00+0200");
action.createParam(PARAM_CREATED_AFTER)
.setDescription("To retrieve issues created after the given date (inclusive). <br>" +
- "Either a date (server timezone) or datetime can be provided. <br>" +
+ "Either a date (use '" + PARAM_TIMEZONE + "' attribute or it will default to server timezone) or datetime can be provided. <br>" +
"If this parameter is set, createdSince must not be set")
.setExampleValue("2017-10-19 or 2017-10-19T13:00:00+0200");
action.createParam(PARAM_CREATED_BEFORE)
.setDescription("To retrieve issues created before the given date (exclusive). <br>" +
- "Either a date (server timezone) or datetime can be provided.")
+ "Either a date (use '" + PARAM_TIMEZONE + "' attribute or it will default to server timezone) or datetime can be provided.")
.setExampleValue("2017-10-19 or 2017-10-19T13:00:00+0200");
action.createParam(PARAM_CREATED_IN_LAST)
.setDescription("To retrieve issues created during a time span before the current time (exclusive). " +
"If this parameter is set to a truthy value, createdAfter must not be set and one component uuid or key must be provided.")
.setBooleanPossibleValues()
.setDefaultValue("false");
+ action.createParam(PARAM_TIMEZONE)
+ .setDescription("To resolve dates passed to '" + PARAM_CREATED_AFTER + "' or '" + PARAM_CREATED_BEFORE + "' (does not apply to datetime) and to compute creation date histogram")
+ .setRequired(false)
+ .setExampleValue("'Europe/Paris', 'Z' or '+02:00'")
+ .setSince("8.6");
}
private static void addComponentRelatedParams(WebService.NewAction action) {
.filter(FACETS_REQUIRING_PROJECT_OR_ORGANIZATION::contains)
.collect(toSet());
checkArgument(facetsRequiringProjectOrOrganizationParameter.isEmpty() ||
- (!query.projectUuids().isEmpty()) || query.organizationUuid() != null, "Facet(s) '%s' require to also filter by project or organization",
+ (!query.projectUuids().isEmpty()) || query.organizationUuid() != null, "Facet(s) '%s' require to also filter by project or organization",
String.join(",", facetsRequiringProjectOrOrganizationParameter));
// execute request
SearchResponseLoader.Collector collector = new SearchResponseLoader.Collector(issueKeys);
collectLoggedInUser(collector);
collectRequestParams(collector, request);
- Facets facets = new Facets(result, system2.getDefaultTimeZone());
+ Facets facets = new Facets(result, Optional.ofNullable(query.timeZone()).orElse(system2.getDefaultTimeZone().toZoneId()));
if (!options.getFacets().isEmpty()) {
// add missing values to facets. For example if assignee "john" and facet on "assignees" are requested, then
// "john" should always be listed in the facet. If it is not present, then it is added with value zero.
.setOwaspTop10(request.paramAsStrings(PARAM_OWASP_TOP_10))
.setSansTop25(request.paramAsStrings(PARAM_SANS_TOP_25))
.setCwe(request.paramAsStrings(PARAM_CWE))
- .setSonarsourceSecurity(request.paramAsStrings(PARAM_SONARSOURCE_SECURITY));
+ .setSonarsourceSecurity(request.paramAsStrings(PARAM_SONARSOURCE_SECURITY))
+ .setTimeZone(request.param(PARAM_TIMEZONE));
}
private void checkIfNeedIssueSync(DbSession dbSession, SearchRequest searchRequest) {
"createdAfter", "createdAt", "createdBefore", "createdInLast", "directories", "facetMode", "facets", "files", "issues", "scopes", "languages", "moduleUuids",
"onComponentOnly",
"p", "projects", "ps", "resolutions", "resolved", "rules", "s", "severities", "sinceLeakPeriod",
- "statuses", "tags", "types", "owaspTop10", "sansTop25", "cwe", "sonarsourceSecurity");
+ "statuses", "tags", "types", "owaspTop10", "sansTop25", "cwe", "sonarsourceSecurity", "timeZone");
assertThat(def.param("organization"))
.matches(WebService.Param::isInternal)
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Sets;
+import java.time.Instant;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
+import org.sonar.api.impl.utils.TestSystem2;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rule.Severity;
import org.sonar.server.es.EsTester;
import org.sonar.server.es.SearchOptions;
import org.sonar.server.exceptions.BadRequestException;
-import org.sonar.server.organization.TestDefaultOrganizationProvider;
import org.sonar.server.rule.index.RuleIndex;
import org.sonar.server.rule.index.RuleIndexer;
import org.sonar.server.rule.index.RuleQuery;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
-import static org.mockito.Mockito.mock;
import static org.sonar.db.rule.RuleTesting.newRule;
import static org.sonar.server.util.TypeValidationsTesting.newFullTypeValidations;
public class RuleCreatorTest {
- private System2 system2 = mock(System2.class);
+ private System2 system2 = new TestSystem2().setNow(Instant.now().toEpochMilli());
@Rule
public ExpectedException expectedException = ExpectedException.none();
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
+import java.time.Instant;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
+import org.sonar.api.impl.utils.TestSystem2;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rule.Severity;
import org.sonar.server.tester.UserSessionRule;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
import static org.sonar.api.rule.Severity.CRITICAL;
import static org.sonar.db.rule.RuleTesting.newRule;
import static org.sonar.server.rule.RuleUpdate.createForCustomRule;
static final RuleKey RULE_KEY = RuleKey.of("squid", "S001");
- private System2 system2 = mock(System2.class);
+ private System2 system2 = new TestSystem2().setNow(Instant.now().toEpochMilli());
@Rule
public ExpectedException expectedException = ExpectedException.none();
public class TestSystem2 extends System2 {
private long now = 0L;
- private TimeZone defaultTimeZone = getDefaultTimeZone();
+ private TimeZone defaultTimeZone = TimeZone.getTimeZone("UTC");
public TestSystem2 setNow(long l) {
this.now = l;
}
/**
- * Warning: may rely on default timezone!
+ * Warning: rely on default timezone!
*
- * @return the datetime, {@code null} if stringDate is null
- * @throws IllegalArgumentException if stringDate is not a correctly formed date or datetime
+ * @see #parseDateOrDateTime(String, ZoneId)
* @since 6.1
*/
@CheckForNull
public static Date parseDateOrDateTime(@Nullable String stringDate) {
+ return parseDateOrDateTime(stringDate, ZoneId.systemDefault());
+ }
+
+ /**
+ * Parse either a full date time (using RFC-822 TZ format), or a local date.
+ * For local dates, the returned {@link Date} will be set at the beginning of the day, in the provided timezone.
+ * @return the datetime, {@code null} if stringDate is null
+ * @throws IllegalArgumentException if stringDate is not a correctly formed date or datetime
+ * @since 8.6
+ */
+ @CheckForNull
+ public static Date parseDateOrDateTime(@Nullable String stringDate, ZoneId timeZone) {
if (stringDate == null) {
return null;
}
LocalDate ld = parseLocalDateQuietly(stringDate);
checkArgument(ld != null, "Date '%s' cannot be parsed as either a date or date+time", stringDate);
- return Date.from(ld.atStartOfDay(ZoneId.systemDefault()).toInstant());
+ return Date.from(ld.atStartOfDay(timeZone).toInstant());
}
/**
- * Warning: may rely on default timezone!
+ * Warning: rely on default timezone for local dates!
*
* @see #parseDateOrDateTime(String)
*/
}
/**
- * Return the datetime if @param stringDate is a datetime, date + 1 day if stringDate is a date.
- * So '2016-09-01' would return a date equivalent to '2016-09-02T00:00:00+0000' in GMT (Warning: relies on default timezone!)
+ * @see #parseDateOrDateTime(String, ZoneId)
+ */
+ @CheckForNull
+ public static Date parseStartingDateOrDateTime(@Nullable String stringDate, ZoneId timeZone) {
+ return parseDateOrDateTime(stringDate, timeZone);
+ }
+
+ /**
+ * Warning: rely on default timezone for local dates!
*
- * @return the datetime, {@code null} if stringDate is null
- * @throws IllegalArgumentException if stringDate is not a correctly formed date or datetime
- * @see #parseDateOrDateTime(String)
+ * @see #parseEndingDateOrDateTime(String, ZoneId)
* @since 6.1
*/
@CheckForNull
public static Date parseEndingDateOrDateTime(@Nullable String stringDate) {
+ return parseEndingDateOrDateTime(stringDate, ZoneId.systemDefault());
+ }
+ /**
+ * Return the datetime if @param stringDate is a datetime, local date + 1 day if stringDate is a local date.
+ * So '2016-09-01' would return a date equivalent to '2016-09-02T00:00:00' in the provided timezone
+ *
+ * @return the datetime, {@code null} if stringDate is null
+ * @throws IllegalArgumentException if stringDate is not a correctly formed date or datetime
+ * @since 8.6
+ */
+ @CheckForNull
+ public static Date parseEndingDateOrDateTime(@Nullable String stringDate, ZoneId timeZone) {
if (stringDate == null) {
return null;
}
- Date date = parseDateTimeQuietly(stringDate);
- if (date != null) {
- return date;
+ OffsetDateTime odt = parseOffsetDateTimeQuietly(stringDate);
+ if (odt != null) {
+ return Date.from(odt.toInstant());
}
- date = parseDateQuietly(stringDate);
- checkArgument(date != null, "Date '%s' cannot be parsed as either a date or date+time", stringDate);
+ LocalDate ld = parseLocalDateQuietly(stringDate);
+ checkArgument(ld != null, "Date '%s' cannot be parsed as either a date or date+time", stringDate);
- return addDays(date, 1);
+ return Date.from(ld.atStartOfDay(timeZone).plusDays(1).toInstant());
}
/**
import com.tngtech.java.junit.dataprovider.DataProvider;
import com.tngtech.java.junit.dataprovider.DataProviderRunner;
import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.time.LocalDate;
+import java.time.OffsetDateTime;
+import java.time.ZoneId;
import java.util.Date;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.api.utils.DateUtils.parseDate;
import static org.sonar.api.utils.DateUtils.parseDateOrDateTime;
-import static org.sonar.api.utils.DateUtils.parseDateTime;
import static org.sonar.api.utils.DateUtils.parseEndingDateOrDateTime;
import static org.sonar.api.utils.DateUtils.parseStartingDateOrDateTime;
@DataProvider
public static Object[][] date_times() {
return new Object[][] {
- {"2014-05-27", parseDate("2014-05-27")},
- {"2014-05-27T15:50:45+0100", parseDateTime("2014-05-27T15:50:45+0100")},
+ {"2014-05-27", Date.from(LocalDate.parse("2014-05-27").atStartOfDay(ZoneId.systemDefault()).toInstant())},
+ {"2014-05-27T15:50:45+0100", Date.from(OffsetDateTime.parse("2014-05-27T15:50:45+01:00").toInstant())},
{null, null}
};
}
assertThat(parseStartingDateOrDateTime(stringDate)).isEqualTo(expectedDate);
}
- @DataProvider
- public static Object[][] ending_date_times() {
- return new Object[][] {
- {"2014-05-27", parseDate("2014-05-28")},
- {"2014-05-27T15:50:45+0100", parseDateTime("2014-05-27T15:50:45+0100")},
- {null, null}
- };
+ @Test
+ public void param_as__date_time_provided_timezone() {
+ final ZoneId zoneId = ZoneId.of("Europe/Moscow");
+ assertThat(parseDateOrDateTime("2020-05-27", zoneId)).isEqualTo(Date.from(OffsetDateTime.parse("2020-05-27T00:00:00+03:00").toInstant()));
+ assertThat(parseStartingDateOrDateTime("2020-05-27", zoneId)).isEqualTo(Date.from(OffsetDateTime.parse("2020-05-27T00:00:00+03:00").toInstant()));
+ }
+
+ @Test
+ public void param_as_ending_date_time_default_timezone() {
+ assertThat(parseEndingDateOrDateTime("2014-05-27")).isEqualTo(Date.from(LocalDate.parse("2014-05-28").atStartOfDay(ZoneId.systemDefault()).toInstant()));
+ assertThat(parseEndingDateOrDateTime("2014-05-27T15:50:45+0100")).isEqualTo(Date.from(OffsetDateTime.parse("2014-05-27T15:50:45+01:00").toInstant()));
+ assertThat(parseEndingDateOrDateTime(null)).isNull();
}
@Test
- @UseDataProvider("ending_date_times")
- public void param_as_ending_date_time(String stringDate, Date expectedDate) {
- assertThat(parseEndingDateOrDateTime(stringDate)).isEqualTo(expectedDate);
+ public void param_as_ending_date_time_provided_timezone() {
+ final ZoneId zoneId = ZoneId.of("Europe/Moscow");
+ assertThat(parseEndingDateOrDateTime("2020-05-27", zoneId)).isEqualTo(Date.from(OffsetDateTime.parse("2020-05-28T00:00:00+03:00").toInstant()));
+ assertThat(parseEndingDateOrDateTime("2014-05-27T15:50:45+0100", zoneId)).isEqualTo(Date.from(OffsetDateTime.parse("2014-05-27T15:50:45+01:00").toInstant()));
+ assertThat(parseEndingDateOrDateTime(null, zoneId)).isNull();
}
@Test
public static final String PARAM_PAGE_INDEX = "pageIndex";
public static final String PARAM_ASC = "asc";
public static final String PARAM_ADDITIONAL_FIELDS = "additionalFields";
+ public static final String PARAM_TIMEZONE = "timeZone";
/**
* @deprecated since 7.9