| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.cassandra.cql3.functions.types; |
| |
| import java.util.*; |
| import java.util.concurrent.TimeUnit; |
| |
| import static com.google.common.base.Preconditions.checkArgument; |
| |
| /** |
| * A date with no time components, no time zone, in the ISO 8601 calendar. |
| * |
| * <p>Note that ISO 8601 has a number of differences with the default gregorian calendar used in |
| * Java: |
| * |
| * <ul> |
| * <li>it uses a proleptic gregorian calendar, meaning that it's gregorian indefinitely back in |
| * the past (there is no gregorian change); |
| * <li>there is a year 0. |
| * </ul> |
| * |
| * <p>This class implements these differences, so that year/month/day fields match exactly the ones |
| * in CQL string literals. |
| * |
| * @since 2.2 |
| */ |
| public final class LocalDate |
| { |
| |
| private static final TimeZone UTC = TimeZone.getTimeZone("UTC"); |
| |
| private final long millisSinceEpoch; |
| private final int daysSinceEpoch; |
| |
| // This gets initialized lazily if we ever need it. Once set, it is effectively immutable. |
| private volatile GregorianCalendar calendar; |
| |
| private LocalDate(int daysSinceEpoch) |
| { |
| this.daysSinceEpoch = daysSinceEpoch; |
| this.millisSinceEpoch = TimeUnit.DAYS.toMillis(daysSinceEpoch); |
| } |
| |
| /** |
| * Builds a new instance from a number of days since January 1st, 1970 GMT. |
| * |
| * @param daysSinceEpoch the number of days. |
| * @return the new instance. |
| */ |
| public static LocalDate fromDaysSinceEpoch(int daysSinceEpoch) |
| { |
| return new LocalDate(daysSinceEpoch); |
| } |
| |
| /** |
| * Builds a new instance from a number of milliseconds since January 1st, 1970 GMT. Note that if |
| * the given number does not correspond to a whole number of days, it will be rounded towards 0. |
| * |
| * @param millisSinceEpoch the number of milliseconds since January 1st, 1970 GMT. |
| * @return the new instance. |
| * @throws IllegalArgumentException if the date is not in the range [-5877641-06-23; |
| * 5881580-07-11]. |
| */ |
| public static LocalDate fromMillisSinceEpoch(long millisSinceEpoch) |
| throws IllegalArgumentException |
| { |
| long daysSinceEpoch = TimeUnit.MILLISECONDS.toDays(millisSinceEpoch); |
| checkArgument( |
| daysSinceEpoch >= Integer.MIN_VALUE && daysSinceEpoch <= Integer.MAX_VALUE, |
| "Date should be in the range [-5877641-06-23; 5881580-07-11]"); |
| |
| return new LocalDate((int) daysSinceEpoch); |
| } |
| |
| /** |
| * Returns the number of days since January 1st, 1970 GMT. |
| * |
| * @return the number of days. |
| */ |
| public int getDaysSinceEpoch() |
| { |
| return daysSinceEpoch; |
| } |
| |
| /** |
| * Returns the year. |
| * |
| * @return the year. |
| */ |
| public int getYear() |
| { |
| GregorianCalendar c = getCalendar(); |
| int year = c.get(Calendar.YEAR); |
| if (c.get(Calendar.ERA) == GregorianCalendar.BC) year = -year + 1; |
| return year; |
| } |
| |
| /** |
| * Returns the month. |
| * |
| * @return the month. It is 1-based, e.g. 1 for January. |
| */ |
| public int getMonth() |
| { |
| return getCalendar().get(Calendar.MONTH) + 1; |
| } |
| |
| /** |
| * Returns the day in the month. |
| * |
| * @return the day in the month. |
| */ |
| public int getDay() |
| { |
| return getCalendar().get(Calendar.DAY_OF_MONTH); |
| } |
| |
| /** |
| * Return a new {@link LocalDate} with the specified (signed) amount of time added to (or |
| * subtracted from) the given {@link Calendar} field, based on the calendar's rules. |
| * |
| * <p>Note that adding any amount to a field smaller than {@link Calendar#DAY_OF_MONTH} will |
| * remain without effect, as this class does not keep time components. |
| * |
| * <p>See {@link Calendar} javadocs for more information. |
| * |
| * @param field a {@link Calendar} field to modify. |
| * @param amount the amount of date or time to be added to the field. |
| * @return a new {@link LocalDate} with the specified (signed) amount of time added to (or |
| * subtracted from) the given {@link Calendar} field. |
| * @throws IllegalArgumentException if the new date is not in the range [-5877641-06-23; |
| * 5881580-07-11]. |
| */ |
| public LocalDate add(int field, int amount) |
| { |
| GregorianCalendar newCalendar = isoCalendar(); |
| newCalendar.setTimeInMillis(millisSinceEpoch); |
| newCalendar.add(field, amount); |
| LocalDate newDate = fromMillisSinceEpoch(newCalendar.getTimeInMillis()); |
| newDate.calendar = newCalendar; |
| return newDate; |
| } |
| |
| @Override |
| public boolean equals(Object o) |
| { |
| if (this == o) return true; |
| |
| if (o instanceof LocalDate) |
| { |
| LocalDate that = (LocalDate) o; |
| return this.daysSinceEpoch == that.daysSinceEpoch; |
| } |
| return false; |
| } |
| |
| @Override |
| public int hashCode() |
| { |
| return daysSinceEpoch; |
| } |
| |
| @Override |
| public String toString() |
| { |
| return String.format("%d-%s-%s", getYear(), pad2(getMonth()), pad2(getDay())); |
| } |
| |
| private static String pad2(int i) |
| { |
| String s = Integer.toString(i); |
| return s.length() == 2 ? s : '0' + s; |
| } |
| |
| private GregorianCalendar getCalendar() |
| { |
| // Two threads can race and both create a calendar. This is not a problem. |
| if (calendar == null) |
| { |
| |
| // Use a local variable to only expose after we're done mutating it. |
| GregorianCalendar tmp = isoCalendar(); |
| tmp.setTimeInMillis(millisSinceEpoch); |
| |
| calendar = tmp; |
| } |
| return calendar; |
| } |
| |
| // This matches what Cassandra uses server side (from Joda Time's LocalDate) |
| private static GregorianCalendar isoCalendar() |
| { |
| GregorianCalendar calendar = new GregorianCalendar(UTC); |
| calendar.setGregorianChange(new Date(Long.MIN_VALUE)); |
| return calendar; |
| } |
| } |